From f9386fcbc063ebd79e0bc59a7c80fed10bbe7e42 Mon Sep 17 00:00:00 2001 From: Chris Pavlina Date: Tue, 10 May 2016 15:07:35 -0400 Subject: [PATCH] Add support for PCB and footprint format versioning Currently, kicad_pcb files have a (version ...) tag, but it is ignored. This commit does the following: 1. Parse the version. If it's later than the last supported version, present an alternative error message suggesting an upgrade if the file does not load correctly. The version will be interpreted as an integer YYYYMMDD to suggest a KiCad release date. 2. Accept a (version ...) tag also in kicad_mod files, but do not write them yet. If no version tag is present in these files, assume the version to be that of the current format version at the time of this commit. This is meant to be merged to the 4.x stable series, and preps for KiCad 5 which will start emitting version tags also in footprints - users with what will then be 'old stable' will not get a parse error when we introduce this for footprints, and we can safely increment the format version later. --- common/richio.cpp | 8 +- include/richio.h | 37 +++- pcbnew/eagle_plugin.cpp | 6 +- pcbnew/eagle_plugin.h | 5 +- pcbnew/files.cpp | 3 +- pcbnew/github/github_plugin.cpp | 2 +- pcbnew/github/github_plugin.h | 2 +- pcbnew/gpcb_plugin.cpp | 3 +- pcbnew/gpcb_plugin.h | 6 +- pcbnew/io_mgr.cpp | 5 +- pcbnew/io_mgr.h | 4 +- pcbnew/kicad_plugin.cpp | 33 +++- pcbnew/kicad_plugin.h | 8 +- pcbnew/legacy_plugin.cpp | 5 +- pcbnew/legacy_plugin.h | 16 +- pcbnew/librairi.cpp | 313 ++++++++++++++++++-------------- pcbnew/pcb_parser.cpp | 136 ++++++++++++-- pcbnew/pcb_parser.h | 43 ++++- pcbnew/plugin.cpp | 2 +- 19 files changed, 436 insertions(+), 201 deletions(-) diff --git a/common/richio.cpp b/common/richio.cpp index 339684ee3c..1096ddc0f6 100644 --- a/common/richio.cpp +++ b/common/richio.cpp @@ -1,9 +1,8 @@ - /* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2007-2011 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2015 KiCad Developers, see change_log.txt for contributors. + * 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 @@ -36,11 +35,6 @@ #endif -// This file defines 3 classes and some functions useful for working with text files -// and is named "richio" after its author, Richard Hollenbeck, aka Dick Hollenbeck. - - - static int vprint( std::string* result, const char* format, va_list ap ) { char msg[512]; diff --git a/include/richio.h b/include/richio.h index 78e95d3551..86d72b2d9a 100644 --- a/include/richio.h +++ b/include/richio.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2007-2010 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2007 KiCad Developers, see change_log.txt for contributors. + * 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 @@ -192,7 +192,10 @@ struct PARSE_ERROR : public IO_ERROR const char* aInputLine, int aLineNumber, int aByteIndex ); - ~PARSE_ERROR() throw ( /*none*/ ){} + virtual ~PARSE_ERROR() throw ( /*none*/ ){} + +protected: + PARSE_ERROR(): IO_ERROR() {} }; @@ -200,6 +203,36 @@ struct PARSE_ERROR : public IO_ERROR throw PARSE_ERROR( __FILE__, __LOC__, aMsg, aSource, aInputLine, aLineNumber, aByteIndex ) +/** + * Struct FUTURE_FORMAT_ERROR + * variant of PARSE_ERROR indicating that a syntax or related error was likely caused + * by a file generated by a newer version of KiCad than this. Can be used to generate + * more informative error messages. + */ +struct FUTURE_FORMAT_ERROR : public PARSE_ERROR +{ + wxString requiredVersion; ///< version or date of KiCad required to open file + + FUTURE_FORMAT_ERROR( const PARSE_ERROR& aParseError, const wxString& aRequiredVersion ) : + PARSE_ERROR(), requiredVersion( aRequiredVersion ) + { + errorText.Printf( _( + "KiCad was unable to open this file, as it was created with a more " + "recent version than the one you are running. To open it, you'll need " + "to upgrade KiCad to a more recent version.\n\n" + "Date of KiCad version required (or newer): %s\n\n" + "Full error text:\n%s" ), + requiredVersion, aParseError.errorText ); + + lineNumber = aParseError.lineNumber; + byteIndex = aParseError.byteIndex; + inputLine = aParseError.inputLine; + } + + ~FUTURE_FORMAT_ERROR() throw () {} +}; + + /** @} exception_types */ diff --git a/pcbnew/eagle_plugin.cpp b/pcbnew/eagle_plugin.cpp index 6297da4329..4040161283 100644 --- a/pcbnew/eagle_plugin.cpp +++ b/pcbnew/eagle_plugin.cpp @@ -3,8 +3,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2012-2015 KiCad Developers, see change_log.txt for contributors. - + * Copyright (C) 2012-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 @@ -3121,7 +3120,8 @@ wxArrayString EAGLE_PLUGIN::FootprintEnumerate( const wxString& aLibraryPath, co } -MODULE* EAGLE_PLUGIN::FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, const PROPERTIES* aProperties ) +MODULE* EAGLE_PLUGIN::FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, + const PROPERTIES* aProperties ) { init( aProperties ); diff --git a/pcbnew/eagle_plugin.h b/pcbnew/eagle_plugin.h index e9bde95969..d12738fced 100644 --- a/pcbnew/eagle_plugin.h +++ b/pcbnew/eagle_plugin.h @@ -5,7 +5,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2012-2015 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2012-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 @@ -87,7 +87,8 @@ public: wxArrayString FootprintEnumerate( const wxString& aLibraryPath, const PROPERTIES* aProperties = NULL); - MODULE* FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, const PROPERTIES* aProperties = NULL ); + MODULE* FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, + const PROPERTIES* aProperties = NULL ); bool IsFootprintLibWritable( const wxString& aLibraryPath ) { diff --git a/pcbnew/files.cpp b/pcbnew/files.cpp index 379a1a25d9..e7202700d5 100644 --- a/pcbnew/files.cpp +++ b/pcbnew/files.cpp @@ -3,7 +3,7 @@ * * Copyright (C) 2004-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2011-2015 Wayne Stambaugh - * Copyright (C) 2015 KiCad Developers, see change_log.txt for contributors. + * 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 @@ -518,7 +518,6 @@ bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector& aFileSet, in GetChars( ioe.errorText ) ); DisplayError( this, msg ); - return false; } diff --git a/pcbnew/github/github_plugin.cpp b/pcbnew/github/github_plugin.cpp index 75d4e7bfb0..e43c2fbb0b 100644 --- a/pcbnew/github/github_plugin.cpp +++ b/pcbnew/github/github_plugin.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2016 KiCad Developers, see CHANGELOG.TXT for contributors. + * 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 diff --git a/pcbnew/github/github_plugin.h b/pcbnew/github/github_plugin.h index 3dc10a69af..92f2c1372d 100644 --- a/pcbnew/github/github_plugin.h +++ b/pcbnew/github/github_plugin.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2013 KiCad Developers, see CHANGELOG.TXT for contributors. + * 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 diff --git a/pcbnew/gpcb_plugin.cpp b/pcbnew/gpcb_plugin.cpp index 16432fb4ea..94815f0430 100644 --- a/pcbnew/gpcb_plugin.cpp +++ b/pcbnew/gpcb_plugin.cpp @@ -2,8 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 Wayne Stambaugh - * Copyright (C) 1992-2015 KiCad Developers, see change_log.txt for contributors. - * + * Copyright (C) 1992-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 diff --git a/pcbnew/gpcb_plugin.h b/pcbnew/gpcb_plugin.h index 4ae65563f9..20951cdf38 100644 --- a/pcbnew/gpcb_plugin.h +++ b/pcbnew/gpcb_plugin.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 Wayne Stambaugh - * Copyright (C) 1992-2012 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 1992-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 @@ -66,10 +66,10 @@ public: const PROPERTIES* aProperties = NULL); MODULE* FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, - const PROPERTIES* aProperties = NULL ); + const PROPERTIES* aProperties = NULL ); void FootprintDelete( const wxString& aLibraryPath, const wxString& aFootprintName, - const PROPERTIES* aProperties = NULL ); + const PROPERTIES* aProperties = NULL ); bool FootprintLibDelete( const wxString& aLibraryPath, const PROPERTIES* aProperties = NULL ); diff --git a/pcbnew/io_mgr.cpp b/pcbnew/io_mgr.cpp index c733d30441..8d3981bd5f 100644 --- a/pcbnew/io_mgr.cpp +++ b/pcbnew/io_mgr.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2011-2012 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2011 KiCad Developers, see change_log.txt for contributors. + * 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 @@ -98,6 +98,9 @@ PLUGIN* IO_MGR::PluginFind( PCB_FILE_T aFileType ) #else THROW_IO_ERROR( "BUILD_GITHUB_PLUGIN not enabled in cmake build environment" ); #endif + + case FILE_TYPE_NONE: + return NULL; } return NULL; diff --git a/pcbnew/io_mgr.h b/pcbnew/io_mgr.h index e2b0135227..80b4821888 100644 --- a/pcbnew/io_mgr.h +++ b/pcbnew/io_mgr.h @@ -5,7 +5,7 @@ * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright (C) 2011-2012 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2011 Kicad Developers, see change_log.txt for contributors. + * 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 @@ -83,6 +83,8 @@ public: // ALTIUM, // etc. + + FILE_TYPE_NONE }; /** diff --git a/pcbnew/kicad_plugin.cpp b/pcbnew/kicad_plugin.cpp index 09abbcd22a..be662c2a73 100644 --- a/pcbnew/kicad_plugin.cpp +++ b/pcbnew/kicad_plugin.cpp @@ -2,8 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 CERN - * Copyright (C) 1992-2011 KiCad Developers, see change_log.txt for contributors. - * + * Copyright (C) 1992-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 @@ -418,7 +417,8 @@ void PCB_IO::Save( const wxString& aFileName, BOARD* aBoard, const PROPERTIES* a } -BOARD_ITEM* PCB_IO::Parse( const wxString& aClipboardSourceInput ) throw( PARSE_ERROR, IO_ERROR ) +BOARD_ITEM* PCB_IO::Parse( const wxString& aClipboardSourceInput ) + throw( FUTURE_FORMAT_ERROR, PARSE_ERROR, IO_ERROR ) { std::string input = TO_UTF8( aClipboardSourceInput ); @@ -426,7 +426,17 @@ BOARD_ITEM* PCB_IO::Parse( const wxString& aClipboardSourceInput ) throw( PARSE_ m_parser->SetLineReader( &reader ); - return m_parser->Parse(); + try + { + return m_parser->Parse(); + } + catch( const PARSE_ERROR& parse_error ) + { + if( m_parser->IsTooRecent() ) + throw FUTURE_FORMAT_ERROR( parse_error, m_parser->GetRequiredVersion() ); + else + throw; + } } @@ -1721,7 +1731,20 @@ BOARD* PCB_IO::Load( const wxString& aFileName, BOARD* aAppendToMe, const PROPER m_parser->SetLineReader( &reader ); m_parser->SetBoard( aAppendToMe ); - BOARD* board = dyn_cast( m_parser->Parse() ); + BOARD* board; + + try + { + board = dynamic_cast( m_parser->Parse() ); + } + catch( const PARSE_ERROR& parse_error ) + { + if( m_parser->IsTooRecent() ) + throw FUTURE_FORMAT_ERROR( parse_error, m_parser->GetRequiredVersion() ); + else + throw; + } + wxASSERT( board ); // Give the filename to the board if it's new diff --git a/pcbnew/kicad_plugin.h b/pcbnew/kicad_plugin.h index 1655293617..f81555f2a3 100644 --- a/pcbnew/kicad_plugin.h +++ b/pcbnew/kicad_plugin.h @@ -2,6 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 CERN. + * Copyright (C) 1992-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 @@ -108,10 +109,11 @@ public: BOARD* Load( const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties = NULL ); - wxArrayString FootprintEnumerate( const wxString& aLibraryPath, const PROPERTIES* aProperties = NULL); + wxArrayString FootprintEnumerate( const wxString& aLibraryPath, + const PROPERTIES* aProperties = NULL ); MODULE* FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, - const PROPERTIES* aProperties = NULL ); + const PROPERTIES* aProperties = NULL ); void FootprintSave( const wxString& aLibraryPath, const MODULE* aFootprint, const PROPERTIES* aProperties = NULL ); @@ -153,7 +155,7 @@ public: void SetOutputFormatter( OUTPUTFORMATTER* aFormatter ) { m_out = aFormatter; } BOARD_ITEM* Parse( const wxString& aClipboardSourceInput ) - throw( PARSE_ERROR, IO_ERROR ); + throw( FUTURE_FORMAT_ERROR, PARSE_ERROR, IO_ERROR ); protected: diff --git a/pcbnew/legacy_plugin.cpp b/pcbnew/legacy_plugin.cpp index c621c716b4..54ccdc880b 100644 --- a/pcbnew/legacy_plugin.cpp +++ b/pcbnew/legacy_plugin.cpp @@ -4,7 +4,7 @@ * * Copyright (C) 2007-2012 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2004 Jean-Pierre Charras, jp.charras@wanadoo.fr - * Copyright (C) 1992-2015 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 1992-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 @@ -387,7 +387,8 @@ static inline long hexParse( const char* next, const char** out = NULL ) } -BOARD* LEGACY_PLUGIN::Load( const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties ) +BOARD* LEGACY_PLUGIN::Load( const wxString& aFileName, BOARD* aAppendToMe, + const PROPERTIES* aProperties ) { LOCALE_IO toggle; // toggles on, then off, the C locale. diff --git a/pcbnew/legacy_plugin.h b/pcbnew/legacy_plugin.h index 2216438f9a..ddf59714f9 100644 --- a/pcbnew/legacy_plugin.h +++ b/pcbnew/legacy_plugin.h @@ -5,7 +5,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2012 KiCad Developers, see change_log.txt for contributors. + * 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 @@ -30,9 +30,11 @@ #include #include +// FOOTPRINT_LIBRARY_HEADER_CNT gives the number of characters to compare to detect +// a footprint library. A few variants may have been used, and so we can only be +// sure that the header contains "PCBNEW-LibModule-V", not "PCBNEW-LibModule-V1". #define FOOTPRINT_LIBRARY_HEADER "PCBNEW-LibModule-V1" -#define FOOTPRINT_LIBRARY_HEADER_CNT 18 - +#define FOOTPRINT_LIBRARY_HEADER_CNT 18 class PCB_TARGET; class MODULE; @@ -77,12 +79,14 @@ public: return wxT( "brd" ); } - BOARD* Load( const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties = NULL ); + BOARD* Load( const wxString& aFileName, BOARD* aAppendToMe, + const PROPERTIES* aProperties = NULL ); - wxArrayString FootprintEnumerate( const wxString& aLibraryPath, const PROPERTIES* aProperties = NULL); + wxArrayString FootprintEnumerate( const wxString& aLibraryPath, + const PROPERTIES* aProperties = NULL ); MODULE* FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, - const PROPERTIES* aProperties = NULL ); + const PROPERTIES* aProperties = NULL ); bool FootprintLibDelete( const wxString& aLibraryPath, const PROPERTIES* aProperties = NULL ); diff --git a/pcbnew/librairi.cpp b/pcbnew/librairi.cpp index 86210c8259..744a3d4e4b 100644 --- a/pcbnew/librairi.cpp +++ b/pcbnew/librairi.cpp @@ -1,8 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 1992-2015 KiCad Developers, see change_log.txt for contributors. - * + * Copyright (C) 1992-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 @@ -99,19 +98,14 @@ static const wxString ModImportFileWildcard( _( "GPcb foot print files (*)|*" ) #define EXPORT_IMPORT_LASTPATH_KEY wxT( "import_last_path" ) -MODULE* FOOTPRINT_EDIT_FRAME::Import_Module() +/** + * Prompt the user for a module file to open. + * @param aParent - parent window for the dialog + * @param aLastPath - last opened path + */ +static wxFileName prompt_for_module( wxWindow* aParent, const wxString& aLastPath ) { - // use the clipboard for this in the future? - - // Some day it might be useful save the last library type selected along with the path. static int lastFilterIndex = 0; - - wxString lastOpenedPathForLoading = m_mruPath; - wxConfigBase* config = Kiface().KifaceSettings(); - - if( config ) - config->Read( EXPORT_IMPORT_LASTPATH_KEY, &lastOpenedPathForLoading ); - wxString wildCard; wildCard << wxGetTranslation( KiCadFootprintLibFileWildcard ) << wxChar( '|' ) @@ -119,159 +113,204 @@ MODULE* FOOTPRINT_EDIT_FRAME::Import_Module() << wxGetTranslation( ModImportFileWildcard ) << wxChar( '|' ) << wxGetTranslation( GedaPcbFootprintLibFileWildcard ); - wxFileDialog dlg( this, FMT_IMPORT_MODULE, - lastOpenedPathForLoading, wxEmptyString, - wildCard, wxFD_OPEN | wxFD_FILE_MUST_EXIST ); + wxFileDialog dlg( aParent, FMT_IMPORT_MODULE, aLastPath, wxEmptyString, wildCard, + wxFD_OPEN | wxFD_FILE_MUST_EXIST ); + dlg.SetFilterIndex( lastFilterIndex ); if( dlg.ShowModal() == wxID_CANCEL ) - return NULL; - + return wxFileName(); + lastFilterIndex = dlg.GetFilterIndex(); - FILE* fp = wxFopen( dlg.GetPath(), wxT( "rt" ) ); + return wxFileName( dlg.GetPath() ); +} + + +/** + * Read a file to detect the type. + * @param aFile - open file to be read. File pointer will be closed. + * @param aFileName - file name to be read + * @param aName - wxString to receive the module name iff type is LEGACY + */ +static IO_MGR::PCB_FILE_T detect_file_type( FILE* aFile, const wxFileName& aFileName, wxString* aName ) +{ + FILE_LINE_READER freader( aFile, aFileName.GetFullPath() ); + WHITESPACE_FILTER_READER reader( freader ); + IO_MGR::PCB_FILE_T file_type; + + wxASSERT( aName ); + + reader.ReadLine(); + char* line = reader.Line(); + + if( !strnicmp( line, "(module", strlen( "(module" ) ) ) + { + file_type = IO_MGR::KICAD; + *aName = aFileName.GetName(); + } + else if( !strnicmp( line, FOOTPRINT_LIBRARY_HEADER, FOOTPRINT_LIBRARY_HEADER_CNT ) ) + { + file_type = IO_MGR::LEGACY; + while( reader.ReadLine() ) + { + if( !strnicmp( line, "$MODULE", strlen( "$MODULE" ) ) ) + { + *aName = FROM_UTF8( StrPurge( line + strlen( "$MODULE" ) ) ); + break; + } + } + } + else if( !strnicmp( line, "Element", strlen( "Element" ) ) ) + { + file_type = IO_MGR::GEDA_PCB; + *aName = aFileName.GetName(); + } + else + { + file_type = IO_MGR::FILE_TYPE_NONE; + } + + return file_type; +} + + +/** + * Parse a footprint using a PLUGIN. + * @param aFileName - file name to parse + * @param aFileType - type of the file + * @param aName - name of the footprint + */ +static MODULE* parse_module_with_plugin( + const wxFileName& aFileName, IO_MGR::PCB_FILE_T aFileType, + const wxString& aName ) +{ + wxString path; + + switch( aFileType ) + { + case IO_MGR::GEDA_PCB: + path = aFileName.GetPath(); + break; + case IO_MGR::LEGACY: + path = aFileName.GetFullPath(); + break; + default: + wxFAIL_MSG( wxT( "unexpected IO_MGR::PCB_FILE_T" ) ); + } + + PLUGIN::RELEASER pi( IO_MGR::PluginFind( aFileType ) ); + + return pi->FootprintLoad( path, aName ); +} + + +/** + * Parse a KICAD footprint. + * @param aFileName - file name to parse + */ +static MODULE* parse_module_kicad( const wxFileName& aFileName ) +{ + wxString fcontents; + PCB_IO pcb_io; + wxFFile f( aFileName.GetFullPath() ); + + if( !f.IsOpened() ) + return NULL; + + f.ReadAll( &fcontents ); + + return dynamic_cast( pcb_io.Parse( fcontents ) ); +} + + +/** + * Try to load a footprint, returning NULL if the file couldn't be accessed. + * @param aFileName - file name to load + * @param aFileType - type of the file to load + * @param aName - footprint name + */ +MODULE* try_load_footprint( const wxFileName& aFileName, IO_MGR::PCB_FILE_T aFileType, + const wxString& aName ) +{ + MODULE* module; + + switch( aFileType ) + { + case IO_MGR::GEDA_PCB: + case IO_MGR::LEGACY: + module = parse_module_with_plugin( aFileName, aFileType, aName ); + break; + + case IO_MGR::KICAD: + module = parse_module_kicad( aFileName ); + break; + + default: + wxFAIL_MSG( wxT( "unexpected IO_MGR::PCB_FILE_T" ) ); + module = NULL; + } + + return module; +} + + +MODULE* FOOTPRINT_EDIT_FRAME::Import_Module() +{ + wxString lastOpenedPathForLoading = m_mruPath; + wxConfigBase* config = Kiface().KifaceSettings(); + + if( config ) + config->Read( EXPORT_IMPORT_LASTPATH_KEY, &lastOpenedPathForLoading ); + + wxFileName fn = prompt_for_module( this, lastOpenedPathForLoading ); + + if( !fn.IsOk() ) + return NULL; + + FILE* fp = wxFopen( fn.GetFullPath(), wxT( "rt" ) ); if( !fp ) { - wxString msg = wxString::Format( FMT_FILE_NOT_FOUND, GetChars( dlg.GetPath() ) ); + wxString msg = wxString::Format( FMT_FILE_NOT_FOUND, GetChars( fn.GetFullPath() ) ); DisplayError( this, msg ); return NULL; } if( config ) // Save file path { - lastOpenedPathForLoading = wxPathOnly( dlg.GetPath() ); + lastOpenedPathForLoading = fn.GetPath(); config->Write( EXPORT_IMPORT_LASTPATH_KEY, lastOpenedPathForLoading ); } wxString moduleName; + IO_MGR::PCB_FILE_T fileType = detect_file_type( fp, fn.GetFullPath(), &moduleName ); - bool isGeda = false; - bool isLegacy = false; - + if( fileType == IO_MGR::FILE_TYPE_NONE ) { - FILE_LINE_READER freader( fp, dlg.GetPath() ); // I own fp, and will close it. - WHITESPACE_FILTER_READER reader( freader ); // skip blank lines - - reader.ReadLine(); - char* line = reader.Line(); - - if( !strnicmp( line, "(module", 7 ) ) - { - // isKicad = true; - } - else if( !strnicmp( line, FOOTPRINT_LIBRARY_HEADER, FOOTPRINT_LIBRARY_HEADER_CNT ) ) - { - isLegacy = true; - - while( reader.ReadLine() ) - { - if( !strnicmp( line, "$MODULE", 7 ) ) - { - moduleName = FROM_UTF8( StrPurge( line + sizeof( "$MODULE" ) -1 ) ); - break; - } - } - } - else if( !strnicmp( line, "Element", 7 ) ) - { - isGeda = true; - } - else - { - DisplayError( this, FMT_NOT_MODULE ); - return NULL; - } - - // fp is closed here by ~FILE_LINE_READER() + DisplayError( this, FMT_NOT_MODULE ); + return NULL; } - MODULE* module; + MODULE* module; + wxString errMessage; - if( isGeda ) + try { - try + module = try_load_footprint( fn, fileType, moduleName ); + + if( !module ) { - wxFileName fn = dlg.GetPath(); - PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::GEDA_PCB ) ); - - moduleName = fn.GetName(); - module = pi->FootprintLoad( fn.GetPath(), moduleName ); - - if( !module ) - { - wxString msg = wxString::Format( - FMT_MOD_NOT_FOUND, GetChars( moduleName ), GetChars( fn.GetPath() ) ); - - DisplayError( this, msg ); - return NULL; - } - } - catch( const IO_ERROR& ioe ) - { - DisplayError( this, ioe.errorText ); + wxString msg = wxString::Format( + FMT_MOD_NOT_FOUND, GetChars( moduleName ), GetChars( fn.GetFullPath() ) ); + DisplayError( this, msg ); return NULL; } } - else if( isLegacy ) + catch( const IO_ERROR& ioe ) { - try - { - PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::LEGACY ) ); - - module = pi->FootprintLoad( dlg.GetPath(), moduleName ); - - if( !module ) - { - wxString msg = wxString::Format( - FMT_MOD_NOT_FOUND, GetChars( moduleName ), GetChars( dlg.GetPath() ) ); - - DisplayError( this, msg ); - return NULL; - } - } - catch( const IO_ERROR& ioe ) - { - DisplayError( this, ioe.errorText ); - return NULL; - } - } - else // if( isKicad ) - { - try - { - // This technique was chosen to create an example of how reading - // the s-expression format from clipboard could be done. - - wxString fcontents; - PCB_IO pcb_io; - wxFFile f( dlg.GetPath() ); - - if( !f.IsOpened() ) - { - wxString msg = wxString::Format( FMT_BAD_PATH, GetChars( dlg.GetPath() ) ); - - DisplayError( this, msg ); - return NULL; - } - - f.ReadAll( &fcontents ); - - module = dyn_cast( pcb_io.Parse( fcontents ) ); - - if( !module ) - { - wxString msg = wxString::Format( FMT_BAD_PATH, GetChars( dlg.GetPath() ) ); - - DisplayError( this, msg ); - return NULL; - } - } - catch( const IO_ERROR& ioe ) - { - DisplayError( this, ioe.errorText ); - return NULL; - } + DisplayError( this, ioe.errorText ); + return NULL; } // Insert footprint in list diff --git a/pcbnew/pcb_parser.cpp b/pcbnew/pcb_parser.cpp index a6a373fef7..09240bf0e8 100644 --- a/pcbnew/pcb_parser.cpp +++ b/pcbnew/pcb_parser.cpp @@ -2,8 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 CERN - * Copyright (C) 2012-2015 KiCad Developers, see change_log.txt for contributors. - * @author Wayne Stambaugh + * Copyright (C) 2012-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 @@ -60,6 +59,8 @@ using namespace PCB_KEYS_T; void PCB_PARSER::init() { + m_tooRecent = false; + m_requiredVersion = 0; m_layerIndices.clear(); m_layerMasks.clear(); @@ -169,6 +170,43 @@ bool PCB_PARSER::parseBool() throw( PARSE_ERROR ) } +int PCB_PARSER::parseVersion() throw( IO_ERROR, PARSE_ERROR ) +{ + if( NextTok() != T_version ) + Expecting( GetTokenText( T_version ) ); + + int pcb_version = parseInt( FromUTF8() ); + + NeedRIGHT(); + + return pcb_version; +} + + +wxString PCB_PARSER::GetRequiredVersion() +{ + int year, month, day; + + year = m_requiredVersion / 10000; + month = ( m_requiredVersion / 100 ) - ( year * 100 ); + day = m_requiredVersion - ( year * 10000 ) - ( month * 100 ); + + // wx throws an assertion, not a catchable exception, when the date is invalid. + // User input shouldn't give wx asserts, so check manually and throw a proper + // error instead + if( day <= 0 || month <= 0 || month > 12 || + day > wxDateTime::GetNumberOfDays( (wxDateTime::Month)( month - 1 ), year ) ) + { + wxString err; + err.Printf( _( "cannot interpret date code %d" ), m_requiredVersion ); + THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() ); + } + + wxDateTime date( day, (wxDateTime::Month)( month - 1 ), year, 0, 0, 0, 0 ); + return date.FormatDate(); +} + + wxPoint PCB_PARSER::parseXY() throw( PARSE_ERROR, IO_ERROR ) { if( CurTok() != T_LEFT ) @@ -411,7 +449,23 @@ BOARD_ITEM* PCB_PARSER::Parse() throw( IO_ERROR, PARSE_ERROR ) } -BOARD* PCB_PARSER::parseBOARD() throw( IO_ERROR, PARSE_ERROR ) +BOARD* PCB_PARSER::parseBOARD() throw( IO_ERROR, PARSE_ERROR, FUTURE_FORMAT_ERROR ) +{ + try + { + return parseBOARD_unchecked(); + } + catch( const PARSE_ERROR& parse_error ) + { + if( m_tooRecent ) + throw FUTURE_FORMAT_ERROR( parse_error, GetRequiredVersion() ); + else + throw; + } +} + + +BOARD* PCB_PARSER::parseBOARD_unchecked() throw( IO_ERROR, PARSE_ERROR ) { T token; @@ -506,24 +560,34 @@ void PCB_PARSER::parseHeader() throw( IO_ERROR, PARSE_ERROR ) wxCHECK_RET( CurTok() == T_kicad_pcb, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a header." ) ); - T token; - NeedLEFT(); - token = NextTok(); - if( token != T_version ) - Expecting( GetTokenText( T_version ) ); + T tok = NextTok(); + if( tok == T_version ) + { + m_requiredVersion = parseInt( FromUTF8() ); + m_tooRecent = ( m_requiredVersion > SEXPR_BOARD_FILE_VERSION ); + NeedRIGHT(); - // Get the file version. - m_board->SetFileFormatVersionAtLoad( parseInt( GetTokenText( T_version ) ) ); + // Skip the host name and host build version information. + NeedLEFT(); + NeedSYMBOL(); + NeedSYMBOL(); + NeedSYMBOL(); + NeedRIGHT(); + } + else + { + m_requiredVersion = SEXPR_BOARD_FILE_VERSION; + m_tooRecent = ( m_requiredVersion > SEXPR_BOARD_FILE_VERSION ); - // Skip the host name and host build version information. - NeedRIGHT(); - NeedLEFT(); - NeedSYMBOL(); - NeedSYMBOL(); - NeedSYMBOL(); - NeedRIGHT(); + // Skip the host name and host build version information. + NeedSYMBOL(); + NeedSYMBOL(); + NeedRIGHT(); + } + + m_board->SetFileFormatVersionAtLoad( m_requiredVersion ); } @@ -1651,7 +1715,25 @@ DIMENSION* PCB_PARSER::parseDIMENSION() throw( IO_ERROR, PARSE_ERROR ) } -MODULE* PCB_PARSER::parseMODULE( wxArrayString* aInitialComments ) throw( IO_ERROR, PARSE_ERROR ) +MODULE* PCB_PARSER::parseMODULE( wxArrayString* aInitialComments ) + throw( IO_ERROR, PARSE_ERROR, FUTURE_FORMAT_ERROR ) +{ + try + { + return parseMODULE_unchecked( aInitialComments ); + } + catch( const PARSE_ERROR& parse_error ) + { + if( m_tooRecent ) + throw FUTURE_FORMAT_ERROR( parse_error, GetRequiredVersion() ); + else + throw; + } +} + + +MODULE* PCB_PARSER::parseMODULE_unchecked( wxArrayString* aInitialComments ) + throw( IO_ERROR, PARSE_ERROR ) { wxCHECK_MSG( CurTok() == T_module, NULL, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as MODULE." ) ); @@ -1665,7 +1747,11 @@ MODULE* PCB_PARSER::parseMODULE( wxArrayString* aInitialComments ) throw( IO_ERR module->SetInitialComments( aInitialComments ); - NeedSYMBOLorNUMBER(); + token = NextTok(); + + if( !IsSymbol( token ) && token != T_NUMBER ) + Expecting( "symbol|number" ); + name = FromUTF8(); if( !name.IsEmpty() && fpid.Parse( FromUTF8() ) >= 0 ) @@ -1683,6 +1769,18 @@ MODULE* PCB_PARSER::parseMODULE( wxArrayString* aInitialComments ) throw( IO_ERR switch( token ) { + case T_version: + { + // Theoretically a module nested in a PCB could declare its own version, though + // as of writing this comment we don't do that. Just in case, take the greater + // version. + int this_version = parseInt( FromUTF8() ); + NeedRIGHT(); + m_requiredVersion = std::max( m_requiredVersion, this_version ); + m_tooRecent = ( m_requiredVersion > SEXPR_BOARD_FILE_VERSION ); + break; + } + case T_locked: module->SetLocked( true ); break; diff --git a/pcbnew/pcb_parser.h b/pcbnew/pcb_parser.h index 9350b4e461..cd815ad22f 100644 --- a/pcbnew/pcb_parser.h +++ b/pcbnew/pcb_parser.h @@ -2,6 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 CERN + * Copyright (C) 2012-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 @@ -68,6 +69,8 @@ class PCB_PARSER : public PCB_LEXER LAYER_ID_MAP m_layerIndices; ///< map layer name to it's index LSET_MAP m_layerMasks; ///< map layer names to their masks std::vector m_netCodes; ///< net codes mapping for boards being loaded + bool m_tooRecent; ///< true if version parses as later than supported + int m_requiredVersion; ///< set to the KiCad format version this board requires ///> Converts net code using the mapping table if available, ///> otherwise returns unchanged net code if < 0 or if is is out of range @@ -113,12 +116,20 @@ class PCB_PARSER : public PCB_LEXER DIMENSION* parseDIMENSION() throw( IO_ERROR, PARSE_ERROR ); /** - * Function parseModule + * Function parseMODULE * @param aInitialComments may be a pointer to a heap allocated initial comment block * or NULL. If not NULL, then caller has given ownership of a wxArrayString to * this function and care must be taken to delete it even on exception. */ - MODULE* parseMODULE( wxArrayString* aInitialComments = 0 ) throw( IO_ERROR, PARSE_ERROR ); + MODULE* parseMODULE( wxArrayString* aInitialComments = 0 ) + throw( IO_ERROR, PARSE_ERROR, FUTURE_FORMAT_ERROR ); + + /** + * Function parseMODULE_unchecked + * Parse a module, but do not replace PARSE_ERROR with FUTURE_FORMAT_ERROR automatically. + */ + MODULE* parseMODULE_unchecked( wxArrayString* aInitialComments = 0 ) + throw( IO_ERROR, PARSE_ERROR ); TEXTE_MODULE* parseTEXTE_MODULE() throw( IO_ERROR, PARSE_ERROR ); EDGE_MODULE* parseEDGE_MODULE() throw( IO_ERROR, PARSE_ERROR ); D_PAD* parseD_PAD( MODULE* aParent = NULL ) throw( IO_ERROR, PARSE_ERROR ); @@ -126,7 +137,13 @@ class PCB_PARSER : public PCB_LEXER VIA* parseVIA() throw( IO_ERROR, PARSE_ERROR ); ZONE_CONTAINER* parseZONE_CONTAINER() throw( IO_ERROR, PARSE_ERROR ); PCB_TARGET* parsePCB_TARGET() throw( IO_ERROR, PARSE_ERROR ); - BOARD* parseBOARD() throw( IO_ERROR, PARSE_ERROR ); + BOARD* parseBOARD() throw( IO_ERROR, PARSE_ERROR, FUTURE_FORMAT_ERROR ); + + /** + * Function parseBOARD_unchecked + * Parse a module, but do not replace PARSE_ERROR with FUTURE_FORMAT_ERROR automatically. + */ + BOARD* parseBOARD_unchecked() throw( IO_ERROR, PARSE_ERROR ); /** @@ -252,6 +269,11 @@ class PCB_PARSER : public PCB_LEXER bool parseBool() throw( PARSE_ERROR ); + /** + * Parse a format version tag like (version 20160417) return the version. + * Expects to start on 'version', and eats the closing paren. + */ + int parseVersion() throw( IO_ERROR, PARSE_ERROR ); public: @@ -284,6 +306,21 @@ public: } BOARD_ITEM* Parse() throw( IO_ERROR, PARSE_ERROR ); + + /** + * Return whether a version number, if any was parsed, was too recent + */ + bool IsTooRecent() + { + return m_tooRecent; + } + + /** + * Return a string representing the version of kicad required to open this + * file. Not particularly meaningful if IsTooRecent() returns false. + */ + wxString GetRequiredVersion(); + }; diff --git a/pcbnew/plugin.cpp b/pcbnew/plugin.cpp index 22e060be09..9b5f5ab8a0 100644 --- a/pcbnew/plugin.cpp +++ b/pcbnew/plugin.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2011-2012 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2011 KiCad Developers, see change_log.txt for contributors. + * 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