diff --git a/CMakeLists.txt b/CMakeLists.txt index d915d8b4bb..7ac2a10491 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,10 @@ option( KICAD_SCRIPTING_WXPYTHON "Build wxPython implementation for wx interface building in Python and py.shell (default OFF)." ) +option( USE_SCH_IO_MANAGER + "Build Eeschema with the I/O manager for handling schematic and symbol library I/O. (default OFF)" + ) + # when option KICAD_SCRIPTING OR KICAD_SCRIPTING_MODULES is enabled: # PYTHON_EXECUTABLE can be defined when invoking cmake # ( use -DPYTHON_EXECUTABLE=<python path>/python.exe or python2 ) diff --git a/CMakeModules/config.h.cmake b/CMakeModules/config.h.cmake index 26a1fb4437..3e6019969c 100644 --- a/CMakeModules/config.h.cmake +++ b/CMakeModules/config.h.cmake @@ -69,6 +69,9 @@ /// When defined, build the GITHUB_PLUGIN for pcbnew. #cmakedefine BUILD_GITHUB_PLUGIN +/// When defined, Eeschema is built with I/O manager plugin. +#cmakedefine USE_SCH_IO_MANAGER + /// A file extension with a leading '.' is a suffix, and this one is used on /// top level program modules which implement the KIFACE. #define KIFACE_SUFFIX wxT( "@KIFACE_SUFFIX@" ) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 6575e863ea..736a4de38d 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -241,6 +241,7 @@ set( COMMON_SRCS netlist_keywords.cpp prependpath.cpp project.cpp + properties.cpp ptree.cpp reporter.cpp richio.cpp diff --git a/common/properties.cpp b/common/properties.cpp new file mode 100644 index 0000000000..6092434608 --- /dev/null +++ b/common/properties.cpp @@ -0,0 +1,37 @@ +/* + * This program source code file is part of KICAD, a free EDA CAD application. + * + * Copyright (C) 2016 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com> + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <properties.h> + + +bool PROPERTIES::Value( const char* aName, UTF8* aFetchedValue ) const +{ + PROPERTIES::const_iterator it = find( aName ); + + if( it != end() ) + { + if( aFetchedValue ) + *aFetchedValue = it->second; + + return true; + } + + return false; +} diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index 89b5304fc8..7c871e9934 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -145,11 +145,14 @@ set( EESCHEMA_SRCS sch_collectors.cpp sch_component.cpp sch_field.cpp + sch_io_mgr.cpp sch_item_struct.cpp sch_junction.cpp + sch_legacy_plugin.cpp sch_line.cpp sch_marker.cpp sch_no_connect.cpp + sch_plugin.cpp sch_screen.cpp sch_sheet.cpp sch_sheet_path.cpp diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp index 0659fc35f2..05ba28ea28 100644 --- a/eeschema/files-io.cpp +++ b/eeschema/files-io.cpp @@ -2,9 +2,9 @@ * 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@verizon.net> + * Copyright (C) 2013-2016 Wayne Stambaugh <stambaughw@verizon.net> * Copyright (C) 2013 CERN (www.cern.ch) - * Copyright (C) 1992-2015 KiCad Developers, see AUTHORS.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 @@ -45,9 +45,14 @@ #include <wildcards_and_files_ext.h> #include <project_rescue.h> #include <eeschema_config.h> +#include <sch_legacy_plugin.h> -bool SCH_EDIT_FRAME::SaveEEFile( SCH_SCREEN* aScreen, bool aSaveUnderNewName, bool aCreateBackupFile ) +//#define USE_SCH_LEGACY_IO_PLUGIN + + +bool SCH_EDIT_FRAME::SaveEEFile( SCH_SCREEN* aScreen, bool aSaveUnderNewName, + bool aCreateBackupFile ) { wxString msg; wxFileName schematicFileName; @@ -204,7 +209,7 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in return false; } - // save any currently open and modified project files. + // Save any currently open and modified project files. for( SCH_SCREEN* screen = screenList.GetFirst(); screen; screen = screenList.GetNext() ) { if( screen->IsModify() ) @@ -295,13 +300,39 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in } else { +#ifdef USE_SCH_IO_MANAGER + 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 ) ); + + try + { + g_RootSheet = pi->Load( fullFileName, &Kiway() ); + m_CurrentSheet->clear(); + m_CurrentSheet->push_back( g_RootSheet ); + } + catch( const IO_ERROR& ioe ) + { + msg.Printf( _( "Error loading schematic file '%s'.\n%s" ), + GetChars( fullFileName ), GetChars( ioe.errorText ) ); + DisplayError( this, msg ); + + msg.Printf( _( "Failed to load '%s'" ), GetChars( fullFileName ) ); + + AppendMsgPanel( wxEmptyString, msg, CYAN ); + Zoom_Automatique( false ); + + return false; + } +#else g_RootSheet->SetScreen( NULL ); DBG( printf( "%s: loading schematic %s\n", __func__, TO_UTF8( fullFileName ) );) bool diag = g_RootSheet->Load( this ); (void) diag; - +#endif SetScreen( m_CurrentSheet->LastScreen() ); GetScreen()->ClrModify(); @@ -327,7 +358,6 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in GetScreen()->SetGrid( ID_POPUP_GRID_LEVEL_1000 + m_LastGridSizeId ); Zoom_Automatique( false ); SetSheetNumberAndCount(); - m_canvas->Refresh( true ); return true; diff --git a/eeschema/sch_component.h b/eeschema/sch_component.h index 5094afc2df..179e201890 100644 --- a/eeschema/sch_component.h +++ b/eeschema/sch_component.h @@ -47,6 +47,7 @@ class NETLIST_OBJECT_LIST; class LIB_PART; class PART_LIBS; class SCH_COLLECTOR; +class SCH_SCREEN; /// A container for several SCH_FIELD items @@ -189,6 +190,8 @@ public: wxString GetPrefix() const { return m_prefix; } + void SetPrefix( const wxString& aPrefix ) { m_prefix = aPrefix; } + TRANSFORM& GetTransform() const { return const_cast< TRANSFORM& >( m_transform ); } void SetTransform( const TRANSFORM& aTransform ); diff --git a/eeschema/sch_io_mgr.cpp b/eeschema/sch_io_mgr.cpp new file mode 100644 index 0000000000..b34143154e --- /dev/null +++ b/eeschema/sch_io_mgr.cpp @@ -0,0 +1,162 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 CERN + * Copyright (C) 2016 KiCad Developers, see change_log.txt for contributors. + * + * @author Wayne Stambaugh <stambaughw@gmail.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <wx/filename.h> +#include <wx/uri.h> + +#include <sch_io_mgr.h> +#include <sch_legacy_plugin.h> + +#include <wildcards_and_files_ext.h> + +#define FMT_UNIMPLEMENTED _( "Plugin '%s' does not implement the '%s' function." ) +#define FMT_NOTFOUND _( "Plugin type '%s' is not found." ) + + + +// Some day plugins might be in separate DLL/DSOs, simply because of numbers of them +// and code size. Until then, use the simplest method: + +// This implementation is one of two which could be done. +// The other one would cater to DLL/DSO's. But since it would be nearly +// impossible to link a KICAD type DLL/DSO right now without pulling in all +// ::Draw() functions, I forgo that option temporarily. + +// Some day it may be possible to have some built in AND some DLL/DSO +// plugins coexisting. + + +SCH_PLUGIN* SCH_IO_MGR::FindPlugin( SCH_FILE_T aFileType ) +{ + // This implementation is subject to change, any magic is allowed here. + // The public SCH_IO_MGR API is the only pertinent public information. + + switch( aFileType ) + { + case SCH_LEGACY: + return new SCH_LEGACY_PLUGIN(); + } + + return NULL; +} + + +void SCH_IO_MGR::ReleasePlugin( SCH_PLUGIN* aPlugin ) +{ + // This function is a place holder for a future point in time where + // the plugin is a DLL/DSO. It could do reference counting, and then + // unload the DLL/DSO when count goes to zero. + + delete aPlugin; +} + + +const wxString SCH_IO_MGR::ShowType( SCH_FILE_T aType ) +{ + // keep this function in sync with EnumFromStr() relative to the + // text spellings. If you change the spellings, you will obsolete + // library tables, so don't do change, only additions are ok. + + switch( aType ) + { + default: + return wxString::Format( _( "Unknown SCH_FILE_T value: %d" ), aType ); + + case SCH_LEGACY: + return wxString( wxT( "Legacy" ) ); + } +} + + +SCH_IO_MGR::SCH_FILE_T SCH_IO_MGR::EnumFromStr( const wxString& aType ) +{ + // keep this function in sync with ShowType() relative to the + // text spellings. If you change the spellings, you will obsolete + // library tables, so don't do change, only additions are ok. + + if( aType == wxT( "Legacy" ) ) + return SCH_LEGACY; + + // wxASSERT( blow up here ) + + return SCH_FILE_T( -1 ); +} + + +const wxString SCH_IO_MGR::GetFileExtension( SCH_FILE_T aFileType ) +{ + wxString ext = wxEmptyString; + SCH_PLUGIN* plugin = FindPlugin( aFileType ); + + if( plugin != NULL ) + { + ext = plugin->GetFileExtension(); + ReleasePlugin( plugin ); + } + + return ext; +} + + +SCH_IO_MGR::SCH_FILE_T SCH_IO_MGR::GuessPluginTypeFromLibPath( const wxString& aLibPath ) +{ + SCH_FILE_T ret = SCH_LEGACY; // default guess, unless detected otherwise. + wxFileName fn( aLibPath ); + + if( fn.GetExt() == SchematicFileWildcard ) + { + ret = SCH_LEGACY; + } + + return ret; +} + + +SCH_SHEET* SCH_IO_MGR::Load( SCH_FILE_T aFileType, const wxString& aFileName, KIWAY* aKiway, + SCH_SHEET* aAppendToMe, const PROPERTIES* aProperties ) +{ + // release the SCH_PLUGIN even if an exception is thrown. + SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( FindPlugin( aFileType ) ); + + if( (SCH_PLUGIN*) pi ) // test pi->plugin + { + return pi->Load( aFileName, aKiway, aAppendToMe, aProperties ); // virtual + } + + THROW_IO_ERROR( wxString::Format( FMT_NOTFOUND, ShowType( aFileType ).GetData() ) ); +} + + +void SCH_IO_MGR::Save( SCH_FILE_T aFileType, const wxString& aFileName, + SCH_SHEET* aSchematic, const PROPERTIES* aProperties ) +{ + // release the SCH_PLUGIN even if an exception is thrown. + SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( FindPlugin( aFileType ) ); + + if( (SCH_PLUGIN*) pi ) // test pi->plugin + { + pi->Save( aFileName, aSchematic, aProperties ); // virtual + return; + } + + THROW_IO_ERROR( wxString::Format( FMT_NOTFOUND, ShowType( aFileType ).GetData() ) ); +} diff --git a/eeschema/sch_io_mgr.h b/eeschema/sch_io_mgr.h new file mode 100644 index 0000000000..4b86392183 --- /dev/null +++ b/eeschema/sch_io_mgr.h @@ -0,0 +1,487 @@ +#ifndef _SCH_IO_MGR_H_ +#define _SCH_IO_MGR_H_ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 CERN + * Copyright (C) 2016 KiCad Developers, see CHANGELOG.TXT for contributors. + * + * @author Wayne Stambaugh <stambaughw@gmail.com> + * + * 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 3 + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <richio.h> +#include <map> + + +class SCH_SHEET; +class SCH_PLUGIN; +class KIWAY; +class LIB_PART; +class PROPERTIES; + + +/** + * Class SCH_IO_MGR + * is a factory which returns an instance of a SCH_PLUGIN. + */ +class SCH_IO_MGR +{ +public: + + /** + * Enum SCH_FILE_T + * is a set of file types that the SCH_IO_MGR knows about, and for which there + * has been a plugin written. + */ + enum SCH_FILE_T + { + SCH_LEGACY, ///< Legacy Eeschema file formats prior to s-expression. + SCH_KICAD, ///< The s-expression version of the schematic file formats. + // Add your schematic type here. + + // ALTIUM, + // etc. + }; + + /** + * Function FindPlugin + * returns a SCH_PLUGIN which the caller can use to import, export, save, or load + * design documents. The returned SCH_PLUGIN, may be reference counted, so please + * call PluginRelease() when you are done using the returned SCH_PLUGIN. It may or + * may not be code running from a DLL/DSO. + * + * @param aFileType is from SCH_FILE_T and tells which plugin to find. + * + * @return SCH_PLUGIN* - the plugin corresponding to aFileType or NULL if not found. + * Caller owns the returned object, and must call PluginRelease when done using it. + */ + static SCH_PLUGIN* FindPlugin( SCH_FILE_T aFileType ); + + /** + * Function PluginRelease + * releases a SCH_PLUGIN back to the system, and may cause it to be unloaded from memory. + * + * @param aPlugin is the one to be released, and which is no longer usable + * after calling this. + */ + static void ReleasePlugin( SCH_PLUGIN* aPlugin ); + + /** + * Function ShowType + * returns a brief name for a plugin, given aFileType enum. + */ + static const wxString ShowType( SCH_FILE_T aFileType ); + + /** + * Function EnumFromStr + * returns the SCH_FILE_T from the corresponding plugin type name: "kicad", "legacy", etc. + */ + static SCH_FILE_T EnumFromStr( const wxString& aFileType ); + + /** + * Function GetFileExtension + * returns the file extension for \a aFileType. + * + * @param aFileType The #SCH_FILE_T type. + * @return A wxString object containing the file extension for \a aFileType or an empty + * string if \a aFileType is invalid. + */ + static const wxString GetFileExtension( SCH_FILE_T aFileType ); + + /** + * Function GuessPluginTypeFromLibPath + * returns a plugin type given a footprint library's libPath. + */ + static SCH_FILE_T GuessPluginTypeFromLibPath( const wxString& aLibPath ); + + /** + * Function Load + * finds the requested SCH_PLUGIN and if found, calls the SCH_PLUGIN->Load(..) funtion + * on it using the arguments passed to this function. After the SCH_PLUGIN->Load() + * function returns, the SCH_PLUGIN is Released() as part of this call. + * + * @param aFileType is the SCH_FILE_T of file to load. + * @param aFileName is the name of the file to load. + * @param aKiway is the #KIWAY object used to access the component libraries loaded + * by the project. + * @param aAppendToMe is an existing #SCHEMATIC to append to, use NULL if a new + * #SCHEMATIC load is wanted. + * @param aProperties is an associative array that allows the caller to pass additional + * tuning parameters to the SCH_PLUGIN. + * + * @return SCHEMATIC* - caller owns it, never NULL because exception thrown if error. + * + * @throw IO_ERROR if the SCH_PLUGIN cannot be found, file cannot be found, + * or file cannot be loaded. + */ + static SCH_SHEET* Load( SCH_FILE_T aFileType, const wxString& aFileName, KIWAY* aKiway, + SCH_SHEET* aAppendToMe = NULL, const PROPERTIES* aProperties = NULL ); + + /** + * Function Save + * will write either a full aSchematic to a storage file in a format that this + * implementation knows about, or it can be used to write a portion of + * aSchematic to a special kind of export file. + * + * @param aFileType is the SCH_FILE_T of file to save. + * + * @param aFileName is the name of a file to save to on disk. + * @param aSchematic is the SCHEMATIC document (data tree) to save or export to disk. + * + * @param aSchematic is the in memory document tree from which to extract information + * when writing to \a aFileName. The caller continues to own the SCHEMATIC, and + * the plugin should refrain from modifying the SCHEMATIC if possible. + * + * @param aProperties is an associative array that can be used to tell the + * saver how to save the file, because it can take any number of + * additional named tuning arguments that the plugin is known to support. + * The caller continues to own this object (plugin may not delete it), and + * plugins should expect it to be optionally NULL. + * + * @throw IO_ERROR if there is a problem saving or exporting. + */ + static void Save( SCH_FILE_T aFileType, const wxString& aFileName, + SCH_SHEET* aSchematic, const PROPERTIES* aProperties = NULL ); +}; + + +/** + * Class SCH_PLUGIN + * is a base class that SCHEMATIC loading and saving plugins should derive from. + * Implementations can provide either Load() or Save() functions, or both. + * SCH_PLUGINs throw exceptions, so it is best that you wrap your calls to these + * functions in a try catch block. Plugins throw exceptions because it is illegal + * for them to have any user interface calls in them whatsoever, i.e. no windowing + * or screen printing at all. + * + * <pre> + * try + * { + * SCH_IO_MGR::Load(...); + * or + * SCH_IO_MGR::Save(...); + * } + * catch( const IO_ERROR& ioe ) + * { + * // grab text from ioe, show in error window. + * } + * </pre> + */ +class SCH_PLUGIN +{ +public: + + //-----<PUBLIC SCH_PLUGIN API>------------------------------------------------- + + /** + * Function GetName + * returns a brief hard coded name for this SCH_PLUGIN. + */ + virtual const wxString GetName() const = 0; + + /** + * Function GetFileExtension + * returns the file extension for the SCH_PLUGIN. + */ + virtual const wxString GetFileExtension() const = 0; + + /** + * Function Load + * loads information from some input file format that this SCH_PLUGIN implementation + * knows about, into either a new SCHEMATIC or an existing one. This may be used to load an + * entire new SCHEMATIC, or to augment an existing one if @a aAppendToMe is not NULL. + * + * @param aFileName is the name of the file to use as input and may be foreign in + * nature or native in nature. + * + * @param aAppendToMe is an existing SCHEMATIC to append to, but if NULL then + * this means "do not append, rather load anew". + * + * @param aProperties is an associative array that can be used to tell the + * loader how to load the file, because it can take any number of + * additional named arguments that the plugin is known to support. These are + * tuning parameters for the import or load. The caller continues to own + * this object (plugin may not delete it), and plugins should expect it to + * be optionally NULL. + * + * @return SCHEMATIC* - the successfully loaded schematic, or the same one as \a aAppendToMe + * if \a aAppendToMe was not NULL, and the caller owns it. + * + * @throw IO_ERROR if there is a problem loading, and its contents should + * say what went wrong, using line number and character offsets of the + * input file if possible. + */ + virtual SCH_SHEET* Load( const wxString& aFileName, KIWAY* aKiway, + SCH_SHEET* aAppendToMe = NULL, const PROPERTIES* aProperties = NULL ); + + /** + * Function Save + * will write @a aSchematic to a storage file in a format that this + * SCH_PLUGIN implementation knows about, or it can be used to write a portion of + * aSchematic to a special kind of export file. + * + * @param aFileName is the name of a file to save to on disk. + * + * @param aSchematic is the class SCHEMATIC in memory document tree from which to + * extract information when writing to \a aFileName. The caller continues to + * own the SCHEMATIC, and the plugin should refrain from modifying the SCHEMATIC if possible. + * + * @param aProperties is an associative array that can be used to tell the + * saver how to save the file, because it can take any number of + * additional named tuning arguments that the plugin is known to support. + * The caller continues to own this object (plugin may not delete it), + * and plugins should expect it to be optionally NULL. + * + * @throw IO_ERROR if there is a problem saving or exporting. + */ + virtual void Save( const wxString& aFileName, SCH_SHEET* aSchematic, + const PROPERTIES* aProperties = NULL ); + + /** + * Function SymbolEnumerate + * returns a list of symbol names contained within the library at @a aLibraryPath. + * + * @param aLibraryPath is a locator for the "library", usually a directory, file, + * or URL containing several footprints. + * + * @param aProperties is an associative array that can be used to tell the + * plugin anything needed about how to perform with respect to @a aLibraryPath. + * The caller continues to own this object (plugin may not delete it), and + * plugins should expect it to be optionally NULL. + * + * @return wxArrayString - is the array of available footprint names inside + * a library + * + * @throw IO_ERROR if the library cannot be found, or footprint cannot be loaded. + */ + virtual wxArrayString SymbolEnumerate( const wxString& aLibraryPath, + const PROPERTIES* aProperties = NULL ); + + /** + * Function SymbolLoad + * loads a footprint having @a aSymbolName from the @a aLibraryPath containing + * a library format that this SCH_PLUGIN knows about. + * + * @param aLibraryPath is a locator for the "library", usually a directory, file, + * or URL containing several footprints. + * + * @param aSymbolName is the name of the footprint to load. + * + * @param aProperties is an associative array that can be used to tell the + * loader implementation to do something special, because it can take any number of + * additional named tuning arguments that the plugin is known to support. + * The caller continues to own this object (plugin may not delete it), and + * plugins should expect it to be optionally NULL. + * + * @return MODULE* - if found caller owns it, else NULL if not found. + * + * @throw IO_ERROR if the library cannot be found or read. No exception + * is thrown in the case where aSymbolName cannot be found. + */ + virtual LIB_PART* SymbolLoad( const wxString& aLibraryPath, const wxString& aSymbolName, + const PROPERTIES* aProperties = NULL ); + + /** + * Function SymbolSave + * will write @a aModule to an existing library located at @a aLibraryPath. + * If a footprint by the same name already exists, it is replaced. + * + * @param aLibraryPath is a locator for the "library", usually a directory, file, + * or URL containing several footprints. + * + * @param aSymbol is what to store in the library. The caller continues + * to own the footprint after this call. + * + * @param aProperties is an associative array that can be used to tell the + * saver how to save the footprint, because it can take any number of + * additional named tuning arguments that the plugin is known to support. + * The caller continues to own this object (plugin may not delete it), and + * plugins should expect it to be optionally NULL. + * + * @throw IO_ERROR if there is a problem saving. + */ + virtual void SymbolSave( const wxString& aLibraryPath, const LIB_PART* aSymbol, + const PROPERTIES* aProperties = NULL ); + + /** + * Function SymbolDelete + * deletes @a aSymbolName from the library at @a aLibraryPath. + * + * @param aLibraryPath is a locator for the "library", usually a directory, file, + * or URL containing several footprints. + * + * @param aSymbolName is the name of a footprint to delete from the specified library. + * + * @param aProperties is an associative array that can be used to tell the + * library delete function anything special, because it can take any number of + * additional named tuning arguments that the plugin is known to support. + * The caller continues to own this object (plugin may not delete it), and + * plugins should expect it to be optionally NULL. + * + * @throw IO_ERROR if there is a problem finding the footprint or the library, or deleting it. + */ + virtual void SymbolDelete( const wxString& aLibraryPath, const wxString& aSymbolName, + const PROPERTIES* aProperties = NULL ); + + /** + * Function SymbolLibCreate + * creates a new empty footprint library at @a aLibraryPath empty. It is an + * error to attempt to create an existing library or to attempt to create + * on a "read only" location. + * + * @param aLibraryPath is a locator for the "library", usually a directory, file, + * or URL containing several footprints. + * + * @param aProperties is an associative array that can be used to tell the + * library create function anything special, because it can take any number of + * additional named tuning arguments that the plugin is known to support. + * The caller continues to own this object (plugin may not delete it), and + * plugins should expect it to be optionally NULL. + * + * @throw IO_ERROR if there is a problem finding the library, or creating it. + */ + virtual void SymbolLibCreate( const wxString& aLibraryPath, + const PROPERTIES* aProperties = NULL ); + + /** + * Function SymbolLibDelete + * deletes an existing footprint library and returns true, or if library does not + * exist returns false, or throws an exception if library exists but is read only or + * cannot be deleted for some other reason. + * + * @param aLibraryPath is a locator for the "library", usually a directory + * or file which will contain footprints. + * + * @param aProperties is an associative array that can be used to tell the + * library delete implementation function anything special, because it can + * take any number of additional named tuning arguments that the plugin is + * known to support. The caller continues to own this object (plugin may + * not delete it), and plugins should expect it to be optionally NULL. + * + * @return bool - true if library deleted, false if library did not exist. + * + * @throw IO_ERROR if there is a problem deleting an existing library. + */ + virtual bool SymbolLibDelete( const wxString& aLibraryPath, + const PROPERTIES* aProperties = NULL ); + + /** + * Function IsSymbolLibWritable + * returns true iff the library at @a aLibraryPath is writable. (Often + * system libraries are read only because of where they are installed.) + * + * @param aLibraryPath is a locator for the "library", usually a directory, file, + * or URL containing several footprints. + * + * @throw IO_ERROR if no library at aLibraryPath exists. + */ + virtual bool IsSymbolLibWritable( const wxString& aLibraryPath ); + + /** + * Function SymbolLibOptions + * appends supported SCH_PLUGIN options to @a aListToAppenTo along with + * internationalized descriptions. Options are typically appended so + * that a derived SCH_PLUGIN can call its base class + * function by the same name first, thus inheriting options declared there. + * (Some base class options could pertain to all Symbol*() functions + * in all derived SCH_PLUGINs.) Note that since aListToAppendTo is a PROPERTIES + * object, all options will be unique and last guy wins. + * + * @param aListToAppendTo holds a tuple of + * <dl> + * <dt>option</dt> + * <dd>This eventually is what shows up into the fp-lib-table "options" + * field, possibly combined with others.</dd> + * <dt>internationalized description</dt> + * <dd>The internationalized description is displayed in DIALOG_FP_SCH_PLUGIN_OPTIONS. + * It may be multi-line and be quite explanatory of the option.</dd> + * </dl> + * <br> + * In the future perhaps @a aListToAppendTo evolves to something capable of also + * holding a wxValidator for the cells in said dialog: + * http://forums.wxwidgets.org/viewtopic.php?t=23277&p=104180. + * This would require a 3 column list, and introducing wx GUI knowledge to + * SCH_PLUGIN, which has been avoided to date. + */ + virtual void SymbolLibOptions( PROPERTIES* aListToAppendTo ) const; + + //-----</PUBLIC SCH_PLUGIN API>------------------------------------------------ + + + /* The compiler writes the "zero argument" constructor for a SCH_PLUGIN + automatically if you do not provide one. If you decide you need to + provide a zero argument constructor of your own design, that is allowed. + It must be public, and it is what the SCH_IO_MGR uses. Parameters may be + passed into a SCH_PLUGIN via the PROPERTIES variable for any of the public + API functions which take one. + */ + virtual ~SCH_PLUGIN() { } + + + /** + * Class SCH_PLUGIN_RELEASER + * releases a SCH_PLUGIN in the context of a potential thrown exception, through + * its destructor. + */ + class SCH_PLUGIN_RELEASER + { + SCH_PLUGIN* plugin; + + // private assignment operator so it's illegal + SCH_PLUGIN_RELEASER& operator=( SCH_PLUGIN_RELEASER& aOther ) { return *this; } + + // private copy constructor so it's illegal + SCH_PLUGIN_RELEASER( const SCH_PLUGIN_RELEASER& aOther ) {} + + public: + SCH_PLUGIN_RELEASER( SCH_PLUGIN* aPlugin = NULL ) : + plugin( aPlugin ) + { + } + + ~SCH_PLUGIN_RELEASER() + { + if( plugin ) + release(); + } + + void release() + { + SCH_IO_MGR::ReleasePlugin( plugin ); + plugin = NULL; + } + + void set( SCH_PLUGIN* aPlugin ) + { + if( plugin ) + release(); + plugin = aPlugin; + } + + operator SCH_PLUGIN* () const + { + return plugin; + } + + SCH_PLUGIN* operator -> () const + { + return plugin; + } + }; +}; + +#endif // _SCH_IO_MGR_H_ diff --git a/eeschema/sch_legacy_plugin.cpp b/eeschema/sch_legacy_plugin.cpp new file mode 100644 index 0000000000..5e65879d96 --- /dev/null +++ b/eeschema/sch_legacy_plugin.cpp @@ -0,0 +1,1379 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 CERN + * Copyright (C) 2016 KiCad Developers, see change_log.txt for contributors. + * + * @author Wayne Stambaugh <stambaughw@gmail.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> + +#include <wx/mstream.h> +#include <wx/filename.h> + +#include <drawtxt.h> +#include <richio.h> +#include <core/typeinfo.h> + +#include <general.h> +#include <lib_field.h> +#include <sch_bus_entry.h> +#include <sch_marker.h> +#include <sch_junction.h> +#include <sch_line.h> +#include <sch_no_connect.h> +#include <sch_component.h> +#include <sch_text.h> +#include <sch_sheet.h> +#include <sch_bitmap.h> +#include <sch_legacy_plugin.h> +#include <template_fieldnames.h> +#include <class_sch_screen.h> + + +#define SCH_PARSE_ERROR( text, reader, pos ) \ + THROW_PARSE_ERROR( wxString::FromUTF8( text ), \ + reader.GetSource(), reader.Line(), \ + reader.LineNumber(), pos - reader.Line() ) + + +// Token delimiters. +const char* delims = " \t\r\n"; + + +/** + * Function strCompare + * + * compares \a aString to the string starting at \a aLine and advances the character point to + * the end of \a String and returns the new pointer position in \a aOutput if it is not NULL. + * + * @param aString - A pointer to the string to compare. + * @param aLine - A pointer to string to begin the comparison. + * @param aOutput - A pointer to a string pointer to the end of the comparison if not NULL. + * @return True if \a aString was found starting at \a aLine. Otherwise false. + */ +static bool strCompare( const char* aString, const char* aLine, const char** aOutput = NULL ) +{ + size_t len = strlen( aString ); + bool retv = ( strnicmp( aLine, aString, len ) == 0 ) && isspace( aLine[ len ] ); + + if( retv && aOutput ) + { + const char* tmp = aLine; + + // Move past the end of the token. + tmp += len; + + // Move to the beginning of the next token. + while( *tmp && isspace( *tmp ) ) + tmp++; + + *aOutput = tmp; + } + + return retv; +} + + +/** + * Function parseInt + * + * parses an ASCII integer string with possible leading whitespace into + * an integer and updates the pointer at \a aOutput if it is not NULL, just + * like "man strtol()". + * + * @param aReader - The line reader used to generate exception throw information. + * @param aLine - A pointer the current position in a string. + * @param aOutput - The pointer to a string pointer to copy the string pointer position when + * the parsing is complete. + * @return A valid integer value. + * @throws An #IO_ERROR on an unexpected end of line. + * @throws A #PARSE_ERROR if the parsed token is not a valid integer. + */ +static int parseInt( FILE_LINE_READER& aReader, const char* aLine, const char** aOutput = NULL ) +{ + if( !*aLine ) + THROW_IO_ERROR( _( "unexpected end of line" ) ); + + // Clear errno before calling strtol() in case some other crt call set it. + errno = 0; + + long retv = strtol( aLine, (char**) aOutput, 10 ); + + // Make sure no error occurred when calling strtol(). + if( errno == ERANGE ) + SCH_PARSE_ERROR( "invalid integer value", aReader, aLine ); + + // strtol does not strip off whitespace before the next token. + if( aOutput ) + { + const char* next = *aOutput; + + while( *next && isspace( *next ) ) + next++; + + *aOutput = next; + } + + return (int) retv; +} + + +/** + * Function parseHex + * + * parses an ASCII hex integer string with possible leading whitespace into + * a long integer and updates the pointer at \a aOutput if it is not NULL, just + * like "man strtol". + * + * @param aReader - The line reader used to generate exception throw information. + * @param aLine - A pointer the current position in a string. + * @param aOutput - The pointer to a string pointer to copy the string pointer position when + * the parsing is complete. + * @return A valid integer value. + * @throws An #IO_ERROR on an unexpected end of line. + * @throws A #PARSE_ERROR if the parsed token is not a valid integer. + */ +static unsigned long parseHex( FILE_LINE_READER& aReader, const char* aLine, + const char** aOutput = NULL ) +{ + if( !*aLine ) + THROW_IO_ERROR( _( "unexpected end of line" ) ); + + unsigned long retv; + + // Clear errno before calling strtoul() in case some other crt call set it. + errno = 0; + retv = strtoul( aLine, (char**) aOutput, 16 ); + + // Make sure no error occurred when calling strtoul(). + if( errno == ERANGE ) + SCH_PARSE_ERROR( "invalid hexadecimal number", aReader, aLine ); + + // Strip off whitespace before the next token. + if( aOutput ) + { + // const char* next = aLine + strlen( token ); + + const char* next = *aOutput; + + while( *next && isspace( *next ) ) + next++; + + *aOutput = next; + } + + return retv; +} + + +/** + * Function parseDouble + * + * parses an ASCII point string with possible leading whitespace into a double precision + * floating point number and updates the pointer at \a aOutput if it is not NULL, just + * like "man strtod". + * + * @param aReader - The line reader used to generate exception throw information. + * @param aLine - A pointer the current position in a string. + * @param aOutput - The pointer to a string pointer to copy the string pointer position when + * the parsing is complete. + * @return A valid double value. + * @throws An #IO_ERROR on an unexpected end of line. + * @throws A #PARSE_ERROR if the parsed token is not a valid integer. + */ +static double parseDouble( FILE_LINE_READER& aReader, const char* aLine, + const char** aOutput = NULL ) +{ + if( !*aLine ) + THROW_IO_ERROR( _( "unexpected end of line" ) ); + + // Clear errno before calling strtod() in case some other crt call set it. + errno = 0; + + double retv = strtod( aLine, (char**) aOutput ); + + // Make sure no error occurred when calling strtod(). + if( errno == ERANGE ) + SCH_PARSE_ERROR( "invalid floating point number", aReader, aLine ); + + // strtod does not strip off whitespace before the next token. + if( aOutput ) + { + const char* next = *aOutput; + + while( *next && isspace( *next ) ) + next++; + + *aOutput = next; + } + + return retv; +} + + +/** + * Function parseChar + * + * parses a single ASCII character and updates the pointer at \a aOutput if it is not NULL. + * + * @param aReader - The line reader used to generate exception throw information. + * @param aCurrentToken - A pointer the current position in a string. + * @param aNextToken - The pointer to a string pointer to copy the string pointer position when + * the parsing is complete. + * @return A valid ASCII character. + * @throws An #IO_ERROR on an unexpected end of line. + * @throws A #PARSE_ERROR if the parsed token is not a a single character token. + */ +static char parseChar( FILE_LINE_READER& aReader, const char* aCurrentToken, + const char** aNextToken = NULL ) +{ + while( *aCurrentToken && isspace( *aCurrentToken ) ) + aCurrentToken++; + + if( !*aCurrentToken ) + SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken ); + + if( *( aCurrentToken + 1 ) != ' ' ) + SCH_PARSE_ERROR( _( "expected single character token" ), aReader, aCurrentToken ); + + if( aNextToken ) + { + const char* next = aCurrentToken + 2; + + while( *next && isspace( *next ) ) + next++; + + *aNextToken = next; + } + + return *aCurrentToken; +} + + +/** + * Function parseUnquotedString. + * + * parses an unquoted utf8 string and updates the pointer at \a aOutput if it is not NULL. + * + * The parsed string must be a continuous string with no white space. + * + * @param aString - A reference to the parsed string. + * @param aReader - The line reader used to generate exception throw information. + * @param aCurrentToken - A pointer the current position in a string. + * @param aNextToken - The pointer to a string pointer to copy the string pointer position when + * the parsing is complete. + * @param aCanBeEmpty - True if the parsed string is optional. False if it is mandatory. + * @throws An #IO_ERROR on an unexpected end of line. + * @throws A #PARSE_ERROR if the \a aCanBeEmpty is false and no string was parsed. + */ +static void parseUnquotedString( wxString& aString, FILE_LINE_READER& aReader, + const char* aCurrentToken, const char** aNextToken = NULL, + bool aCanBeEmpty = false ) +{ + if( !*aCurrentToken ) + THROW_IO_ERROR( _( "unexpected end of line" ) ); + + const char* tmp = aCurrentToken; + + while( *tmp && isspace( *tmp ) ) + tmp++; + + if( !*tmp ) + { + if( aCanBeEmpty ) + return; + else + THROW_IO_ERROR( _( "unexpected end of line" ) ); + } + + std::string utf8; + + while( *tmp && !isspace( *tmp ) ) + utf8 += *tmp++; + + aString = FROM_UTF8( utf8.c_str() ); + + if( aString.IsEmpty() && !aCanBeEmpty ) + SCH_PARSE_ERROR( _( "expected unquoted string" ), aReader, aCurrentToken ); + + if( aNextToken ) + { + const char* next = tmp; + + while( *next && isspace( *next ) ) + next++; + + *aNextToken = next; + } +} + + +/** + * Function parseQuotedString. + * + * parses an quoted ASCII utf8 and updates the pointer at \a aOutput if it is not NULL. + * + * The parsed string must be contained within a single line. There are no multi-line + * quoted strings in the legacy schematic file format. + * + * @param aString - A reference to the parsed string. + * @param aReader - The line reader used to generate exception throw information. + * @param aCurrentToken - A pointer the current position in a string. + * @param aNextToken - The pointer to a string pointer to copy the string pointer position when + * the parsing is complete. + * @param aCanBeEmpty - True if the parsed string is optional. False if it is mandatory. + * @throws An #IO_ERROR on an unexpected end of line. + * @throws A #PARSE_ERROR if the \a aCanBeEmpty is false and no string was parsed. + */ +static void parseQuotedString( wxString& aString, FILE_LINE_READER& aReader, + const char* aCurrentToken, const char** aNextToken = NULL, + bool aCanBeEmpty = false ) +{ + if( !*aCurrentToken ) + { + if( aCanBeEmpty ) + return; + else + THROW_IO_ERROR( _( "unexpected end of line" ) ); + } + + const char* tmp = aCurrentToken; + + while( *tmp && isspace( *tmp ) ) + tmp++; + + if( !*tmp ) + { + if( aCanBeEmpty ) + return; + else + THROW_IO_ERROR( _( "unexpected end of line" ) ); + } + + // Verify opening quote. + if( *tmp != '"' ) + THROW_IO_ERROR( _( "expecting opening quote" ) ); + + tmp++; + + std::string utf8; // utf8 without escapes and quotes. + + // Fetch everything up to closing quote. + while( *tmp ) + { + if( *tmp == '\\' ) + { + tmp++; + + if( !*tmp ) + THROW_IO_ERROR( _( "unexpected end of line" ) ); + + // Do not copy the escape byte if it is followed by \ or " + if( *tmp != '"' && *tmp != '\\' ) + utf8 += '\\'; + + utf8 += *tmp; + } + else if( *tmp == '"' ) // Closing double quote. + { + break; + } + else + { + utf8 += *tmp; + } + + tmp++; + } + + aString = FROM_UTF8( utf8.c_str() ); + + if( aString.IsEmpty() && !aCanBeEmpty ) + SCH_PARSE_ERROR( _( "expected quoted string" ), aReader, aCurrentToken ); + + if( *tmp && *tmp != '"' ) + SCH_PARSE_ERROR( _( "no closing quote for string found" ), aReader, tmp ); + + // Move past the closing quote. + tmp++; + + if( aNextToken ) + { + const char* next = tmp; + + while( *next && *next == ' ' ) + next++; + + *aNextToken = next; + } +} + + +SCH_LEGACY_PLUGIN::SCH_LEGACY_PLUGIN() +{ + init( NULL ); +} + + +void SCH_LEGACY_PLUGIN::init( KIWAY* aKiway, const PROPERTIES* aProperties ) +{ + m_version = 0; + m_rootSheet = NULL; + m_props = aProperties; + m_kiway = aKiway; +} + + +SCH_SHEET* SCH_LEGACY_PLUGIN::Load( const wxString& aFileName, KIWAY* aKiway, + SCH_SHEET* aAppendToMe, const PROPERTIES* aProperties ) +{ + wxASSERT( !aFileName || aKiway != NULL ); + + LOCALE_IO toggle; // toggles on, then off, the C locale. + SCH_SHEET* sheet; + + wxFileName fn = aFileName; + + // Unfortunately child sheet file names the legacy schematic file format are not fully + // qualified and are always appended to the project path. The aFileName attribute must + // always be an absolute path so the project path can be used for load child sheet files. + wxASSERT( fn.IsAbsolute() ); + + m_path = fn.GetPath(); + + init( aKiway, aProperties ); + + if( aAppendToMe == NULL ) + { + // Clean up any allocated memory if an exception occurs loading the schematic. + std::auto_ptr< SCH_SHEET > newSheet( new SCH_SHEET ); + newSheet->SetFileName( aFileName ); + m_rootSheet = newSheet.get(); + loadHierarchy( newSheet.get() ); + + // If we got here, the schematic loaded successfully. + sheet = newSheet.release(); + } + else + { + m_rootSheet = aAppendToMe->GetRootSheet(); + wxASSERT( m_rootSheet != NULL ); + sheet = aAppendToMe; + loadHierarchy( sheet ); + } + + return sheet; +} + + +// Everything below this comment is recursive. Modify with care. + +void SCH_LEGACY_PLUGIN::loadHierarchy( SCH_SHEET* aSheet ) +{ + SCH_SCREEN* screen = NULL; + + if( !aSheet->GetScreen() ) + { + m_rootSheet->SearchHierarchy( aSheet->GetFileName(), &screen ); + + if( screen ) + { + aSheet->SetScreen( screen ); + + // Do not need to load the sub-sheets - this has already been done. + } + else + { + aSheet->SetScreen( new SCH_SCREEN( m_kiway ) ); + + wxFileName fileName = aSheet->GetFileName(); + + if( !fileName.IsAbsolute() ) + fileName.SetPath( m_path ); + + aSheet->GetScreen()->SetFileName( fileName.GetFullPath() ); + loadFile( fileName.GetFullPath(), aSheet->GetScreen() ); + + EDA_ITEM* item = aSheet->GetScreen()->GetDrawItems(); + + while( item ) + { + if( item->Type() == SCH_SHEET_T ) + { + SCH_SHEET* sheet = (SCH_SHEET*) item; + + // Set the parent to aSheet. This effectively creates a method to find + // the root sheet from any sheet so a pointer to the root sheet does not + // need to be stored globally. Note: this is not the same as a hierarchy. + // Complex hierarchies can have multiple copies of a sheet. This only + // provides a simple tree to find the root sheet. + sheet->SetParent( aSheet ); + + // Recursion starts here. + loadHierarchy( sheet ); + } + + item = item->Next(); + } + } + } +} + + +void SCH_LEGACY_PLUGIN::loadFile( const wxString& aFileName, SCH_SCREEN* aScreen ) +{ + FILE_LINE_READER reader( aFileName ); + + loadHeader( reader, aScreen ); + + while( reader.ReadLine() ) + { + char* line = reader.Line(); + + while( *line && *line == ' ' ) + line++; + + // Either an object will be loaded properly or the file load will fail and raise + // an exception. + if( strCompare( "$Descr", line ) ) + loadPageSettings( reader, aScreen ); + else if( strCompare( "$Comp", line ) ) + aScreen->Append( loadComponent( reader ) ); + else if( strCompare( "$Sheet", line ) ) + aScreen->Append( loadSheet( reader ) ); + else if( strCompare( "$Bitmap", line ) ) + aScreen->Append( loadBitmap( reader ) ); + else if( strCompare( "Connection", line ) ) + aScreen->Append( loadJunction( reader ) ); + else if( strCompare( "NoConn", line ) ) + aScreen->Append( loadNoConnect( reader ) ); + else if( strCompare( "Wire", line ) ) + aScreen->Append( loadWire( reader ) ); + else if( strCompare( "Entry", line ) ) + aScreen->Append( loadBusEntry( reader ) ); + else if( strCompare( "Text", line ) ) + aScreen->Append( loadText( reader ) ); + else if( strCompare( "$EndSCHEMATC", line ) ) + return; + } + + + THROW_IO_ERROR( "'$EndSCHEMATC' not found" ); +} + + +void SCH_LEGACY_PLUGIN::loadHeader( FILE_LINE_READER& aReader, SCH_SCREEN* aScreen ) +{ + const char* line = aReader.ReadLine(); + + if( !strCompare( "Eeschema Schematic File Version", line, &line ) ) + { + m_error.Printf( _( "'%s' does not appear to be an Eeschema file" ), + GetChars( aScreen->GetFileName() ) ); + THROW_IO_ERROR( m_error ); + } + + // get the file version here. + m_version = parseInt( aReader, line, &line ); + + // The next lines are the lib list section, and are mainly comments, like: + // LIBS:power + // the lib list is not used, but is in schematic file just in case. + // It is usually not empty, but we accept empty list. + // If empty, there is a legacy section, not used + // EELAYER i j + // and the last line is + // EELAYER END + // Skip all lines until the end of header "EELAYER END" is found + while( aReader.ReadLine() ) + { + line = aReader.Line(); + + while( *line == ' ' ) + line++; + + if( strCompare( "EELAYER END", line ) ) + return; + } + + THROW_IO_ERROR( _( "Missing 'EELAYER END'" ) ); +} + + +void SCH_LEGACY_PLUGIN::loadPageSettings( FILE_LINE_READER& aReader, SCH_SCREEN* aScreen ) +{ + wxASSERT( aScreen != NULL ); + + wxString buf; + const char* line = aReader.Line(); + + PAGE_INFO pageInfo; + TITLE_BLOCK tb; + + wxCHECK_RET( strCompare( "$Descr", line, &line ), "Invalid sheet description" ); + + parseUnquotedString( buf, aReader, line, &line ); + + if( !pageInfo.SetType( buf ) ) + SCH_PARSE_ERROR( _( "invalid page size" ), aReader, line ); + + if( buf == PAGE_INFO::Custom ) + { + pageInfo.SetWidthMils( parseInt( aReader, line, &line ) ); + pageInfo.SetHeightMils( parseInt( aReader, line, &line ) ); + } + else + { + wxString orientation; + + // Non custom size, set portrait if its present. Can be empty string which defaults + // to landscape. + parseUnquotedString( orientation, aReader, line, &line, true ); + + if( orientation == "portrait" ) + pageInfo.SetPortrait( true ); + } + + aScreen->SetPageSettings( pageInfo ); + + while( line != NULL ) + { + buf.clear(); + + if( !aReader.ReadLine() ) + SCH_PARSE_ERROR( _( "unexpected end of file" ), aReader, line ); + + line = aReader.Line(); + + if( strCompare( "Sheet", line, &line ) ) + { + aScreen->m_ScreenNumber = parseInt( aReader, line, &line ); + aScreen->m_NumberOfScreens = parseInt( aReader, line, &line ); + } + else if( strCompare( "Title", line, &line ) ) + { + parseQuotedString( buf, aReader, line, &line, true ); + tb.SetTitle( buf ); + } + else if( strCompare( "Date", line, &line ) ) + { + parseQuotedString( buf, aReader, line, &line, true ); + tb.SetDate( buf ); + } + else if( strCompare( "Rev", line, &line ) ) + { + parseQuotedString( buf, aReader, line, &line, true ); + tb.SetRevision( buf ); + } + else if( strCompare( "Comp", line, &line ) ) + { + parseQuotedString( buf, aReader, line, &line, true ); + tb.SetCompany( buf ); + } + else if( strCompare( "Comment1", line, &line ) ) + { + parseQuotedString( buf, aReader, line, &line, true ); + tb.SetComment1( buf ); + } + else if( strCompare( "Comment2", line, &line ) ) + { + parseQuotedString( buf, aReader, line, &line, true ); + tb.SetComment2( buf ); + } + else if( strCompare( "Comment3", line, &line ) ) + { + parseQuotedString( buf, aReader, line, &line, true ); + tb.SetComment3( buf ); + } + else if( strCompare( "Comment4", line, &line ) ) + { + parseQuotedString( buf, aReader, line, &line, true ); + tb.SetComment4( buf ); + } + else if( strCompare( "$EndDescr", line ) ) + { + aScreen->SetTitleBlock( tb ); + return; + } + } + + SCH_PARSE_ERROR( _( "missing 'EndDescr'" ), aReader, line ); +} + + +SCH_SHEET* SCH_LEGACY_PLUGIN::loadSheet( FILE_LINE_READER& aReader ) +{ + std::auto_ptr< SCH_SHEET > sheet( new SCH_SHEET() ); + + sheet->SetTimeStamp( GetNewTimeStamp() ); + + const char* line = aReader.ReadLine(); + + while( line != NULL ) + { + if( strCompare( "S", line, &line ) ) // Sheet dimensions. + { + wxPoint position; + + position.x = parseInt( aReader, line, &line ); + position.y = parseInt( aReader, line, &line ); + sheet->SetPosition( position ); + + wxSize size; + + size.SetWidth( parseInt( aReader, line, &line ) ); + size.SetHeight( parseInt( aReader, line, &line ) ); + sheet->SetSize( size ); + } + else if( strCompare( "U", line, &line ) ) // Sheet time stamp. + { + sheet->SetTimeStamp( parseHex( aReader, line ) ); + } + else if( *line == 'F' ) // Sheet field. + { + line++; + + wxString text; + int size; + int fieldId = parseInt( aReader, line, &line ); + + if( fieldId == 0 || fieldId == 1 ) // Sheet name and file name. + { + parseQuotedString( text, aReader, line, &line ); + size = parseInt( aReader, line, &line ); + + if( fieldId == 0 ) + { + sheet->SetName( text ); + sheet->SetSheetNameSize( size ); + } + else + { + sheet->SetFileName( text ); + sheet->SetFileNameSize( size ); + } + } + else // Sheet pin. + { + std::auto_ptr< SCH_SHEET_PIN > sheetPin( new SCH_SHEET_PIN( sheet.get() ) ); + + sheetPin->SetNumber( fieldId ); + + // Can be empty fields. + parseQuotedString( text, aReader, line, &line, true ); + + sheetPin->SetText( text ); + + if( line == NULL ) + THROW_IO_ERROR( _( "unexpected end of line" ) ); + + switch( parseChar( aReader, line, &line ) ) + { + case 'I': + sheetPin->SetShape( NET_INPUT ); + break; + + case 'O': + sheetPin->SetShape( NET_OUTPUT ); + break; + + case 'B': + sheetPin->SetShape( NET_BIDI ); + break; + + case 'T': + sheetPin->SetShape( NET_TRISTATE ); + break; + + case 'U': + sheetPin->SetShape( NET_UNSPECIFIED ); + break; + default: + SCH_PARSE_ERROR( _( "invalid sheet pin type" ), aReader, line ); + } + + switch( parseChar( aReader, line, &line ) ) + { + case 'R': /* pin on right side */ + sheetPin->SetEdge( SCH_SHEET_PIN::SHEET_RIGHT_SIDE ); + break; + + case 'T': /* pin on top side */ + sheetPin->SetEdge( SCH_SHEET_PIN::SHEET_TOP_SIDE ); + break; + + case 'B': /* pin on bottom side */ + sheetPin->SetEdge( SCH_SHEET_PIN::SHEET_BOTTOM_SIDE ); + break; + + case 'L': /* pin on left side */ + sheetPin->SetEdge( SCH_SHEET_PIN::SHEET_LEFT_SIDE ); + break; + default: + SCH_PARSE_ERROR( _( "invalid sheet pin side" ), aReader, line ); + } + + wxPoint position; + + position.x = parseInt( aReader, line, &line ); + position.y = parseInt( aReader, line, &line ); + sheetPin->SetPosition( position ); + + size = parseInt( aReader, line, &line ); + + sheetPin->SetSize( wxSize( size, size ) ); + + sheet->AddPin( sheetPin.release() ); + } + } + else if( strCompare( "$EndSheet", line ) ) + return sheet.release(); + + line = aReader.ReadLine(); + } + + SCH_PARSE_ERROR( _( "missing '$EndSheet`" ), aReader, line ); + + return NULL; // Prevents compiler warning. Should never get here. +} + + +SCH_BITMAP* SCH_LEGACY_PLUGIN::loadBitmap( FILE_LINE_READER& aReader ) +{ + std::auto_ptr< SCH_BITMAP > bitmap( new SCH_BITMAP ); + + const char* line = aReader.Line(); + + wxASSERT( strCompare( "$Bitmap", line, &line ) ); + + line = aReader.ReadLine(); + + while( line != NULL ) + { + if( strCompare( "Pos", line, &line ) ) + { + wxPoint position; + + position.x = parseInt( aReader, line, &line ); + position.y = parseInt( aReader, line, &line ); + bitmap->SetPosition( position ); + } + else if( strCompare( "Scale", line, &line ) ) + { + /// @todo Make m_scale private and add accessors. + bitmap->m_Image->m_Scale = parseDouble( aReader, line, &line ); + } + else if( strCompare( "Data", line, &line ) ) + { + wxMemoryOutputStream stream; + + while( line ) + { + if( !aReader.ReadLine() ) + SCH_PARSE_ERROR( _( "Unexpected end of file" ), aReader, line ); + + line = aReader.Line(); + + if( strCompare( "EndData", line ) ) + { + // all the PNG date is read. + // We expect here m_image and m_bitmap are void + wxImage* image = new wxImage(); + wxMemoryInputStream istream( stream ); + image->LoadFile( istream, wxBITMAP_TYPE_PNG ); + bitmap->m_Image->SetImage( image ); + bitmap->m_Image->SetBitmap( new wxBitmap( *image ) ); + break; + } + + // Read PNG data, stored in hexadecimal, + // each byte = 2 hexadecimal digits and a space between 2 bytes + // and put it in memory stream buffer + int len = strlen( line ); + + for( ; len > 0 && !isspace( *line ); len -= 3, line += 3 ) + { + int value = 0; + + if( sscanf( line, "%X", &value ) == 1 ) + stream.PutC( (char) value ); + else + THROW_IO_ERROR( "invalid PNG data" ); + } + } + + if( line == NULL ) + THROW_IO_ERROR( _( "unexpected end of file" ) ); + } + else if( strCompare( "$EndBitmap", line ) ) + return bitmap.release(); + + line = aReader.ReadLine(); + } + + THROW_IO_ERROR( _( "unexpected end of file" ) ); +} + + +SCH_JUNCTION* SCH_LEGACY_PLUGIN::loadJunction( FILE_LINE_READER& aReader ) +{ + std::auto_ptr< SCH_JUNCTION > junction( new SCH_JUNCTION ); + + const char* line = aReader.Line(); + + wxASSERT( strCompare( "Connection", line, &line ) ); + + wxString name; + + parseUnquotedString( name, aReader, line, &line ); + + wxPoint position; + + position.x = parseInt( aReader, line, &line ); + position.y = parseInt( aReader, line, &line ); + junction->SetPosition( position ); + + return junction.release(); +} + + +SCH_NO_CONNECT* SCH_LEGACY_PLUGIN::loadNoConnect( FILE_LINE_READER& aReader ) +{ + std::auto_ptr< SCH_NO_CONNECT > no_connect( new SCH_NO_CONNECT ); + + const char* line = aReader.Line(); + + wxASSERT( strCompare( "NoConn", line, &line ) ); + + wxString name; + + parseUnquotedString( name, aReader, line, &line ); + + wxPoint position; + + position.x = parseInt( aReader, line, &line ); + position.y = parseInt( aReader, line, &line ); + no_connect->SetPosition( position ); + + return no_connect.release(); +} + + +SCH_LINE* SCH_LEGACY_PLUGIN::loadWire( FILE_LINE_READER& aReader ) +{ + std::auto_ptr< SCH_LINE > wire( new SCH_LINE ); + + const char* line = aReader.Line(); + + wxASSERT( strCompare( "Wire", line, &line ) ); + + if( strCompare( "Wire", line, &line ) ) + wire->SetLayer( LAYER_WIRE ); + else if( strCompare( "Bus", line, &line ) ) + wire->SetLayer( LAYER_BUS ); + else if( strCompare( "Notes", line, &line ) ) + wire->SetLayer( LAYER_NOTES ); + else + SCH_PARSE_ERROR( "invalid line type", aReader, line ); + + if( !strCompare( "Line", line, &line ) ) + SCH_PARSE_ERROR( "invalid wire definition", aReader, line ); + + line = aReader.ReadLine(); + + wxPoint begin, end; + + begin.x = parseInt( aReader, line, &line ); + begin.y = parseInt( aReader, line, &line ); + end.x = parseInt( aReader, line, &line ); + end.y = parseInt( aReader, line, &line ); + + wire->SetStartPoint( begin ); + wire->SetEndPoint( end ); + + return wire.release(); +} + + +SCH_BUS_ENTRY_BASE* SCH_LEGACY_PLUGIN::loadBusEntry( FILE_LINE_READER& aReader ) +{ + const char* line = aReader.Line(); + + wxASSERT( strCompare( "Entry", line, &line ) ); + + std::auto_ptr< SCH_BUS_ENTRY_BASE > busEntry; + + if( strCompare( "Wire", line, &line ) ) + { + busEntry.reset( new SCH_BUS_WIRE_ENTRY ); + + if( !strCompare( "Line", line, &line ) ) + SCH_PARSE_ERROR( "invalid bus entry definition expected 'Line'", aReader, line ); + } + else if( strCompare( "Bus", line, &line ) ) + { + busEntry.reset( new SCH_BUS_BUS_ENTRY ); + + if( !strCompare( "Bus", line, &line ) ) + SCH_PARSE_ERROR( "invalid bus entry definition expected 'Bus'", aReader, line ); + } + else + SCH_PARSE_ERROR( "invalid bus entry type", aReader, line ); + + line = aReader.ReadLine(); + + wxPoint pos; + wxSize size; + + pos.x = parseInt( aReader, line, &line ); + pos.y = parseInt( aReader, line, &line ); + size.x = parseInt( aReader, line, &line ); + size.y = parseInt( aReader, line, &line ); + + size.x -= pos.x; + size.y -= pos.y; + + busEntry->SetPosition( pos ); + busEntry->SetSize( size ); + + return busEntry.release(); +} + + +SCH_TEXT* SCH_LEGACY_PLUGIN::loadText( FILE_LINE_READER& aReader ) +{ + const char* line = aReader.Line(); + + wxASSERT( strCompare( "Text", line, &line ) ); + + std::auto_ptr< SCH_TEXT> text; + + if( strCompare( "Notes", line, &line ) ) + text.reset( new SCH_TEXT ); + else if( strCompare( "Label", line, &line ) ) + text.reset( new SCH_LABEL ); + else if( strCompare( "GLabel", line, &line ) ) + text.reset( new SCH_GLOBALLABEL ); + else if( strCompare( "HLabel", line, &line ) ) + text.reset( new SCH_HIERLABEL ); + else + SCH_PARSE_ERROR( "unknown Text type", aReader, line ); + + // Parse the parameters common to all text objects. + wxPoint position; + + position.x = parseInt( aReader, line, &line ); + position.y = parseInt( aReader, line, &line ); + text->SetPosition( position ); + text->SetOrientation( parseInt( aReader, line, &line ) ); + + int size = parseInt( aReader, line, &line ); + + text->SetSize( wxSize( size, size ) ); + + // Parse the global and hierarchical label type. + if( text->Type() == SCH_HIERARCHICAL_LABEL_T || text->Type() == SCH_GLOBAL_LABEL_T ) + { + if( strCompare( SheetLabelType[NET_INPUT], line, &line ) ) + text->SetShape( NET_INPUT ); + else if( strCompare( SheetLabelType[NET_OUTPUT], line, &line ) ) + text->SetShape( NET_OUTPUT ); + else if( strCompare( SheetLabelType[NET_BIDI], line, &line ) ) + text->SetShape( NET_BIDI ); + else if( strCompare( SheetLabelType[NET_TRISTATE], line, &line ) ) + text->SetShape( NET_TRISTATE ); + else if( strCompare( SheetLabelType[NET_UNSPECIFIED], line, &line ) ) + text->SetShape( NET_UNSPECIFIED ); + else + SCH_PARSE_ERROR( _( "invalid label type" ), aReader, line ); + } + + // Parse the italics indicator. + if( strCompare( "Italic", line, &line ) ) + text->SetItalic( true ); + else if( !strCompare( "~", line, &line ) ) + SCH_PARSE_ERROR( _( "expected 'Italics' or '~'" ), aReader, line ); + + int thickness = parseInt( aReader, line ); + + text->SetBold( thickness != 0 ); + text->SetThickness( thickness != 0 ? GetPenSizeForBold( size ) : 0 ); + + // Read the text string for the text. + char* tmp = aReader.ReadLine(); + + tmp = strtok( tmp, "\r\n" ); + wxString val = FROM_UTF8( tmp ); + + for( ; ; ) + { + int i = val.find( wxT( "\\n" ) ); + + if( i == wxNOT_FOUND ) + break; + + val.erase( i, 2 ); + val.insert( i, wxT( "\n" ) ); + } + + text->SetText( val ); + + return text.release(); +} + + +SCH_COMPONENT* SCH_LEGACY_PLUGIN::loadComponent( FILE_LINE_READER& aReader ) +{ + const char* line = aReader.Line(); + + wxASSERT( strCompare( "$Comp", line, &line ) ); + + std::auto_ptr< SCH_COMPONENT > component( new SCH_COMPONENT() ); + + line = aReader.ReadLine(); + + while( line != NULL ) + { + if( strCompare( "L", line, &line ) ) + { + wxString libName; + + parseUnquotedString( libName, aReader, line, &line ); + libName.Replace( "~", " " ); + component->SetPartName( libName ); + + wxString refDesignator; + + parseUnquotedString( refDesignator, aReader, line, &line ); + refDesignator.Replace( "~", " " ); + + wxString prefix = refDesignator; + + while( prefix.Length() ) + { + if( ( prefix.Last() < '0' || prefix.Last() > '9') && prefix.Last() != '?' ) + break; + + prefix.RemoveLast(); + } + + // Avoid a prefix containing trailing/leading spaces + prefix.Trim( true ); + prefix.Trim( false ); + + if( prefix.IsEmpty() ) + component->SetPrefix( wxString( "U" ) ); + else + component->SetPrefix( prefix ); + } + else if( strCompare( "U", line, &line ) ) + { + component->SetUnit( parseInt( aReader, line, &line ) ); + component->SetConvert( parseInt( aReader, line, &line ) ); + component->SetTimeStamp( parseHex( aReader, line, &line ) ); + } + else if( strCompare( "P", line, &line ) ) + { + wxPoint pos; + + pos.x = parseInt( aReader, line, &line ); + pos.y = parseInt( aReader, line, &line ); + component->SetPosition( pos ); + } + else if( strCompare( "AR", line, &line ) ) + { + const char* strCompare = "Path="; + int len = strlen( strCompare ); + + if( strnicmp( strCompare, line, len ) != 0 ) + SCH_PARSE_ERROR( "missing 'Path=' token", aReader, line ); + + line += len; + wxString path, reference, unit; + + parseQuotedString( path, aReader, line, &line ); + + strCompare = "Ref="; + len = strlen( strCompare ); + + if( strnicmp( strCompare, line, len ) != 0 ) + SCH_PARSE_ERROR( "missing 'Ref=' token", aReader, line ); + + line+= len; + parseQuotedString( reference, aReader, line, &line ); + + strCompare = "Part="; + len = strlen( strCompare ); + + if( strnicmp( strCompare, line, len ) != 0 ) + SCH_PARSE_ERROR( "missing 'Part=' token", aReader, line ); + + line+= len; + parseQuotedString( unit, aReader, line, &line ); + + long tmp; + + if( !unit.ToLong( &tmp, 10 ) ) + SCH_PARSE_ERROR( "expected integer value", aReader, line ); + + if( tmp < 0 || tmp > 26 ) + SCH_PARSE_ERROR( "unit value out of range", aReader, line ); + + component->AddHierarchicalReference( path, reference, (int)tmp ); + component->GetField( REFERENCE )->SetText( reference ); + + } + else if( strCompare( "F", line, &line ) ) + { + int index = parseInt( aReader, line, &line ); + + wxString text, name, textAttrs; + + parseQuotedString( text, aReader, line, &line, true ); + + char orientation = parseChar( aReader, line, &line ); + wxPoint pos; + pos.x = parseInt( aReader, line, &line ); + pos.y = parseInt( aReader, line, &line ); + int size = parseInt( aReader, line, &line ); + int attributes = parseHex( aReader, line, &line ); + char hjustify = parseChar( aReader, line, &line ); + + parseUnquotedString( textAttrs, aReader, line, &line ); + + // The name of the field is optional. + parseQuotedString( name, aReader, line, &line, true ); + + if( name.IsEmpty() ) + name = TEMPLATE_FIELDNAME::GetDefaultFieldName( index ); + + if( index >= component->GetFieldCount() ) + { + // The first MANDATOR_FIELDS _must_ be constructed within + // the SCH_COMPONENT constructor. This assert is simply here + // to guard against a change in that constructor. + wxASSERT( component->GetFieldCount() >= MANDATORY_FIELDS ); + + // Ignore the _supplied_ fieldNdx. It is not important anymore + // if within the user defined fields region (i.e. >= MANDATORY_FIELDS). + // We freely renumber the index to fit the next available field slot. + index = component->GetFieldCount(); // new has this index after insertion + + SCH_FIELD field( wxPoint( 0, 0 ), -1, component.get(), name ); + component->AddField( field ); + } + else + { + component->GetField( index )->SetName( name ); + } + + component->GetField( index )->SetText( text ); + + if( orientation == 'H' ) + component->GetField( index )->SetOrientation( TEXT_ORIENT_HORIZ ); + else if( orientation == 'V' ) + component->GetField( index )->SetOrientation( TEXT_ORIENT_VERT ); + else + SCH_PARSE_ERROR( _( "component field orientation must be H or V" ), aReader, line ); + + if( hjustify == 'L' ) + component->GetField( index )->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); + else if( hjustify == 'R' ) + component->GetField( index )->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); + else if( hjustify != 'C' ) + SCH_PARSE_ERROR( _( "component field text horizontal justification must be " + "L, R, or C" ), aReader, line ); + + component->GetField( index )->SetTextPosition( pos ); + component->GetField( index )->SetAttributes( attributes ); + component->GetField( index )->SetSize( wxSize( size, size ) ); + + // We are guaranteed to have a least one character here for older file formats + // otherwise an exception would have been raised.. + if( textAttrs[0] == 'T' ) + component->GetField( index )->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); + else if( textAttrs[0] == 'B' ) + component->GetField( index )->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); + else if( textAttrs[0] != 'C' ) + SCH_PARSE_ERROR( _( "component field text vertical justification must be " + "B, T, or C" ), aReader, line ); + + // Newer file formats include the bold and italics text attribute. + if( textAttrs.Length() != 3 ) + SCH_PARSE_ERROR( _( "component field text attributes must be 3 characters wide" ), + aReader, line ); + + if( textAttrs[1] == 'I' ) + component->GetField( index )->SetItalic( true ); + else if( textAttrs[1] != 'N' ) + SCH_PARSE_ERROR( _( "component field text italics indicator must be I or N" ), + aReader, line ); + + if( textAttrs[2] == 'B' ) + component->GetField( index )->SetBold( true ); + else if( textAttrs[2] != 'N' ) + SCH_PARSE_ERROR( _( "component field text bold indicator must be B or N" ), + aReader, line ); + } + else if( strCompare( "$EndComp", line ) ) + return component.release(); + else + { + // There are two lines that begin with a tab or spaces that includes a line with the + // redundant position information and the transform matrix settings. + + // Parse the redundant position information just the same to check for formatting + // errors. + parseInt( aReader, line, &line ); // Always 1. + parseInt( aReader, line, &line ); // The X coordinate. + parseInt( aReader, line, &line ); // The Y coordinate. + + line = aReader.ReadLine(); + + TRANSFORM transform; + + transform.x1 = parseInt( aReader, line, &line ); + + if( transform.x1 < -1 || transform.x1 > 1 ) + SCH_PARSE_ERROR( _( "invalid component X1 transform value" ), aReader, line ); + + transform.y1 = parseInt( aReader, line, &line ); + + if( transform.y1 < -1 || transform.y1 > 1 ) + SCH_PARSE_ERROR( _( "invalid component Y1 transform value" ), aReader, line ); + + transform.x2 = parseInt( aReader, line, &line ); + + if( transform.x2 < -1 || transform.x2 > 1 ) + SCH_PARSE_ERROR( _( "invalid component X2 transform value" ), aReader, line ); + + transform.y2 = parseInt( aReader, line, &line ); + + if( transform.y2 < -1 || transform.y2 > 1 ) + SCH_PARSE_ERROR( _( "invalid component Y2 transform value" ), aReader, line ); + + component->SetTransform( transform ); + } + + line = aReader.ReadLine(); + } + + SCH_PARSE_ERROR( "invalid component line", aReader, line ); + + return NULL; // Prevents compiler warning. Should never get here. +} diff --git a/eeschema/sch_legacy_plugin.h b/eeschema/sch_legacy_plugin.h new file mode 100644 index 0000000000..3d8f4b9d3d --- /dev/null +++ b/eeschema/sch_legacy_plugin.h @@ -0,0 +1,108 @@ +#ifndef _SCH_LEGACY_PLUGIN_H_ +#define _SCH_LEGACY_PLUGIN_H_ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 CERN + * Copyright (C) 2016 KiCad Developers, see change_log.txt for contributors. + * + * @author Wayne Stambaugh <stambaughw@gmail.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <sch_io_mgr.h> + + +class KIWAY; +class LINE_READER; +class SCH_SCREEN; +class SCH_SHEET; +class SCH_BITMAP; +class SCH_JUNCTION; +class SCH_NO_CONNECT; +class SCH_LINE; +class SCH_BUS_ENTRY_BASE; +class SCH_TEXT; +class SCH_COMPONENT; +class PROPERTIES; + + +/** + * Class SCH_LEGACY_PLUGIN + * + * is a #SCH_PLUGIN derivation for loading schematic files created before the new + * s-expression file format. + * + * The legacy parser and formatter attempt to be compatible with the legacy file format. + * The original parser was very forgiving in that it would parse only part of a keyword. + * So "$C", "$Co", and "$Com" could be used for "$Comp" and the old parser would allow + * this. This parser is not that forgiving and sticks to the legacy file format document. + * + * As with all SCH_PLUGINs there is no UI dependencies i.e. windowing calls allowed. + */ +class SCH_LEGACY_PLUGIN : public SCH_PLUGIN +{ +public: + + const wxString GetName() const + { + return wxT( "Eeschema-Legacy" ); + } + + const wxString GetFileExtension() const + { + return wxT( "sch" ); + } + + SCH_SHEET* Load( const wxString& aFileName, KIWAY* aKiway, + SCH_SHEET* aAppendToMe = NULL, const PROPERTIES* aProperties = NULL ); + + void Save( const wxString& aFileName, SCH_SHEET* aSheet, + const PROPERTIES* aProperties = NULL ) {} + + //-----</PLUGIN IMPLEMENTATION>--------------------------------------------- + + SCH_LEGACY_PLUGIN(); + virtual ~SCH_LEGACY_PLUGIN() {} + +private: + void loadHierarchy( SCH_SHEET* aSheet ); + void loadHeader( FILE_LINE_READER& aReader, SCH_SCREEN* aScreen ); + void loadPageSettings( FILE_LINE_READER& aReader, SCH_SCREEN* aScreen ); + void loadFile( const wxString& aFileName, SCH_SCREEN* aScreen ); + SCH_SHEET* loadSheet( FILE_LINE_READER& aReader ); + SCH_BITMAP* loadBitmap( FILE_LINE_READER& aReader ); + SCH_JUNCTION* loadJunction( FILE_LINE_READER& aReader ); + SCH_NO_CONNECT* loadNoConnect( FILE_LINE_READER& aReader ); + SCH_LINE* loadWire( FILE_LINE_READER& aReader ); + SCH_BUS_ENTRY_BASE* loadBusEntry( FILE_LINE_READER& aReader ); + SCH_TEXT* loadText( FILE_LINE_READER& aReader ); + SCH_COMPONENT* loadComponent( FILE_LINE_READER& aReader ); + +protected: + + int m_version; ///< Version of file being loaded. + wxString m_error; ///< For throwing exceptions + wxString m_path; ///< Root project path for loading child sheets. + const PROPERTIES* m_props; ///< Passed via Save() or Load(), no ownership, may be NULL. + KIWAY* m_kiway; ///< Required for path to legacy component libraries. + SCH_SHEET* m_rootSheet; ///< The root sheet of the schematic being loaded.. + + /// initialize PLUGIN like a constructor would. + void init( KIWAY* aKiway, const PROPERTIES* aProperties = NULL ); +}; + +#endif // _SCH_LEGACY_PLUGIN_H_ diff --git a/eeschema/sch_plugin.cpp b/eeschema/sch_plugin.cpp new file mode 100644 index 0000000000..b80239e375 --- /dev/null +++ b/eeschema/sch_plugin.cpp @@ -0,0 +1,152 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016 CERN + * Copyright (C) 2016 KiCad Developers, see change_log.txt for contributors. + * + * @author Wayne Stambaugh <stambaughw@gmail.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <properties.h> + +#include <sch_io_mgr.h> + +#define FMT_UNIMPLEMENTED _( "Plugin '%s' does not implement the '%s' function." ) + +/** + * Function not_implemented + * throws an IO_ERROR and complains of an API function not being implemented. + * + * @param aPlugin is a SCH_PLUGIN instance + * @param aCaller is the name of the unimplemented API function. + */ +static void not_implemented( SCH_PLUGIN* aPlugin, const char* aCaller ) +{ + THROW_IO_ERROR( wxString::Format( FMT_UNIMPLEMENTED, + aPlugin->GetName().GetData(), + wxString::FromUTF8( aCaller ).GetData() ) + ); +} + + +SCH_SHEET* SCH_PLUGIN::Load( const wxString& aFileName, KIWAY* aKiway, SCH_SHEET* aAppendToMe, + const PROPERTIES* aProperties ) +{ + not_implemented( this, __FUNCTION__ ); + return NULL; +} + + +void SCH_PLUGIN::Save( const wxString& aFileName, SCH_SHEET* aSchematic, + const PROPERTIES* aProperties ) +{ + // not pure virtual so that plugins only have to implement subset of the SCH_PLUGIN interface. + not_implemented( this, __FUNCTION__ ); +} + + +wxArrayString SCH_PLUGIN::SymbolEnumerate( const wxString& aLibraryPath, + const PROPERTIES* aProperties ) +{ + // not pure virtual so that plugins only have to implement subset of the SCH_PLUGIN interface. + not_implemented( this, __FUNCTION__ ); + return wxArrayString(); +} + + +LIB_PART* SCH_PLUGIN::SymbolLoad( const wxString& aLibraryPath, const wxString& aSymbolName, + const PROPERTIES* aProperties ) +{ + // not pure virtual so that plugins only have to implement subset of the SCH_PLUGIN interface. + not_implemented( this, __FUNCTION__ ); + return NULL; +} + + +void SCH_PLUGIN::SymbolSave( const wxString& aLibraryPath, const LIB_PART* aSymbol, + const PROPERTIES* aProperties ) +{ + // not pure virtual so that plugins only have to implement subset of the SCH_PLUGIN interface. + not_implemented( this, __FUNCTION__ ); +} + + +void SCH_PLUGIN::SymbolDelete( const wxString& aLibraryPath, const wxString& aSymbolName, + const PROPERTIES* aProperties ) +{ + // not pure virtual so that plugins only have to implement subset of the SCH_PLUGIN interface. + not_implemented( this, __FUNCTION__ ); +} + + +void SCH_PLUGIN::SymbolLibCreate( const wxString& aLibraryPath, const PROPERTIES* aProperties ) +{ + // not pure virtual so that plugins only have to implement subset of the SCH_PLUGIN interface. + not_implemented( this, __FUNCTION__ ); +} + + +bool SCH_PLUGIN::SymbolLibDelete( const wxString& aLibraryPath, const PROPERTIES* aProperties ) +{ + // not pure virtual so that plugins only have to implement subset of the SCH_PLUGIN interface. + not_implemented( this, __FUNCTION__ ); + return false; +} + + +bool SCH_PLUGIN::IsSymbolLibWritable( const wxString& aLibraryPath ) +{ + // not pure virtual so that plugins only have to implement subset of the SCH_PLUGIN interface. + not_implemented( this, __FUNCTION__ ); + return false; +} + + +void SCH_PLUGIN::SymbolLibOptions( PROPERTIES* aListToAppendTo ) const +{ + // disable all these in another couple of months, after everyone has seen them: +#if 1 + (*aListToAppendTo)["debug_level"] = UTF8( _( + "Enable <b>debug</b> logging for Symbol*() functions in this SCH_PLUGIN." + )); + + (*aListToAppendTo)["read_filter_regex"] = UTF8( _( + "Regular expression <b>symbol name</b> filter." + )); + + (*aListToAppendTo)["enable_transaction_logging"] = UTF8( _( + "Enable transaction logging. The mere presence of this option turns on the " + "logging, no need to set a Value." + )); + + (*aListToAppendTo)["username"] = UTF8( _( + "User name for <b>login</b> to some special library server." + )); + + (*aListToAppendTo)["password"] = UTF8( _( + "Password for <b>login</b> to some special library server." + )); +#endif + +#if 1 + // Suitable for a C++ to python SCH_PLUGIN::Footprint*() adapter, move it to the adapter + // if and when implemented. + (*aListToAppendTo)["python_symbol_plugin"] = UTF8( _( + "Enter the python symbol which implements the SCH_PLUGIN::Symbol*() functions." + )); +#endif +} + diff --git a/eeschema/sch_sheet.cpp b/eeschema/sch_sheet.cpp index 58b702044d..f360974df2 100644 --- a/eeschema/sch_sheet.cpp +++ b/eeschema/sch_sheet.cpp @@ -134,6 +134,18 @@ int SCH_SHEET::GetScreenCount() const } +SCH_SHEET* SCH_SHEET::GetRootSheet() +{ + SCH_SHEET* sheet = dynamic_cast< SCH_SHEET* >( GetParent() ); + + if( sheet == NULL ) + return this; + + // Recurse until a sheet is found with no parent which is the root sheet. + return sheet->GetRootSheet(); +} + + bool SCH_SHEET::Save( FILE* aFile ) const { if( fprintf( aFile, "$Sheet\n" ) == EOF @@ -765,6 +777,7 @@ bool SCH_SHEET::Load( SCH_EDIT_FRAME* aFrame ) bool success = true; SCH_SCREEN* screen = NULL; + if( !m_screen ) { g_RootSheet->SearchHierarchy( m_fileName, &screen ); @@ -789,9 +802,16 @@ bool SCH_SHEET::Load( SCH_EDIT_FRAME* aFrame ) { if( bs->Type() == SCH_SHEET_T ) { - SCH_SHEET* sheetstruct = (SCH_SHEET*) bs; + SCH_SHEET* sheet = (SCH_SHEET*) bs; - if( !sheetstruct->Load( aFrame ) ) + // Set the parent to this sheet. This effectively creates a method + // to find the root sheet from any sheet so a pointer to the root + // sheet does not need to be stored globally. Note: this is not the + // same as a hierarchy. Complex hierarchies can have multiple copies + // of a sheet. This only provides a simple tree to find the root sheet. + sheet->SetParent( this ); + + if( !sheet->Load( aFrame ) ) success = false; } diff --git a/eeschema/sch_sheet.h b/eeschema/sch_sheet.h index ddb7e7970c..7568ff50ee 100644 --- a/eeschema/sch_sheet.h +++ b/eeschema/sch_sheet.h @@ -61,12 +61,7 @@ class NETLIST_OBJECT_LIST; */ class SCH_SHEET_PIN : public SCH_HIERLABEL { -private: - int m_number; ///< Label number use for saving sheet label to file. - ///< Sheet label numbering begins at 2. - ///< 0 is reserved for the sheet name. - ///< 1 is reserve for the sheet file name. - +public: /** * Defines the edge of the sheet that the sheet pin is positioned * SHEET_LEFT_SIDE = 0: pin on left side @@ -84,6 +79,13 @@ private: SHEET_BOTTOM_SIDE, SHEET_UNDEFINED_SIDE }; + +private: + int m_number; ///< Label number use for saving sheet label to file. + ///< Sheet label numbering begins at 2. + ///< 0 is reserved for the sheet name. + ///< 1 is reserve for the sheet file name. + SHEET_SIDE m_edge; public: @@ -287,6 +289,19 @@ public: void SetSize( const wxSize& aSize ) { m_size = aSize; } + /** + * Function GetRootSheet + * + * returns the root sheet of this SCH_SHEET object. + * + * The root (top level) sheet can be found by walking up the parent links until the only + * sheet that has no parent is found. The root sheet can be found from any sheet without + * having to maintain a global root sheet pointer. + * + * @return a SCH_SHEET pointer to the root sheet. + */ + SCH_SHEET* GetRootSheet(); + /** * Function SetScreen * sets the screen associated with this sheet to \a aScreen. diff --git a/include/class_bitmap_base.h b/include/class_bitmap_base.h index 79f6465b96..3819dffce0 100644 --- a/include/class_bitmap_base.h +++ b/include/class_bitmap_base.h @@ -2,7 +2,7 @@ * 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 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2013-2016 KiCad Developers, see change_log.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 @@ -56,7 +56,8 @@ private: int m_ppi; // the bitmap definition. the default is 300PPI -public: BITMAP_BASE( const wxPoint& pos = wxPoint( 0, 0 ) ); +public: + BITMAP_BASE( const wxPoint& pos = wxPoint( 0, 0 ) ); BITMAP_BASE( const BITMAP_BASE& aSchBitmap ); @@ -73,6 +74,11 @@ public: BITMAP_BASE( const wxPoint& pos = wxPoint( 0, 0 ) ); double GetPixelScaleFactor() { return m_pixelScaleFactor; } void SetPixelScaleFactor( double aSF ) { m_pixelScaleFactor = aSF; } wxImage* GetImageData() { return m_image; } + void SetImage( wxImage* aImage ) + { + delete m_image; + m_image = aImage; + } /* * Function RebuildBitmap @@ -81,6 +87,12 @@ public: BITMAP_BASE( const wxPoint& pos = wxPoint( 0, 0 ) ); */ void RebuildBitmap() { *m_bitmap = wxBitmap( *m_image ); } + void SetBitmap( wxBitmap* aBitMap ) + { + delete m_bitmap; + m_bitmap = aBitMap; + } + /** * Function ImportData * Copy aItem image to me and update m_bitmap diff --git a/include/fp_lib_table.h b/include/fp_lib_table.h index a56897a79f..02db89506f 100644 --- a/include/fp_lib_table.h +++ b/include/fp_lib_table.h @@ -31,6 +31,7 @@ #include <map> #include <io_mgr.h> #include <project.h> +#include <properties.h> #include <boost/interprocess/exceptions.hpp> #define FP_LATE_ENVVAR 1 ///< late=1/early=0 environment variable expansion diff --git a/include/properties.h b/include/properties.h new file mode 100644 index 0000000000..8202cd5dc4 --- /dev/null +++ b/include/properties.h @@ -0,0 +1,52 @@ +#ifndef _PROPERTIES_H_ +#define _PROPERTIES_H_ + +/* + * This program source code file is part of KICAD, a free EDA CAD application. + * + * Copyright (C) 2016 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com> + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <string> +#include <map> +#include <utf8.h> + + +/** + * Class PROPERTIES + * is a name/value tuple with unique names and optional values. The names + * may be iterated alphabetically. + */ +class PROPERTIES : public std::map< std::string, UTF8 > +{ + // alphabetical tuple of name and value hereby defined. + +public: + + /** + * Function Value + * fetches a property by aName and returns true if that property was found, else false. + * If not found, aFetchedValue is not touched. + * @param aName is the property or option to look for. + * @param aFetchedValue is where to put the value of the property if it + * exists and aFetchedValue is not NULL. + * @return bool - true if property is found, else false. + */ + bool Value( const char* aName, UTF8* aFetchedValue = NULL ) const; +}; + +#endif // _PROPERTIES_H_ diff --git a/pcbnew/append_board_to_current.cpp b/pcbnew/append_board_to_current.cpp index 06e0630113..aff84e1a57 100644 --- a/pcbnew/append_board_to_current.cpp +++ b/pcbnew/append_board_to_current.cpp @@ -29,6 +29,7 @@ #include <fctsys.h> #include <confirm.h> +#include <properties.h> #include <wxPcbStruct.h> #include <pcbnew.h> #include <io_mgr.h> diff --git a/pcbnew/eagle_plugin.cpp b/pcbnew/eagle_plugin.cpp index 481430f739..8e51b5763c 100644 --- a/pcbnew/eagle_plugin.cpp +++ b/pcbnew/eagle_plugin.cpp @@ -65,6 +65,7 @@ Load() TODO's #include <trigo.h> #include <macros.h> #include <kicad_string.h> +#include <properties.h> #include <wx/filename.h> #include <class_board.h> diff --git a/pcbnew/io_mgr.cpp b/pcbnew/io_mgr.cpp index 8d3981bd5f..2b41083775 100644 --- a/pcbnew/io_mgr.cpp +++ b/pcbnew/io_mgr.cpp @@ -43,21 +43,6 @@ #define FMT_NOTFOUND _( "Plugin type '%s' is not found." ) -// is there a better place for this function? -bool PROPERTIES::Value( const char* aName, UTF8* aFetchedValue ) const -{ - PROPERTIES::const_iterator it = find( aName ); - - if( it != end() ) - { - if( aFetchedValue ) - *aFetchedValue = it->second; - return true; - } - return false; -} - - // Some day plugins might be in separate DLL/DSOs, simply because of numbers of them // and code size. Until then, use the simplest method: diff --git a/pcbnew/io_mgr.h b/pcbnew/io_mgr.h index 80b4821888..24d548a08a 100644 --- a/pcbnew/io_mgr.h +++ b/pcbnew/io_mgr.h @@ -32,29 +32,7 @@ class BOARD; class PLUGIN; class MODULE; - -/** - * Class PROPERTIES - * is a name/value tuple with unique names and optional values. The names - * may be iterated alphabetically. - */ -class PROPERTIES : public std::map< std::string, UTF8 > -{ - // alphabetical tuple of name and value hereby defined. - -public: - - /** - * Function Value - * fetches a property by aName and returns true if that property was found, else false. - * If not found, aFetchedValue is not touched. - * @param aName is the property or option to look for. - * @param aFetchedValue is where to put the value of the property if it - * exists and aFetchedValue is not NULL. - * @return bool - true if property is found, else false. - */ - bool Value( const char* aName, UTF8* aFetchedValue = NULL ) const; -}; +class PROPERTIES; /** diff --git a/pcbnew/legacy_plugin.cpp b/pcbnew/legacy_plugin.cpp index e27761963e..385d380923 100644 --- a/pcbnew/legacy_plugin.cpp +++ b/pcbnew/legacy_plugin.cpp @@ -68,6 +68,7 @@ #include <kicad_string.h> #include <macros.h> +#include <properties.h> #include <zones.h> #include <class_board.h> diff --git a/pcbnew/plugin.cpp b/pcbnew/plugin.cpp index 9b5f5ab8a0..5d9364712f 100644 --- a/pcbnew/plugin.cpp +++ b/pcbnew/plugin.cpp @@ -23,6 +23,8 @@ */ #include <io_mgr.h> +#include <properties.h> + #define FMT_UNIMPLEMENTED _( "Plugin '%s' does not implement the '%s' function." ) diff --git a/pcbnew/tools/pcbnew_control.cpp b/pcbnew/tools/pcbnew_control.cpp index d0e6542116..fae470ed8e 100644 --- a/pcbnew/tools/pcbnew_control.cpp +++ b/pcbnew/tools/pcbnew_control.cpp @@ -36,6 +36,7 @@ #include <confirm.h> #include <hotkeys_basic.h> +#include <properties.h> #include <io_mgr.h> #include <pcbnew_id.h>