1115 lines
39 KiB
C++
1115 lines
39 KiB
C++
/*
|
|
* 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-2019 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 <fctsys.h>
|
|
#include <sch_draw_panel.h>
|
|
#include <confirm.h>
|
|
#include <gestfich.h>
|
|
#include <sch_edit_frame.h>
|
|
#include <pgm_base.h>
|
|
#include <kiface_i.h>
|
|
#include <richio.h>
|
|
#include <trace_helpers.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <eeschema_id.h>
|
|
#include <class_library.h>
|
|
#include <lib_edit_frame.h>
|
|
#include <sch_sheet.h>
|
|
#include <sch_sheet_path.h>
|
|
#include <sch_component.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
#include <project_rescue.h>
|
|
#include <eeschema_config.h>
|
|
#include <sch_legacy_plugin.h>
|
|
#include <sch_eagle_plugin.h>
|
|
#include <symbol_lib_table.h>
|
|
#include <dialog_symbol_remap.h>
|
|
#include <dialog_migrate_buses.h>
|
|
#include <ws_data_model.h>
|
|
#include <connection_graph.h>
|
|
#include <tool/actions.h>
|
|
|
|
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' suffixed one:
|
|
backupFileName.SetExt( schematicFileName.GetExt() + GetBackupSuffix() );
|
|
|
|
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( GetAutoSaveFilePrefix() + schematicFileName.GetName() );
|
|
|
|
if( autoSaveFileName.FileExists() )
|
|
{
|
|
wxLogTrace( traceAutoSave,
|
|
wxT( "Removing auto save file <" ) + autoSaveFileName.GetFullPath() +
|
|
wxT( ">" ) );
|
|
|
|
wxRemoveFile( autoSaveFileName.GetFullPath() );
|
|
}
|
|
|
|
// Update the screen and frame info and reset the lock file.
|
|
if( aSaveUnderNewName )
|
|
{
|
|
aScreen->SetFileName( schematicFileName.GetFullPath() );
|
|
LockFile( 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( bool doSaveAs )
|
|
{
|
|
if( doSaveAs )
|
|
{
|
|
if( SaveEEFile( NULL, true ) )
|
|
CreateArchiveLibraryCacheFile( true );
|
|
}
|
|
else
|
|
{
|
|
SaveEEFile( NULL );
|
|
}
|
|
|
|
UpdateTitle();
|
|
}
|
|
|
|
|
|
bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, int aCtl )
|
|
{
|
|
// implement the pseudo code from KIWAY_PLAYER.h:
|
|
|
|
// 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( "Path is not absolute!" ) );
|
|
|
|
if( !LockFile( fullFileName ) )
|
|
{
|
|
wxString msg = wxString::Format( _( "Schematic file \"%s\" is already open." ),
|
|
fullFileName );
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
if( !AskToSaveChanges() )
|
|
return false;
|
|
|
|
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?" ),
|
|
fullFileName );
|
|
if( !IsOK( this, ask ) )
|
|
return false;
|
|
}
|
|
|
|
// unload current project file before loading new
|
|
{
|
|
SetScreen( nullptr );
|
|
delete g_RootSheet;
|
|
if( g_CurrentSheet )
|
|
g_CurrentSheet->clear();
|
|
g_RootSheet = nullptr;
|
|
|
|
CreateScreens();
|
|
}
|
|
|
|
GetScreen()->SetFileName( fullFileName );
|
|
g_RootSheet->SetFileName( fullFileName );
|
|
|
|
SetStatusText( wxEmptyString );
|
|
ClearMsgPanel();
|
|
|
|
// 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();
|
|
}
|
|
|
|
LoadProjectFile();
|
|
|
|
// 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
|
|
{
|
|
SetScreen( nullptr );
|
|
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( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) );
|
|
|
|
// This will rename the file if there is an autosave and the user want to recover
|
|
CheckForAutoSaveFile( fullFileName );
|
|
|
|
try
|
|
{
|
|
g_RootSheet = pi->Load( fullFileName, &Kiway() );
|
|
|
|
g_CurrentSheet = new SCH_SHEET_PATH();
|
|
g_CurrentSheet->clear();
|
|
g_CurrentSheet->push_back( g_RootSheet );
|
|
|
|
if( !pi->GetError().IsEmpty() )
|
|
{
|
|
DisplayErrorMessage( this,
|
|
_( "The entire schematic could not be loaded. Errors "
|
|
"occurred attempting to load \nhierarchical sheet "
|
|
"schematics." ),
|
|
pi->GetError() );
|
|
}
|
|
}
|
|
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;
|
|
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;
|
|
}
|
|
|
|
// 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 );
|
|
|
|
SCH_SCREENS schematic;
|
|
|
|
// Convert old projects over to use symbol library table.
|
|
if( schematic.HasNoFullyDefinedLibIds() )
|
|
{
|
|
DIALOG_SYMBOL_REMAP dlgRemap( this );
|
|
|
|
dlgRemap.ShowQuasiModal();
|
|
}
|
|
else
|
|
{
|
|
// 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 )
|
|
RescueSymbolLibTableProject( false );
|
|
}
|
|
|
|
schematic.UpdateSymbolLinks(); // Update all symbol library links for all sheets.
|
|
SetScreen( g_CurrentSheet->LastScreen() );
|
|
|
|
// Ensure the schematic is fully segmented on first display
|
|
NormalizeSchematicOnFirstLoad( true );
|
|
GetScreen()->ClearUndoORRedoList( GetScreen()->m_UndoList, 1 );
|
|
|
|
GetScreen()->TestDanglingEnds(); // Only perform the dangling end test on root sheet.
|
|
|
|
// Migrate conflicting bus definitions
|
|
// TODO(JE) This should only run once based on schematic file version
|
|
if( g_ConnectionGraph->GetBusesNeedingMigration().size() > 0 )
|
|
{
|
|
DIALOG_MIGRATE_BUSES dlg( this );
|
|
dlg.ShowQuasiModal();
|
|
|
|
RecalculateConnections();
|
|
OnModify();
|
|
}
|
|
|
|
GetScreen()->m_Initialized = true;
|
|
}
|
|
|
|
GetScreen()->SetGrid( ID_POPUP_GRID_LEVEL_1000 + m_LastGridSizeId );
|
|
m_toolManager->RunAction( ACTIONS::zoomFitScreen, true );
|
|
SetSheetNumberAndCount();
|
|
|
|
// 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.
|
|
FixupJunctions();
|
|
|
|
SyncView();
|
|
GetScreen()->ClearDrawingState();
|
|
|
|
UpdateTitle();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SCH_EDIT_FRAME::AppendSchematic()
|
|
{
|
|
int i;
|
|
wxString msg;
|
|
wxString fullFileName;
|
|
wxString topLevelSheetPath;
|
|
wxFileName tmp;
|
|
SCH_SCREEN* screen = GetScreen();
|
|
bool libTableChanged = false;
|
|
|
|
|
|
if( !screen )
|
|
{
|
|
wxLogError( wxT( "Document not ready, cannot import" ) );
|
|
return false;
|
|
}
|
|
|
|
// open file chooser dialog
|
|
wxString path = wxPathOnly( Prj().GetProjectFullName() );
|
|
|
|
wxFileDialog dlg( this, _( "Append 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();
|
|
}
|
|
|
|
// Load the schematic into a temporary sheet.
|
|
SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) );
|
|
std::unique_ptr< SCH_SHEET> newSheet( new SCH_SHEET );
|
|
|
|
newSheet->SetFileName( fullFileName );
|
|
|
|
try
|
|
{
|
|
pi->Load( fullFileName, &Kiway(), newSheet.get() );
|
|
|
|
if( !pi->GetError().IsEmpty() )
|
|
{
|
|
DisplayErrorMessage( this,
|
|
_( "The entire schematic could not be loaded. Errors "
|
|
"occurred attempting to load hierarchical sheet "
|
|
"schematics." ),
|
|
pi->GetError() );
|
|
}
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
msg.Printf( _( "Error occurred loading schematic file \"%s\"." ), fullFileName );
|
|
DisplayErrorMessage( this, msg, ioe.What() );
|
|
|
|
msg.Printf( _( "Failed to load schematic \"%s\"" ), fullFileName );
|
|
AppendMsgPanel( wxEmptyString, msg, CYAN );
|
|
|
|
return false;
|
|
}
|
|
|
|
tmp = fn;
|
|
|
|
// If the appended schematic is in a different folder from the current project and
|
|
// it contains hierarchical sheets, the hierarchical sheet paths need to be updated.
|
|
if( fn.GetPath( wxPATH_GET_SEPARATOR ) != Prj().GetProjectPath() && newSheet->CountSheets() )
|
|
{
|
|
// Give the user the option to choose relative path if possible.
|
|
if( tmp.MakeRelativeTo( Prj().GetProjectPath() ) )
|
|
{
|
|
wxMessageDialog msgDlg1(
|
|
this,
|
|
"Do you want to use a relative path to the appended "
|
|
"schematic?", "Select Path Type",
|
|
wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION | wxCENTER );
|
|
msgDlg1.SetYesNoLabels( wxMessageDialog::ButtonLabel( "Use Relative Path" ),
|
|
wxMessageDialog::ButtonLabel( "Use Absolute Path" ) );
|
|
int rsp = msgDlg1.ShowModal();
|
|
|
|
if( rsp == wxID_CANCEL )
|
|
{
|
|
return false;
|
|
}
|
|
else if( rsp == wxID_NO )
|
|
{
|
|
topLevelSheetPath = fn.GetPathWithSep();
|
|
|
|
if( wxFileName::GetPathSeparator() == '\\' )
|
|
topLevelSheetPath.Replace( "\\", "/" );
|
|
}
|
|
else
|
|
{
|
|
topLevelSheetPath = tmp.GetPathWithSep( wxPATH_UNIX );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
topLevelSheetPath = tmp.GetPathWithSep();
|
|
|
|
if( wxFileName::GetPathSeparator() == '\\' )
|
|
topLevelSheetPath.Replace( "\\", "/" );
|
|
}
|
|
}
|
|
|
|
// Make sure any new sheet changes do not cause any recursion issues.
|
|
SCH_SHEET_LIST hierarchy( g_RootSheet ); // This is the schematic sheet hierarchy.
|
|
SCH_SHEET_LIST sheetHierarchy( newSheet.get() ); // This is the hierarchy of the import.
|
|
|
|
wxFileName destFile = screen->GetFileName();
|
|
|
|
if( destFile.IsRelative() )
|
|
destFile.MakeAbsolute( Prj().GetProjectPath() );
|
|
|
|
if( hierarchy.TestForRecursion( sheetHierarchy, destFile.GetFullPath( wxPATH_UNIX ) ) )
|
|
{
|
|
msg.Printf( _( "The sheet changes cannot be made because the destination sheet already "
|
|
"has the sheet \"%s\" or one of it's subsheets as a parent somewhere in "
|
|
"the schematic hierarchy." ),
|
|
destFile.GetFullPath() );
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
wxArrayString names;
|
|
|
|
// Make sure the imported schematic has been remapped to the symbol library table.
|
|
SCH_SCREENS newScreens( newSheet.get() ); // All screens associated with the import.
|
|
|
|
if( newScreens.HasNoFullyDefinedLibIds() )
|
|
{
|
|
DisplayInfoMessage( this,
|
|
"This schematic has not been remapped to the symbol library\n"
|
|
"table. The project this schematic belongs to must first be\n"
|
|
"remapped before it can be imported into the current project." );
|
|
return false;
|
|
}
|
|
|
|
wxArrayString newLibNames;
|
|
SCH_SCREENS prjScreens( g_RootSheet );
|
|
|
|
newScreens.GetLibNicknames( names );
|
|
|
|
wxMessageDialog::ButtonLabel okButtonLabel( _( "Continue Append" ) );
|
|
wxMessageDialog::ButtonLabel cancelButtonLabel( _( "Cancel Append" ) );
|
|
|
|
if( fn.GetPath( wxPATH_GET_SEPARATOR ) == Prj().GetProjectPath()
|
|
&& !prjScreens.HasSchematic( fullFileName ) )
|
|
{
|
|
// A schematic in the current project path that isn't part of the current project.
|
|
// It's possible the user copied this schematic from another project so the library
|
|
// links may not be avaible. Even this is check is no guarantee that all symbol
|
|
// library links are valid but it's better than nothing.
|
|
for( const auto& name : names )
|
|
{
|
|
if( !Prj().SchSymbolLibTable()->HasLibrary( name ) )
|
|
newLibNames.Add( name );
|
|
}
|
|
|
|
if( !newLibNames.IsEmpty() )
|
|
{
|
|
msg = _( "There are library names in the appended schematic that are missing "
|
|
"from the project library table. This may result in broken symbol "
|
|
"library links for the appended schematic. Do you wish to continue?" );
|
|
wxMessageDialog msgDlg1( this, msg, _( "Continue Append Schematic" ),
|
|
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
|
|
wxCENTER | wxICON_QUESTION );
|
|
msgDlg1.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
|
|
|
|
if( msgDlg1.ShowModal() == wxID_CANCEL )
|
|
return false;
|
|
}
|
|
}
|
|
else if( fn.GetPath( wxPATH_GET_SEPARATOR ) != Prj().GetProjectPath() )
|
|
{
|
|
// A schematic loaded from a path other than the current project path.
|
|
|
|
// If there are symbol libraries in the imported schematic that are not in the
|
|
// symbol library table of this project, there could be a lot of broken symbol
|
|
// library links. Attempt to add the missing libraries to the project symbol
|
|
// library table.
|
|
wxArrayString duplicateLibNames;
|
|
|
|
for( const auto& name : names )
|
|
{
|
|
if( !Prj().SchSymbolLibTable()->HasLibrary( name ) )
|
|
newLibNames.Add( name );
|
|
else
|
|
duplicateLibNames.Add( name );
|
|
}
|
|
|
|
SYMBOL_LIB_TABLE table;
|
|
wxFileName symLibTableFn( fn.GetPath(), SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
|
|
|
|
// If there are any new or duplicate libraries, check to see if it's possible that
|
|
// there could be any missing libraries that would cause broken symbol library links.
|
|
if( !newLibNames.IsEmpty() || !duplicateLibNames.IsEmpty() )
|
|
{
|
|
if( !symLibTableFn.Exists() || !symLibTableFn.IsFileReadable() )
|
|
{
|
|
msg.Printf( _( "The project library table \"%s\" does not exist or cannot "
|
|
"be read. This may result in broken symbol links for the "
|
|
"appended schematic. Do you wish to continue?" ),
|
|
fn.GetFullPath() );
|
|
wxMessageDialog msgDlg2( this, msg, _( "Continue Append Schematic" ),
|
|
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
|
|
wxCENTER | wxICON_QUESTION );
|
|
msgDlg2.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
|
|
|
|
if( msgDlg2.ShowModal() == wxID_CANCEL )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
table.Load( symLibTableFn.GetFullPath() );
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
msg.Printf( _( "An error occurred loading the symbol library table \"%s\"." ),
|
|
symLibTableFn.GetFullPath() );
|
|
DisplayErrorMessage( NULL, msg, ioe.What() );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check to see if any of the symbol libraries found in the appended schematic do
|
|
// not exist in the current project are missing from the appended project symbol
|
|
// library table.
|
|
if( !newLibNames.IsEmpty() )
|
|
{
|
|
bool missingLibNames = table.IsEmpty();
|
|
|
|
if( !missingLibNames )
|
|
{
|
|
for( const auto& newLibName : newLibNames )
|
|
{
|
|
if( !table.HasLibrary( newLibName ) )
|
|
{
|
|
missingLibNames = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( missingLibNames )
|
|
{
|
|
msg = _( "There are library names in the appended schematic that are missing "
|
|
"from the appended schematic project library table. This may result "
|
|
"in broken symbol library links for the appended schematic. "
|
|
"Do you wish to continue?" );
|
|
wxMessageDialog msgDlg3( this, msg, _( "Continue Append Schematic" ),
|
|
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
|
|
wxCENTER | wxICON_QUESTION );
|
|
msgDlg3.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
|
|
|
|
if( msgDlg3.ShowModal() == wxID_CANCEL )
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// The library name already exists in the current project. Check to see if the
|
|
// duplicate name is the same library in the current project. If it's not, it's
|
|
// most likely that the symbol library links will be broken.
|
|
if( !duplicateLibNames.IsEmpty() && !table.IsEmpty() )
|
|
{
|
|
bool libNameConflict = false;
|
|
|
|
for( const auto& duplicateLibName : duplicateLibNames )
|
|
{
|
|
const SYMBOL_LIB_TABLE_ROW* thisRow = nullptr;
|
|
const SYMBOL_LIB_TABLE_ROW* otherRow = nullptr;
|
|
|
|
if( Prj().SchSymbolLibTable()->HasLibrary( duplicateLibName ) )
|
|
thisRow = Prj().SchSymbolLibTable()->FindRow( duplicateLibName );
|
|
|
|
if( table.HasLibrary( duplicateLibName ) )
|
|
otherRow = table.FindRow( duplicateLibName );
|
|
|
|
// It's in the global library table so there is no conflict.
|
|
if( thisRow && !otherRow )
|
|
continue;
|
|
|
|
if( !thisRow || !otherRow )
|
|
continue;
|
|
|
|
wxFileName otherUriFileName;
|
|
wxString thisURI = thisRow->GetFullURI( true );
|
|
wxString otherURI = otherRow->GetFullURI( false);
|
|
|
|
if( otherURI.Contains( "${KIPRJMOD}" ) || otherURI.Contains( "$(KIPRJMOD)" ) )
|
|
{
|
|
// Cannot use relative paths here, "${KIPRJMOD}../path-to-cache-lib" does
|
|
// not expand to a valid symbol library path.
|
|
otherUriFileName.SetPath( fn.GetPath() );
|
|
otherUriFileName.SetFullName( otherURI.AfterLast( '}' ) );
|
|
otherURI = otherUriFileName.GetFullPath();
|
|
}
|
|
|
|
if( thisURI != otherURI )
|
|
{
|
|
libNameConflict = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( libNameConflict )
|
|
{
|
|
msg = _( "A duplicate library name that references a different library exists "
|
|
"in the current library table. This conflict cannot be resolved and "
|
|
"may result in broken symbol library links for the appended schematic. "
|
|
"Do you wish to continue?" );
|
|
wxMessageDialog msgDlg4( this, msg, _( "Continue Append Schematic" ),
|
|
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
|
|
wxCENTER | wxICON_QUESTION );
|
|
msgDlg4.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
|
|
|
|
if( msgDlg4.ShowModal() == wxID_CANCEL )
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// All (most?) of the possible broken symbol library link cases are covered. Map the
|
|
// new appended schematic project symbol library table entries to the current project
|
|
// symbol library table.
|
|
if( !newLibNames.IsEmpty() && !table.IsEmpty() )
|
|
{
|
|
for( const auto& libName : newLibNames )
|
|
{
|
|
if( !table.HasLibrary( libName )
|
|
|| Prj().SchSymbolLibTable()->HasLibrary( libName ) )
|
|
continue;
|
|
|
|
// Don't expand environment variable because KIPRJMOD will not be correct
|
|
// for a different project.
|
|
wxString uri = table.GetFullURI( libName, false );
|
|
wxFileName newLib;
|
|
|
|
if( uri.Contains( "${KIPRJMOD}" ) || uri.Contains( "$(KIPRJMOD)" ) )
|
|
{
|
|
// Cannot use relative paths here, "${KIPRJMOD}../path-to-cache-lib" does
|
|
// not expand to a valid symbol library path.
|
|
newLib.SetPath( fn.GetPath() );
|
|
newLib.SetFullName( uri.AfterLast( '}' ) );
|
|
uri = newLib.GetFullPath();
|
|
}
|
|
else
|
|
{
|
|
uri = table.GetFullURI( libName );
|
|
}
|
|
|
|
// Add the library from the imported project to the current project
|
|
// symbol library table.
|
|
const SYMBOL_LIB_TABLE_ROW* row = table.FindRow( libName );
|
|
|
|
auto newRow = new SYMBOL_LIB_TABLE_ROW( libName, uri, row->GetType(),
|
|
row->GetOptions(), row->GetDescr() );
|
|
|
|
Prj().SchSymbolLibTable()->InsertRow( newRow );
|
|
libTableChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
newScreens.ClearAnnotation();
|
|
|
|
// Check for duplicate sheet names in the current page.
|
|
wxArrayString duplicateSheetNames;
|
|
EE_TYPE_COLLECTOR sheets;
|
|
|
|
sheets.Collect( screen->GetDrawItems(), EE_COLLECTOR::SheetsOnly );
|
|
|
|
for( i = 0; i < sheets.GetCount(); ++i )
|
|
{
|
|
if( newSheet->GetScreen()->GetSheet( ( ( SCH_SHEET* ) sheets[i] )->GetName() ) )
|
|
duplicateSheetNames.Add( ( ( SCH_SHEET* ) sheets[i] )->GetName() );
|
|
}
|
|
|
|
if( !duplicateSheetNames.IsEmpty() )
|
|
{
|
|
msg.Printf( "Duplicate sheet names exist on the current page. Do you want to "
|
|
"automatically rename the duplicate sheet names?" );
|
|
|
|
if( !IsOK( this, msg ) )
|
|
return false;
|
|
}
|
|
|
|
SCH_SCREEN* newScreen = newSheet->GetScreen();
|
|
wxCHECK_MSG( newScreen, false, "No screen defined for imported sheet." );
|
|
|
|
for( const auto& duplicateName : duplicateSheetNames )
|
|
{
|
|
SCH_SHEET* renamedSheet = newScreen->GetSheet( duplicateName );
|
|
|
|
wxCHECK2_MSG( renamedSheet, continue,
|
|
"Sheet " + duplicateName + " not found in imported schematic." );
|
|
|
|
timestamp_t newtimestamp = GetNewTimeStamp();
|
|
renamedSheet->SetTimeStamp( newtimestamp );
|
|
renamedSheet->SetName( wxString::Format( "Sheet%8.8lX", (unsigned long) newtimestamp ) );
|
|
}
|
|
|
|
EE_TYPE_COLLECTOR newTopLevelSheets;
|
|
|
|
newTopLevelSheets.Collect( newSheet->GetScreen()->GetDrawItems(), EE_COLLECTOR::SheetsOnly );
|
|
|
|
for( i = 0; i < newTopLevelSheets.GetCount(); ++i )
|
|
{
|
|
SCH_SHEET* tmpSheet = dynamic_cast< SCH_SHEET* >( newTopLevelSheets[i] );
|
|
wxCHECK2( tmpSheet != nullptr, continue );
|
|
tmpSheet->SetFileName( topLevelSheetPath + tmpSheet->GetFileName() );
|
|
}
|
|
|
|
if( libTableChanged )
|
|
Prj().SchSymbolLibTable()->Save( Prj().GetProjectPath() +
|
|
SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
|
|
|
|
// It is finally safe to add the imported schematic.
|
|
screen->Append( newScreen );
|
|
|
|
SCH_SCREENS allScreens;
|
|
allScreens.ReplaceDuplicateTimeStamps();
|
|
|
|
SCH_SCREENS screens( GetCurrentSheet().Last() );
|
|
screens.UpdateSymbolLinks( true );
|
|
|
|
// Clear all annotation in the imported schematic to prevent clashes with existing annotation.
|
|
// Must be done after updating the symbol links as we need to know about multi-unit parts.
|
|
// screens.ClearAnnotation();
|
|
|
|
screens.TestDanglingEnds();
|
|
|
|
GetScreen()->SetGrid( ID_POPUP_GRID_LEVEL_1000 + m_LastGridSizeId );
|
|
m_toolManager->RunAction( ACTIONS::zoomFitScreen, true );
|
|
SetSheetNumberAndCount();
|
|
|
|
SyncView();
|
|
HardRedraw(); // Full reinit of the current screen and the display.
|
|
OnModify();
|
|
|
|
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
|
|
bool setProject = Prj().GetProjectFullName().IsEmpty();
|
|
wxString path = wxPathOnly( Prj().GetProjectFullName() );
|
|
|
|
wxFileDialog dlg( this, _( "Import Schematic" ), path, wxEmptyString,
|
|
EagleSchematicFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
|
|
|
|
if( dlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
if( setProject )
|
|
{
|
|
wxFileName projectFn( dlg.GetPath() );
|
|
projectFn.SetExt( ProjectFileExtension );
|
|
Prj().SetProjectFullName( projectFn.GetFullPath() );
|
|
}
|
|
|
|
// For now there is only one import plugin
|
|
importFile( dlg.GetPath(), SCH_IO_MGR::SCH_EAGLE );
|
|
}
|
|
|
|
|
|
bool SCH_EDIT_FRAME::SaveProject()
|
|
{
|
|
SCH_SCREEN* screen;
|
|
SCH_SCREENS screenList;
|
|
bool success = true;
|
|
|
|
// 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." ), fn.GetPath() );
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
for( screen = screenList.GetFirst(); screen; screen = screenList.GetNext() )
|
|
success &= SaveEEFile( screen );
|
|
|
|
CreateArchiveLibraryCacheFile();
|
|
|
|
UpdateTitle();
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
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 GetAutoSavePrefix().
|
|
fn.SetName( GetAutoSaveFilePrefix() + 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;
|
|
}
|
|
|
|
|
|
bool SCH_EDIT_FRAME::importFile( const wxString& aFileName, int aFileType )
|
|
{
|
|
wxString projectpath;
|
|
wxFileName newfilename;
|
|
SCH_SHEET_LIST sheetList( g_RootSheet );
|
|
|
|
switch( (SCH_IO_MGR::SCH_FILE_T) aFileType )
|
|
{
|
|
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( wxFileName( aFileName ).IsAbsolute(),
|
|
wxT( "Import eagle schematic caller didn't send full filename" ) );
|
|
|
|
if( !LockFile( aFileName ) )
|
|
{
|
|
wxString msg = wxString::Format( _( "Schematic file \"%s\" is already open." ),
|
|
aFileName );
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
delete g_RootSheet;
|
|
g_RootSheet = nullptr;
|
|
SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_EAGLE ) );
|
|
g_RootSheet = pi->Load( aFileName, &Kiway() );
|
|
|
|
// Eagle sheets do not use a worksheet frame by default, so set it to an empty one
|
|
WS_DATA_MODEL& pglayout = WS_DATA_MODEL::GetTheInstance();
|
|
pglayout.SetEmptyLayout();
|
|
|
|
BASE_SCREEN::m_PageLayoutDescrFileName = "empty.kicad_wks";
|
|
wxFileName layoutfn( Kiway().Prj().GetProjectPath(),
|
|
BASE_SCREEN::m_PageLayoutDescrFileName );
|
|
wxFile layoutfile;
|
|
|
|
if( layoutfile.Create( layoutfn.GetFullPath() ) )
|
|
{
|
|
layoutfile.Write( WS_DATA_MODEL::EmptyLayout() );
|
|
layoutfile.Close();
|
|
}
|
|
|
|
projectpath = Kiway().Prj().GetProjectPath();
|
|
newfilename.SetPath( Prj().GetProjectPath() );
|
|
newfilename.SetName( Prj().GetProjectName() );
|
|
newfilename.SetExt( SchematicFileExtension );
|
|
|
|
g_CurrentSheet->clear();
|
|
g_CurrentSheet->push_back( g_RootSheet );
|
|
SetScreen( g_CurrentSheet->LastScreen() );
|
|
|
|
g_RootSheet->SetFileName( newfilename.GetFullPath() );
|
|
GetScreen()->SetFileName( newfilename.GetFullPath() );
|
|
GetScreen()->SetModify();
|
|
SaveProjectSettings( false );
|
|
|
|
UpdateFileHistory( aFileName );
|
|
SCH_SCREENS schematic;
|
|
schematic.UpdateSymbolLinks(); // Update all symbol library links for all sheets.
|
|
|
|
// Ensure the schematic is fully segmented on first display
|
|
NormalizeSchematicOnFirstLoad( false );
|
|
|
|
GetScreen()->m_Initialized = true;
|
|
|
|
EE_TYPE_COLLECTOR components;
|
|
SCH_SCREENS allScreens;
|
|
|
|
for( SCH_SCREEN* screen = allScreens.GetFirst(); screen; screen = allScreens.GetNext() )
|
|
{
|
|
components.Collect( screen->GetDrawItems(), EE_COLLECTOR::ComponentsOnly );
|
|
|
|
for( int cmpIdx = 0; cmpIdx < components.GetCount(); ++cmpIdx )
|
|
{
|
|
std::vector<wxPoint> pts;
|
|
SCH_COMPONENT* cmp = static_cast<SCH_COMPONENT*>( components[cmpIdx] );
|
|
|
|
// Update footprint LIB_ID to point to the imported Eagle library
|
|
auto fpField = cmp->GetField( FOOTPRINT );
|
|
|
|
if( !fpField->GetText().IsEmpty() )
|
|
{
|
|
LIB_ID fpId;
|
|
fpId.Parse( fpField->GetText(), LIB_ID::ID_SCH, true );
|
|
fpId.SetLibNickname( newfilename.GetName() );
|
|
fpField->SetText( fpId.Format() );
|
|
}
|
|
}
|
|
}
|
|
|
|
GetScreen()->ClearUndoORRedoList( GetScreen()->m_UndoList, 1 );
|
|
// Only perform the dangling end test on root sheet.
|
|
GetScreen()->TestDanglingEnds();
|
|
RecalculateConnections();
|
|
|
|
GetScreen()->SetGrid( ID_POPUP_GRID_LEVEL_1000 + m_LastGridSizeId );
|
|
m_toolManager->RunAction( ACTIONS::zoomFitScreen, true );
|
|
SetSheetNumberAndCount();
|
|
SyncView();
|
|
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;
|
|
msg.Printf( _( "Error loading schematic \"%s\".\n%s" ), aFileName, ioe.What() );
|
|
DisplayError( this, msg );
|
|
|
|
msg.Printf( _( "Failed to load \"%s\"" ), aFileName );
|
|
AppendMsgPanel( wxEmptyString, msg, CYAN );
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool SCH_EDIT_FRAME::AskToSaveChanges()
|
|
{
|
|
SCH_SCREENS screenList;
|
|
|
|
// Save any currently open and modified project files.
|
|
for( SCH_SCREEN* screen = screenList.GetFirst(); screen; screen = screenList.GetNext() )
|
|
{
|
|
if( screen->IsModify() )
|
|
{
|
|
if( !HandleUnsavedChanges( this, _( "The current schematic has been modified. "
|
|
"Save changes?" ),
|
|
[&]()->bool { return SaveProject(); } ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|