From 14f6e32c74492a76a87e168cb63708aa3d7be93e Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Wed, 31 May 2023 13:37:58 -0700 Subject: [PATCH] ADDED: Change watcher for libraries When editing or viewing library symbols, the files are watched for underlying changes. If any occur, the user is either prompted to reload (if reloading would overwrite their current edits) or the file is silently updated to the current version on disk. This also sets a custom assertion handler to avoid unneeded crashes when recieving invalid SAMBA packets and turns off assertions entirely when running in release (non-debug) mode --- common/eda_draw_frame.cpp | 7 +- common/single_top.cpp | 23 ++++ cvpcb/display_footprints_frame.cpp | 32 ++++++ cvpcb/display_footprints_frame.h | 1 + eeschema/sch_base_frame.cpp | 107 +++++++++++++++++ eeschema/sch_base_frame.h | 26 +++++ eeschema/symbol_editor/symbol_edit_frame.cpp | 49 ++++++++ eeschema/symbol_editor/symbol_editor.cpp | 9 +- eeschema/symbol_library.cpp | 43 +++++++ eeschema/symbol_library.h | 8 ++ eeschema/symbol_library_manager.cpp | 29 +++++ eeschema/symbol_library_manager.h | 5 + eeschema/symbol_viewer_frame.cpp | 27 +++++ include/eda_draw_frame.h | 2 +- include/mail_type.h | 3 +- include/pcb_base_frame.h | 33 ++++++ kicad/kicad.cpp | 25 +++- pcbnew/footprint_edit_frame.cpp | 16 ++- pcbnew/footprint_edit_frame.h | 6 + pcbnew/footprint_libraries_utils.cpp | 1 + pcbnew/footprint_viewer_frame.cpp | 57 ++++------ pcbnew/footprint_viewer_frame.h | 6 + pcbnew/pcb_base_frame.cpp | 114 +++++++++++++++++++ 23 files changed, 579 insertions(+), 50 deletions(-) diff --git a/common/eda_draw_frame.cpp b/common/eda_draw_frame.cpp index 8b5934e4fd..4bcbb8e46f 100644 --- a/common/eda_draw_frame.cpp +++ b/common/eda_draw_frame.cpp @@ -222,10 +222,9 @@ bool EDA_DRAW_FRAME::LockFile( const wxString& aFileName ) m_file_checker = std::make_unique( aFileName ); - // If the file is not valid, or is successfully locked, return true - // Invalid lockfiles are the result of bad permissions, so this is - // likely not a file that we can override regardless - return !m_file_checker->Valid() || m_file_checker->Locked(); + // If the file is valid, return true. This could mean that the file is + // locked or it could mean that the file is read-only + return m_file_checker->Valid(); } diff --git a/common/single_top.cpp b/common/single_top.cpp index 055c35ce11..56b0dc86dc 100644 --- a/common/single_top.cpp +++ b/common/single_top.cpp @@ -133,6 +133,26 @@ private: }; wxIMPLEMENT_DYNAMIC_CLASS(HtmlModule, wxModule); +// Define a custom assertion handler +void CustomAssertHandler(const wxString& file, + int line, + const wxString& func, + const wxString& cond, + const wxString& msg) +{ + // Log the assertion details to standard log + if (!msg.empty()) + { + wxLogError( "Assertion failed at %s:%d in %s: %s - %s", + file, line, func, cond, msg); + } + else + { + wxLogError( "Assertion failed at %s:%d in %s: %s", + file, line, func, cond); + } +} + /** * Struct APP_SINGLE_TOP * implements a bare naked wxApp (so that we don't become dependent on @@ -149,6 +169,9 @@ struct APP_SINGLE_TOP : public wxApp bool OnInit() override { + wxDISABLE_DEBUG_SUPPORT(); + wxSetAssertHandler( CustomAssertHandler ); + // Perform platform-specific init tasks if( !KIPLATFORM::APP::Init() ) return false; diff --git a/cvpcb/display_footprints_frame.cpp b/cvpcb/display_footprints_frame.cpp index 36271d4e62..52705ed1f1 100644 --- a/cvpcb/display_footprints_frame.cpp +++ b/cvpcb/display_footprints_frame.cpp @@ -188,6 +188,7 @@ DISPLAY_FOOTPRINTS_FRAME::~DISPLAY_FOOTPRINTS_FRAME() delete GetScreen(); SetScreen( nullptr ); // Be sure there is no double deletion + setFPWatcher( nullptr ); } @@ -454,6 +455,7 @@ FOOTPRINT* DISPLAY_FOOTPRINTS_FRAME::GetFootprint( const wxString& aFootprintNam if( footprint ) { + footprint->SetFPID( fpid ); footprint->SetParent( (EDA_ITEM*) GetBoard() ); footprint->SetPosition( VECTOR2I( 0, 0 ) ); return footprint; @@ -465,6 +467,35 @@ FOOTPRINT* DISPLAY_FOOTPRINTS_FRAME::GetFootprint( const wxString& aFootprintNam } +void DISPLAY_FOOTPRINTS_FRAME::ReloadFootprint( FOOTPRINT* aFootprint ) +{ + if( !aFootprint || !m_currentComp ) + return; + + GetBoard()->DeleteAllFootprints(); + GetBoard()->GetNetInfo().RemoveUnusedNets(); + GetCanvas()->GetView()->Clear(); + + + for( PAD* pad : aFootprint->Pads() ) + { + const COMPONENT_NET& net = m_currentComp->GetNet( pad->GetNumber() ); + + if( !net.GetPinFunction().IsEmpty() ) + { + NETINFO_ITEM* netinfo = new NETINFO_ITEM( GetBoard() ); + netinfo->SetNetname( net.GetPinFunction() ); + GetBoard()->Add( netinfo ); + pad->SetNet( netinfo ); + } + } + + GetBoard()->Add( aFootprint ); + updateView(); + GetCanvas()->Refresh(); +} + + void DISPLAY_FOOTPRINTS_FRAME::InitDisplay() { CVPCB_MAINFRAME* parentframe = (CVPCB_MAINFRAME *) GetParent(); @@ -517,6 +548,7 @@ void DISPLAY_FOOTPRINTS_FRAME::InitDisplay() GetBoard()->Add( footprint ); m_currentFootprint = footprintName; m_currentComp = comp; + setFPWatcher( footprint ); } if( fpInfo ) diff --git a/cvpcb/display_footprints_frame.h b/cvpcb/display_footprints_frame.h index ec78420dff..004f4ebd17 100644 --- a/cvpcb/display_footprints_frame.h +++ b/cvpcb/display_footprints_frame.h @@ -95,6 +95,7 @@ public: SELECTION& GetCurrentSelection() override; + void ReloadFootprint( FOOTPRINT* aFootprint ) override; DECLARE_EVENT_TABLE() protected: diff --git a/eeschema/sch_base_frame.cpp b/eeschema/sch_base_frame.cpp index 7402dde68a..774834d164 100644 --- a/eeschema/sch_base_frame.cpp +++ b/eeschema/sch_base_frame.cpp @@ -107,6 +107,8 @@ SCH_BASE_FRAME::SCH_BASE_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAME_T aWindo selTool->OnIdle( aEvent ); } } ); + + m_watcherDebounceTimer.Bind( wxEVT_TIMER, &SCH_BASE_FRAME::OnSymChangeDebounceTimer, this ); } @@ -619,3 +621,108 @@ wxString SCH_BASE_FRAME::SelectLibraryFromList() return libName; } + + +void SCH_BASE_FRAME::setSymWatcher( const LIB_ID* aID ) +{ + Unbind( wxEVT_FSWATCHER, &SCH_BASE_FRAME::OnSymChange, this ); + + if( !aID ) + { + wxLogTrace( "KICAD_LIB_WATCH", "No symbol library specified, disabling watcher" ); + m_watcher.reset(); + return; + } + + wxString libfullname; + SYMBOL_LIB_TABLE* tbl = Prj().SchSymbolLibTable(); + + if( !tbl ) + return; + + try + { + const SYMBOL_LIB_TABLE_ROW* row = tbl->FindRow( aID->GetLibNickname() ); + + if( !row ) + return; + + libfullname = row->GetFullURI( true ); + } + catch( const std::exception& e ) + { + DisplayInfoMessage( this, e.what() ); + return; + } + catch( const IO_ERROR& error ) + { + wxLogTrace( "KICAD_LIB_WATCH", "Error: %s", error.What() ); + return; + } + + wxLogTrace( "KICAD_LIB_WATCH", "Setting up watcher for %s", libfullname ); + m_watcherFileName.Assign( libfullname ); + + if( !m_watcherFileName.FileExists() ) + return; + + wxLog::EnableLogging( false ); + m_watcherLastModified = m_watcherFileName.GetModificationTime(); + wxLog::EnableLogging( true ); + + Bind( wxEVT_FSWATCHER, &SCH_BASE_FRAME::OnSymChange, this ); + m_watcher = std::make_unique(); + m_watcher->SetOwner( this ); + + wxFileName fn; + fn.AssignDir( m_watcherFileName.GetPath() ); + fn.DontFollowLink(); + + m_watcher->AddTree( fn ); +} + + +void SCH_BASE_FRAME::OnSymChange( wxFileSystemWatcherEvent& aEvent ) +{ + SYMBOL_LIBS* libs = Prj().SchLibs(); + + wxLogTrace( "KICAD_LIB_WATCH", "OnSymChange: %s, watcher file: %s", + aEvent.GetPath().GetFullPath(), m_watcherFileName.GetFullPath() ); + + if( !libs || !m_watcher || !m_watcher.get() || m_watcherFileName.GetPath().IsEmpty() ) + return; + + if( aEvent.GetPath() != m_watcherFileName ) + return; + + // Start the debounce timer (set to 1 second) + if( !m_watcherDebounceTimer.StartOnce( 1000 ) ) + { + wxLogTrace( "KICAD_LIB_WATCH", "Failed to start the debounce timer" ); + return; + } +} + + +void SCH_BASE_FRAME::OnSymChangeDebounceTimer( wxTimerEvent& aEvent ) +{ + wxLogTrace( "KICAD_LIB_WATCH", "OnSymChangeDebounceTimer" ); + // Disable logging to avoid spurious messages and check if the file has changed + wxLog::EnableLogging( false ); + wxDateTime lastModified = m_watcherFileName.GetModificationTime(); + wxLog::EnableLogging( true ); + + if( lastModified == m_watcherLastModified || !lastModified.IsValid() ) + return; + + m_watcherLastModified = lastModified; + + if( !GetScreen()->IsContentModified() || IsOK( this, _( "The library containing the current symbol has changed.\n" + "Do you want to reload the library?" ) ) ) + { + wxLogTrace( "KICAD_LIB_WATCH", "Sending refresh symbol mail" ); + std::string libName = m_watcherFileName.GetFullPath().ToStdString(); + Kiway().ExpressMail( FRAME_SCH_VIEWER, MAIL_REFRESH_SYMBOL, libName ); + Kiway().ExpressMail( FRAME_SCH_SYMBOL_EDITOR, MAIL_REFRESH_SYMBOL, libName ); + } +} \ No newline at end of file diff --git a/eeschema/sch_base_frame.h b/eeschema/sch_base_frame.h index 407bb5b15b..86c2f4c2ef 100644 --- a/eeschema/sch_base_frame.h +++ b/eeschema/sch_base_frame.h @@ -35,8 +35,11 @@ #include #include #include +#include +#include #include #include +#include #include @@ -251,6 +254,16 @@ public: void ActivateGalCanvas() override; + /** + * Handler for Symbol change events. Responds to the filesystem watcher set in #setSymWatcher. + */ + void OnSymChange( wxFileSystemWatcherEvent& aEvent ); + + /** + * Handler for the filesystem watcher debounce timer. + */ + void OnSymChangeDebounceTimer( wxTimerEvent& aEvent ); + protected: void handleActivateEvent( wxActivateEvent& aEvent ) override; @@ -265,11 +278,24 @@ protected: */ bool saveSymbolLibTables( bool aGlobal, bool aProject ); + /** + * Creates (or removes) a watcher on the specified symbol library + * @param aSymbol If nullptr, the watcher is removed. Otherwise, set a change watcher + */ + void setSymWatcher( const LIB_ID* aSymbol ); + /// These are only used by symbol_editor. Eeschema should be using the one inside /// the SCHEMATIC. SCHEMATIC_SETTINGS m_base_frame_defaults; private: + + /// These are file watchers for the symbol library tables. + std::unique_ptr m_watcher; + wxFileName m_watcherFileName; + wxDateTime m_watcherLastModified; + wxTimer m_watcherDebounceTimer; + NL_SCHEMATIC_PLUGIN* m_spaceMouse; }; diff --git a/eeschema/symbol_editor/symbol_edit_frame.cpp b/eeschema/symbol_editor/symbol_edit_frame.cpp index 255e92e94c..d3d2f366e2 100644 --- a/eeschema/symbol_editor/symbol_edit_frame.cpp +++ b/eeschema/symbol_editor/symbol_edit_frame.cpp @@ -264,6 +264,8 @@ SYMBOL_EDIT_FRAME::~SYMBOL_EDIT_FRAME() if( m_toolManager ) m_toolManager->ShutdownAllTools(); + setSymWatcher( nullptr ); + if( IsSymbolFromSchematic() ) { delete m_symbol; @@ -1371,6 +1373,52 @@ void SYMBOL_EDIT_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail ) break; } + case MAIL_REFRESH_SYMBOL: + { + SYMBOL_LIB_TABLE* tbl = Prj().SchSymbolLibTable(); + LIB_SYMBOL* symbol = GetCurSymbol(); + + wxLogTrace( "KICAD_LIB_WATCH", "Received refresh symbol request for %s", + payload ); + + if( !tbl || !symbol ) + break; + + wxString libName = symbol->GetLibId().GetLibNickname(); + const SYMBOL_LIB_TABLE_ROW* row = tbl->FindRow( libName ); + + if( !row ) + return; + + wxFileName libfullname( row->GetFullURI( true ) ); + + wxFileName changedLib( mail.GetPayload() ); + wxLogTrace( "KICAD_LIB_WATCH", + "Received refresh symbol request for %s, current symbols is %s", + changedLib.GetFullPath(), libfullname.GetFullPath() ); + + if( changedLib == libfullname ) + { + wxLogTrace( "KICAD_LIB_WATCH", "Refreshing symbol %s", symbol->GetName() ); + + m_libMgr->UpdateLibraryBuffer( libName ); + + LIB_SYMBOL* lib_symbol = m_libMgr->GetBufferedSymbol( symbol->GetName(), libName ); + wxCHECK2_MSG( lib_symbol, break, wxString::Format( "Symbol %s not found in library %s", + symbol->GetName(), libName ) ); + + // The buffered screen for the symbol + SCH_SCREEN* symbol_screen = m_libMgr->GetScreen( lib_symbol->GetName(), libName ); + + SetScreen( symbol_screen ); + SetCurSymbol( new LIB_SYMBOL( *lib_symbol ), false ); + RebuildSymbolUnitsList(); + SetShowDeMorgan( GetCurSymbol()->HasConversion() ); + } + + break; + } + default: ; } @@ -1518,6 +1566,7 @@ void SYMBOL_EDIT_FRAME::LoadSymbolFromSchematic( SCH_SYMBOL* aSymbol ) SetScreen( tmpScreen ); SetCurSymbol( symbol.release(), true ); + setSymWatcher( nullptr ); ReCreateMenuBar(); ReCreateHToolbar(); diff --git a/eeschema/symbol_editor/symbol_editor.cpp b/eeschema/symbol_editor/symbol_editor.cpp index 766e50eafe..6750e00ae2 100644 --- a/eeschema/symbol_editor/symbol_editor.cpp +++ b/eeschema/symbol_editor/symbol_editor.cpp @@ -198,6 +198,7 @@ bool SYMBOL_EDIT_FRAME::LoadSymbol( const LIB_ID& aLibId, int aUnit, int aConver m_centerItemOnIdle = libId; Bind( wxEVT_IDLE, &SYMBOL_EDIT_FRAME::centerItemIdleHandler, this ); + setSymWatcher( &libId ); return true; } @@ -305,9 +306,11 @@ bool SYMBOL_EDIT_FRAME::LoadOneLibrarySymbolAux( LIB_SYMBOL* aEntry, const wxStr ClearUndoRedoList(); - // Let tools add things to the view if necessary - if( m_toolManager ) - m_toolManager->ResetTools( TOOL_BASE::MODEL_RELOAD ); + if( !IsSymbolFromSchematic() ) + { + LIB_ID libId = GetCurSymbol()->GetLibId(); + setSymWatcher( &libId ); + } // Display the document information based on the entry selected just in // case the entry is an alias. diff --git a/eeschema/symbol_library.cpp b/eeschema/symbol_library.cpp index 164fa483a1..2fa454ff44 100644 --- a/eeschema/symbol_library.cpp +++ b/eeschema/symbol_library.cpp @@ -310,6 +310,49 @@ SYMBOL_LIB* SYMBOL_LIBS::AddLibrary( const wxString& aFileName, SYMBOL_LIBS::ite } +bool SYMBOL_LIBS::ReloadLibrary( const wxString &aFileName ) +{ + SYMBOL_LIB *lib; + wxFileName fn = aFileName; + + // Check if the library already exists. + if( !( lib = FindLibrary( fn.GetName() ) ) ) + return false; + + // Create a clone of the library pointer in case we need to re-add it + SYMBOL_LIB *cloneLib = lib; + + // Try to find the iterator of the library + for( auto it = begin(); it != end(); ++it ) + { + if( it->GetName() == fn.GetName() ) + { + // Remove the old library and keep the pointer + lib = &*it; + release( it ); + break; + } + } + + // Try to reload the library + try + { + lib = SYMBOL_LIB::LoadSymbolLibrary( aFileName ); + + // If the library is successfully reloaded, add it back to the set. + push_back( lib ); + return true; + } + catch( ... ) + { + // If an exception occurs, ensure that the SYMBOL_LIBS remains unchanged + // by re-adding the old library back to the set. + push_back( cloneLib ); + return false; + } +} + + SYMBOL_LIB* SYMBOL_LIBS::FindLibrary( const wxString& aName ) { for( SYMBOL_LIBS::iterator it = begin(); it!=end(); ++it ) diff --git a/eeschema/symbol_library.h b/eeschema/symbol_library.h index ac0366c4e7..e48b061824 100644 --- a/eeschema/symbol_library.h +++ b/eeschema/symbol_library.h @@ -84,6 +84,14 @@ public: */ SYMBOL_LIB* AddLibrary( const wxString& aFileName, SYMBOL_LIBS::iterator& aIterator ); + /** + * Refreshes the library from the (possibly updated) contents on disk + * + * @param aFileName is the file name of the symbol library + * @return true if successfully updated + */ + bool ReloadLibrary( const wxString& aFileName ); + /** * Load all of the project's libraries into this container, which should * be cleared before calling it. diff --git a/eeschema/symbol_library_manager.cpp b/eeschema/symbol_library_manager.cpp index 3e64ed56c0..18a59f04bd 100644 --- a/eeschema/symbol_library_manager.cpp +++ b/eeschema/symbol_library_manager.cpp @@ -831,6 +831,35 @@ SYMBOL_LIBRARY_MANAGER::LIB_BUFFER& SYMBOL_LIBRARY_MANAGER::getLibraryBuffer( } +bool SYMBOL_LIBRARY_MANAGER::UpdateLibraryBuffer( const wxString& aLibrary ) +{ + try + { + m_libs.erase( aLibrary ); + getLibraryBuffer( aLibrary ); + } + catch(const std::exception& e) + { + wxLogError( _( "Error updating library buffer: %s" ), e.what() ); + return false; + } + catch( const IO_ERROR& e ) + { + wxLogError( _( "Error updating library buffer: %s" ), e.What() ); + return false; + } + catch(...) + { + wxLogError( _( "Error updating library buffer." ) ); + return false; + } + + getLibraryBuffer( aLibrary ); + + return true; +} + + SYMBOL_LIBRARY_MANAGER::SYMBOL_BUFFER::SYMBOL_BUFFER( LIB_SYMBOL* aSymbol, std::unique_ptr aScreen ) : m_screen( std::move( aScreen ) ), diff --git a/eeschema/symbol_library_manager.h b/eeschema/symbol_library_manager.h index 6b8236369f..37866f8f79 100644 --- a/eeschema/symbol_library_manager.h +++ b/eeschema/symbol_library_manager.h @@ -117,6 +117,11 @@ public: bool UpdateSymbolAfterRename( LIB_SYMBOL* aSymbol, const wxString& oldAlias, const wxString& aLibrary ); + /** + * Update the library buffer with a new version of the library. + */ + bool UpdateLibraryBuffer( const wxString& aLibrary ); + /** * Remove the symbol from the symbol buffer. * It is required to save the library to have the symbol removed in the schematic editor. diff --git a/eeschema/symbol_viewer_frame.cpp b/eeschema/symbol_viewer_frame.cpp index cec4f2cd98..8deb82ddf5 100644 --- a/eeschema/symbol_viewer_frame.cpp +++ b/eeschema/symbol_viewer_frame.cpp @@ -1312,6 +1312,7 @@ SELECTION& SYMBOL_VIEWER_FRAME::GetCurrentSelection() void SYMBOL_VIEWER_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail ) { + switch( mail.Command() ) { case MAIL_RELOAD_LIB: @@ -1319,6 +1320,32 @@ void SYMBOL_VIEWER_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail ) ReCreateLibList(); break; } + case MAIL_REFRESH_SYMBOL: + { + SYMBOL_LIB_TABLE* tbl = Prj().SchSymbolLibTable(); + LIB_SYMBOL* symbol = GetSelectedSymbol(); + const SYMBOL_LIB_TABLE_ROW* row = tbl->FindRow( symbol->GetLibId().GetLibNickname() ); + + if( !row ) + return; + + wxString libfullname = row->GetFullURI( true ); + + if( symbol ) + { + wxString lib( mail.GetPayload() ); + wxLogTrace( "KICAD_LIB_WATCH", "Received refresh symbol request for %s, current symbols is %s", lib, libfullname ); + + if( lib == libfullname ) + { + wxLogTrace( "KICAD_LIB_WATCH", "Refreshing symbol %s", symbol->GetName() ); + updatePreviewSymbol(); + GetCanvas()->GetView()->UpdateAllItems( KIGFX::ALL); + } + } + + break; + } default:; } } diff --git a/include/eda_draw_frame.h b/include/eda_draw_frame.h index 72c18a72df..6b642eee06 100644 --- a/include/eda_draw_frame.h +++ b/include/eda_draw_frame.h @@ -84,7 +84,7 @@ public: * Use #ReleaseFile() to undo this. * * @param aFileName full path to the file. - * @return false if the file was already locked, true otherwise. + * @return true if the file is locked or read-only, false otherwise. */ bool LockFile( const wxString& aFileName ); diff --git a/include/mail_type.h b/include/mail_type.h index d5efa0e978..fa32ed8d07 100644 --- a/include/mail_type.h +++ b/include/mail_type.h @@ -53,7 +53,8 @@ enum MAIL_T MAIL_LIB_EDIT, MAIL_FP_EDIT, MAIL_RELOAD_LIB, // Reload Library List if one was added - MAIL_RELOAD_PLUGINS // Reload python plugins + MAIL_RELOAD_PLUGINS, // Reload python plugins + MAIL_REFRESH_SYMBOL // Refresh symbol in symbol viewer }; #endif // MAIL_TYPE_H_ diff --git a/include/pcb_base_frame.h b/include/pcb_base_frame.h index 73d853851a..d350802996 100644 --- a/include/pcb_base_frame.h +++ b/include/pcb_base_frame.h @@ -39,6 +39,9 @@ #include #include +#include +#include +#include /* Forward declarations of classes. */ class APP_SETTINGS_BASE; @@ -183,6 +186,15 @@ public: virtual const PCB_PLOT_PARAMS& GetPlotSettings() const; virtual void SetPlotSettings( const PCB_PLOT_PARAMS& aSettings ); + /** + * Reload the footprint from the library. + * @param aFootprint is the footprint to reload. + */ + virtual void ReloadFootprint( FOOTPRINT* aFootprint ) + { + wxFAIL_MSG( wxT( "Attempted to reload a footprint for PCB_BASE_FRAME that does not override!" ) ); + } + /** * Set the #m_Pcb member in such as way as to ensure deleting any previous #BOARD. */ @@ -392,6 +404,16 @@ public: */ void RemoveBoardChangeListener( wxEvtHandler* aListener ); + /** + * Handler for FP change events. Responds to the filesystem watcher set in #setFPWatcher. + */ + void OnFPChange( wxFileSystemWatcherEvent& aEvent ); + + /** + * Handler for the filesystem watcher debounce timer. + */ + void OnFpChangeDebounceTimer( wxTimerEvent& aEvent ); + protected: bool canCloseWindow( wxCloseEvent& aCloseEvent ) override; @@ -416,6 +438,12 @@ protected: void rebuildConnectivity(); + /** + * Creates (or removes) a watcher on the specified footprint + * @param aFootprint If nullptr, the watcher is removed. Otherwise, set a change watcher + */ + void setFPWatcher( FOOTPRINT* aFootprint ); + protected: BOARD* m_pcb; PCB_DISPLAY_OPTIONS m_displayOptions; @@ -424,6 +452,11 @@ protected: private: NL_PCBNEW_PLUGIN* m_spaceMouse; + std::unique_ptr m_watcher; + wxFileName m_watcherFileName; + wxDateTime m_watcherLastModified; + wxTimer m_watcherDebounceTimer; + std::vector m_boardChangeListeners; }; diff --git a/kicad/kicad.cpp b/kicad/kicad.cpp index 78b229d50d..7af74eabcb 100644 --- a/kicad/kicad.cpp +++ b/kicad/kicad.cpp @@ -407,6 +407,26 @@ void PGM_KICAD::Destroy() KIWAY Kiway( &Pgm(), KFCTL_CPP_PROJECT_SUITE ); +// Define a custom assertion handler +void CustomAssertHandler(const wxString& file, + int line, + const wxString& func, + const wxString& cond, + const wxString& msg) +{ + // Log the assertion details to standard log + if (!msg.empty()) + { + wxLogError( "Assertion failed at %s:%d in %s: %s - %s", + file, line, func, cond, msg); + } + else + { + wxLogError( "Assertion failed at %s:%d in %s: %s", + file, line, func, cond); + } +} + /** * Not publicly visible because most of the action is in #PGM_KICAD these days. */ @@ -421,6 +441,9 @@ struct APP_KICAD : public wxApp bool OnInit() override { + wxDISABLE_DEBUG_SUPPORT(); + wxSetAssertHandler( CustomAssertHandler ); + // Perform platform-specific init tasks if( !KIPLATFORM::APP::Init() ) return false; @@ -438,10 +461,8 @@ struct APP_KICAD : public wxApp { program.OnPgmExit(); -#if defined(__FreeBSD__) // Avoid wxLog crashing when used in destructors. wxLog::EnableLogging( false ); -#endif return wxApp::OnExit(); } diff --git a/pcbnew/footprint_edit_frame.cpp b/pcbnew/footprint_edit_frame.cpp index 3f5063e204..576d101049 100644 --- a/pcbnew/footprint_edit_frame.cpp +++ b/pcbnew/footprint_edit_frame.cpp @@ -338,6 +338,9 @@ FOOTPRINT_EDIT_FRAME::~FOOTPRINT_EDIT_FRAME() // save the footprint in the PROJECT retainLastFootprint(); + // Clear the watched file + setFPWatcher( nullptr ); + delete m_selectionFilterPanel; delete m_appearancePanel; delete m_treePane; @@ -535,7 +538,7 @@ void FOOTPRINT_EDIT_FRAME::restoreLastFootprint() } -void FOOTPRINT_EDIT_FRAME::AddFootprintToBoard( FOOTPRINT* aFootprint ) +void FOOTPRINT_EDIT_FRAME::ReloadFootprint( FOOTPRINT* aFootprint ) { m_originalFootprintCopy.reset( static_cast( aFootprint->Clone() ) ); m_originalFootprintCopy->SetParent( nullptr ); @@ -566,6 +569,17 @@ void FOOTPRINT_EDIT_FRAME::AddFootprintToBoard( FOOTPRINT* aFootprint ) } +void FOOTPRINT_EDIT_FRAME::AddFootprintToBoard( FOOTPRINT* aFootprint ) +{ + ReloadFootprint( aFootprint ); + + if( IsCurrentFPFromBoard() ) + setFPWatcher( nullptr ); + else + setFPWatcher( aFootprint ); +} + + const wxChar* FOOTPRINT_EDIT_FRAME::GetFootprintEditorFrameName() { return FOOTPRINT_EDIT_FRAME_NAME; diff --git a/pcbnew/footprint_edit_frame.h b/pcbnew/footprint_edit_frame.h index 5d36fe54d0..aa4d897c12 100644 --- a/pcbnew/footprint_edit_frame.h +++ b/pcbnew/footprint_edit_frame.h @@ -278,6 +278,12 @@ public: */ void AddFootprintToBoard( FOOTPRINT* aFootprint ) override; + /** + * Override from PCB_BASE_FRAME which reloads the footprint from the library without + * setting the footprint watcher + */ + void ReloadFootprint( FOOTPRINT* aFootprint ) override; + /** * Update visible items after a language change. */ diff --git a/pcbnew/footprint_libraries_utils.cpp b/pcbnew/footprint_libraries_utils.cpp index 937fb92d9e..004850d4aa 100644 --- a/pcbnew/footprint_libraries_utils.cpp +++ b/pcbnew/footprint_libraries_utils.cpp @@ -53,6 +53,7 @@ #include "footprint_viewer_frame.h" #include #include +#include // unique, "file local" translations: diff --git a/pcbnew/footprint_viewer_frame.cpp b/pcbnew/footprint_viewer_frame.cpp index a29428dd13..e2c98891c5 100644 --- a/pcbnew/footprint_viewer_frame.cpp +++ b/pcbnew/footprint_viewer_frame.cpp @@ -68,9 +68,10 @@ using namespace std::placeholders; -#define NEXT_PART 1 #define NEW_PART 0 -#define PREVIOUS_PART -1 +#define NEXT_PART 1 +#define PREVIOUS_PART 2 +#define RELOAD_PART 3 BEGIN_EVENT_TABLE( FOOTPRINT_VIEWER_FRAME, PCB_BASE_FRAME ) @@ -260,7 +261,10 @@ FOOTPRINT_VIEWER_FRAME::FOOTPRINT_VIEWER_FRAME( KIWAY* aKiway, wxWindow* aParent FOOTPRINT* footprint = loadFootprint( id ); if( footprint ) + { GetBoard()->Add( footprint ); + setFPWatcher( footprint ); + } } drawPanel->DisplayBoard( m_pcb ); @@ -361,6 +365,7 @@ FOOTPRINT_VIEWER_FRAME::~FOOTPRINT_VIEWER_FRAME() // Be sure any event cannot be fired after frame deletion: GetCanvas()->SetEvtHandlerEnabled( false ); m_fpList->Disconnect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( FOOTPRINT_VIEWER_FRAME::DClickOnFootprintList ), nullptr, this ); + setFPWatcher( nullptr ); } @@ -753,42 +758,7 @@ void FOOTPRINT_VIEWER_FRAME::ClickOnFootprintList( wxCommandEvent& aEvent ) if( getCurFootprintName().CmpNoCase( name ) != 0 ) { setCurFootprintName( name ); - - // Delete the current footprint (MUST reset tools first) - GetToolManager()->ResetTools( TOOL_BASE::MODEL_RELOAD ); - - GetBoard()->DeleteAllFootprints(); - GetBoard()->GetNetInfo().RemoveUnusedNets(); - - LIB_ID id; - id.SetLibNickname( getCurNickname() ); - id.SetLibItemName( getCurFootprintName() ); - - FOOTPRINT* footprint = nullptr; - - try - { - footprint = loadFootprint( id ); - } - catch( const IO_ERROR& ioe ) - { - wxString msg = wxString::Format( _( "Could not load footprint '%s' from library '%s'." - "\n\n%s" ), - getCurFootprintName(), - getCurNickname(), - ioe.Problem() ); - DisplayError( this, msg ); - } - - if( footprint ) - displayFootprint( footprint ); - - UpdateTitle(); - - updateView(); - - GetCanvas()->Refresh(); - Update3DView( true, true ); + SelectAndViewFootprint( NEW_PART ); } } @@ -1049,6 +1019,14 @@ void FOOTPRINT_VIEWER_FRAME::OnUpdateFootprintButton( wxUpdateUIEvent& aEvent ) } +void FOOTPRINT_VIEWER_FRAME::ReloadFootprint( FOOTPRINT* aFootprint ) +{ + setCurNickname( aFootprint->GetFPID().GetLibNickname() ); + setCurFootprintName( aFootprint->GetFPID().GetLibItemName() ); + SelectAndViewFootprint( RELOAD_PART ); +} + + void FOOTPRINT_VIEWER_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail ) { const std::string& payload = mail.GetPayload(); @@ -1247,6 +1225,9 @@ void FOOTPRINT_VIEWER_FRAME::SelectAndViewFootprint( int aMode ) if( footprint ) displayFootprint( footprint ); + if( aMode != RELOAD_PART ) + setFPWatcher( footprint ); + Update3DView( true, true ); updateView(); } diff --git a/pcbnew/footprint_viewer_frame.h b/pcbnew/footprint_viewer_frame.h index e2444c4f71..1c44402265 100644 --- a/pcbnew/footprint_viewer_frame.h +++ b/pcbnew/footprint_viewer_frame.h @@ -72,6 +72,12 @@ public: */ void OnUpdateFootprintButton( wxUpdateUIEvent& aEvent ); + /** + * Override from PCB_BASE_FRAME which reloads the footprint from the library without + * setting the footprint watcher + */ + void ReloadFootprint( FOOTPRINT* aFootprint ) override; + ///< @copydoc EDADRAW_FRAME::UpdateMsgPanel void UpdateMsgPanel() override; diff --git a/pcbnew/pcb_base_frame.cpp b/pcbnew/pcb_base_frame.cpp index b72d3a5d03..6fcc3354d7 100644 --- a/pcbnew/pcb_base_frame.cpp +++ b/pcbnew/pcb_base_frame.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,7 @@ PCB_BASE_FRAME::PCB_BASE_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAME_T aFrame m_originTransforms( *this ), m_spaceMouse( nullptr ) { + m_watcherDebounceTimer.Bind( wxEVT_TIMER, &PCB_BASE_FRAME::OnFpChangeDebounceTimer, this ); } @@ -1138,3 +1140,115 @@ void PCB_BASE_FRAME::SetDisplayOptions( const PCB_DISPLAY_OPTIONS& aOptions, boo if( aRefresh ) canvas->Refresh(); } + + +void PCB_BASE_FRAME::setFPWatcher( FOOTPRINT* aFootprint ) +{ + Unbind( wxEVT_FSWATCHER, &PCB_BASE_FRAME::OnFPChange, this ); + + if( !aFootprint ) + { + m_watcher.reset(); + return; + } + + wxString libfullname; + FP_LIB_TABLE* tbl = Prj().PcbFootprintLibs(); + + if( !aFootprint || !tbl ) + return; + + try + { + const FP_LIB_TABLE_ROW* row = tbl->FindRow( aFootprint->GetFPID().GetLibNickname() ); + + if( !row ) + return; + + libfullname = row->GetFullURI( true ); + } + catch( const std::exception& e ) + { + DisplayInfoMessage( this, e.what() ); + return; + } + catch( const IO_ERROR& error ) + { + wxLogTrace( "KICAD_LIB_WATCH", "Error: %s", error.What() ); + return; + } + + m_watcherFileName.Assign( libfullname, aFootprint->GetFPID().GetLibItemName(), + KiCadFootprintFileExtension ); + + if( !m_watcherFileName.FileExists() ) + return; + + m_watcherLastModified = m_watcherFileName.GetModificationTime(); + + Bind( wxEVT_FSWATCHER, &PCB_BASE_FRAME::OnFPChange, this ); + m_watcher = std::make_unique(); + m_watcher->SetOwner( this ); + + wxFileName fn; + fn.AssignDir( m_watcherFileName.GetPath() ); + fn.DontFollowLink(); + + m_watcher->AddTree( fn ); +} + + +void PCB_BASE_FRAME::OnFPChange( wxFileSystemWatcherEvent& aEvent ) +{ + if( aEvent.GetPath() != m_watcherFileName.GetFullPath() ) + return; + + // Start the debounce timer (set to 1 second) + if( !m_watcherDebounceTimer.StartOnce( 1000 ) ) + { + wxLogTrace( "KICAD_LIB_WATCH", "Failed to start the debounce timer" ); + return; + } +} + + +void PCB_BASE_FRAME::OnFpChangeDebounceTimer( wxTimerEvent& aEvent ) +{ + wxLogTrace( "KICAD_LIB_WATCH", "OnFpChangeDebounceTimer" ); + + // Disable logging to avoid spurious messages and check if the file has changed + wxLog::EnableLogging( false ); + wxDateTime lastModified = m_watcherFileName.GetModificationTime(); + wxLog::EnableLogging( true ); + + if( lastModified == m_watcherLastModified || !lastModified.IsValid() ) + return; + + m_watcherLastModified = lastModified; + + FOOTPRINT* fp = GetBoard()->GetFirstFootprint(); + FP_LIB_TABLE* tbl = Prj().PcbFootprintLibs(); + + if( !fp || !tbl ) + return; + + if( !GetScreen()->IsContentModified() + || IsOK( this, _( "The library containing the current footprint has changed.\n" + "Do you want to reload the footprint?" ) ) ) + { + wxString fpname = fp->GetFPID().GetLibItemName(); + wxString nickname = fp->GetFPID().GetLibNickname(); + + try + { + FOOTPRINT* newfp = tbl->FootprintLoad( nickname, fpname ); + + if( newfp ) + ReloadFootprint( newfp ); + } + catch( const IO_ERROR& ioe ) + { + DisplayError( this, ioe.What() ); + } + } +} \ No newline at end of file