/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2013 Wayne Stambaugh <stambaughw@gmail.com> * Copyright (C) 2013 CERN (www.cern.ch) * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 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 <symbol_library.h> #include <confirm.h> #include <connection_graph.h> #include <dialog_migrate_buses.h> #include <dialog_symbol_remap.h> #include <eeschema_settings.h> #include <id.h> #include <kiface_i.h> #include <kiplatform/app.h> #include <pgm_base.h> #include <profile.h> #include <project/project_file.h> #include <project_rescue.h> #include <wx_html_report_box.h> #include <dialog_HTML_reporter_base.h> #include <reporter.h> #include <richio.h> #include <sch_edit_frame.h> #include <sch_plugins/legacy/sch_legacy_plugin.h> #include <sch_file_versions.h> #include <sch_sheet.h> #include <sch_sheet_path.h> #include <schematic.h> #include <settings/settings_manager.h> #include <tool/actions.h> #include <tool/tool_manager.h> #include <tools/sch_editor_control.h> #include <trace_helpers.h> #include <widgets/infobar.h> #include <wildcards_and_files_ext.h> #include <drawing_sheet/ds_data_model.h> #include <wx/ffile.h> #include <wx/filedlg.h> #include <wx/log.h> #include <tools/ee_inspection_tool.h> #include <paths.h> #include <wx_filename.h> // For ::ResolvePossibleSymlinks #include <widgets/wx_progress_reporters.h> ///< Helper widget to select whether a new project should be created for a file when saving class CREATE_PROJECT_CHECKBOX : public wxPanel { public: CREATE_PROJECT_CHECKBOX( wxWindow* aParent ) : wxPanel( aParent ) { m_cbCreateProject = new wxCheckBox( this, wxID_ANY, _( "Create a new project for this schematic" ) ); m_cbCreateProject->SetValue( true ); m_cbCreateProject->SetToolTip( _( "Creating a project will enable features such as " "text variables, net classes, and ERC exclusions" ) ); wxBoxSizer* sizer = new wxBoxSizer( wxHORIZONTAL ); sizer->Add( m_cbCreateProject, 0, wxALL, 8 ); SetSizerAndFit( sizer ); } bool GetValue() const { return m_cbCreateProject->GetValue(); } static wxWindow* Create( wxWindow* aParent ) { return new CREATE_PROJECT_CHECKBOX( aParent ); } protected: wxCheckBox* m_cbCreateProject; }; bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, int aCtl ) { // implement the pseudo code from KIWAY_PLAYER.h: wxString msg; EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ); // This is for python: if( aFileSet.size() != 1 ) { msg.Printf( "Eeschema:%s() takes only a single filename.", __WXFUNCTION__ ); DisplayError( this, msg ); return false; } wxString fullFileName( aFileSet[0] ); wxFileName wx_filename( fullFileName ); // We insist on caller sending us an absolute path, if it does not, we say it's a bug. wxASSERT_MSG( wx_filename.IsAbsolute(), wxT( "Path is not absolute!" ) ); if( !LockFile( fullFileName ) ) { msg.Printf( _( "Schematic '%s' is already open." ), wx_filename.GetFullName() ); if( !OverrideLock( this, msg ) ) return false; } if( !AskToSaveChanges() ) return false; #ifdef PROFILE PROF_COUNTER openFiles( "OpenProjectFile" ); #endif wxFileName pro = fullFileName; pro.SetExt( ProjectFileExtension ); bool is_new = !wxFileName::IsFileReadable( fullFileName ); // If its a non-existent schematic and caller thinks it exists if( is_new && !( aCtl & KICTL_CREATE ) ) { // notify user that fullFileName does not exist, ask if user wants to create it. msg.Printf( _( "Schematic '%s' does not exist. Do you wish to create it?" ), fullFileName ); if( !IsOK( this, msg ) ) return false; } // unload current project file before loading new { SetScreen( nullptr ); m_toolManager->GetTool<EE_INSPECTION_TOOL>()->Reset( TOOL_BASE::MODEL_RELOAD ); CreateScreens(); } SetStatusText( wxEmptyString ); m_infoBar->Dismiss(); WX_PROGRESS_REPORTER progressReporter( this, is_new ? _( "Creating Schematic" ) : _( "Loading Schematic" ), 1 ); bool differentProject = pro.GetFullPath() != Prj().GetProjectFullName(); if( differentProject ) { if( !Prj().IsNullProject() ) GetSettingsManager()->SaveProject(); Schematic().SetProject( nullptr ); GetSettingsManager()->UnloadProject( &Prj(), false ); GetSettingsManager()->LoadProject( pro.GetFullPath() ); wxFileName legacyPro( pro ); legacyPro.SetExt( LegacyProjectFileExtension ); // Do not allow saving a project if one doesn't exist. This normally happens if we are // standalone and opening a schematic that has been moved from its project folder. if( !pro.Exists() && !legacyPro.Exists() && !( aCtl & KICTL_CREATE ) ) Prj().SetReadOnly(); CreateScreens(); } SCH_IO_MGR::SCH_FILE_T schFileType = SCH_IO_MGR::GuessPluginTypeFromSchPath( fullFileName ); if( schFileType == SCH_IO_MGR::SCH_LEGACY ) { // Don't reload the symbol libraries if we are just launching Eeschema from KiCad again. // They are already saved in the kiface project object. if( differentProject || !Prj().GetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS ) ) { // load the libraries here, not in SCH_SCREEN::Draw() which is a context // that will not tolerate DisplayError() dialog since we're already in an // event handler in there. // And when a schematic file is loaded, we need these libs to initialize // some parameters (links to PART LIB, dangling ends ...) Prj().SetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS, nullptr ); Prj().SchLibs(); } } else { // No legacy symbol libraries including the cache are loaded with the new file format. Prj().SetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS, nullptr ); } // Load the symbol library table, this will be used forever more. Prj().SetElem( PROJECT::ELEM_SYMBOL_LIB_TABLE, nullptr ); Prj().SchSymbolLibTable(); // Load project settings after schematic has been set up with the project link, since this will // update some of the needed schematic settings such as drawing defaults LoadProjectSettings(); wxFileName rfn( GetCurrentFileName() ); rfn.MakeRelativeTo( Prj().GetProjectPath() ); LoadWindowState( rfn.GetFullPath() ); KIPLATFORM::APP::SetShutdownBlockReason( this, _( "Schematic file changes are unsaved" ) ); if( Kiface().IsSingle() ) { KIPLATFORM::APP::RegisterApplicationRestart( fullFileName ); } if( is_new ) { // mark new, unsaved file as modified. GetScreen()->SetContentModified(); GetScreen()->SetFileName( fullFileName ); } else { // This will rename the file if there is an autosave and the user want to recover. CheckForAutoSaveFile( fullFileName ); SetScreen( nullptr ); SCH_PLUGIN* plugin = SCH_IO_MGR::FindPlugin( schFileType ); SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( plugin ); pi->SetProgressReporter( &progressReporter ); bool failedLoad = false; try { Schematic().SetRoot( pi->Load( fullFileName, &Schematic() ) ); if( !pi->GetError().IsEmpty() ) { DisplayErrorMessage( this, _( "The entire schematic could not be loaded. Errors " "occurred attempting to load hierarchical sheets." ), pi->GetError() ); } } catch( const FUTURE_FORMAT_ERROR& ffe ) { msg.Printf( _( "Error loading schematic '%s'." ), fullFileName); progressReporter.Hide(); DisplayErrorMessage( this, msg, ffe.Problem() ); failedLoad = true; } catch( const IO_ERROR& ioe ) { msg.Printf( _( "Error loading schematic '%s'." ), fullFileName); progressReporter.Hide(); DisplayErrorMessage( this, msg, ioe.What() ); failedLoad = true; } catch( const std::bad_alloc& ) { msg.Printf( _( "Memory exhausted loading schematic '%s'." ), fullFileName ); progressReporter.Hide(); DisplayErrorMessage( this, msg, wxEmptyString ); failedLoad = true; } if( failedLoad ) { // Do not leave g_RootSheet == NULL because it is expected to be // a valid sheet. Therefore create a dummy empty root sheet and screen. CreateScreens(); m_toolManager->RunAction( ACTIONS::zoomFitScreen, true ); msg.Printf( _( "Failed to load '%s'." ), fullFileName ); SetMsgPanel( wxEmptyString, msg ); return false; } // It's possible the schematic parser fixed errors due to bugs so warn the user // that the schematic has been fixed (modified). SCH_SHEET_LIST sheetList = Schematic().GetSheets(); if( sheetList.IsModified() ) { DisplayInfoMessage( this, _( "An error was found when loading the schematic that has " "been automatically fixed. Please save the schematic to " "repair the broken file or it may not be usable with other " "versions of KiCad." ) ); } if( sheetList.AllSheetPageNumbersEmpty() ) sheetList.SetInitialPageNumbers(); UpdateFileHistory( fullFileName ); SCH_SCREENS schematic( Schematic().Root() ); // LIB_ID checks and symbol rescue only apply to the legacy file formats. if( schFileType == SCH_IO_MGR::SCH_LEGACY ) { // Convert old projects over to use symbol library table. if( schematic.HasNoFullyDefinedLibIds() ) { DIALOG_SYMBOL_REMAP dlgRemap( this ); dlgRemap.ShowQuasiModal(); } else { // Double check to ensure no legacy library list entries have been // added to the projec file symbol library list. wxString paths; wxArrayString libNames; SYMBOL_LIBS::LibNamesAndPaths( &Prj(), false, &paths, &libNames ); if( !libNames.IsEmpty() ) { if( eeconfig()->m_Appearance.show_illegal_symbol_lib_dialog ) { wxRichMessageDialog invalidLibDlg( this, _( "Illegal entry found in project file symbol library list." ), _( "Project Load Warning" ), wxOK | wxCENTER | wxICON_EXCLAMATION ); invalidLibDlg.ShowDetailedText( _( "Symbol libraries defined in the project file symbol library " "list are no longer supported and will be removed.\n\n" "This may cause broken symbol library links under certain " "conditions." ) ); invalidLibDlg.ShowCheckBox( _( "Do not show this dialog again." ) ); invalidLibDlg.ShowModal(); eeconfig()->m_Appearance.show_illegal_symbol_lib_dialog = !invalidLibDlg.IsCheckBoxChecked(); } libNames.Clear(); paths.Clear(); SYMBOL_LIBS::LibNamesAndPaths( &Prj(), true, &paths, &libNames ); } if( !cfg || !cfg->m_RescueNeverShow ) { SCH_EDITOR_CONTROL* editor = m_toolManager->GetTool<SCH_EDITOR_CONTROL>(); editor->RescueSymbolLibTableProject( false ); } } // Ensure there is only one legacy library loaded and that it is the cache library. SYMBOL_LIBS* legacyLibs = Schematic().Prj().SchLibs(); if( legacyLibs->GetLibraryCount() == 0 ) { wxString extMsg; wxFileName cacheFn = pro; cacheFn.SetName( cacheFn.GetName() + "-cache" ); cacheFn.SetExt( LegacySymbolLibFileExtension ); msg.Printf( _( "The project symbol library cache file '%s' was not found." ), cacheFn.GetFullName() ); extMsg = _( "This can result in a broken schematic under certain conditions. " "If the schematic does not have any missing symbols upon opening, " "save it immediately before making any changes to prevent data " "loss. If there are missing symbols, either manual recovery of " "the schematic or recovery of the symbol cache library file and " "reloading the schematic is required." ); wxMessageDialog dlgMissingCache( this, msg, _( "Warning" ), wxOK | wxCANCEL | wxICON_EXCLAMATION | wxCENTER ); dlgMissingCache.SetExtendedMessage( extMsg ); dlgMissingCache.SetOKCancelLabels( wxMessageDialog::ButtonLabel( _( "Load Without Cache File" ) ), wxMessageDialog::ButtonLabel( _( "Abort" ) ) ); if( dlgMissingCache.ShowModal() == wxID_CANCEL ) { Schematic().Reset(); CreateScreens(); return false; } } // Update all symbol library links for all sheets. schematic.UpdateSymbolLinks(); m_infoBar->RemoveAllButtons(); m_infoBar->AddCloseButton(); m_infoBar->ShowMessage( _( "This file was created by an older version of KiCad. " "It will be converted to the new format when saved." ), wxICON_WARNING, WX_INFOBAR::MESSAGE_TYPE::OUTDATED_SAVE ); // Legacy schematic can have duplicate time stamps so fix that before converting // to the s-expression format. schematic.ReplaceDuplicateTimeStamps(); // Allow the schematic to be saved to new file format without making any edits. OnModify(); } else // S-expression schematic. { if( schematic.GetFirst()->GetFileFormatVersionAtLoad() < SEXPR_SCHEMATIC_FILE_VERSION ) { m_infoBar->RemoveAllButtons(); m_infoBar->AddCloseButton(); m_infoBar->ShowMessage( _( "This file was created by an older version of KiCad. " "It will be converted to the new format when saved." ), wxICON_WARNING, WX_INFOBAR::MESSAGE_TYPE::OUTDATED_SAVE ); } for( SCH_SCREEN* screen = schematic.GetFirst(); screen; screen = schematic.GetNext() ) screen->UpdateLocalLibSymbolLinks(); // Restore all of the loaded symbol and sheet instances from the root sheet. sheetList.UpdateSymbolInstances( Schematic().RootScreen()->GetSymbolInstances() ); sheetList.UpdateSheetInstances( Schematic().RootScreen()->GetSheetInstances() ); } Schematic().ConnectionGraph()->Reset(); SetScreen( GetCurrentSheet().LastScreen() ); // Migrate conflicting bus definitions // TODO(JE) This should only run once based on schematic file version if( Schematic().ConnectionGraph()->GetBusesNeedingMigration().size() > 0 ) { DIALOG_MIGRATE_BUSES dlg( this ); dlg.ShowQuasiModal(); RecalculateConnections( NO_CLEANUP ); OnModify(); } RecalculateConnections( GLOBAL_CLEANUP ); ClearUndoRedoList(); } // Load any exclusions from the project file ResolveERCExclusions(); initScreenZoom(); SetSheetNumberAndCount(); RecomputeIntersheetRefs(); GetCurrentSheet().UpdateAllScreenReferences(); // re-create junctions if needed. Eeschema optimizes wires by merging // colinear segments. If a schematic is saved without a valid // cache library or missing installed libraries, this can cause connectivity errors // unless junctions are added. if( schFileType == SCH_IO_MGR::SCH_LEGACY ) FixupJunctions(); SyncView(); GetScreen()->ClearDrawingState(); UpdateHierarchyNavigator(); UpdateTitle(); wxFileName fn = Prj().AbsolutePath( GetScreen()->GetFileName() ); if( fn.FileExists() && !fn.IsFileWritable() ) { m_infoBar->RemoveAllButtons(); m_infoBar->AddCloseButton(); m_infoBar->ShowMessage( _( "Schematic is read only." ), wxICON_WARNING ); } #ifdef PROFILE openFiles.Show(); #endif return true; } bool SCH_EDIT_FRAME::AppendSchematic() { wxString fullFileName; SCH_SCREEN* screen = GetScreen(); if( !screen ) { wxLogError( wxT( "Document not ready, cannot import" ) ); return false; } // open file chooser dialog wxString path = wxPathOnly( Prj().GetProjectFullName() ); wxFileDialog dlg( this, _( "Insert Schematic" ), path, wxEmptyString, KiCadSchematicFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST ); if( dlg.ShowModal() == wxID_CANCEL ) return false; fullFileName = dlg.GetPath(); if( !LoadSheetFromFile( GetCurrentSheet().Last(), &GetCurrentSheet(), fullFileName ) ) return false; initScreenZoom(); SetSheetNumberAndCount(); SyncView(); OnModify(); HardRedraw(); // Full reinit of the current screen and the display. UpdateHierarchyNavigator(); return true; } void SCH_EDIT_FRAME::OnAppendProject( wxCommandEvent& event ) { if( GetScreen() && GetScreen()->IsModified() ) { wxString msg = _( "This operation cannot be undone.\n\n" "Do you want to save the current document before proceeding?" ); if( IsOK( this, msg ) ) SaveProject(); } AppendSchematic(); } void SCH_EDIT_FRAME::OnImportProject( wxCommandEvent& aEvent ) { if( !AskToSaveChanges() ) return; // Set the project location if none is set or if we are running in standalone mode bool setProject = Prj().GetProjectFullName().IsEmpty() || Kiface().IsSingle(); wxString path = wxPathOnly( Prj().GetProjectFullName() ); std::list<std::pair<const wxString, const SCH_IO_MGR::SCH_FILE_T>> loaders; // Import Altium schematic files. loaders.emplace_back( AltiumSchematicFileWildcard(), SCH_IO_MGR::SCH_ALTIUM ); // Import CADSTAR Schematic Archive files. loaders.emplace_back( CadstarSchematicArchiveFileWildcard(), SCH_IO_MGR::SCH_CADSTAR_ARCHIVE ); // Import Eagle schematic files. loaders.emplace_back( EagleSchematicFileWildcard(), SCH_IO_MGR::SCH_EAGLE ); wxString fileFilters; wxString allWildcards; for( std::pair<const wxString, const SCH_IO_MGR::SCH_FILE_T>& loader : loaders ) { if( !fileFilters.IsEmpty() ) fileFilters += wxChar( '|' ); fileFilters += wxGetTranslation( loader.first ); SCH_PLUGIN::SCH_PLUGIN_RELEASER plugin( SCH_IO_MGR::FindPlugin( loader.second ) ); wxCHECK( plugin, /*void*/ ); allWildcards += "*." + formatWildcardExt( plugin->GetFileExtension() ) + ";"; } fileFilters = _( "All supported formats|" ) + allWildcards + "|" + fileFilters; wxFileDialog dlg( this, _( "Import Schematic" ), path, wxEmptyString, fileFilters, wxFD_OPEN | wxFD_FILE_MUST_EXIST ); // TODO if( dlg.ShowModal() == wxID_CANCEL ) return; if( setProject ) { Schematic().SetProject( nullptr ); GetSettingsManager()->UnloadProject( &Prj(), false ); Schematic().Reset(); wxFileName projectFn( dlg.GetPath() ); projectFn.SetExt( ProjectFileExtension ); GetSettingsManager()->LoadProject( projectFn.GetFullPath() ); Schematic().SetProject( &Prj() ); } wxFileName fn = dlg.GetPath(); SCH_IO_MGR::SCH_FILE_T pluginType = SCH_IO_MGR::SCH_FILE_T::SCH_FILE_UNKNOWN; for( std::pair<const wxString, const SCH_IO_MGR::SCH_FILE_T>& loader : loaders ) { if( fn.GetExt().CmpNoCase( SCH_IO_MGR::GetFileExtension( loader.second ) ) == 0 ) { pluginType = loader.second; break; } } if( pluginType == SCH_IO_MGR::SCH_FILE_T::SCH_FILE_UNKNOWN ) { wxLogError( _( "Unexpected file extension: '%s'." ), fn.GetExt() ); return; } m_toolManager->GetTool<EE_SELECTION_TOOL>()->ClearSelection(); importFile( dlg.GetPath(), pluginType ); RefreshCanvas(); } bool SCH_EDIT_FRAME::saveSchematicFile( SCH_SHEET* aSheet, const wxString& aSavePath ) { wxString msg; wxFileName schematicFileName; wxFileName oldFileName; bool success; SCH_SCREEN* screen = aSheet->GetScreen(); wxCHECK( screen, false ); // Construct the name of the file to be saved schematicFileName = Prj().AbsolutePath( aSavePath ); oldFileName = schematicFileName; // Write through symlinks, don't replace them WX_FILENAME::ResolvePossibleSymlinks( schematicFileName ); if( !IsWritable( schematicFileName ) ) return false; wxFileName tempFile( schematicFileName ); tempFile.SetName( wxT( "." ) + tempFile.GetName() ); tempFile.SetExt( tempFile.GetExt() + wxT( "$" ) ); // Save wxLogTrace( traceAutoSave, "Saving file " + schematicFileName.GetFullPath() ); SCH_IO_MGR::SCH_FILE_T pluginType = SCH_IO_MGR::GuessPluginTypeFromSchPath( schematicFileName.GetFullPath() ); SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( pluginType ) ); try { pi->Save( tempFile.GetFullPath(), aSheet, &Schematic() ); success = true; } catch( const IO_ERROR& ioe ) { msg.Printf( _( "Error saving schematic file '%s'.\n%s" ), schematicFileName.GetFullPath(), ioe.What() ); DisplayError( this, msg ); msg.Printf( _( "Failed to create temporary file '%s'." ), tempFile.GetFullPath() ); SetMsgPanel( wxEmptyString, msg ); // In case we started a file but didn't fully write it, clean up wxRemoveFile( tempFile.GetFullPath() ); success = false; } if( success ) { // Replace the original with the temporary file we just wrote success = wxRenameFile( tempFile.GetFullPath(), schematicFileName.GetFullPath() ); if( !success ) { msg.Printf( _( "Error saving schematic file '%s'.\n" "Failed to rename temporary file '%s'." ), schematicFileName.GetFullPath(), tempFile.GetFullPath() ); DisplayError( this, msg ); msg.Printf( _( "Failed to rename temporary file '%s'." ), tempFile.GetFullPath() ); SetMsgPanel( wxEmptyString, msg ); } } if( success ) { // Delete auto save file. wxFileName autoSaveFileName = schematicFileName; autoSaveFileName.SetName( GetAutoSaveFilePrefix() + schematicFileName.GetName() ); if( autoSaveFileName.FileExists() ) { wxLogTrace( traceAutoSave, wxT( "Removing auto save file <" ) + autoSaveFileName.GetFullPath() + wxT( ">" ) ); wxRemoveFile( autoSaveFileName.GetFullPath() ); } screen->SetContentModified( false ); msg.Printf( _( "File '%s' saved." ), screen->GetFileName() ); SetStatusText( msg, 0 ); } else { DisplayError( this, _( "File write operation failed." ) ); } return success; } bool SCH_EDIT_FRAME::SaveProject( bool aSaveAs ) { wxString msg; SCH_SCREEN* screen; SCH_SCREENS screens( Schematic().Root() ); bool saveCopyAs = aSaveAs && !Kiface().IsSingle(); bool success = true; bool updateFileType = false; bool createNewProject = false; // I want to see it in the debugger, show me the string! Can't do that with wxFileName. wxString fileName = Prj().AbsolutePath( Schematic().Root().GetFileName() ); wxFileName fn = fileName; // Path to save each screen to: will be the stored filename by default, but is overwritten by // a Save As Copy operation. std::unordered_map<SCH_SCREEN*, wxString> filenameMap; // Handle "Save As" and saving a new project/schematic for the first time in standalone if( Prj().IsNullProject() || aSaveAs ) { // Null project should only be possible in standalone mode. wxCHECK( Kiface().IsSingle() || aSaveAs, false ); wxFileName newFileName; wxFileName savePath( Prj().GetProjectFullName() ); if( !savePath.IsOk() || !savePath.IsDirWritable() ) { savePath = GetMruPath(); if( !savePath.IsOk() || !savePath.IsDirWritable() ) savePath = PATHS::GetDefaultUserProjectsPath(); } if( savePath.HasExt() ) savePath.SetExt( KiCadSchematicFileExtension ); else savePath.SetName( wxEmptyString ); wxFileDialog dlg( this, _( "Schematic Files" ), savePath.GetPath(), savePath.GetFullName(), KiCadSchematicFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if( Kiface().IsSingle() || aSaveAs ) { // Add a "Create a project" checkbox in standalone mode and one isn't loaded dlg.SetExtraControlCreator( &CREATE_PROJECT_CHECKBOX::Create ); } if( dlg.ShowModal() == wxID_CANCEL ) return false; newFileName = dlg.GetPath(); newFileName.SetExt( KiCadSchematicFileExtension ); if( ( !newFileName.DirExists() && !newFileName.Mkdir() ) || !newFileName.IsDirWritable() ) { msg.Printf( _( "Folder '%s' could not be created.\n\n" "Make sure you have write permissions and try again." ), newFileName.GetPath() ); wxMessageDialog dlgBadPath( this, msg, _( "Error" ), wxOK | wxICON_EXCLAMATION | wxCENTER ); dlgBadPath.ShowModal(); return false; } if( wxWindow* ec = dlg.GetExtraControl() ) createNewProject = static_cast<CREATE_PROJECT_CHECKBOX*>( ec )->GetValue(); if( !saveCopyAs ) { Schematic().Root().SetFileName( newFileName.GetFullName() ); Schematic().RootScreen()->SetFileName( newFileName.GetFullPath() ); } else { filenameMap[Schematic().RootScreen()] = newFileName.GetFullPath(); } // Set the base path to all new sheets. for( size_t i = 0; i < screens.GetCount(); i++ ) { screen = screens.GetScreen( i ); wxCHECK2( screen, continue ); // The root screen file name has already been set. if( screen == Schematic().RootScreen() ) continue; wxFileName tmp = screen->GetFileName(); // Assume existing sheet files are being reused and do not save them to the new // path. Maybe in the future, add a user option to copy schematic files to the // new project path. if( tmp.FileExists() ) continue; if( tmp.GetPath().IsEmpty() ) { tmp.SetPath( newFileName.GetPath() ); } else if( tmp.GetPath() == fn.GetPath() ) { tmp.SetPath( newFileName.GetPath() ); } else if( tmp.GetPath().StartsWith( fn.GetPath() ) ) { // NOTE: this hasn't been tested because the sheet properties dialog no longer // allows adding a path specifier in the file name field. wxString newPath = newFileName.GetPath(); newPath += tmp.GetPath().Right( fn.GetPath().Length() ); tmp.SetPath( newPath ); } wxLogTrace( tracePathsAndFiles, wxT( "Moving schematic from '%s' to '%s'." ), screen->GetFileName(), tmp.GetFullPath() ); if( !tmp.DirExists() && !tmp.Mkdir() ) { msg.Printf( _( "Folder '%s' could not be created.\n\n" "Make sure you have write permissions and try again." ), newFileName.GetPath() ); wxMessageDialog dlgBadFilePath( this, msg, _( "Error" ), wxOK | wxICON_EXCLAMATION | wxCENTER ); dlgBadFilePath.ShowModal(); return false; } if( saveCopyAs ) filenameMap[screen] = tmp.GetFullPath(); else screen->SetFileName( tmp.GetFullPath() ); } // Attempt to make sheet file name paths relative to the new root schematic path. SCH_SHEET_LIST sheets = Schematic().GetSheets(); for( SCH_SHEET_PATH& sheet : sheets ) { if( sheet.Last()->IsRootSheet() ) continue; sheet.MakeFilePathRelativeToParentSheet(); } } if( filenameMap.empty() || !saveCopyAs ) { for( size_t i = 0; i < screens.GetCount(); i++ ) filenameMap[screens.GetScreen( i )] = screens.GetScreen( i )->GetFileName(); } // Warn user on potential file overwrite. This can happen on shared sheets. wxArrayString overwrittenFiles; for( size_t i = 0; i < screens.GetCount(); i++ ) { screen = screens.GetScreen( i ); wxCHECK2( screen, continue ); // Convert legacy schematics file name extensions for the new format. wxFileName tmpFn = filenameMap[screen]; if( !tmpFn.IsOk() ) continue; if( tmpFn.GetExt() == KiCadSchematicFileExtension ) continue; tmpFn.SetExt( KiCadSchematicFileExtension ); if( tmpFn.FileExists() ) overwrittenFiles.Add( tmpFn.GetFullPath() ); } if( !overwrittenFiles.IsEmpty() ) { for( const wxString& overwrittenFile : overwrittenFiles ) { if( msg.IsEmpty() ) msg = overwrittenFile; else msg += "\n" + overwrittenFile; } wxRichMessageDialog dlg( this, _( "Saving will overwrite existing files." ), _( "Save Warning" ), wxOK | wxCANCEL | wxCANCEL_DEFAULT | wxCENTER | wxICON_EXCLAMATION ); dlg.ShowDetailedText( _( "The following files will be overwritten:\n\n" ) + msg ); dlg.SetOKCancelLabels( wxMessageDialog::ButtonLabel( _( "Overwrite Files" ) ), wxMessageDialog::ButtonLabel( _( "Abort Project Save" ) ) ); if( dlg.ShowModal() == wxID_CANCEL ) return false; } screens.BuildClientSheetPathList(); for( size_t i = 0; i < screens.GetCount(); i++ ) { screen = screens.GetScreen( i ); wxCHECK2( screen, continue ); // Convert legacy schematics file name extensions for the new format. wxFileName tmpFn = filenameMap[screen]; if( tmpFn.IsOk() && tmpFn.GetExt() != KiCadSchematicFileExtension ) { updateFileType = true; tmpFn.SetExt( KiCadSchematicFileExtension ); for( auto item : screen->Items().OfType( SCH_SHEET_T ) ) { SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item ); wxFileName sheetFileName = sheet->GetFileName(); if( !sheetFileName.IsOk() || sheetFileName.GetExt() == KiCadSchematicFileExtension ) continue; sheetFileName.SetExt( KiCadSchematicFileExtension ); sheet->SetFileName( sheetFileName.GetFullPath() ); UpdateItem( sheet ); } filenameMap[screen] = tmpFn.GetFullPath(); if( !saveCopyAs ) screen->SetFileName( tmpFn.GetFullPath() ); } std::vector<SCH_SHEET_PATH>& sheets = screen->GetClientSheetPaths(); if( sheets.size() == 1 ) screen->SetVirtualPageNumber( 1 ); else screen->SetVirtualPageNumber( 0 ); // multiple uses; no way to store the real sheet # // This is a new schematic file so make sure it has a unique ID. if( !saveCopyAs && tmpFn.GetFullPath() != screen->GetFileName() ) screen->AssignNewUuid(); success &= saveSchematicFile( screens.GetSheet( i ), tmpFn.GetFullPath() ); } if( aSaveAs && success ) LockFile( Schematic().RootScreen()->GetFileName() ); if( updateFileType ) UpdateFileHistory( Schematic().RootScreen()->GetFileName() ); // Save the sheet name map to the project file std::vector<FILE_INFO_PAIR>& sheets = Prj().GetProjectFile().GetSheets(); sheets.clear(); for( SCH_SHEET_PATH& sheetPath : Schematic().GetSheets() ) { SCH_SHEET* sheet = sheetPath.Last(); wxCHECK2( sheet, continue ); // Use the schematic UUID for the root sheet. if( sheet->IsRootSheet() ) { screen = sheet->GetScreen(); wxCHECK2( screen, continue ); sheets.emplace_back( std::make_pair( screen->GetUuid(), sheet->GetName() ) ); } else { sheets.emplace_back( std::make_pair( sheet->m_Uuid, sheet->GetName() ) ); } } wxASSERT( filenameMap.count( Schematic().RootScreen() ) ); wxFileName projectPath( filenameMap.at( Schematic().RootScreen() ) ); projectPath.SetExt( ProjectFileExtension ); if( Prj().IsNullProject() || ( aSaveAs && !saveCopyAs ) ) { Prj().SetReadOnly( !createNewProject ); GetSettingsManager()->SaveProjectAs( projectPath.GetFullPath() ); } else if( saveCopyAs && createNewProject ) { GetSettingsManager()->SaveProjectCopy( projectPath.GetFullPath() ); } else { GetSettingsManager()->SaveProject(); } if( !Kiface().IsSingle() ) { WX_STRING_REPORTER backupReporter( &msg ); if( !GetSettingsManager()->TriggerBackupIfNeeded( backupReporter ) ) SetStatusText( msg, 0 ); } UpdateTitle(); if( m_infoBar->GetMessageType() == WX_INFOBAR::MESSAGE_TYPE::OUTDATED_SAVE ) m_infoBar->Dismiss(); return success; } bool SCH_EDIT_FRAME::doAutoSave() { wxFileName tmpFileName = Schematic().Root().GetFileName(); wxFileName fn = tmpFileName; wxFileName tmp; SCH_SCREENS screens( Schematic().Root() ); // Don't run autosave if content has not been modified if( !IsContentModified() ) return true; bool autoSaveOk = true; if( fn.GetPath().IsEmpty() ) tmp.AssignDir( Prj().GetProjectPath() ); else tmp.AssignDir( fn.GetPath() ); if( !tmp.IsOk() ) return false; if( !IsWritable( tmp ) ) return false; wxString title = GetTitle(); // Save frame title, that can be modified by the save process for( size_t i = 0; i < screens.GetCount(); i++ ) { // Only create auto save files for the schematics that have been modified. if( !screens.GetScreen( i )->IsContentModified() ) continue; tmpFileName = fn = screens.GetScreen( i )->GetFileName(); // Auto save file name is the normal file name prefixed with GetAutoSavePrefix(). fn.SetName( GetAutoSaveFilePrefix() + fn.GetName() ); if( saveSchematicFile( screens.GetSheet( i ), fn.GetFullPath() ) ) screens.GetScreen( i )->SetContentModified(); else autoSaveOk = false; } if( autoSaveOk ) { m_autoSaveState = false; if( !Kiface().IsSingle() && GetSettingsManager()->GetCommonSettings()->m_Backup.backup_on_autosave ) { GetSettingsManager()->TriggerBackupIfNeeded( NULL_REPORTER::GetInstance() ); } } SetTitle( title ); return autoSaveOk; } bool SCH_EDIT_FRAME::importFile( const wxString& aFileName, int aFileType ) { wxFileName filename( aFileName ); wxFileName newfilename; SCH_SHEET_LIST sheetList = Schematic().GetSheets(); SCH_IO_MGR::SCH_FILE_T fileType = (SCH_IO_MGR::SCH_FILE_T) aFileType; switch( fileType ) { case SCH_IO_MGR::SCH_ALTIUM: case SCH_IO_MGR::SCH_CADSTAR_ARCHIVE: case SCH_IO_MGR::SCH_EAGLE: // We insist on caller sending us an absolute path, if it does not, we say it's a bug. wxASSERT_MSG( filename.IsAbsolute(), wxT( "Import schematic: path is not absolute!" ) ); if( !LockFile( aFileName ) ) { wxString msg; msg.Printf( _( "Schematic '%s' is already open." ), filename.GetFullName() ); if( !OverrideLock( this, msg ) ) return false; } try { SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( fileType ) ); DIALOG_HTML_REPORTER errorReporter( this ); WX_PROGRESS_REPORTER progressReporter( this, _( "Importing Schematic" ), 1 ); pi->SetReporter( errorReporter.m_Reporter ); pi->SetProgressReporter( &progressReporter ); Schematic().SetRoot( pi->Load( aFileName, &Schematic() ) ); if( errorReporter.m_Reporter->HasMessage() ) { errorReporter.m_Reporter->Flush(); // Build HTML messages errorReporter.ShowModal(); } // Non-KiCad schematics do not use a drawing-sheet (or if they do, it works differently // to KiCad), so set it to an empty one DS_DATA_MODEL& drawingSheet = DS_DATA_MODEL::GetTheInstance(); drawingSheet.SetEmptyLayout(); BASE_SCREEN::m_DrawingSheetFileName = "empty.kicad_wks"; wxFileName layoutfn( Prj().GetProjectPath(), BASE_SCREEN::m_DrawingSheetFileName ); wxFFile layoutfile; if( layoutfile.Open( layoutfn.GetFullPath(), "wb" ) ) { layoutfile.Write( DS_DATA_MODEL::EmptyLayout() ); layoutfile.Close(); } newfilename.SetPath( Prj().GetProjectPath() ); newfilename.SetName( Prj().GetProjectName() ); newfilename.SetExt( KiCadSchematicFileExtension ); SetScreen( GetCurrentSheet().LastScreen() ); Schematic().Root().SetFileName( newfilename.GetFullPath() ); GetScreen()->SetFileName( newfilename.GetFullPath() ); GetScreen()->SetContentModified(); // Only fix junctions for CADSTAR importer for now as it may cause issues with // other importers if( fileType == SCH_IO_MGR::SCH_CADSTAR_ARCHIVE ) { FixupJunctions(); RecalculateConnections( GLOBAL_CLEANUP ); } // Only perform the dangling end test on root sheet. GetScreen()->TestDanglingEnds(); ClearUndoRedoList(); initScreenZoom(); SetSheetNumberAndCount(); SyncView(); UpdateHierarchyNavigator(); UpdateTitle(); } catch( const IO_ERROR& ioe ) { // Do not leave g_RootSheet == NULL because it is expected to be // a valid sheet. Therefore create a dummy empty root sheet and screen. CreateScreens(); m_toolManager->RunAction( ACTIONS::zoomFitScreen, true ); wxString msg = wxString::Format( _( "Error loading schematic '%s'." ), aFileName ); DisplayErrorMessage( this, msg, ioe.What() ); msg.Printf( _( "Failed to load '%s'." ), aFileName ); SetMsgPanel( wxEmptyString, msg ); return false; } return true; default: return false; } } bool SCH_EDIT_FRAME::AskToSaveChanges() { SCH_SCREENS screenList( Schematic().Root() ); // Save any currently open and modified project files. for( SCH_SCREEN* screen = screenList.GetFirst(); screen; screen = screenList.GetNext() ) { if( screen->IsContentModified() ) { if( !HandleUnsavedChanges( this, _( "The current schematic has been modified. " "Save changes?" ), [&]()->bool { return SaveProject(); } ) ) { return false; } } } return true; }