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
This commit is contained in:
Seth Hillbrand 2023-05-31 13:37:58 -07:00
parent d94fd5f979
commit 14f6e32c74
23 changed files with 579 additions and 50 deletions

View File

@ -222,10 +222,9 @@ bool EDA_DRAW_FRAME::LockFile( const wxString& aFileName )
m_file_checker = std::make_unique<LOCKFILE>( 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();
}

View File

@ -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;

View File

@ -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 )

View File

@ -95,6 +95,7 @@ public:
SELECTION& GetCurrentSelection() override;
void ReloadFootprint( FOOTPRINT* aFootprint ) override;
DECLARE_EVENT_TABLE()
protected:

View File

@ -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<wxFileSystemWatcher>();
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 );
}
}

View File

@ -35,8 +35,11 @@
#include <utility>
#include <vector>
#include <wx/event.h>
#include <wx/fswatcher.h>
#include <wx/datetime.h>
#include <wx/gdicmn.h>
#include <wx/string.h>
#include <wx/timer.h>
#include <template_fieldnames.h>
@ -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<wxFileSystemWatcher> m_watcher;
wxFileName m_watcherFileName;
wxDateTime m_watcherLastModified;
wxTimer m_watcherDebounceTimer;
NL_SCHEMATIC_PLUGIN* m_spaceMouse;
};

View File

@ -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();

View File

@ -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.

View File

@ -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 )

View File

@ -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.

View File

@ -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<SCH_SCREEN> aScreen ) :
m_screen( std::move( aScreen ) ),

View File

@ -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.

View File

@ -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:;
}
}

View File

@ -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 );

View File

@ -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_

View File

@ -39,6 +39,9 @@
#include <richio.h>
#include <vector>
#include <wx/fswatcher.h>
#include <wx/datetime.h>
#include <wx/timer.h>
/* 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<wxFileSystemWatcher> m_watcher;
wxFileName m_watcherFileName;
wxDateTime m_watcherLastModified;
wxTimer m_watcherDebounceTimer;
std::vector<wxEvtHandler*> m_boardChangeListeners;
};

View File

@ -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();
}

View File

@ -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<FOOTPRINT*>( 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;

View File

@ -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.
*/

View File

@ -53,6 +53,7 @@
#include "footprint_viewer_frame.h"
#include <wx/choicdlg.h>
#include <wx/filedlg.h>
#include <wx/fswatcher.h>
// unique, "file local" translations:

View File

@ -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();
}

View File

@ -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;

View File

@ -51,6 +51,7 @@
#include <pcb_base_frame.h>
#include <pcb_draw_panel_gal.h>
#include <pgm_base.h>
#include <wildcards_and_files_ext.h>
#include <zoom_defines.h>
#include <math/vector2d.h>
@ -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<wxFileSystemWatcher>();
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() );
}
}
}