mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
* When Pcbnew is launched in the stand alone mode with no board file command line option, the project path is set to the current working directory. The user may not have write access to the current working directory which would cause the auto save to generate an error. If the user does not have write access to the current working directory, set the path to a platform specific temporary path that the user has write permission. * When Pcbnew is launched in the stand alone mode with no board file command line option, the default BOARD object has no file name. Set the file name to "noname.kicad_pcb" to fix incorrect auto save file name and set the path to the user's platform specific document folder. * Delete orphaned auto save files when closing without saving due to broken auto save file generation logic which prepended the file name with "$" rather than "_autosave-". * Fixes: lp:1596382
872 lines
26 KiB
C++
872 lines
26 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2004-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
|
|
* Copyright (C) 2011-2016 Wayne Stambaugh <stambaughw@verizon.net>
|
|
* Copyright (C) 2016 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 pcbnew/files.cpp
|
|
* @brief Read and write board files.
|
|
*/
|
|
|
|
#include <fctsys.h>
|
|
#include <class_drawpanel.h>
|
|
#include <confirm.h>
|
|
#include <kicad_string.h>
|
|
#include <gestfich.h>
|
|
#include <wxPcbStruct.h>
|
|
#include <macros.h>
|
|
#include <3d_viewer/eda_3d_viewer.h>
|
|
#include <richio.h>
|
|
#include <filter_reader.h>
|
|
#include <pgm_base.h>
|
|
#include <msgpanel.h>
|
|
#include <fp_lib_table.h>
|
|
#include <ratsnest_data.h>
|
|
|
|
#include <pcbnew.h>
|
|
#include <pcbnew_id.h>
|
|
#include <io_mgr.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
|
|
#include <class_board.h>
|
|
#include <build_version.h> // LEGACY_BOARD_FILE_VERSION
|
|
#include <module_editor_frame.h>
|
|
#include <modview_frame.h>
|
|
|
|
#include <wx/stdpaths.h>
|
|
|
|
|
|
//#define USE_INSTRUMENTATION 1
|
|
#define USE_INSTRUMENTATION 0
|
|
|
|
|
|
static const wxChar backupSuffix[] = wxT( "-bak" );
|
|
static const wxChar autosavePrefix[] = wxT( "_autosave-" );
|
|
|
|
|
|
wxString PCB_EDIT_FRAME::GetAutoSaveFilePrefix()
|
|
{
|
|
return wxString( autosavePrefix );
|
|
}
|
|
|
|
|
|
/**
|
|
* Function AskLoadBoardFileName
|
|
* puts up a wxFileDialog asking for a BOARD filename to open.
|
|
*
|
|
* @param aParent is a wxFrame passed to wxFileDialog.
|
|
* @param aCtl is where to put the OpenProjectFiles() control bits.
|
|
*
|
|
* @param aFileName on entry is a probable choice, on return is the chosen filename.
|
|
* @param aKicadFilesOnly true to list kiacad pcb files plugins only, false to list import plugins.
|
|
*
|
|
* @return bool - true if chosen, else false if user aborted.
|
|
*/
|
|
bool AskLoadBoardFileName( wxWindow* aParent, int* aCtl, wxString* aFileName, bool aKicadFilesOnly )
|
|
{
|
|
// This is a subset of all PLUGINs which are trusted to be able to
|
|
// load a BOARD. User may occasionally use the wrong plugin to load a
|
|
// *.brd file (since both legacy and eagle use *.brd extension),
|
|
// but eventually *.kicad_pcb will be more common than legacy *.brd files.
|
|
static const struct
|
|
{
|
|
const wxString& filter;
|
|
IO_MGR::PCB_FILE_T pluginType;
|
|
} loaders[] =
|
|
{
|
|
{ PcbFileWildcard, IO_MGR::KICAD }, // Current Kicad board files
|
|
{ LegacyPcbFileWildcard, IO_MGR::LEGACY }, // Old Kicad board files
|
|
{ EaglePcbFileWildcard, IO_MGR::EAGLE }, // Import board files
|
|
{ PCadPcbFileWildcard, IO_MGR::PCAD }, // Import board files
|
|
};
|
|
|
|
wxFileName fileName( *aFileName );
|
|
wxString fileFilters;
|
|
|
|
if( aKicadFilesOnly )
|
|
{
|
|
for( unsigned ii = 0; ii < 2; ++ii )
|
|
{
|
|
if( !fileFilters.IsEmpty() )
|
|
fileFilters += wxChar( '|' );
|
|
|
|
fileFilters += wxGetTranslation( loaders[ii].filter );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( unsigned ii = 2; ii < DIM( loaders ); ++ii )
|
|
{
|
|
if( !fileFilters.IsEmpty() )
|
|
fileFilters += wxChar( '|' );
|
|
|
|
fileFilters += wxGetTranslation( loaders[ii].filter );
|
|
}
|
|
}
|
|
|
|
wxString path;
|
|
wxString name;
|
|
|
|
if( fileName.FileExists() )
|
|
{
|
|
path = fileName.GetPath();
|
|
name = fileName.GetFullName();
|
|
}
|
|
else
|
|
{
|
|
path = wxStandardPaths::Get().GetDocumentsDir();
|
|
// leave name empty
|
|
}
|
|
|
|
wxFileDialog dlg( aParent,
|
|
aKicadFilesOnly ? _( "Open Board File" ) : _( "Import Non Kicad Board File" ),
|
|
path, name, fileFilters,
|
|
wxFD_OPEN | wxFD_FILE_MUST_EXIST );
|
|
|
|
if( dlg.ShowModal() == wxID_OK )
|
|
{
|
|
// For import option, if Eagle (*.brd files), tell OpenProjectFiles() to use Eagle plugin.
|
|
// It's the only special case because of the duplicate use of the *.brd file extension.
|
|
// Other cases are clear because of unique file extensions.
|
|
*aCtl = aKicadFilesOnly ? 0 : KICTL_EAGLE_BRD;
|
|
*aFileName = dlg.GetPath();
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function AskSaveBoardFileName
|
|
* puts up a wxFileDialog asking for a BOARD filename to save.
|
|
*
|
|
* @param aParent is a wxFrame passed to wxFileDialog.
|
|
* @param aFileName on entry is a probable choice, on return is the
|
|
* chosen full filename (includes path).
|
|
*
|
|
* @return bool - true if chosen, else false if user aborted.
|
|
*/
|
|
bool AskSaveBoardFileName( wxWindow* aParent, wxString* aFileName )
|
|
{
|
|
wxString wildcard = wxGetTranslation( PcbFileWildcard );
|
|
wxFileName fn = *aFileName;
|
|
|
|
fn.SetExt( KiCadPcbFileExtension );
|
|
|
|
wxFileDialog dlg( aParent,
|
|
_( "Save Board File As" ),
|
|
fn.GetPath(),
|
|
fn.GetFullName(),
|
|
wildcard,
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
|
|
);
|
|
|
|
if( dlg.ShowModal() != wxID_OK )
|
|
return false;
|
|
|
|
fn = dlg.GetPath();
|
|
|
|
// always enforce filename extension, user may not have entered it.
|
|
fn.SetExt( KiCadPcbFileExtension );
|
|
|
|
*aFileName = fn.GetFullPath();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void PCB_EDIT_FRAME::OnFileHistory( wxCommandEvent& event )
|
|
{
|
|
wxString fn = GetFileFromHistory( event.GetId(), _( "Printed circuit board" ) );
|
|
|
|
if( !!fn )
|
|
{
|
|
int open_ctl = 0;
|
|
|
|
m_canvas->EndMouseCapture( ID_NO_TOOL_SELECTED, m_canvas->GetDefaultCursor() );
|
|
|
|
if( !wxFileName::IsFileReadable( fn ) )
|
|
{
|
|
if( !AskLoadBoardFileName( this, &open_ctl, &fn, true ) )
|
|
return;
|
|
}
|
|
|
|
OpenProjectFiles( std::vector<wxString>( 1, fn ), open_ctl );
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_EDIT_FRAME::Files_io( wxCommandEvent& event )
|
|
{
|
|
int id = event.GetId();
|
|
Files_io_from_id( id );
|
|
}
|
|
|
|
void PCB_EDIT_FRAME::Files_io_from_id( int id )
|
|
{
|
|
wxString msg;
|
|
|
|
// If an edition is in progress, stop it.
|
|
// For something else than save, get rid of current tool.
|
|
if( id == ID_SAVE_BOARD )
|
|
m_canvas->EndMouseCapture( -1, m_canvas->GetDefaultCursor() );
|
|
else
|
|
m_canvas->EndMouseCapture( ID_NO_TOOL_SELECTED, m_canvas->GetDefaultCursor() );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_LOAD_FILE:
|
|
{
|
|
int open_ctl = 0;
|
|
wxString fileName = Prj().AbsolutePath( GetBoard()->GetFileName() );
|
|
|
|
if( !AskLoadBoardFileName( this, &open_ctl, &fileName, true ) )
|
|
return;
|
|
|
|
OpenProjectFiles( std::vector<wxString>( 1, fileName ), open_ctl );
|
|
}
|
|
break;
|
|
|
|
case ID_IMPORT_NON_KICAD_BOARD:
|
|
{
|
|
int open_ctl = 1;
|
|
wxString fileName;// = Prj().AbsolutePath( GetBoard()->GetFileName() );
|
|
|
|
if( !AskLoadBoardFileName( this, &open_ctl, &fileName, false ) )
|
|
return;
|
|
|
|
OpenProjectFiles( std::vector<wxString>( 1, fileName ), open_ctl );
|
|
}
|
|
break;
|
|
|
|
case ID_MENU_READ_BOARD_BACKUP_FILE:
|
|
case ID_MENU_RECOVER_BOARD_AUTOSAVE:
|
|
{
|
|
wxFileName currfn = Prj().AbsolutePath( GetBoard()->GetFileName() );
|
|
wxFileName fn = currfn;
|
|
|
|
if( id == ID_MENU_RECOVER_BOARD_AUTOSAVE )
|
|
{
|
|
wxString rec_name = wxString( autosavePrefix ) + fn.GetName();
|
|
fn.SetName( rec_name );
|
|
}
|
|
else
|
|
{
|
|
wxString backup_ext = fn.GetExt()+ backupSuffix;
|
|
fn.SetExt( backup_ext );
|
|
}
|
|
|
|
if( !fn.FileExists() )
|
|
{
|
|
msg.Printf( _( "Recovery file '%s' not found." ),
|
|
GetChars( fn.GetFullPath() ) );
|
|
DisplayInfoMessage( this, msg );
|
|
break;
|
|
}
|
|
|
|
msg.Printf( _( "OK to load recovery or backup file '%s'" ),
|
|
GetChars(fn.GetFullPath() ) );
|
|
|
|
if( !IsOK( this, msg ) )
|
|
break;
|
|
|
|
GetScreen()->ClrModify(); // do not prompt the user for changes
|
|
|
|
// LoadOnePcbFile( fn.GetFullPath(), aAppend=false, aForceFileDialog=false );
|
|
OpenProjectFiles( std::vector<wxString>( 1, fn.GetFullPath() ) );
|
|
|
|
// Re-set the name since name or extension was changed
|
|
GetBoard()->SetFileName( currfn.GetFullPath() );
|
|
UpdateTitle();
|
|
}
|
|
break;
|
|
|
|
case ID_APPEND_FILE:
|
|
{
|
|
int open_ctl;
|
|
wxString fileName;
|
|
|
|
if( !AskLoadBoardFileName( this, &open_ctl, &fileName, true ) )
|
|
break;
|
|
|
|
AppendBoardFile( fileName, open_ctl );
|
|
|
|
m_canvas->Refresh();
|
|
}
|
|
break;
|
|
|
|
case ID_NEW_BOARD:
|
|
{
|
|
if( !Clear_Pcb( true ) )
|
|
break;
|
|
|
|
wxFileName fn( wxStandardPaths::Get().GetDocumentsDir(), wxT( "noname" ),
|
|
ProjectFileExtension );
|
|
|
|
Prj().SetProjectFullName( fn.GetFullPath() );
|
|
|
|
fn.SetExt( PcbFileExtension );
|
|
|
|
GetBoard()->SetFileName( fn.GetFullPath() );
|
|
UpdateTitle();
|
|
ReCreateLayerBox();
|
|
OnModify();
|
|
break;
|
|
}
|
|
|
|
case ID_SAVE_BOARD:
|
|
if( ! GetBoard()->GetFileName().IsEmpty() )
|
|
{
|
|
SavePcbFile( Prj().AbsolutePath( GetBoard()->GetFileName() ) );
|
|
break;
|
|
}
|
|
// Fall through
|
|
case ID_COPY_BOARD_AS:
|
|
case ID_SAVE_BOARD_AS:
|
|
{
|
|
wxString pro_dir = wxPathOnly( Prj().GetProjectFullName() );
|
|
wxFileName fn( pro_dir, _( "noname" ), KiCadPcbFileExtension );
|
|
wxString filename = fn.GetFullPath();
|
|
|
|
if( AskSaveBoardFileName( this, &filename ) )
|
|
{
|
|
if( id == ID_COPY_BOARD_AS )
|
|
SavePcbCopy( filename );
|
|
else
|
|
SavePcbFile( filename, NO_BACKUP_FILE );
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DisplayError( this, wxT( "File_io Internal Error" ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// The KIWAY_PLAYER::OpenProjectFiles() API knows nothing about plugins, so
|
|
// determine how to load the BOARD here, with minor assistance from KICTL_EAGLE_BRD
|
|
// bit flag.
|
|
IO_MGR::PCB_FILE_T plugin_type( const wxString& aFileName, int aCtl )
|
|
{
|
|
IO_MGR::PCB_FILE_T pluginType;
|
|
|
|
wxFileName fn = aFileName;
|
|
|
|
// Note: file extensions are expected to be in ower case.
|
|
// This is not always true, especially when importing files, so the string
|
|
// comparisons are case insensitive to try to find the suitable plugin.
|
|
|
|
if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::LEGACY ) ) == 0 )
|
|
{
|
|
// both legacy and eagle share a common file extension.
|
|
pluginType = ( aCtl & KICTL_EAGLE_BRD ) ? IO_MGR::EAGLE : IO_MGR::LEGACY;
|
|
}
|
|
else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::LEGACY ) + backupSuffix ) == 0 )
|
|
{
|
|
pluginType = IO_MGR::LEGACY;
|
|
}
|
|
else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::PCAD ) ) == 0 )
|
|
{
|
|
pluginType = IO_MGR::PCAD;
|
|
}
|
|
else
|
|
{
|
|
pluginType = IO_MGR::KICAD;
|
|
}
|
|
|
|
return pluginType;
|
|
}
|
|
|
|
|
|
bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, int aCtl )
|
|
{
|
|
// This is for python:
|
|
if( aFileSet.size() != 1 )
|
|
{
|
|
UTF8 msg = StrPrintf( "Pcbnew:%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( _(
|
|
"PCB file '%s' is already open." ),
|
|
GetChars( fullFileName )
|
|
);
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
if( GetScreen()->IsModify() )
|
|
{
|
|
int response = YesNoCancelDialog( this, _(
|
|
"The current board 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 )
|
|
SavePcbFile( GetBoard()->GetFileName(), CREATE_BACKUP_FILE );
|
|
else
|
|
{
|
|
// response == wxID_NO, fall thru
|
|
}
|
|
}
|
|
|
|
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( _(
|
|
"Board '%s' does not exist. Do you wish to create it?" ),
|
|
GetChars( fullFileName )
|
|
);
|
|
if( !IsOK( this, ask ) )
|
|
return false;
|
|
}
|
|
|
|
Clear_Pcb( false ); // pass false since we prompted above for a modified board
|
|
|
|
IO_MGR::PCB_FILE_T pluginType = plugin_type( fullFileName, aCtl );
|
|
|
|
bool converted = pluginType != IO_MGR::LEGACY && pluginType != IO_MGR::KICAD;
|
|
|
|
if( !converted )
|
|
{
|
|
// 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.
|
|
Prj().SetProjectFullName( pro.GetFullPath() );
|
|
|
|
// load project settings before BOARD
|
|
LoadProjectSettings();
|
|
}
|
|
|
|
if( is_new )
|
|
{
|
|
OnModify();
|
|
}
|
|
else
|
|
{
|
|
BOARD* loadedBoard = 0; // it will be set to non-NULL if loaded OK
|
|
|
|
PLUGIN::RELEASER pi( IO_MGR::PluginFind( pluginType ) );
|
|
|
|
try
|
|
{
|
|
PROPERTIES props;
|
|
char xbuf[30];
|
|
char ybuf[30];
|
|
|
|
// EAGLE_PLUGIN can use this info to center the BOARD, but it does not yet.
|
|
sprintf( xbuf, "%d", GetPageSizeIU().x );
|
|
sprintf( ybuf, "%d", GetPageSizeIU().y );
|
|
|
|
props["page_width"] = xbuf;
|
|
props["page_height"] = ybuf;
|
|
|
|
#if USE_INSTRUMENTATION
|
|
// measure the time to load a BOARD.
|
|
unsigned startTime = GetRunningMicroSecs();
|
|
#endif
|
|
|
|
loadedBoard = pi->Load( fullFileName, NULL, &props );
|
|
|
|
#if USE_INSTRUMENTATION
|
|
unsigned stopTime = GetRunningMicroSecs();
|
|
printf( "PLUGIN::Load(): %u usecs\n", stopTime - startTime );
|
|
#endif
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"Error loading board.\n%s" ),
|
|
GetChars( ioe.errorText )
|
|
);
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
SetBoard( loadedBoard );
|
|
|
|
// we should not ask PLUGINs to do these items:
|
|
loadedBoard->BuildListOfNets();
|
|
loadedBoard->SynchronizeNetsAndNetClasses();
|
|
|
|
SetStatusText( wxEmptyString );
|
|
BestZoom();
|
|
|
|
// update the layer names in the listbox
|
|
ReCreateLayerBox( false );
|
|
|
|
GetScreen()->ClrModify();
|
|
|
|
{
|
|
wxFileName fn = fullFileName;
|
|
CheckForAutoSaveFile( fullFileName, fn.GetExt() );
|
|
}
|
|
|
|
if( pluginType == IO_MGR::LEGACY &&
|
|
loadedBoard->GetFileFormatVersionAtLoad() < LEGACY_BOARD_FILE_VERSION )
|
|
{
|
|
DisplayInfoMessage( this,
|
|
_( "This file was created by an older version of Pcbnew.\n"
|
|
"It will be stored in the new file format when you save this file again." ) );
|
|
}
|
|
}
|
|
|
|
{
|
|
wxFileName fn = fullFileName;
|
|
|
|
if( converted )
|
|
fn.SetExt( PcbFileExtension );
|
|
|
|
wxString fname = fn.GetFullPath();
|
|
|
|
fname.Replace( WIN_STRING_DIR_SEP, UNIX_STRING_DIR_SEP );
|
|
|
|
GetBoard()->SetFileName( fname );
|
|
}
|
|
|
|
UpdateTitle();
|
|
|
|
if( !converted )
|
|
UpdateFileHistory( GetBoard()->GetFileName() );
|
|
|
|
// Rebuild the new pad list (for drc and ratsnet control ...)
|
|
GetBoard()->m_Status_Pcb = 0;
|
|
|
|
// Select netclass Default as current netclass (it always exists)
|
|
SetCurrentNetClass( NETCLASS::Default );
|
|
|
|
// Rebuild list of nets (full ratsnest rebuild)
|
|
{
|
|
wxBusyCursor dummy; // Displays an Hourglass while building connectivity
|
|
Compile_Ratsnest( NULL, true );
|
|
GetBoard()->GetRatsnest()->ProcessBoard();
|
|
}
|
|
|
|
// Update info shown by the horizontal toolbars
|
|
ReFillLayerWidget();
|
|
ReCreateLayerBox();
|
|
|
|
// upate the layer widget to match board visibility states, both layers and render columns.
|
|
syncLayerVisibilities();
|
|
syncLayerWidgetLayer();
|
|
syncRenderStates();
|
|
|
|
// Update the tracks / vias available sizes list:
|
|
ReCreateAuxiliaryToolbar();
|
|
|
|
// Update the RATSNEST items, which were not loaded at the time
|
|
// BOARD::SetVisibleElements() was called from within any PLUGIN.
|
|
// See case RATSNEST_VISIBLE: in BOARD::SetElementVisibility()
|
|
GetBoard()->SetVisibleElements( GetBoard()->GetVisibleElements() );
|
|
|
|
// Display the loaded board:
|
|
Zoom_Automatique( false );
|
|
|
|
SetMsgPanel( GetBoard() );
|
|
|
|
// Refresh the 3D view, if any
|
|
EDA_3D_VIEWER* draw3DFrame = Get3DViewerFrame();
|
|
|
|
if( draw3DFrame )
|
|
draw3DFrame->NewDisplay();
|
|
|
|
#if 0 && defined(DEBUG)
|
|
// Output the board object tree to stdout, but please run from command prompt:
|
|
GetBoard()->Show( 0, std::cout );
|
|
#endif
|
|
|
|
// from EDA_APPL which was first loaded BOARD only:
|
|
{
|
|
/* For an obscure reason the focus is lost after loading a board file
|
|
* when starting up the process.
|
|
* (seems due to the recreation of the layer manager after loading the file)
|
|
* Give focus to main window and Drawpanel
|
|
* must be done for these 2 windows (for an obscure reason ...)
|
|
* Linux specific
|
|
* This is more a workaround than a fix.
|
|
*/
|
|
SetFocus();
|
|
GetCanvas()->SetFocus();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static wxString create_backup_file( const wxString& aFileName )
|
|
{
|
|
wxFileName fn = aFileName;
|
|
wxFileName backupFileName = aFileName;
|
|
|
|
backupFileName.SetExt( fn.GetExt() + backupSuffix );
|
|
|
|
// If an old backup file exists, delete it. If an old board file exists,
|
|
// rename it to the backup file name.
|
|
if( fn.FileExists() )
|
|
{
|
|
// Remove the old file xxx.000 if it exists.
|
|
if( backupFileName.FileExists() )
|
|
wxRemoveFile( backupFileName.GetFullPath() );
|
|
|
|
// Rename the current file from <xxx>.kicad_pcb to <xxx>.kicad_pcb-bak
|
|
if( !wxRenameFile( fn.GetFullPath(), backupFileName.GetFullPath() ) )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"Warning: unable to create backup file '%s'" ),
|
|
GetChars( backupFileName.GetFullPath() )
|
|
);
|
|
DisplayError( NULL, msg );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
backupFileName.Clear();
|
|
}
|
|
|
|
return backupFileName.GetFullPath();
|
|
}
|
|
|
|
|
|
bool PCB_EDIT_FRAME::SavePcbFile( const wxString& aFileName, bool aCreateBackupFile )
|
|
{
|
|
// please, keep it simple. prompting goes elsewhere.
|
|
|
|
wxFileName pcbFileName = aFileName;
|
|
|
|
if( pcbFileName.GetExt() == LegacyPcbFileExtension )
|
|
pcbFileName.SetExt( KiCadPcbFileExtension );
|
|
|
|
if( !IsWritable( pcbFileName ) )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"No access rights to write to file '%s'" ),
|
|
GetChars( pcbFileName.GetFullPath() )
|
|
);
|
|
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
wxString backupFileName;
|
|
|
|
// aCreateBackupFile == false is mainly used to write autosave files
|
|
// or new files in save as... command
|
|
if( aCreateBackupFile )
|
|
{
|
|
backupFileName = create_backup_file( aFileName );
|
|
}
|
|
|
|
GetBoard()->m_Status_Pcb &= ~CONNEXION_OK;
|
|
|
|
GetBoard()->SynchronizeNetsAndNetClasses();
|
|
|
|
// Select default Netclass before writing file.
|
|
// Useful to save default values in headers
|
|
SetCurrentNetClass( NETCLASS::Default );
|
|
|
|
ClearMsgPanel();
|
|
|
|
wxString upperTxt;
|
|
wxString lowerTxt;
|
|
|
|
try
|
|
{
|
|
PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::KICAD ) );
|
|
|
|
wxASSERT( pcbFileName.IsAbsolute() );
|
|
|
|
pi->Save( pcbFileName.GetFullPath(), GetBoard(), NULL );
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"Error saving board file '%s'.\n%s" ),
|
|
GetChars( pcbFileName.GetFullPath() ),
|
|
GetChars( ioe.errorText )
|
|
);
|
|
DisplayError( this, msg );
|
|
|
|
lowerTxt.Printf( _( "Failed to create '%s'" ), GetChars( pcbFileName.GetFullPath() ) );
|
|
|
|
AppendMsgPanel( upperTxt, lowerTxt, CYAN );
|
|
|
|
return false;
|
|
}
|
|
|
|
GetBoard()->SetFileName( pcbFileName.GetFullPath() );
|
|
UpdateTitle();
|
|
|
|
// Put the saved file in File History, unless aCreateBackupFile
|
|
// is false.
|
|
// aCreateBackupFile == false is mainly used to write autosave files
|
|
// and not need to have an autosave file in file history
|
|
if( aCreateBackupFile )
|
|
UpdateFileHistory( GetBoard()->GetFileName() );
|
|
|
|
// Delete auto save file on successful save.
|
|
wxFileName autoSaveFileName = pcbFileName;
|
|
|
|
autoSaveFileName.SetName( wxString( autosavePrefix ) + pcbFileName.GetName() );
|
|
|
|
if( autoSaveFileName.FileExists() )
|
|
wxRemoveFile( autoSaveFileName.GetFullPath() );
|
|
|
|
if( !!backupFileName )
|
|
upperTxt.Printf( _( "Backup file: '%s'" ), GetChars( backupFileName ) );
|
|
|
|
lowerTxt.Printf( _( "Wrote board file: '%s'" ), GetChars( pcbFileName.GetFullPath() ) );
|
|
|
|
AppendMsgPanel( upperTxt, lowerTxt, CYAN );
|
|
|
|
GetScreen()->ClrModify();
|
|
GetScreen()->ClrSave();
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PCB_EDIT_FRAME::SavePcbCopy( const wxString& aFileName )
|
|
{
|
|
wxFileName pcbFileName = aFileName;
|
|
|
|
// Ensure the file ext is the right ext:
|
|
pcbFileName.SetExt( KiCadPcbFileExtension );
|
|
|
|
if( !IsWritable( pcbFileName ) )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"No access rights to write to file '%s'" ),
|
|
GetChars( pcbFileName.GetFullPath() )
|
|
);
|
|
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
GetBoard()->m_Status_Pcb &= ~CONNEXION_OK;
|
|
GetBoard()->SynchronizeNetsAndNetClasses();
|
|
|
|
// Select default Netclass before writing file.
|
|
// Useful to save default values in headers
|
|
SetCurrentNetClass( NETCLASS::Default );
|
|
|
|
try
|
|
{
|
|
PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::KICAD ) );
|
|
|
|
wxASSERT( pcbFileName.IsAbsolute() );
|
|
|
|
pi->Save( pcbFileName.GetFullPath(), GetBoard(), NULL );
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"Error saving board file '%s'.\n%s" ),
|
|
GetChars( pcbFileName.GetFullPath() ),
|
|
GetChars( ioe.errorText )
|
|
);
|
|
DisplayError( this, msg );
|
|
|
|
return false;
|
|
}
|
|
|
|
DisplayInfoMessage( this, wxString::Format( _( "Board copied to:\n'%s'" ),
|
|
GetChars( pcbFileName.GetFullPath() ) ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PCB_EDIT_FRAME::doAutoSave()
|
|
{
|
|
wxFileName tmpFileName;
|
|
|
|
if( GetBoard()->GetFileName().IsEmpty() )
|
|
{
|
|
tmpFileName = wxFileName( wxStandardPaths::Get().GetDocumentsDir(), wxT( "noname" ),
|
|
KiCadPcbFileExtension );
|
|
GetBoard()->SetFileName( tmpFileName.GetFullPath() );
|
|
}
|
|
else
|
|
{
|
|
tmpFileName = Prj().AbsolutePath( GetBoard()->GetFileName() );
|
|
}
|
|
|
|
wxFileName autoSaveFileName = tmpFileName;
|
|
|
|
// Auto save file name is the board file name prepended with autosaveFilePrefix string.
|
|
autoSaveFileName.SetName( wxString( autosavePrefix ) + autoSaveFileName.GetName() );
|
|
|
|
if( !autoSaveFileName.IsOk() )
|
|
return false;
|
|
|
|
// If the board file path is not writable, try writing to a platform specific temp file
|
|
// path. If that path isn't writabe, give up.
|
|
if( !autoSaveFileName.IsDirWritable() )
|
|
{
|
|
autoSaveFileName.SetPath( wxFileName::GetTempDir() );
|
|
|
|
if( !autoSaveFileName.IsOk() || !autoSaveFileName.IsDirWritable() )
|
|
return false;
|
|
}
|
|
|
|
wxLogTrace( traceAutoSave, "Creating auto save file <" + autoSaveFileName.GetFullPath() + ">" );
|
|
|
|
if( SavePcbFile( autoSaveFileName.GetFullPath(), NO_BACKUP_FILE ) )
|
|
{
|
|
GetScreen()->SetModify();
|
|
GetBoard()->SetFileName( tmpFileName.GetFullPath() );
|
|
UpdateTitle();
|
|
m_autoSaveState = false;
|
|
return true;
|
|
}
|
|
|
|
GetBoard()->SetFileName( tmpFileName.GetFullPath() );
|
|
|
|
return false;
|
|
}
|