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