From 4ed346ea64fd704c5c970474349b651c9896d444 Mon Sep 17 00:00:00 2001
From: Wayne Stambaugh <stambaughw@verizon.net>
Date: Wed, 6 Jul 2016 05:22:56 -0400
Subject: [PATCH] Eeschema: initial schematic I/O plugin.

* Factor out PROPERTIES object from the PCB plugin code and move it into
  common so it can be used by both the Pcbnew and Eeschema plugins.

* Add schematic I/O plugin manager for loading and saving schematic and
  component library files.

* Add initial attempt at a parser for current schematic file format.  This
  parser will be infinitely more strict than the current parser which is very
  forgiving in what it parses.

* Make minor changes to the base bitmap class to support the new parser.

* Add find root sheet support to sheet object to allow fetching the root
  sheet from any sheet in the stack.
---
 CMakeLists.txt                     |    4 +
 CMakeModules/config.h.cmake        |    3 +
 common/CMakeLists.txt              |    1 +
 common/properties.cpp              |   37 +
 eeschema/CMakeLists.txt            |    3 +
 eeschema/files-io.cpp              |   42 +-
 eeschema/sch_component.h           |    3 +
 eeschema/sch_io_mgr.cpp            |  162 ++++
 eeschema/sch_io_mgr.h              |  487 ++++++++++
 eeschema/sch_legacy_plugin.cpp     | 1379 ++++++++++++++++++++++++++++
 eeschema/sch_legacy_plugin.h       |  108 +++
 eeschema/sch_plugin.cpp            |  152 +++
 eeschema/sch_sheet.cpp             |   24 +-
 eeschema/sch_sheet.h               |   27 +-
 include/class_bitmap_base.h        |   16 +-
 include/fp_lib_table.h             |    1 +
 include/properties.h               |   52 ++
 pcbnew/append_board_to_current.cpp |    1 +
 pcbnew/eagle_plugin.cpp            |    1 +
 pcbnew/io_mgr.cpp                  |   15 -
 pcbnew/io_mgr.h                    |   24 +-
 pcbnew/legacy_plugin.cpp           |    1 +
 pcbnew/plugin.cpp                  |    2 +
 pcbnew/tools/pcbnew_control.cpp    |    1 +
 24 files changed, 2492 insertions(+), 54 deletions(-)
 create mode 100644 common/properties.cpp
 create mode 100644 eeschema/sch_io_mgr.cpp
 create mode 100644 eeschema/sch_io_mgr.h
 create mode 100644 eeschema/sch_legacy_plugin.cpp
 create mode 100644 eeschema/sch_legacy_plugin.h
 create mode 100644 eeschema/sch_plugin.cpp
 create mode 100644 include/properties.h

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>