/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2013 CERN (www.cern.ch) * Copyright The 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 "kicad_id.h" #include "pcm.h" #include "pgm_kicad.h" #include "project_tree_pane.h" #include "local_history_pane.h" #include "widgets/bitmap_button.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include <../pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h> // for SEXPR_BOARD_FILE_VERSION def #ifdef __WXMAC__ #include #include #endif #include "kicad_manager_frame.h" #include "settings/kicad_settings.h" #include #define EDITORS_CAPTION _( "Editors" ) #define PROJECT_FILES_CAPTION _( "Project Files" ) #define ID_INIT_WATCHED_PATHS 52913 #define SEP() wxFileName::GetPathSeparator() // Menubar and toolbar event table BEGIN_EVENT_TABLE( KICAD_MANAGER_FRAME, EDA_BASE_FRAME ) // Window events EVT_SIZE( KICAD_MANAGER_FRAME::OnSize ) EVT_IDLE( KICAD_MANAGER_FRAME::OnIdle ) // Menu events EVT_MENU( wxID_EXIT, KICAD_MANAGER_FRAME::OnExit ) EVT_MENU( ID_EDIT_LOCAL_FILE_IN_TEXT_EDITOR, KICAD_MANAGER_FRAME::OnOpenFileInTextEditor ) EVT_MENU( ID_EDIT_ADVANCED_CFG, KICAD_MANAGER_FRAME::OnEditAdvancedCfg ) EVT_MENU( ID_IMPORT_CADSTAR_ARCHIVE_PROJECT, KICAD_MANAGER_FRAME::OnImportCadstarArchiveFiles ) EVT_MENU( ID_IMPORT_EAGLE_PROJECT, KICAD_MANAGER_FRAME::OnImportEagleFiles ) EVT_MENU( ID_IMPORT_EASYEDA_PROJECT, KICAD_MANAGER_FRAME::OnImportEasyEdaFiles ) EVT_MENU( ID_IMPORT_EASYEDAPRO_PROJECT, KICAD_MANAGER_FRAME::OnImportEasyEdaProFiles ) EVT_MENU( ID_IMPORT_ALTIUM_PROJECT, KICAD_MANAGER_FRAME::OnImportAltiumProjectFiles ) EVT_MENU( ID_IMPORT_PADS_PROJECT, KICAD_MANAGER_FRAME::OnImportPadsProjectFiles ) EVT_MENU( ID_IMPORT_GEDA_PROJECT, KICAD_MANAGER_FRAME::OnImportGedaFiles ) // Range menu events EVT_MENU_RANGE( ID_FILE1, ID_FILEMAX, KICAD_MANAGER_FRAME::OnFileHistory ) EVT_MENU( ID_FILE_LIST_CLEAR, KICAD_MANAGER_FRAME::OnClearFileHistory ) // Special functions EVT_MENU( ID_INIT_WATCHED_PATHS, KICAD_MANAGER_FRAME::OnChangeWatchedPaths ) // Drop files event EVT_DROP_FILES( KICAD_MANAGER_FRAME::OnDropFiles ) END_EVENT_TABLE() // See below the purpose of this include #include KICAD_MANAGER_FRAME::KICAD_MANAGER_FRAME( wxWindow* parent, const wxString& title, const wxPoint& pos, const wxSize& size ) : EDA_BASE_FRAME( parent, KICAD_MAIN_FRAME_T, title, pos, size, KICAD_DEFAULT_DRAWFRAME_STYLE, KICAD_MANAGER_FRAME_NAME, &::Kiway, unityScale ), m_openSavedWindows( false ), m_restoredFromHistory( false ), m_active_project( false ), m_showHistoryPanel( false ), m_projectTreePane( nullptr ), m_historyPane( nullptr ), m_launcher( nullptr ), m_lastToolbarIconSize( 0 ), m_pcmButton( nullptr ), m_pcmUpdateCount( 0 ) { const int defaultLeftWinWidth = FromDIP( 250 ); m_leftWinWidth = defaultLeftWinWidth; // Default value m_aboutTitle = "KiCad"; // JPC: A very ugly hack to fix an issue on Linux: if the wxbase315u_xml_gcc_custom.so is // used **only** in PCM, it is not found in some cases at run time. // So just use it in the main module to avoid a not found issue // wxbase315u_xml_gcc_custom shared object when launching Kicad wxXmlDocument dummy; // Create the status line (bottom of the frame). Left half is for project name; right half // is for Reporter (currently used by archiver/unarchiver and PCM). // Note: this is a KISTATUSBAR status bar. Therefore the specified number of fields // is the extra number of fields, not the full field count. // We need here 2 fields: the extra fiels to display the project name, and another field // to display a info (specific to Windows) using the FIELD_OFFSET_BGJOB_TEXT id offset (=1) // So the extra field count is 1 CreateStatusBar( 2 ); Pgm().GetBackgroundJobMonitor().RegisterStatusBar( (KISTATUSBAR*) GetStatusBar() ); Pgm().GetNotificationsManager().RegisterStatusBar( (KISTATUSBAR*) GetStatusBar() ); Pgm().RegisterLibraryLoadStatusBar( (KISTATUSBAR*) GetStatusBar() ); GetStatusBar()->SetFont( KIUI::GetStatusFont( this ) ); // Give an icon wxIcon icon; wxIconBundle icon_bundle; if( IsNightlyVersion()) { icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_kicad_nightly, 48 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_kicad_nightly, 128 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_kicad_nightly, 256 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_kicad_nightly_32 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_kicad_nightly_16 ) ); icon_bundle.AddIcon( icon ); } else { icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_kicad, 48 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_kicad, 128 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_kicad, 256 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_kicad_32 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_kicad_16 ) ); icon_bundle.AddIcon( icon ); } SetIcons( icon_bundle ); // Load the settings LoadSettings( config() ); // Left window: is the box which display tree project m_projectTreePane = new PROJECT_TREE_PANE( this ); setupTools(); setupUIConditions(); m_toolbarSettings = GetToolbarSettings( "kicad-toolbars" ); configureToolbars(); RecreateToolbars(); ReCreateMenuBar(); m_auimgr.SetManagedWindow( this ); m_auimgr.SetFlags( wxAUI_MGR_LIVE_RESIZE ); m_auimgr.AddPane( m_tbLeft, EDA_PANE().VToolbar().Name( "TopMainToolbar" ).Left().Layer( 2 ) ); // There is no wxAUIPaneInfo::SetSize(), but a trick is to use MinSize() to set the required pane width, // and after give a reasonable MinSize value. m_auimgr.AddPane( m_projectTreePane, EDA_PANE().Palette().Name( "ProjectTree" ).Left().Layer( 1 ) .Caption( PROJECT_FILES_CAPTION ).PaneBorder( false ) .MinSize( m_leftWinWidth, -1 ).Floatable( false ).Movable( false ) ); m_historyPane = new LOCAL_HISTORY_PANE( this ); m_auimgr.AddPane( m_historyPane, EDA_PANE().Palette().Name( "LocalHistory" ).Left().Layer( 1 ).Position( 1 ) .Caption( _( "Local History" ) ).PaneBorder( false ) .Floatable( false ).Movable( false ).CloseButton( true ).Hide() ); if( m_showHistoryPanel ) m_auimgr.GetPane( m_historyPane ).Show(); wxSize client_size = GetClientSize(); m_notebook = new wxAuiNotebook( this, wxID_ANY, wxPoint( client_size.x, client_size.y ), FromDIP( wxSize( 700, 590 ) ), wxAUI_NB_TOP | wxAUI_NB_CLOSE_ON_ALL_TABS | wxAUI_NB_TAB_MOVE | wxAUI_NB_SCROLL_BUTTONS | wxNO_BORDER ); m_notebook->SetArtProvider( new WX_AUI_TAB_ART() ); m_notebook->Bind( wxEVT_AUINOTEBOOK_PAGE_CLOSE, &KICAD_MANAGER_FRAME::onNotebookPageCloseRequest, this ); m_notebook->Bind( wxEVT_AUINOTEBOOK_PAGE_CLOSED, &KICAD_MANAGER_FRAME::onNotebookPageCountChanged, this ); m_launcher = new PANEL_KICAD_LAUNCHER( m_notebook ); m_notebook->Freeze(); m_launcher->SetClosable( false ); m_notebook->AddPage( m_launcher, EDITORS_CAPTION, false ); m_notebook->SetTabCtrlHeight( 0 ); m_notebook->Thaw(); m_auimgr.AddPane( m_notebook, EDA_PANE().Canvas().Name( "Editors" ).Center().Caption( EDITORS_CAPTION ) .PaneBorder( false ).MinSize( m_notebook->GetBestSize() ) ); m_auimgr.Update(); // Now the actual m_projectTreePane size is set, give it a reasonable min width m_auimgr.GetPane( m_projectTreePane ).MinSize( defaultLeftWinWidth, FromDIP( 80 ) ); wxSizer* mainSizer = GetSizer(); // Only fit the initial window size the first time KiCad is run. if( mainSizer && config()->m_Window.state.size_x == 0 && config()->m_Window.state.size_y == 0 ) { Layout(); mainSizer->Fit( this ); Center(); } if( ADVANCED_CFG::GetCfg().m_HideVersionFromTitle ) SetTitle( wxT( "KiCad" ) ); else SetTitle( wxString( "KiCad " ) + GetMajorMinorVersion() ); // Do not let the messages window have initial focus m_projectTreePane->SetFocus(); // Init for dropping files m_acceptedExts.emplace( FILEEXT::ProjectFileExtension, &KICAD_MANAGER_ACTIONS::loadProject ); m_acceptedExts.emplace( FILEEXT::LegacyProjectFileExtension, &KICAD_MANAGER_ACTIONS::loadProject ); // Gerber files // Note that all gerber files are aliased as GerberFileExtension m_acceptedExts.emplace( FILEEXT::GerberFileExtension, &KICAD_MANAGER_ACTIONS::viewDroppedGerbers ); m_acceptedExts.emplace( FILEEXT::GerberJobFileExtension, &KICAD_MANAGER_ACTIONS::viewDroppedGerbers ); m_acceptedExts.emplace( FILEEXT::DrillFileExtension, &KICAD_MANAGER_ACTIONS::viewDroppedGerbers ); DragAcceptFiles( true ); } KICAD_MANAGER_FRAME::~KICAD_MANAGER_FRAME() { Unbind( wxEVT_CHAR, &TOOL_DISPATCHER::DispatchWxEvent, m_toolDispatcher ); Unbind( wxEVT_CHAR_HOOK, &TOOL_DISPATCHER::DispatchWxEvent, m_toolDispatcher ); m_notebook->Unbind( wxEVT_AUINOTEBOOK_PAGE_CLOSE, &KICAD_MANAGER_FRAME::onNotebookPageCloseRequest, this ); m_notebook->Unbind( wxEVT_AUINOTEBOOK_PAGE_CLOSED, &KICAD_MANAGER_FRAME::onNotebookPageCountChanged, this ); Pgm().GetBackgroundJobMonitor().UnregisterStatusBar( (KISTATUSBAR*) GetStatusBar() ); Pgm().GetNotificationsManager().UnregisterStatusBar( (KISTATUSBAR*) GetStatusBar() ); Pgm().UnregisterLibraryLoadStatusBar( (KISTATUSBAR*) GetStatusBar() ); // Shutdown all running tools if( m_toolManager ) m_toolManager->ShutdownAllTools(); if( m_pcm ) m_pcm->StopBackgroundUpdate(); // Stop update manager before tearing down the AUI framework. The update // task runs on the thread pool and may call CallAfter on this frame, so it // must complete before we uninitialize AUI or destroy child windows. m_updateManager.reset(); delete m_actions; delete m_toolManager; delete m_toolDispatcher; m_auimgr.UnInit(); } void KICAD_MANAGER_FRAME::HideTabsIfNeeded() { if( m_notebook->GetPageCount() == 1 ) m_notebook->SetTabCtrlHeight( 0 ); else m_notebook->SetTabCtrlHeight( -1 ); } void KICAD_MANAGER_FRAME::onNotebookPageCountChanged( wxAuiNotebookEvent& evt ) { HideTabsIfNeeded(); } void KICAD_MANAGER_FRAME::onNotebookPageCloseRequest( wxAuiNotebookEvent& evt ) { wxAuiNotebook* notebook = (wxAuiNotebook*) evt.GetEventObject(); wxWindow* page = notebook->GetPage( evt.GetSelection() ); if( PANEL_NOTEBOOK_BASE* panel = dynamic_cast( page ) ) { if( panel->GetClosable() ) { if( !panel->GetCanClose() ) evt.Veto(); CallAfter( [this]() { SaveOpenJobSetsToLocalSettings(); } ); } else { evt.Veto(); } } } wxStatusBar* KICAD_MANAGER_FRAME::OnCreateStatusBar( int number, long style, wxWindowID id, const wxString& name ) { KISTATUSBAR* sb = new KISTATUSBAR( number, this, id, static_cast( KISTATUSBAR::NOTIFICATION_ICON | KISTATUSBAR::CANCEL_BUTTON | KISTATUSBAR::WARNING_ICON ) ); size_t sbFieldCnt = static_cast( sb->GetFieldsCount() ); std::vector sbFieldSizes( sbFieldCnt ); for( size_t i = 0; i < sbFieldCnt; i++ ) sbFieldSizes[i] = sb->GetStatusWidth( static_cast( i ) ); // Field 0 (the project path) is the only stretchable field so it uses all the leftover width // before ellipsizing. Field 1 gets a fixed width sized to its watcher text; longer content // (archive progress) is ellipsized rather than allowed to steal the path's width. if( sbFieldCnt > 0 ) sbFieldSizes[0] = -1; if( sbFieldCnt > 1 ) { int margin = KIUI::GetTextSize( wxT( "XX" ), sb ).x; int watcherWidth = std::max( KIUI::GetTextSize( _( "Local path: monitoring folder changes" ), sb ).x, KIUI::GetTextSize( _( "Network path: not monitoring folder changes" ), sb ).x ); sbFieldSizes[1] = watcherWidth + margin; } sb->SetStatusWidths( sbFieldCnt, sbFieldSizes.data() ); return sb; } void KICAD_MANAGER_FRAME::CreatePCM() { // creates the PLUGIN_CONTENT_MANAGER, if not exists if( m_pcm ) return; m_pcm = std::make_shared( [this]( int aUpdateCount ) { if( Pgm().m_Quitting ) return; m_pcmUpdateCount = aUpdateCount; if( aUpdateCount > 0 ) { Pgm().GetNotificationsManager().CreateOrUpdate( wxS( "pcm" ), _( "PCM Updates Available" ), wxString::Format( _( "%d package update(s) available" ), aUpdateCount ), wxT( "" ) ); } else { Pgm().GetNotificationsManager().Remove( wxS( "pcm" ) ); } CallAfter( [this]() { updatePcmButtonBadge(); } ); }); m_pcm->SetRepositoryList( kicadSettings()->m_PcmRepositories ); } void KICAD_MANAGER_FRAME::setupTools() { // Create the manager m_toolManager = new TOOL_MANAGER; m_toolManager->SetEnvironment( nullptr, nullptr, nullptr, config(), this ); m_actions = new KICAD_MANAGER_ACTIONS(); m_toolDispatcher = new TOOL_DISPATCHER( m_toolManager ); // Attach the events to the tool dispatcher Bind( wxEVT_CHAR, &TOOL_DISPATCHER::DispatchWxEvent, m_toolDispatcher ); Bind( wxEVT_CHAR_HOOK, &TOOL_DISPATCHER::DispatchWxEvent, m_toolDispatcher ); // Register tools m_toolManager->RegisterTool( new COMMON_CONTROL ); m_toolManager->RegisterTool( new KICAD_MANAGER_CONTROL ); m_toolManager->InitTools(); } void KICAD_MANAGER_FRAME::setupUIConditions() { EDA_BASE_FRAME::setupUIConditions(); ACTION_MANAGER* manager = m_toolManager->GetActionManager(); wxASSERT( manager ); auto activeProject = [this] ( const SELECTION& ) { return m_active_project; }; #define ENABLE( x ) ACTION_CONDITIONS().Enable( x ) ACTION_CONDITIONS activeProjectCond; activeProjectCond.Enable( activeProject ); manager->SetConditions( ACTIONS::saveAs, activeProjectCond ); manager->SetConditions( KICAD_MANAGER_ACTIONS::closeProject, activeProjectCond ); manager->SetConditions( KICAD_MANAGER_ACTIONS::archiveProject, activeProjectCond ); manager->SetConditions( KICAD_MANAGER_ACTIONS::newJobsetFile, activeProjectCond ); manager->SetConditions( KICAD_MANAGER_ACTIONS::openJobsetFile, activeProjectCond ); auto historyCond = [this]( const SELECTION& ) { return HistoryPanelShown(); }; manager->SetConditions( KICAD_MANAGER_ACTIONS::showLocalHistory, ACTION_CONDITIONS().Check( historyCond ) ); // These are just here for text boxes, search boxes, etc. in places such as the standard // file dialogs. manager->SetConditions( ACTIONS::cut, ENABLE( SELECTION_CONDITIONS::ShowNever ) ); manager->SetConditions( ACTIONS::copy, ENABLE( SELECTION_CONDITIONS::ShowNever ) ); manager->SetConditions( ACTIONS::paste, ENABLE( SELECTION_CONDITIONS::ShowNever ) ); #undef ENABLE } wxWindow* KICAD_MANAGER_FRAME::GetToolCanvas() const { return m_projectTreePane; } APP_SETTINGS_BASE* KICAD_MANAGER_FRAME::config() const { APP_SETTINGS_BASE* ret = PgmTop().PgmSettings(); wxASSERT( ret ); return ret; } KICAD_SETTINGS* KICAD_MANAGER_FRAME::kicadSettings() const { KICAD_SETTINGS* ret = dynamic_cast( config() ); wxASSERT( ret ); return ret; } void KICAD_MANAGER_FRAME::PreloadAllLibraries() { CallAfter( [&]() { KIFACE *schface = Kiway().KiFACE( KIWAY::FACE_SCH ); schface->PreloadLibraries( &Kiway() ); KIFACE *pcbface = Kiway().KiFACE( KIWAY::FACE_PCB ); pcbface->PreloadLibraries( &Kiway() ); Pgm().PreloadDesignBlockLibraries( &Kiway() ); } ); } wxString KICAD_MANAGER_FRAME::GetCurrentFileName() const { return GetProjectFileName(); } const wxString KICAD_MANAGER_FRAME::GetProjectFileName() const { return Pgm().GetSettingsManager().IsProjectOpen() ? Prj().GetProjectFullName() : wxString( wxEmptyString ); } const wxString KICAD_MANAGER_FRAME::SchFileName() { wxFileName fn( GetProjectFileName() ); fn.SetExt( FILEEXT::KiCadSchematicFileExtension ); return fn.GetFullPath(); } const wxString KICAD_MANAGER_FRAME::SchLegacyFileName() { wxFileName fn( GetProjectFileName() ); fn.SetExt( FILEEXT::LegacySchematicFileExtension ); return fn.GetFullPath(); } const wxString KICAD_MANAGER_FRAME::PcbFileName() { wxFileName fn( GetProjectFileName() ); fn.SetExt( FILEEXT::PcbFileExtension ); return fn.GetFullPath(); } const wxString KICAD_MANAGER_FRAME::PcbLegacyFileName() { wxFileName fn( GetProjectFileName() ); fn.SetExt( FILEEXT::LegacyPcbFileExtension ); return fn.GetFullPath(); } void KICAD_MANAGER_FRAME::ReCreateTreePrj() { m_projectTreePane->ReCreateTreePrj(); } const SEARCH_STACK& KICAD_MANAGER_FRAME::sys_search() { return PgmTop().SysSearch(); } wxString KICAD_MANAGER_FRAME::help_name() { return PgmTop().GetHelpFileName(); } void KICAD_MANAGER_FRAME::OnSize( wxSizeEvent& event ) { if( m_auimgr.GetManagedWindow() ) m_auimgr.Update(); event.Skip(); } void KICAD_MANAGER_FRAME::DoWithAcceptedFiles() { // All fileNames are now in m_AcceptedFiles vector. // Check if contains a project file name and load project. // If not, open files in dedicated app. for( const wxFileName& fileName : m_AcceptedFiles ) { wxString ext = fileName.GetExt(); if( ext == FILEEXT::ProjectFileExtension || ext == FILEEXT::LegacyProjectFileExtension ) { wxString fn = fileName.GetFullPath(); m_toolManager->RunAction( *m_acceptedExts.at( fileName.GetExt() ), &fn ); return; } } // Then stock gerber files in gerberFiles and run action for other files. wxString gerberFiles; // Gerbview editor should be able to open Gerber and drill files for( const wxFileName& fileName : m_AcceptedFiles ) { wxString ext = fileName.GetExt(); if( ext == FILEEXT::GerberJobFileExtension || ext == FILEEXT::DrillFileExtension || FILEEXT::IsGerberFileExtension( ext ) ) { gerberFiles += wxT( '\"' ); gerberFiles += fileName.GetFullPath() + wxT( '\"' ); gerberFiles = gerberFiles.Pad( 1 ); } else { wxString fn = fileName.GetFullPath(); m_toolManager->RunAction( *m_acceptedExts.at( fileName.GetExt() ), &fn ); } } // Execute Gerbviewer if( !gerberFiles.IsEmpty() ) { wxString fullEditorName = FindKicadFile( GERBVIEW_EXE ); if( wxFileExists( fullEditorName ) ) { wxString command = fullEditorName + " " + gerberFiles; m_toolManager->RunAction( *m_acceptedExts.at( FILEEXT::GerberFileExtension ), &command ); } } } bool KICAD_MANAGER_FRAME::canCloseWindow( wxCloseEvent& aEvent ) { KICAD_SETTINGS* settings = kicadSettings(); settings->m_OpenProjects = GetSettingsManager()->GetOpenProjects(); for( size_t i = 0; i < m_notebook->GetPageCount(); i++ ) { wxWindow* page = m_notebook->GetPage( i ); if( PANEL_NOTEBOOK_BASE* panel = dynamic_cast( page ) ) { if( !panel->GetCanClose() ) return false; } } // CloseProject will recursively ask all the open editors if they need to save changes. // If any of them cancel then we need to cancel closing the KICAD_MANAGER_FRAME. if( CloseProject( true ) ) { // Don't propagate event to frames which have already been closed aEvent.StopPropagation(); return true; } else { if( aEvent.CanVeto() ) aEvent.Veto(); return false; } } void KICAD_MANAGER_FRAME::doCloseWindow() { #ifdef _WINDOWS_ // For some obscure reason, on Windows, when killing Kicad from the Windows task manager // if a editor frame (schematic, library, board editor or fp editor) is open and has // some edition to save, OnCloseWindow is run twice *at the same time*, creating race // conditions between OnCloseWindow() code. // Therefore I added (JPC) a ugly hack to discard the second call (unwanted) during // execution of the first call (only one call is right). // Note also if there is no change made in editors, this behavior does not happen. static std::atomic lock_close_event( 0 ); if( ++lock_close_event > 1 ) // Skip extra calls { return; } #endif m_projectTreePane->Show( false ); Pgm().m_Quitting = true; Destroy(); #ifdef _WINDOWS_ lock_close_event = 0; // Reenable event management #endif } void KICAD_MANAGER_FRAME::SaveOpenJobSetsToLocalSettings( bool aIsExplicitUserSave ) { PROJECT_LOCAL_SETTINGS& cfg = Prj().GetLocalSettings(); if( !aIsExplicitUserSave && !cfg.ShouldAutoSave() ) return; cfg.m_OpenJobSets.clear(); for( size_t i = 0; i < m_notebook->GetPageCount(); i++ ) { if( PANEL_JOBSET* jobset = dynamic_cast( m_notebook->GetPage( i ) ) ) { wxFileName jobsetFn( jobset->GetFilePath() ); jobsetFn.MakeRelativeTo( Prj().GetProjectPath() ); cfg.m_OpenJobSets.emplace_back( jobsetFn.GetFullPath() ); } } cfg.SaveToFile( Prj().GetProjectPath() ); } void KICAD_MANAGER_FRAME::OnExit( wxCommandEvent& event ) { Close( true ); } bool KICAD_MANAGER_FRAME::CloseProject( bool aSave ) { if( !Kiway().PlayersClose( false ) ) return false; // Abort any in-progress background load, since the threads depend on the project not changing KIFACE *schface = Kiway().KiFACE( KIWAY::FACE_SCH ); schface->CancelPreload(); KIFACE *pcbface = Kiway().KiFACE( KIWAY::FACE_PCB ); pcbface->CancelPreload(); // Save the project file for the currently loaded project. if( m_active_project ) { SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager(); if( Prj().GetLocalSettings().ShouldAutoSave() && Prj().GetProjectFile().ShouldAutoSave() ) { mgr.TriggerBackupIfNeeded( NULL_REPORTER::GetInstance() ); if( aSave ) mgr.SaveProject(); } // Ensure the Last_Save tag is at HEAD before closing. This handles the case where // autosave commits were made after the last explicit save - without this, the next // project load would offer to restore the autosave state, which is incorrect after // a clean close. wxString projPath = Prj().GetProjectPath(); // Wait for any in-flight autosave so the HEAD check below isn't racing it. Kiway().LocalHistory().WaitForPendingSave(); if( Pgm().GetCommonSettings()->AutosaveUsesLocalHistory() && !projPath.IsEmpty() && Kiway().LocalHistory().HistoryExists( projPath ) ) { if( Kiway().LocalHistory().HeadNewerThanLastSave( projPath ) ) { // Tag unconditionally: even on no-op snapshots Last_Save must anchor at HEAD. Kiway().LocalHistory().CommitFullProjectSnapshot( projPath, wxS( "Close" ) ); Kiway().LocalHistory().TagSave( projPath, wxS( "project" ) ); } } // The editors clean up autosaves for sheets actually dirtied in their session. // Anything still on disk here was deferred by the user in the recovery dialog // and must survive so the dialog can offer it again on the next open. m_active_project = false; // Enforce local history size limit (if enabled) once all pending saves/backups are done. if( Pgm().GetCommonSettings() && Pgm().GetCommonSettings()->AutosaveUsesLocalHistory() ) { unsigned long long int limit = Pgm().GetCommonSettings()->m_Backup.limit_total_size; if( limit > 0 ) { WX_PROGRESS_REPORTER reporter( this, _( "Local History" ), 3, PR_NO_ABORT ); Kiway().LocalHistory().EnforceSizeLimit( Prj().GetProjectPath(), (size_t) limit, &reporter ); } } // Unregister the project saver before unloading the project to prevent // dangling references Kiway().LocalHistory().UnregisterSaver( &Prj() ); mgr.UnloadProject( &Prj() ); } SetStatusText( "" ); // Traverse pages in reverse order so deleting them doesn't mess up our iterator. for( int i = (int) m_notebook->GetPageCount() - 1; i >= 0; i-- ) { wxWindow* page = m_notebook->GetPage( i ); if( PANEL_NOTEBOOK_BASE* panel = dynamic_cast( page ) ) { if( panel->GetProjectTied() ) m_notebook->DeletePage( i ); } } m_projectTreePane->EmptyTreePrj(); HideTabsIfNeeded(); return true; } void KICAD_MANAGER_FRAME::OpenJobsFile( const wxFileName& aFileName, bool aCreate, bool aResaveProjectPreferences ) { for( size_t i = 0; i < m_notebook->GetPageCount(); i++ ) { if( PANEL_JOBSET* panel = dynamic_cast( m_notebook->GetPage( i ) ) ) { if( aFileName.GetFullPath() == panel->GetFilePath() ) { m_notebook->SetSelection( i ); return; } } } try { std::unique_ptr jobsFile = std::make_unique( aFileName.GetFullPath().ToStdString() ); jobsFile->LoadFromFile(); if( aCreate && !aFileName.FileExists() ) { JOBSET_DESTINATION* dest = jobsFile->AddNewDestination( JOBSET_DESTINATION_T::FOLDER ); dest->m_outputHandler->SetOutputPath( aFileName.GetName() ); jobsFile->SaveToFile( wxEmptyString, true ); } PANEL_JOBSET* jobPanel = new PANEL_JOBSET( m_notebook, this, std::move( jobsFile ) ); jobPanel->SetProjectTied( true ); jobPanel->SetClosable( true ); m_notebook->AddPage( jobPanel, aFileName.GetFullName(), true ); HideTabsIfNeeded(); if( aResaveProjectPreferences ) SaveOpenJobSetsToLocalSettings(); } catch( ... ) { DisplayErrorMessage( this, _( "Error opening jobs file" ) ); } } bool KICAD_MANAGER_FRAME::LoadProject( const wxFileName& aProjectFileName ) { // The project file should be valid by the time we get here or something has gone wrong. if( !aProjectFileName.Exists() ) return false; wxString fullPath = aProjectFileName.GetFullPath(); // Check if a lock file already exists BEFORE we try to acquire it. We only want to warn // the user if the lock file pre-existed, not if we're about to create it ourselves. // The actual lock acquisition happens in SETTINGS_MANAGER::LoadProject(). wxFileName lockFn( fullPath ); lockFn.SetName( FILEEXT::LockFilePrefix + lockFn.GetName() ); lockFn.SetExt( lockFn.GetExt() + wxS( "." ) + FILEEXT::LockFileExtension ); bool lockFilePreExisted = lockFn.FileExists(); bool lockOverrideGranted = false; if( lockFilePreExisted ) { // A lock file exists. Create a LOCKFILE to read who owns it and decide what to do. LOCKFILE lockFile( fullPath ); if( !lockFile.Valid() && lockFile.IsLockedByMe() ) { // If we cannot acquire the lock but we appear to be the one who locked it, check to // see if there is another KiCad instance running. If not, then we can override the // lock. This could happen if KiCad crashed or was interrupted. if( !Pgm().SingleInstance()->IsAnotherRunning() ) lockFile.OverrideLock(); } if( !lockFile.Valid() ) { wxString msg; msg.Printf( _( "Project '%s' is already open by '%s' at '%s'." ), fullPath, lockFile.GetUsername(), lockFile.GetHostname() ); if( !AskOverrideLock( this, msg ) ) return false; // User clicked Cancel - abort project loading entirely lockFile.OverrideLock(); lockOverrideGranted = true; } // The LOCKFILE goes out of scope here and releases/removes the lock file. // SETTINGS_MANAGER::LoadProject() will create the actual persistent lock. } // Any open KIFACE's must be closed if they are not part of the new project. // (We never want a KIWAY_PLAYER open on a KIWAY that isn't in the same project.) // User is prompted here to close those KIWAY_PLAYERs: if( !CloseProject( true ) ) return false; m_active_project = true; // NB: when loading a legacy project SETTINGS_MANAGER::LoadProject() will convert it to // current extension. Be very careful with aProjectFileName vs. Prj().GetProjectPath() // from here on out. Pgm().GetSettingsManager().LoadProject( fullPath ); // Propagate lock override decision to the loaded project if( lockOverrideGranted ) Prj().SetLockOverrideGranted( true ); LoadWindowState( aProjectFileName.GetFullName() ); if( aProjectFileName.IsDirWritable() ) SetMruPath( Prj().GetProjectPath() ); if( Pgm().GetCommonSettings()->AutosaveUsesLocalHistory() && Kiway().LocalHistory().HeadNewerThanLastSave( Prj().GetProjectPath() ) ) { wxString head = Kiway().LocalHistory().GetHeadHash( Prj().GetProjectPath() ); KICAD_MESSAGE_DIALOG dlg( this, _( "KiCad found unsaved changes from your last session that are newer than " "the saved project files." ), _( "Recover Unsaved Changes" ), wxYES_NO | wxICON_QUESTION ); dlg.SetExtendedMessage( _( "This can happen if your previous session ended unexpectedly.\n\n" "Choose 'Restore' to recover those changes, or 'Discard' to keep the " "currently saved files." ) ); dlg.SetYesNoLabels( _( "Restore" ), _( "Discard" ) ); if( dlg.ShowModal() == wxID_YES ) { // The dialog above is the confirmation, skip RestoreCommit's own prompt. Kiway().LocalHistory().RestoreCommit( Prj().GetProjectPath(), head, this, false ); } else { // User declined; commit on-disk state and tag unconditionally so Last_Save anchors // at HEAD even if no new commit was needed. Kiway().LocalHistory().CommitFullProjectSnapshot( Prj().GetProjectPath(), wxS( "Declined restore" ) ); Kiway().LocalHistory().TagSave( Prj().GetProjectPath(), wxS( "project" ) ); } } // Save history & window state to disk now. Don't wait around for a crash. KICAD_SETTINGS* settings = kicadSettings(); SaveSettings( settings ); settings->SaveToFile( Pgm().GetSettingsManager().GetPathForSettingsFile( settings ) ); m_projectTreePane->ReCreateTreePrj(); m_historyPane->RefreshHistory( Prj().GetProjectPath() ); for( const wxString& jobset : Prj().GetLocalSettings().m_OpenJobSets ) { wxFileName jobsetFn( jobset ); jobsetFn.MakeAbsolute( Prj().GetProjectPath() ); if( jobsetFn.Exists() ) OpenJobsFile( jobsetFn.GetFullPath(), false, false ); } // Always start with the apps page m_notebook->SetSelection( 0 ); // Rebuild the list of watched paths. // however this is possible only when the main loop event handler is running, // so we use it to run the rebuild function. wxCommandEvent cmd( wxEVT_COMMAND_MENU_SELECTED, ID_INIT_WATCHED_PATHS ); wxPostEvent( this, cmd ); PrintPrjInfo(); KIPLATFORM::APP::RegisterApplicationRestart( aProjectFileName.GetFullPath() ); m_openSavedWindows = true; KIPLATFORM::ENV::AddToRecentDocs( aProjectFileName.GetFullPath() ); // Now that we have a new project, trigger a library preload, which will load in any // project-specific symbol and footprint libraries into the manager PreloadAllLibraries(); return true; } void KICAD_MANAGER_FRAME::CreateNewProject( const wxFileName& aProjectFileName, bool aCreateStubFiles ) { wxCHECK_RET( aProjectFileName.DirExists() && aProjectFileName.IsDirWritable(), "Project folder must exist and be writable to create a new project." ); // If the project is legacy, convert it if( !aProjectFileName.FileExists() ) { wxFileName legacyPro( aProjectFileName ); legacyPro.SetExt( FILEEXT::LegacyProjectFileExtension ); if( legacyPro.FileExists() ) { GetSettingsManager()->LoadProject( legacyPro.GetFullPath() ); GetSettingsManager()->SaveProject(); wxRemoveFile( legacyPro.GetFullPath() ); } else { // Copy template project file from template folder. wxString srcFileName = sys_search().FindValidPath( "kicad.kicad_pro" ); wxFileName destFileName( aProjectFileName ); destFileName.SetExt( FILEEXT::ProjectFileExtension ); // Create a minimal project file if the template project file could not be copied if( !wxFileName::FileExists( srcFileName ) || !wxCopyFile( srcFileName, destFileName.GetFullPath() ) ) { wxFFile file( destFileName.GetFullPath(), "wb" ); if( file.IsOpened() ) file.Write( wxT( "{\n}\n") ); // wxFFile dtor will close the file } } } // Create a "stub" for a schematic root sheet and a board if requested. // It will avoid messages from the schematic editor or the board editor to create a new file // And forces the user to create main files under the right name for the project manager if( aCreateStubFiles ) { wxFileName fn( aProjectFileName.GetFullPath() ); fn.SetExt( FILEEXT::KiCadSchematicFileExtension ); // If a .kicad_sch file does not exist, create a "stub" file ( minimal schematic // file ). if( !fn.FileExists() ) { wxFFile file( fn.GetFullPath(), "wb" ); if( file.IsOpened() ) { file.Write( wxString::Format( "(kicad_sch\n" "\t(version %d)\n" "\t(generator \"eeschema\")\n" "\t(generator_version \"%s\")\n" "\t(uuid %s)\n" "\t(paper \"A4\")\n" "\t(lib_symbols)\n" "\t(sheet_instances\n" "\t\t(path \"/\"\n" "\t\t\t(page \"1\")\n" "\t\t)\n" "\t)\n" "\t(embedded_fonts no)\n" ")", SEXPR_SCHEMATIC_FILE_VERSION, GetMajorMinorVersion(), KIID().AsString() ) ); } // wxFFile dtor will close the file } // If a .kicad_pcb or .brd file does not exist, // create a .kicad_pcb "stub" file fn.SetExt( FILEEXT::KiCadPcbFileExtension ); wxFileName leg_fn( fn ); leg_fn.SetExt( FILEEXT::LegacyPcbFileExtension ); if( !fn.FileExists() && !leg_fn.FileExists() ) { wxFFile file( fn.GetFullPath(), "wb" ); if( file.IsOpened() ) { // Create a small dummy file as a stub for pcbnew: file.Write( wxString::Format( "(kicad_pcb (version %d) (generator \"pcbnew\") (generator_version \"%s\")\n)", SEXPR_BOARD_FILE_VERSION, GetMajorMinorVersion() ) ); } // wxFFile dtor will close the file } } // Save history & window state to disk now. Don't wait around for a crash. KICAD_SETTINGS* settings = kicadSettings(); SaveSettings( settings ); settings->SaveToFile( Pgm().GetSettingsManager().GetPathForSettingsFile( settings ) ); m_openSavedWindows = true; } void KICAD_MANAGER_FRAME::OnOpenFileInTextEditor( wxCommandEvent& event ) { // show all files in file dialog (in Kicad all files are editable texts): wxString wildcard = FILEEXT::AllFilesWildcard(); wxString default_dir = Prj().GetProjectPath(); wxFileDialog dlg( this, _( "Edit File in Text Editor" ), default_dir, wxEmptyString, wildcard, wxFD_OPEN ); KIPLATFORM::UI::AllowNetworkFileSystems( &dlg ); if( dlg.ShowModal() == wxID_CANCEL ) return; wxString filename = dlg.GetPath(); if( !dlg.GetPath().IsEmpty() && !Pgm().GetTextEditor().IsEmpty() ) m_toolManager->RunAction( KICAD_MANAGER_ACTIONS::openTextEditor, &filename ); } void KICAD_MANAGER_FRAME::OnEditAdvancedCfg( wxCommandEvent& WXUNUSED( event ) ) { DIALOG_EDIT_CFG dlg( this ); dlg.ShowModal(); } void KICAD_MANAGER_FRAME::RefreshProjectTree() { m_projectTreePane->ReCreateTreePrj(); } void KICAD_MANAGER_FRAME::ShowChangedLanguage() { // call my base class EDA_BASE_FRAME::ShowChangedLanguage(); // tooltips in toolbars RecreateToolbars(); m_launcher->CreateLaunchers(); // update captions int pageId = m_notebook->FindPage( m_launcher ); if( pageId != wxNOT_FOUND ) m_notebook->SetPageText( pageId, EDITORS_CAPTION ); m_auimgr.GetPane( m_projectTreePane ).Caption( PROJECT_FILES_CAPTION ); m_auimgr.Update(); m_projectTreePane->FileWatcherReset(); PrintPrjInfo(); } void KICAD_MANAGER_FRAME::CommonSettingsChanged( int aFlags ) { EDA_BASE_FRAME::CommonSettingsChanged( aFlags ); if( m_pcm && ( aFlags & ENVVARS_CHANGED ) ) m_pcm->ReadEnvVar(); COMMON_SETTINGS* settings = Pgm().GetCommonSettings(); if( m_lastToolbarIconSize == 0 || m_lastToolbarIconSize != settings->m_Appearance.toolbar_icon_size ) { onToolbarSizeChanged(); m_lastToolbarIconSize = settings->m_Appearance.toolbar_icon_size; } m_projectTreePane->ReCreateTreePrj(); } void KICAD_MANAGER_FRAME::ProjectChanged() { wxString file = GetProjectFileName(); // empty file string means no project loaded if( !Prj().IsNullProject() && Prj().GetProjectLock() == nullptr ) { LOCKFILE lockFile( file ); if( !lockFile.Valid() && lockFile.IsLockedByMe() ) { // If we cannot acquire the lock but we appear to be the one who // locked it, check to see if there is another KiCad instance running. // If there is not, then we can override the lock. This could happen if // KiCad crashed or was interrupted if( !Pgm().SingleInstance()->IsAnotherRunning() ) { lockFile.OverrideLock(); } } if( !lockFile.Valid() ) { wxString msg; msg.Printf( _( "Project '%s' is already open by '%s' at '%s'." ), file, lockFile.GetUsername(), lockFile.GetHostname() ); if( AskOverrideLock( this, msg ) ) { lockFile.OverrideLock(); } } Prj().SetReadOnly( !lockFile.Valid() || Prj().GetProjectFile().IsReadOnly() ); Prj().SetProjectLock( new LOCKFILE( std::move( lockFile ) ) ); } wxString title; if( !file.IsEmpty() ) { wxFileName fn( file ); title = fn.GetName(); if( Prj().IsReadOnly() ) title += wxS( " " ) + _( "[Read Only]" ); } else { title = _( "[no project loaded]" ); } if( ADVANCED_CFG::GetCfg().m_HideVersionFromTitle ) title += wxT( " \u2014 " ) + wxString( wxS( "KiCad" ) ); else title += wxT( " \u2014 " ) + wxString( wxS( "KiCad " ) ) + GetMajorMinorVersion(); SetTitle( title ); // Register project file saver. Ensures project file participates in // autosave history commits without affecting dirty state. Kiway().LocalHistory().RegisterSaver( &Prj(), [this]( const wxString& aProjectPath, std::vector& aFileData ) { Prj().SaveToHistory( aProjectPath, aFileData ); }, Prj().GetHistoryLifetimeToken() ); } void KICAD_MANAGER_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg ) { EDA_BASE_FRAME::LoadSettings( aCfg ); auto settings = dynamic_cast( aCfg ); wxCHECK( settings, /*void*/ ); m_leftWinWidth = settings->m_LeftWinWidth; m_showHistoryPanel = settings->m_ShowHistoryPanel; } void KICAD_MANAGER_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg ) { EDA_BASE_FRAME::SaveSettings( aCfg ); KICAD_SETTINGS* settings = dynamic_cast( aCfg ); wxCHECK( settings, /*void*/ ); settings->m_LeftWinWidth = m_projectTreePane->GetSize().x; settings->m_ShowHistoryPanel = m_historyPane && m_auimgr.GetPane( m_historyPane ).IsShown(); if( !m_isClosing ) settings->m_OpenProjects = GetSettingsManager()->GetOpenProjects(); } void KICAD_MANAGER_FRAME::PrintPrjInfo() { wxString status = wxString::Format( _( "Project: %s" ), Prj().GetProjectFullName() ); SetStatusText( status, 0 ); } bool KICAD_MANAGER_FRAME::IsProjectActive() { return m_active_project; } void KICAD_MANAGER_FRAME::OnIdle( wxIdleEvent& aEvent ) { /** * We start loading the saved previously open windows on idle to avoid locking up the GUI * earlier in project loading. This gives us the visual effect of a opened KiCad project but * with a "busy" progress reporter */ if( !m_openSavedWindows ) return; m_openSavedWindows = false; if( Pgm().GetCommonSettings()->m_Session.remember_open_files ) { int previousOpenCount = std::count_if( Prj().GetLocalSettings().m_files.begin(), Prj().GetLocalSettings().m_files.end(), [&]( const PROJECT_FILE_STATE& f ) { return !f.fileName.EndsWith( FILEEXT::ProjectFileExtension ) && f.open; } ); if( previousOpenCount > 0 ) { APP_PROGRESS_DIALOG progressReporter( _( "Restoring session" ), wxEmptyString, previousOpenCount, this ); // We don't currently support opening more than one view per file std::set openedFiles; int i = 0; for( const PROJECT_FILE_STATE& file : Prj().GetLocalSettings().m_files ) { if( file.open && !openedFiles.count( file.fileName ) ) { progressReporter.Update( i++, wxString::Format( _( "Restoring '%s'" ), file.fileName ) ); openedFiles.insert( file.fileName ); wxFileName fn( file.fileName ); if( fn.GetExt() == FILEEXT::LegacySchematicFileExtension || fn.GetExt() == FILEEXT::KiCadSchematicFileExtension ) { GetToolManager()->RunAction( KICAD_MANAGER_ACTIONS::editSchematic ); } else if( fn.GetExt() == FILEEXT::LegacyPcbFileExtension || fn.GetExt() == FILEEXT::KiCadPcbFileExtension ) { GetToolManager()->RunAction( KICAD_MANAGER_ACTIONS::editPCB ); } } wxYield(); } } } // clear file states regardless if we opened windows or not due to setting Prj().GetLocalSettings().ClearFileState(); // After restore from history, mark open editors as dirty so user is prompted to save if( m_restoredFromHistory ) { m_restoredFromHistory = false; // Mark schematic editor as dirty if open if( KIWAY_PLAYER* schFrame = Kiway().Player( FRAME_SCH, false ) ) schFrame->OnModify(); // Mark PCB editor as dirty if open if( KIWAY_PLAYER* pcbFrame = Kiway().Player( FRAME_PCB_EDITOR, false ) ) pcbFrame->OnModify(); } KICAD_SETTINGS* settings = kicadSettings(); if( KIPLATFORM::POLICY::GetPolicyBool( POLICY_KEY_PCM ) != KIPLATFORM::POLICY::PBOOL::DISABLED && settings->m_PcmUpdateCheck ) { if( !m_pcm ) CreatePCM(); m_pcm->RunBackgroundUpdate(); } #ifdef KICAD_UPDATE_CHECK if( !m_updateManager && settings->m_KiCadUpdateCheck ) { m_updateManager = std::make_unique(); m_updateManager->CheckForUpdate( this ); } #endif // This little diddy is needed to get the window put into the Mac dock icon's context menu. Raise(); } void KICAD_MANAGER_FRAME::SetPcmButton( BITMAP_BUTTON* aButton ) { m_pcmButton = aButton; updatePcmButtonBadge(); } void KICAD_MANAGER_FRAME::updatePcmButtonBadge() { if( m_pcmButton ) { if( m_pcmUpdateCount > 0 ) { m_pcmButton->SetShowBadge( true ); m_pcmButton->SetBadgeText( wxString::Format( "%d", m_pcmUpdateCount ) ); } else { m_pcmButton->SetShowBadge( false ); } m_pcmButton->Refresh(); } } void KICAD_MANAGER_FRAME::onToolbarSizeChanged() { // No idea why, but the same mechanism used in EDA_DRAW_FRAME doesn't work here // the only thing that seems to work is to blow it all up and start from scratch. m_auimgr.DetachPane( m_tbLeft ); delete m_tbLeft; m_tbLeft = nullptr; RecreateToolbars(); m_auimgr.AddPane( m_tbLeft, EDA_PANE().HToolbar().Name( "TopMainToolbar" ).Left().Layer( 2 ) ); m_auimgr.Update(); } void KICAD_MANAGER_FRAME::ToggleLocalHistory() { wxAuiPaneInfo& pane = m_auimgr.GetPane( m_historyPane ); bool show = !pane.IsShown(); pane.Show( show ); if( show ) m_historyPane->RefreshHistory( Prj().GetProjectPath() ); m_auimgr.Update(); } void KICAD_MANAGER_FRAME::RestoreCommitFromHistory( const wxString& aHash ) { if( !Kiway().PlayersClose( true ) ) return; if( Kiway().LocalHistory().RestoreCommit( Prj().GetProjectPath(), aHash, this ) ) { m_restoredFromHistory = true; // Mark editors dirty when they reopen } m_projectTreePane->ReCreateTreePrj(); m_openSavedWindows = true; m_historyPane->RefreshHistory( Prj().GetProjectPath() ); } bool KICAD_MANAGER_FRAME::HistoryPanelShown() { return m_historyPane && m_auimgr.GetPane( m_historyPane ).IsShown(); }