/* * 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-2017 Wayne Stambaugh * Copyright (C) 2013 CERN (www.cern.ch) * Copyright (C) 1992-2017 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 */ /** * @file eeschema/files-io.cpp */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define USE_SCH_LEGACY_IO_PLUGIN bool SCH_EDIT_FRAME::SaveEEFile( SCH_SCREEN* aScreen, bool aSaveUnderNewName, bool aCreateBackupFile ) { wxString msg; wxFileName schematicFileName; bool success; if( aScreen == NULL ) aScreen = GetScreen(); // If no name exists in the window yet - save as new. if( aScreen->GetFileName().IsEmpty() ) aSaveUnderNewName = true; // Construct the name of the file to be saved schematicFileName = Prj().AbsolutePath( aScreen->GetFileName() ); if( aSaveUnderNewName ) { wxFileDialog dlg( this, _( "Schematic Files" ), wxPathOnly( Prj().GetProjectFullName() ), schematicFileName.GetFullName(), SchematicFileWildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if( dlg.ShowModal() == wxID_CANCEL ) return false; schematicFileName = dlg.GetPath(); if( schematicFileName.GetExt() != SchematicFileExtension ) schematicFileName.SetExt( SchematicFileExtension ); } if( !IsWritable( schematicFileName ) ) return false; // Create backup if requested if( aCreateBackupFile && schematicFileName.FileExists() ) { wxFileName backupFileName = schematicFileName; // Rename the old file to a '.bak' one: backupFileName.SetExt( SchematicBackupFileExtension ); if( backupFileName.FileExists() ) wxRemoveFile( backupFileName.GetFullPath() ); if( !wxRenameFile( schematicFileName.GetFullPath(), backupFileName.GetFullPath() ) ) { msg.Printf( _( "Could not save backup of file '%s'" ), GetChars( schematicFileName.GetFullPath() ) ); DisplayError( this, msg ); } } // Save wxLogTrace( traceAutoSave, wxT( "Saving file <" ) + schematicFileName.GetFullPath() + wxT( ">" ) ); SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) ); try { pi->Save( schematicFileName.GetFullPath(), aScreen, &Kiway() ); success = true; } catch( const IO_ERROR& ioe ) { msg.Printf( _( "Error saving schematic file '%s'.\n%s" ), GetChars( schematicFileName.GetFullPath() ), GetChars( ioe.What() ) ); DisplayError( this, msg ); msg.Printf( _( "Failed to save '%s'" ), GetChars( schematicFileName.GetFullPath() ) ); AppendMsgPanel( wxEmptyString, msg, CYAN ); success = false; } if( success ) { // Delete auto save file. wxFileName autoSaveFileName = schematicFileName; autoSaveFileName.SetName( AUTOSAVE_PREFIX_FILENAME + schematicFileName.GetName() ); if( autoSaveFileName.FileExists() ) { wxLogTrace( traceAutoSave, wxT( "Removing auto save file <" ) + autoSaveFileName.GetFullPath() + wxT( ">" ) ); wxRemoveFile( autoSaveFileName.GetFullPath() ); } // Update the screen and frame info. if( aSaveUnderNewName ) aScreen->SetFileName( schematicFileName.GetFullPath() ); aScreen->ClrSave(); aScreen->ClrModify(); msg.Printf( _( "File %s saved" ), GetChars( aScreen->GetFileName() ) ); SetStatusText( msg, 0 ); } else { DisplayError( this, _( "File write operation failed." ) ); } return success; } void SCH_EDIT_FRAME::Save_File( wxCommandEvent& event ) { int id = event.GetId(); switch( id ) { case ID_UPDATE_ONE_SHEET: SaveEEFile( NULL ); break; case ID_SAVE_ONE_SHEET_UNDER_NEW_NAME: if( SaveEEFile( NULL, true ) ) { CreateArchiveLibraryCacheFile( true ); } break; } UpdateTitle(); } bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector& aFileSet, int aCtl ) { // implement the pseudo code from KIWAY_PLAYER.h: SCH_SCREENS screenList; // This is for python: if( aFileSet.size() != 1 ) { UTF8 msg = StrPrintf( "Eeschema:%s() takes only a single filename", __func__ ); DisplayError( this, msg ); return false; } wxString fullFileName( aFileSet[0] ); // We insist on caller sending us an absolute path, if it does not, we say it's a bug. wxASSERT_MSG( wxFileName( fullFileName ).IsAbsolute(), wxT( "bug in single_top.cpp or project manager." ) ); if( !LockFile( fullFileName ) ) { wxString msg = wxString::Format( _( "Schematic file '%s' is already open." ), GetChars( fullFileName ) ); DisplayError( this, msg ); return false; } // Save any currently open and modified project files. for( SCH_SCREEN* screen = screenList.GetFirst(); screen; screen = screenList.GetNext() ) { if( screen->IsModify() ) { int response = YesNoCancelDialog( this, _( "The current schematic has been modified. Do you wish to save the changes?" ), wxEmptyString, _( "Save and Load" ), _( "Load Without Saving" ) ); if( response == wxID_CANCEL ) { return false; } else if( response == wxID_YES ) { wxCommandEvent dummy; OnSaveProject( dummy ); } else { // response == wxID_NO, fall thru } break; } } 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. wxString ask = wxString::Format( _( "Schematic '%s' does not exist. Do you wish to create it?" ), GetChars( fullFileName ) ); if( !IsOK( this, ask ) ) return false; } // unload current project file before loading new { delete g_RootSheet; g_RootSheet = NULL; CreateScreens(); } GetScreen()->SetFileName( fullFileName ); g_RootSheet->SetFileName( fullFileName ); SetStatusText( wxEmptyString ); ClearMsgPanel(); LoadProjectFile(); // PROJECT::SetProjectFullName() is an impactful function. It should only be // called under carefully considered circumstances. // The calling code should know not to ask me here to change projects unless // it knows what consequences that will have on other KIFACEs running and using // this same PROJECT. It can be very harmful if that calling code is stupid. // 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( pro.GetFullPath() != Prj().GetProjectFullName() || !Prj().GetElem( PROJECT::ELEM_SCH_PART_LIBS ) ) { Prj().SetProjectFullName( pro.GetFullPath() ); // 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_PART_LIBS, NULL ); Prj().SchLibs(); } // Load the symbol library table, this will be used forever more. Prj().SetElem( PROJECT::ELEM_SYMBOL_LIB_TABLE, NULL ); Prj().SchSymbolLibTable(); if( is_new ) { // mark new, unsaved file as modified. GetScreen()->SetModify(); } else { delete g_RootSheet; // Delete the current project. g_RootSheet = NULL; // Force CreateScreens() to build new empty project on load failure. SCH_PLUGIN::SCH_PLUGIN_RELEASER pi; // Iterate through the available plugins to determine the file type for( auto pluginType : SCH_IO_MGR::SCH_FILE_T_vector ) { pi.set( SCH_IO_MGR::FindPlugin( pluginType ) ); if( pi && pi->CheckHeader( fullFileName ) ) break; // got the right plugin else pi.set( nullptr ); // do not give a false impression that we have a valid plugin } try { if( !pi ) THROW_IO_ERROR( _( "File format not recognized" ) ); g_RootSheet = pi->Load( fullFileName, &Kiway() ); m_CurrentSheet->clear(); m_CurrentSheet->push_back( g_RootSheet ); } 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(); Zoom_Automatique( false ); wxString msg; msg.Printf( _( "Error loading schematic file '%s'.\n%s" ), GetChars( fullFileName ), GetChars( ioe.What() ) ); DisplayError( this, msg ); msg.Printf( _( "Failed to load '%s'" ), GetChars( fullFileName ) ); AppendMsgPanel( wxEmptyString, msg, CYAN ); return false; } SetScreen( m_CurrentSheet->LastScreen() ); // 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( g_RootSheet ); 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." ) ); } UpdateFileHistory( fullFileName ); // Check to see whether some old library parts need to be rescued // Only do this if RescueNeverShow was not set. wxConfigBase *config = Kiface().KifaceSettings(); bool rescueNeverShow = false; config->Read( RescueNeverShowEntry, &rescueNeverShow, false ); if( !rescueNeverShow ) { RescueProject( false ); } SCH_SCREENS schematic; schematic.UpdateSymbolLinks(); // Update all symbol library links for all sheets. GetScreen()->TestDanglingEnds(); // Only perform the dangling end test on root sheet. } GetScreen()->SetGrid( ID_POPUP_GRID_LEVEL_1000 + m_LastGridSizeId ); Zoom_Automatique( false ); SetSheetNumberAndCount(); m_canvas->Refresh( true ); return true; } bool SCH_EDIT_FRAME::AppendOneEEProject() { 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, _( "Import Schematic" ), path, wxEmptyString, SchematicFileWildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST ); if( dlg.ShowModal() == wxID_CANCEL ) return false; fullFileName = dlg.GetPath(); wxFileName fn = fullFileName; if( fn.IsRelative() ) { fn.MakeAbsolute(); fullFileName = fn.GetFullPath(); } wxString cache_name = PART_LIBS::CacheName( fullFileName ); if( !!cache_name ) { PART_LIBS* libs = Prj().SchLibs(); try { if( PART_LIB* lib = libs->AddLibrary( cache_name ) ) lib->SetCache(); } catch( const IO_ERROR& ioe ) { DisplayError( this, ioe.What() ); } } wxLogDebug( wxT( "Importing schematic " ) + fullFileName ); // Keep trace of the last item in list. // New items will be loaded after this one. SCH_ITEM* bs = screen->GetDrawItems(); if( bs ) { while( bs->Next() ) bs = bs->Next(); } // load the project bool success = LoadOneEEFile( screen, fullFileName, true ); if( success ) { // the new loaded items need cleaning to avoid duplicate parameters // which should be unique (ref and time stamp). // Clear ref and set a new time stamp for new items if( bs == NULL ) bs = screen->GetDrawItems(); else bs = bs->Next(); while( bs ) { SCH_ITEM* nextbs = bs->Next(); // To avoid issues with the current hieratchy, // do not load included sheets files and give new filenames // and new sheet names. // There are many tricky cases (loops, creation of complex hierarchies // with duplicate file names, duplicate sheet names...) // So the included sheets names are renamed if existing, // and filenames are just renamed to avoid loops and // creation of complex hierarchies. // If someone want to change it for a better append function, remember // these cases need work to avoid issues. if( bs->Type() == SCH_SHEET_T ) { SCH_SHEET * sheet = (SCH_SHEET *) bs; time_t newtimestamp = GetNewTimeStamp(); sheet->SetTimeStamp( newtimestamp ); // Check for existing subsheet name in the current sheet wxString tmp = sheet->GetName(); sheet->SetName( wxEmptyString ); const SCH_SHEET* subsheet = GetScreen()->GetSheet( tmp ); if( subsheet ) sheet->SetName( wxString::Format( wxT( "Sheet%8.8lX" ), (long) newtimestamp ) ); else sheet->SetName( tmp ); sheet->SetFileName( wxString::Format( wxT( "file%8.8lX.sch" ), (long) newtimestamp ) ); SCH_SCREEN* new_screen = new SCH_SCREEN( &Kiway() ); new_screen->SetMaxUndoItems( m_UndoRedoCountMax ); sheet->SetScreen( new_screen ); sheet->GetScreen()->SetFileName( sheet->GetFileName() ); } // clear annotation and init new time stamp for the new components else if( bs->Type() == SCH_COMPONENT_T ) { ( (SCH_COMPONENT*) bs )->SetTimeStamp( GetNewTimeStamp() ); ( (SCH_COMPONENT*) bs )->ClearAnnotation( NULL ); // Clear flags, which are set by these previous modifications: bs->ClearFlags(); } bs = nextbs; } } OnModify(); // redraw base screen (ROOT) if necessary GetScreen()->SetGrid( ID_POPUP_GRID_LEVEL_1000 + m_LastGridSizeId ); Zoom_Automatique( false ); SetSheetNumberAndCount(); m_canvas->Refresh( true ); return success; } void SCH_EDIT_FRAME::OnAppendProject( wxCommandEvent& event ) { wxString msg = _( "This operation cannot be undone. " "Besides, take into account that hierarchical sheets will not be appended.\n\n" "Do you want to save the current document before proceeding?" ); if( IsOK( this, msg ) ) OnSaveProject( event ); AppendOneEEProject(); } void SCH_EDIT_FRAME::OnSaveProject( wxCommandEvent& aEvent ) { SCH_SCREEN* screen; SCH_SCREENS screenList; // I want to see it in the debugger, show me the string! Can't do that with wxFileName. wxString fileName = Prj().AbsolutePath( g_RootSheet->GetFileName() ); wxFileName fn = fileName; if( !fn.IsDirWritable() ) { wxString msg = wxString::Format( _( "Directory '%s' is not writable" ), GetChars( fn.GetPath() ) ); DisplayError( this, msg ); return; } for( screen = screenList.GetFirst(); screen; screen = screenList.GetNext() ) SaveEEFile( screen ); CreateArchiveLibraryCacheFile(); UpdateTitle(); } bool SCH_EDIT_FRAME::doAutoSave() { wxFileName tmpFileName = g_RootSheet->GetFileName(); wxFileName fn = tmpFileName; wxFileName tmp; SCH_SCREENS screens; bool autoSaveOk = true; tmp.AssignDir( fn.GetPath() ); if( !tmp.IsOk() ) return false; if( !IsWritable( tmp ) ) return false; for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() ) { // Only create auto save files for the schematics that have been modified. if( !screen->IsSave() ) continue; tmpFileName = fn = screen->GetFileName(); // Auto save file name is the normal file name prefixed with AUTOSAVE_PREFIX_FILENAME. fn.SetName( AUTOSAVE_PREFIX_FILENAME + fn.GetName() ); screen->SetFileName( fn.GetFullPath() ); if( SaveEEFile( screen, false, NO_BACKUP_FILE ) ) screen->SetModify(); else autoSaveOk = false; screen->SetFileName( tmpFileName.GetFullPath() ); } if( autoSaveOk ) m_autoSaveState = false; return autoSaveOk; }