← Back to team overview

kicad-developers team mailing list archive

[PATCH] lib-cache-rescue upgrade

 

Hi all,

It's come to my attention that we have another old project compatibility 
issue:

https://forum.kicad.info/t/exisiting-schematic-component-not-being-found-in-library-error/976

The behavior when loading a very old project with case-insensitive part 
names isn't nice at all, there's no easy way to change them over. It 
gives suggestions for new part names, but you still have to change them 
one by one.

I adapted lib-cache-rescue into a more generic project-rescue, which can 
handle multiple types of rescue cases, and added one for missing symbols 
with differently cased versions available.

It looks roughly like this now (though I've changed the descriptive text 
at the top since taking that screenshot):

http://misc.c4757p.com/newrescue.png

Please, as many people as possible, try this out.

--
Chris
diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt
index 0982735..d6cc1cd 100644
--- a/eeschema/CMakeLists.txt
+++ b/eeschema/CMakeLists.txt
@@ -114,7 +114,6 @@ set( EESCHEMA_SRCS
     libedit_undo_redo.cpp
     lib_arc.cpp
     lib_bezier.cpp
-    lib_cache_rescue.cpp
     lib_circle.cpp
     lib_collectors.cpp
     lib_draw_item.cpp
@@ -139,6 +138,7 @@ set( EESCHEMA_SRCS
     plot_schematic_PS.cpp
     plot_schematic_PDF.cpp
     plot_schematic_SVG.cpp
+    project_rescue.cpp
     sch_base_frame.cpp
     sch_bitmap.cpp
     sch_bus_entry.cpp
diff --git a/eeschema/class_library.cpp b/eeschema/class_library.cpp
index 4fac061..bbda657 100644
--- a/eeschema/class_library.cpp
+++ b/eeschema/class_library.cpp
@@ -38,7 +38,7 @@
 #include <richio.h>
 #include <config_params.h>
 #include <wildcards_and_files_ext.h>
-#include <lib_cache_rescue.h>
+#include <project_rescue.h>
 //#include <richio.h>
 
 #include <general.h>
diff --git a/eeschema/dialogs/dialog_rescue_each.cpp b/eeschema/dialogs/dialog_rescue_each.cpp
index 8b896f4..31c424a 100644
--- a/eeschema/dialogs/dialog_rescue_each.cpp
+++ b/eeschema/dialogs/dialog_rescue_each.cpp
@@ -32,7 +32,7 @@
 #include <set>
 #include <vector>
 #include <boost/foreach.hpp>
-#include <lib_cache_rescue.h>
+#include <project_rescue.h>
 #include <eeschema_config.h>
 
 class DIALOG_RESCUE_EACH: public DIALOG_RESCUE_EACH_BASE
@@ -43,24 +43,19 @@ public:
      * This dialog asks the user which rescuable, cached parts he wants to rescue.
      * Any rejects will be pruned from aCandidates.
      * @param aCaller - the SCH_EDIT_FRAME calling this
-     * @param aCandidates - the list of RESCUE_CANDIDATES
-     * @param aComponents - a vector of all the components in the schematic
+     * @param aRescuer - the active RESCUER instance
      * @param aAskShowAgain - if true, a "Never Show Again" button will be included
      */
-    DIALOG_RESCUE_EACH( SCH_EDIT_FRAME* aParent, std::vector<RESCUE_CANDIDATE>& aCandidates,
-            std::vector<SCH_COMPONENT*>& aComponents, bool aAskShowAgain );
+    DIALOG_RESCUE_EACH( SCH_EDIT_FRAME* aParent, RESCUER& aRescuer, bool aAskShowAgain );
 
     ~DIALOG_RESCUE_EACH();
 
 private:
     SCH_EDIT_FRAME* m_Parent;
     wxConfigBase*   m_Config;
-    std::vector<RESCUE_CANDIDATE>* m_Candidates;
-    std::vector<SCH_COMPONENT*>* m_Components;
+    RESCUER*        m_Rescuer;
     bool            m_AskShowAgain;
 
-    bool m_insideUpdateEvent;
-
     bool TransferDataToWindow();
     bool TransferDataFromWindow();
     void PopulateConflictList();
@@ -75,31 +70,22 @@ private:
 };
 
 
-DIALOG_RESCUE_EACH::DIALOG_RESCUE_EACH( SCH_EDIT_FRAME* aParent, std::vector<RESCUE_CANDIDATE>& aCandidates,
-        std::vector<SCH_COMPONENT*>& aComponents, bool aAskShowAgain )
-
+DIALOG_RESCUE_EACH::DIALOG_RESCUE_EACH( SCH_EDIT_FRAME* aParent, RESCUER& aRescuer,
+            bool aAskShowAgain )
     : DIALOG_RESCUE_EACH_BASE( aParent ),
       m_Parent( aParent ),
-      m_Candidates( &aCandidates ),
-      m_Components( &aComponents ),
-      m_AskShowAgain( aAskShowAgain ),
-      m_insideUpdateEvent( false )
+      m_Rescuer( &aRescuer ),
+      m_AskShowAgain( aAskShowAgain )
 {
     m_Config = Kiface().KifaceSettings();
     m_stdButtonsOK->SetDefault();
 
     // Set the info message, customized to include the proper suffix.
-    wxString info_message;
-    info_message.Printf(
-        _( "This project uses symbols that no longer match the ones in the system libraries.\n"
-           "Using this tool, you can rescue these cached symbols into a new library.\n"
-           "\n"
-           "Choose \"Rescue\" for any parts you would like to save from this project's cache,\n"
-           "or press \"Cancel\" to allow the symbols to be updated to the new versions.\n"
-           "\n"
-           "All rescued components will be renamed with a new suffix of \"-RESCUE-%s\"\n"
-           "to avoid naming conflicts." ),
-        Prj().GetProjectName() );
+    wxString info_message =
+        _( "It looks like this project was made using older schematic component libraries.\n"
+           "Some parts may need to be relinked to a different symbol name, and some symbols\n"
+           "may need to be \"rescued\" into a new library.\n"
+           "The following changes are recommended to update the project.\n" );
     m_lblInfo->SetLabel( info_message );
 }
 
@@ -114,8 +100,9 @@ bool DIALOG_RESCUE_EACH::TransferDataToWindow()
     if( !wxDialog::TransferDataToWindow() )
         return false;
 
-    m_ListOfConflicts->AppendToggleColumn( _( "Rescue symbol" ) );
-    m_ListOfConflicts->AppendTextColumn( _( "Symbol name" ) );
+    m_ListOfConflicts->AppendToggleColumn( _( "Accept" ) );
+    m_ListOfConflicts->AppendTextColumn( _( "Symbol" ) );
+    m_ListOfConflicts->AppendTextColumn( _( "Action" ) );
     m_ListOfInstances->AppendTextColumn( _( "Reference" ) );
     m_ListOfInstances->AppendTextColumn( _( "Value" ) );
     PopulateConflictList();
@@ -136,11 +123,13 @@ bool DIALOG_RESCUE_EACH::TransferDataToWindow()
 void DIALOG_RESCUE_EACH::PopulateConflictList()
 {
     wxVector<wxVariant> data;
-    BOOST_FOREACH( RESCUE_CANDIDATE& each_candidate, *m_Candidates )
+    BOOST_FOREACH( RESCUE_CANDIDATE& each_candidate, m_Rescuer->m_all_candidates )
     {
         data.clear();
         data.push_back( wxVariant( true ) );
-        data.push_back( each_candidate.requested_name );
+        data.push_back( each_candidate.GetRequestedName() );
+        data.push_back( each_candidate.GetActionDescription() );
+
         m_ListOfConflicts->AppendItem( data );
     }
 }
@@ -155,12 +144,12 @@ void DIALOG_RESCUE_EACH::PopulateInstanceList()
     if( row == wxNOT_FOUND )
         row = 0;
 
-    RESCUE_CANDIDATE& selected_part = (*m_Candidates)[row];
+    RESCUE_CANDIDATE& selected_part = m_Rescuer->m_all_candidates[row];
 
     wxVector<wxVariant> data;
-    BOOST_FOREACH( SCH_COMPONENT* each_component, *m_Components )
+    BOOST_FOREACH( SCH_COMPONENT* each_component, *m_Rescuer->GetComponents() )
     {
-        if( each_component->GetPartName() != selected_part.requested_name )
+        if( each_component->GetPartName() != selected_part.GetRequestedName() )
             continue;
 
         SCH_FIELD* valueField = each_component->GetField( 1 );
@@ -181,9 +170,10 @@ void DIALOG_RESCUE_EACH::OnHandleCachePreviewRepaint( wxPaintEvent& aRepaintEven
     if( row == wxNOT_FOUND )
         row = 0;
 
-    RESCUE_CANDIDATE& selected_part = (*m_Candidates)[row];
+    RESCUE_CANDIDATE& selected_part = m_Rescuer->m_all_candidates[row];
 
-    renderPreview( selected_part.cache_candidate, 0, m_componentViewOld );
+    if( selected_part.GetCacheCandidate() )
+        renderPreview( selected_part.GetCacheCandidate(), 0, m_componentViewOld );
 }
 
 
@@ -194,9 +184,10 @@ void DIALOG_RESCUE_EACH::OnHandleLibraryPreviewRepaint( wxPaintEvent& aRepaintEv
     if( row == wxNOT_FOUND )
         row = 0;
 
-    RESCUE_CANDIDATE& selected_part = (*m_Candidates)[row];
+    RESCUE_CANDIDATE& selected_part = m_Rescuer->m_all_candidates[row];
 
-    renderPreview( selected_part.lib_candidate, 0, m_componentViewNew );
+    if( selected_part.GetLibCandidate() )
+        renderPreview( selected_part.GetLibCandidate(), 0, m_componentViewNew );
 }
 
 
@@ -269,19 +260,15 @@ bool DIALOG_RESCUE_EACH::TransferDataFromWindow()
     if( !wxDialog::TransferDataFromWindow() )
         return false;
 
-    std::vector<RESCUE_CANDIDATE>::iterator it = m_Candidates->begin();
-    for( size_t index = 0; it != m_Candidates->end(); ++index )
+    for( size_t index = 0; index < m_Rescuer->GetCandidateCount(); ++index )
     {
         wxVariant val;
         m_ListOfConflicts->GetValue( val, index, 0 );
         bool rescue_part = val.GetBool();
 
-        if( !rescue_part )
-            m_Candidates->erase( it );
-        else
-            ++it;
+        if( rescue_part )
+            m_Rescuer->m_chosen_candidates.push_back( &m_Rescuer->m_all_candidates[index] );
     }
-
     return true;
 }
 
@@ -299,7 +286,7 @@ void DIALOG_RESCUE_EACH::OnNeverShowClick( wxCommandEvent& aEvent )
     if( resp == wxID_YES )
     {
         m_Config->Write( RESCUE_NEVER_SHOW_KEY, true );
-        m_Candidates->clear();
+        m_Rescuer->m_chosen_candidates.clear();
         Close();
     }
 }
@@ -307,14 +294,13 @@ void DIALOG_RESCUE_EACH::OnNeverShowClick( wxCommandEvent& aEvent )
 
 void DIALOG_RESCUE_EACH::OnCancelClick( wxCommandEvent& aEvent )
 {
-    m_Candidates->clear();
+    m_Rescuer->m_chosen_candidates.clear();
     DIALOG_RESCUE_EACH_BASE::OnCancelClick( aEvent );
 }
 
 
-int InvokeDialogRescueEach( SCH_EDIT_FRAME* aCaller, std::vector<RESCUE_CANDIDATE>& aCandidates,
-        std::vector<SCH_COMPONENT*>& aComponents, bool aAskShowAgain )
+int InvokeDialogRescueEach( SCH_EDIT_FRAME* aCaller, RESCUER& aRescuer, bool aAskShowAgain )
 {
-    DIALOG_RESCUE_EACH dlg( aCaller, aCandidates, aComponents, aAskShowAgain );
+    DIALOG_RESCUE_EACH dlg( aCaller, aRescuer, aAskShowAgain );
     return dlg.ShowModal();
 }
diff --git a/eeschema/dialogs/dialog_rescue_each_base.cpp b/eeschema/dialogs/dialog_rescue_each_base.cpp
index 2e7549d..99cdbcc 100644
--- a/eeschema/dialogs/dialog_rescue_each_base.cpp
+++ b/eeschema/dialogs/dialog_rescue_each_base.cpp
@@ -20,7 +20,7 @@ DIALOG_RESCUE_EACH_BASE::DIALOG_RESCUE_EACH_BASE( wxWindow* parent, wxWindowID i
 	m_lblInfo->Wrap( 500 );
 	bSizerMain->Add( m_lblInfo, 0, wxALL|wxEXPAND, 5 );
 	
-	m_staticText5 = new wxStaticText( this, wxID_ANY, _("Symbols with cache/library conflicts:"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText5 = new wxStaticText( this, wxID_ANY, _("Symbols to update:"), wxDefaultPosition, wxDefaultSize, 0 );
 	m_staticText5->Wrap( -1 );
 	m_staticText5->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) );
 	
diff --git a/eeschema/dialogs/dialog_rescue_each_base.fbp b/eeschema/dialogs/dialog_rescue_each_base.fbp
index dee774b..fcc7882 100644
--- a/eeschema/dialogs/dialog_rescue_each_base.fbp
+++ b/eeschema/dialogs/dialog_rescue_each_base.fbp
@@ -208,7 +208,7 @@
                         <property name="gripper">0</property>
                         <property name="hidden">0</property>
                         <property name="id">wxID_ANY</property>
-                        <property name="label">Symbols with cache/library conflicts:</property>
+                        <property name="label">Symbols to update:</property>
                         <property name="max_size"></property>
                         <property name="maximize_button">0</property>
                         <property name="maximum_size"></property>
diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp
index 39922f5..3bf97b9 100644
--- a/eeschema/files-io.cpp
+++ b/eeschema/files-io.cpp
@@ -43,7 +43,7 @@
 #include <sch_sheet_path.h>
 #include <sch_component.h>
 #include <wildcards_and_files_ext.h>
-#include <lib_cache_rescue.h>
+#include <project_rescue.h>
 #include <eeschema_config.h>
 
 
@@ -307,7 +307,7 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
 
         UpdateFileHistory( fullFileName );
 
-        // Check to see whether some old, cached library parts need to be rescued
+        // Check to see whether some old library parts need to be rescued
         // Only do this if RescueNeverShow was not set.
         wxConfigBase *config = Kiface().KifaceSettings();
         bool rescueNeverShow = false;
@@ -315,7 +315,7 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
 
         if( !rescueNeverShow )
         {
-            if( RescueCacheConflicts( false ) )
+            if( RescueProject( false ) )
             {
                 GetScreen()->CheckComponentsToPartsLinks();
                 GetScreen()->TestDanglingEnds();
diff --git a/eeschema/invoke_sch_dialog.h b/eeschema/invoke_sch_dialog.h
index 0a9cbe4..e4395ee 100644
--- a/eeschema/invoke_sch_dialog.h
+++ b/eeschema/invoke_sch_dialog.h
@@ -48,8 +48,7 @@ class wxDialog;
 class LIB_PART;
 class PART_LIBS;
 class SCH_COMPONENT;
-class RESCUE_CANDIDATE;
-class RESCUE_LOG;
+class RESCUER;
 
 // Often this is not used in the prototypes, since wxFrame is good enough and would
 // represent maximum information hiding.
@@ -60,12 +59,10 @@ class SCH_EDIT_FRAME;
  * This dialog asks the user which rescuable, cached parts he wants to rescue.
  * Any rejects will be pruned from aCandidates.
  * @param aCaller - the SCH_EDIT_FRAME calling this
- * @param aCandidates - the list of RESCUE_CANDIDATES
- * @param aComponents - a vector of all the components in the schematic
+ * @param aRescuer - the active RESCUER instance
  * @param aAskShowAgain - if true, a "Never Show Again" button will be included
  */
-int InvokeDialogRescueEach( SCH_EDIT_FRAME* aCaller, std::vector<RESCUE_CANDIDATE>& aCandidates,
-        std::vector<SCH_COMPONENT*>& aComponents, bool aAskShowAgain );
+int InvokeDialogRescueEach( SCH_EDIT_FRAME* aCaller, RESCUER& aRescuer, bool aAskShowAgain );
 
 /// Create and show DIALOG_ANNOTATE and return whatever
 /// DIALOG_ANNOTATE::ShowModal() returns.
diff --git a/eeschema/lib_cache_rescue.cpp b/eeschema/lib_cache_rescue.cpp
deleted file mode 100644
index da74997..0000000
--- a/eeschema/lib_cache_rescue.cpp
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * This program source code file is part of KiCad, a free EDA CAD application.
- *
- * Copyright (C) 2015 Chris Pavlina <pavlina.chris@xxxxxxxxx>
- * Copyright (C) 2015 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
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, you may find one here:
- * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
- * or you may search the http://www.gnu.org website for the version 2 license,
- * or you may write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
- */
-
-#include <class_drawpanel.h>
-#include <class_library.h>
-#include <confirm.h>
-#include <invoke_sch_dialog.h>
-#include <kicad_device_context.h>
-#include <lib_cache_rescue.h>
-#include <sch_component.h>
-#include <sch_sheet.h>
-#include <schframe.h>
-#include <wildcards_and_files_ext.h>
-
-#include <boost/foreach.hpp>
-#include <map>
-
-
-typedef std::pair<SCH_COMPONENT*, wxString> COMPONENT_NAME_PAIR;
-
-
-/**
- * Function save_library
- * writes the library out to disk. Returns true on success.
- *
- * @param aFileName - Filename to receive the library
- * @param aLibrary - Library to write
- * @param aEditFrame - the calling SCH_EDIT_FRAME
- */
-static bool save_library( const wxString& aFileName, PART_LIB* aLibrary, SCH_EDIT_FRAME* aEditFrame )
-{
-    try
-    {
-        FILE_OUTPUTFORMATTER formatter( aFileName );
-
-        if( !aLibrary->Save( formatter ) )
-        {
-            wxString msg = wxString::Format( _(
-                "An error occurred attempting to save component library '%s'." ),
-                GetChars( aFileName )
-                );
-            DisplayError( aEditFrame, msg );
-            return false;
-        }
-    }
-    catch( ... /* IO_ERROR ioe */ )
-    {
-        wxString msg = wxString::Format( _(
-            "Failed to create component library file '%s'" ),
-            GetChars( aFileName )
-            );
-        DisplayError( aEditFrame, msg );
-        return false;
-    }
-
-    return true;
-}
-
-
-/**
- * Function insert_library
- * inserts a library into the project and refreshes libraries.
- *
- * @param aProject - project that will be modified
- * @param aLibrary - PART_LIB to add
- * @param aIndex - index in the list at which the library is to be inserted
- *
- * @return true on success, false on failure
- */
-static bool insert_library( PROJECT *aProject, PART_LIB *aLibrary, size_t aIndex ) throw( boost::bad_pointer )
-{
-    wxArrayString libNames;
-    wxString libPaths;
-
-    wxString libName = aLibrary->GetName();
-    PART_LIBS *libs = dynamic_cast<PART_LIBS*>( aProject->GetElem( PROJECT::ELEM_SCH_PART_LIBS ) );
-    if( !libs )
-    {
-        libs = new PART_LIBS();
-        aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, libs );
-    }
-
-    PART_LIBS::LibNamesAndPaths( aProject, false, &libPaths, &libNames );
-
-    // Make sure the library is not already in the list
-    while( libNames.Index( libName ) != wxNOT_FOUND )
-        libNames.Remove( libName );
-
-    // Add the library to the list and save
-    libNames.Insert( libName, aIndex );
-    PART_LIBS::LibNamesAndPaths( aProject, true, &libPaths, &libNames );
-
-    // Save the old libraries in case there is a problem after clear(). We'll
-    // put them back in.
-    boost::ptr_vector<PART_LIB> libsSave;
-    libsSave.transfer( libsSave.end(), libs->begin(), libs->end(), *libs );
-
-    aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, NULL );
-
-    libs = new PART_LIBS();
-    try
-    {
-        libs->LoadAllLibraries( aProject );
-    }
-    catch( const PARSE_ERROR& e )
-    {
-        // Some libraries were not found. There's no point in showing the error,
-        // because it was already shown. Just don't do anything.
-    }
-    catch( const IO_ERROR& e )
-    {
-        // Restore the old list
-        libs->clear();
-        libs->transfer( libs->end(), libsSave.begin(), libsSave.end(), libsSave );
-        return false;
-    }
-    aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, libs );
-
-    return true;
-}
-
-
-/**
- * Function get_components
- * Fills a vector with all of the project's components, to ease iterating over them.
- *
- * @param aComponents - a vector that will take the components
- */
-static void get_components( std::vector<SCH_COMPONENT*>& aComponents )
-{
-    SCH_SCREENS screens;
-    for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
-    {
-        for( SCH_ITEM* item = screen->GetDrawItems(); item; item = item->Next() )
-        {
-            if( item->Type() != SCH_COMPONENT_T ) continue;
-            SCH_COMPONENT* component = dynamic_cast<SCH_COMPONENT*>( item );
-            aComponents.push_back( component );
-        }
-    }
-}
-
-
-/**
- * Function find_component
- * Search the libraries for the first component with a given name.
- *
- * @param aName - name to search for
- * @param aLibs - the loaded PART_LIBS
- * @param aCached - whether we are looking for the cached part
- */
-static LIB_PART* find_component( wxString aName, PART_LIBS* aLibs, bool aCached )
-{
-    LIB_PART *part = NULL;
-
-    BOOST_FOREACH( PART_LIB& each_lib, *aLibs )
-    {
-        if( aCached && !each_lib.IsCache() )
-            continue;
-
-        if( !aCached && each_lib.IsCache() )
-            continue;
-
-        part = each_lib.FindPart( aName );
-        if( part )
-            break;
-    }
-
-    return part;
-}
-
-
-/**
- * Function find_rescues
- * Search components for any that request a part that conflicts with the
- * library parts.
- *
- * This is done from the component side to track requested aliases.
- *
- * @param aComponents - a vector of components to scan
- * @param aLibs - the loaded PART_LIBS
- * @param aCandidates - a vector to hold rescue candidates
- */
-static void find_rescues( std::vector<SCH_COMPONENT*>& aComponents, PART_LIBS* aLibs,
-        std::vector<RESCUE_CANDIDATE>& aCandidates )
-{
-    // We need to narrow down the list and avoid having multiple copies of the
-    // same name. Therefore, we'll assemble in a map first, before pushing to
-    // the vector.
-    typedef std::map<wxString, RESCUE_CANDIDATE> candidate_map_t;
-    candidate_map_t candidate_map;
-    BOOST_FOREACH( SCH_COMPONENT* each_component, aComponents )
-    {
-        wxString part_name( each_component->GetPartName() );
-        LIB_PART* cache_match = find_component( part_name, aLibs, /* aCached */ true );
-        LIB_PART* lib_match = find_component( part_name, aLibs, /* aCached */ false );
-
-        // Test whether there is a conflict
-        if( !cache_match || !lib_match )
-            continue;
-        if( !cache_match->PinsConflictWith( *lib_match, /* aTestNums */ true, /* aTestNames */ true,
-                /* aTestType */ true, /* aTestOrientation */ true, /* aTestLength */ false ))
-            continue;
-
-        RESCUE_CANDIDATE candidate;
-        candidate.requested_name = part_name;
-        candidate.cache_candidate = cache_match;
-        candidate.lib_candidate = lib_match;
-
-        candidate_map[part_name] = candidate;
-    }
-
-    // Now, dump the map into aCandidates
-    BOOST_FOREACH( const candidate_map_t::value_type& each_pair, candidate_map )
-    {
-        aCandidates.push_back( each_pair.second );
-    }
-}
-
-
-/**
- * Function create_rescue_library
- * Creates and returns a PART_LIB object for storing rescued components.
- * @param aFileName - wxFileName to receive the library's file name
- */
-static PART_LIB* create_rescue_library( wxFileName& aFileName )
-{
-    wxFileName fn( g_RootSheet->GetScreen()->GetFileName() );
-    fn.SetName( fn.GetName() + wxT( "-rescue" ) );
-    fn.SetExt( SchematicLibraryFileExtension );
-    aFileName.SetPath( fn.GetPath() );
-    aFileName.SetName( fn.GetName() );
-    aFileName.SetExt( wxT( "lib" ) );
-    return new PART_LIB( LIBRARY_TYPE_EESCHEMA, fn.GetFullPath() );
-}
-
-
-/**
- * Function rescue_components
- * Rescues components from aCandidates into aLibrary
- * @param aCandidates - list of final rescue candidates to be rescued
- * @param aLibrary - library for them to be rescued into
- * @param aSuffix - part name suffix to apply to them
- */
-static void rescue_components( std::vector<RESCUE_CANDIDATE>& aCandidates, PART_LIB* aLibrary, const wxString &aSuffix )
-{
-    BOOST_FOREACH( RESCUE_CANDIDATE& each_candidate, aCandidates )
-    {
-        LIB_PART new_part( *each_candidate.cache_candidate, aLibrary );
-        new_part.SetName( each_candidate.requested_name + aSuffix );
-        new_part.RemoveAllAliases();
-        aLibrary->AddPart( &new_part );
-    }
-}
-
-
-/**
- * Function update_components
- * Update components to reflect changed names of rescued parts.
- * Saves components with the original names to aRescueLog to allow recovering from errors and
- * displaying summary.
- *
- * @param aComponents - a populated list of all components
- * @param aCandidates - list of rescue candidates
- * @param aSuffix - part name suffix
- * @param aRescueLog - rescue log
- */
-static void update_components( std::vector<SCH_COMPONENT*>& aComponents, std::vector<RESCUE_CANDIDATE>& aCandidates,
-        const wxString& aSuffix, std::vector<RESCUE_LOG>& aRescueLog )
-{
-    BOOST_FOREACH( RESCUE_CANDIDATE& each_candidate, aCandidates )
-    {
-        BOOST_FOREACH( SCH_COMPONENT* each_component, aComponents )
-        {
-            if( each_component->GetPartName() != each_candidate.requested_name ) continue;
-
-            wxString new_name = each_candidate.requested_name + aSuffix;
-            each_component->SetPartName( new_name );
-
-            RESCUE_LOG log_item;
-            log_item.component = each_component;
-            log_item.old_name = each_candidate.requested_name;
-            log_item.new_name = new_name;
-            aRescueLog.push_back( log_item );
-        }
-    }
-}
-
-
-bool SCH_EDIT_FRAME::RescueCacheConflicts( bool aRunningOnDemand )
-{
-    // Data that will be used throughout the operation
-    std::vector<RESCUE_CANDIDATE>   candidates;
-    std::vector<SCH_COMPONENT*>     components;
-    PART_LIBS*              libs;
-    wxString                part_name_suffix;
-    PROJECT*                prj;
-
-    // Prepare data
-    get_components( components );
-    prj = &Prj();
-    libs = prj->SchLibs();
-    part_name_suffix =  wxT( "-RESCUE-" ) + prj->GetProjectName();
-
-    // Start!
-    find_rescues( components, libs, candidates );
-    if( candidates.empty() )
-    {
-        if( aRunningOnDemand )
-        {
-            wxMessageDialog dlg( this, _( "There are no conflicting symbols to rescue from the cache." ) );
-            dlg.ShowModal();
-        }
-        return true;
-    }
-    InvokeDialogRescueEach( this, candidates, components, /* aAskShowAgain */ !aRunningOnDemand );
-    wxFileName library_fn;
-    std::auto_ptr<PART_LIB> rescue_lib( create_rescue_library( library_fn ) );
-    rescue_components( candidates, rescue_lib.get(), part_name_suffix );
-
-    // If no components were rescued, let the user know what's going on. He might
-    // have clicked cancel by mistake, and should have some indication of that.
-    if( candidates.empty() )
-    {
-        wxMessageDialog dlg( this, _( "No cached symbols were rescued." ) );
-        dlg.ShowModal();
-        return true;
-    }
-
-    if( !save_library( library_fn.GetFullPath(), rescue_lib.get(), this ) )
-    {
-        // Save failed. Do not update the components.
-        return false;
-    }
-
-    // Update components to reflect changed names
-    std::vector<RESCUE_LOG> rescue_log;
-    update_components( components, candidates, part_name_suffix, rescue_log );
-
-    wxASSERT( !rescue_log.empty() );
-
-    // Try inserting the library into the project
-    if( insert_library( prj, rescue_lib.get(), 0 ) )
-    {
-        // Clean up wire ends
-        INSTALL_UNBUFFERED_DC( dc, m_canvas );
-        GetScreen()->SchematicCleanUp( NULL, &dc );
-        m_canvas->Refresh( true );
-        OnModify();
-
-        return true;
-    }
-    else
-    {
-        // Unsuccessful! Restore all the components
-        BOOST_FOREACH( RESCUE_LOG& rescue_log_item, rescue_log )
-        {
-            rescue_log_item.component->SetPartName( rescue_log_item.old_name );
-        }
-        wxMessageDialog dlg( this, _( "An error occurred while attempting to rescue components. No changes have been made." ) );
-        dlg.ShowModal();
-        return false;
-    }
-}
diff --git a/eeschema/lib_cache_rescue.h b/eeschema/lib_cache_rescue.h
deleted file mode 100644
index b84138f..0000000
--- a/eeschema/lib_cache_rescue.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * This program source code file is part of KiCad, a free EDA CAD application.
- *
- * Copyright (C) 2015 Chris Pavlina <pavlina.chris@xxxxxxxxx>
- * Copyright (C) 2015 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
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, you may find one here:
- * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
- * or you may search the http://www.gnu.org website for the version 2 license,
- * or you may write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
- */
-
-#ifndef _LIB_CACHE_RESCUE_H_
-#define _LIB_CACHE_RESCUE_H_
-
-/* This code handles the case where an old schematic has parts that have
- * changed in the system libraries, such that their pins no longer line up.
- * The function of note is a member of SCH_EDIT_FRAME, defined thus:
- *
- * bool SCH_EDIT_FRAME::RescueCacheConflicts( bool aSilentIfNone );
- *
- * When this is called, a list of component names referring to conflicting
- * symbols is compiled. If this list is empty, then the function displays
- * a notification and returns (if aSilentIfNone is true, the notification is
- * silenced).
- *
- * The user is then prompted to select which parts he would like to rescue.
- * Any remaining after he's through are rescued: they are renamed to avoid
- * further conflicts, and then they are copied into a new library. The
- * schematic components are updated to link to these new names, the library
- * is saved, and the library is added to the project at the top of the
- * search path.
- */
-
-#include <vector>
-#include <wx/string.h>
-
-class LIB_PART;
-class SCH_COMPONENT;
-
-class RESCUE_CANDIDATE
-{
-public:
-    wxString   requested_name;
-    LIB_PART*  cache_candidate;
-    LIB_PART*  lib_candidate;
-};
-
-class RESCUE_LOG
-{
-public:
-    SCH_COMPONENT*  component;
-    wxString        old_name;
-    wxString        new_name;
-};
-
-#endif // _LIB_CACHE_RESCUE_H_
diff --git a/eeschema/menubar.cpp b/eeschema/menubar.cpp
index d3f1205..c959c25 100644
--- a/eeschema/menubar.cpp
+++ b/eeschema/menubar.cpp
@@ -433,8 +433,8 @@ void SCH_EDIT_FRAME::ReCreateMenuBar()
 
     AddMenuItem( toolsMenu,
                  ID_RESCUE_CACHED,
-                 _( "&Rescue Cached Components" ),
-                 _( "Find old components in the project cache and rescue them to a new library" ),
+                 _( "&Rescue Old Components" ),
+                 _( "Find old components in the project and rename/rescue them" ),
                  KiBitmap( copycomponent_xpm ) );
 
     toolsMenu->AppendSeparator();
diff --git a/eeschema/project_rescue.cpp b/eeschema/project_rescue.cpp
new file mode 100644
index 0000000..76ccc46
--- /dev/null
+++ b/eeschema/project_rescue.cpp
@@ -0,0 +1,540 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2015 Chris Pavlina <pavlina.chris@xxxxxxxxx>
+ * Copyright (C) 2015 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <class_drawpanel.h>
+#include <class_library.h>
+#include <confirm.h>
+#include <invoke_sch_dialog.h>
+#include <kicad_device_context.h>
+#include <project_rescue.h>
+#include <sch_component.h>
+#include <sch_sheet.h>
+#include <schframe.h>
+#include <wildcards_and_files_ext.h>
+
+#include <boost/foreach.hpp>
+#include <map>
+
+
+typedef std::pair<SCH_COMPONENT*, wxString> COMPONENT_NAME_PAIR;
+
+
+/**
+ * Function save_library
+ * writes the library out to disk. Returns true on success.
+ *
+ * @param aFileName - Filename to receive the library
+ * @param aLibrary - Library to write
+ * @param aEditFrame - the calling SCH_EDIT_FRAME
+ */
+static bool save_library( const wxString& aFileName, PART_LIB* aLibrary, SCH_EDIT_FRAME* aEditFrame )
+{
+    try
+    {
+        FILE_OUTPUTFORMATTER formatter( aFileName );
+
+        if( !aLibrary->Save( formatter ) )
+        {
+            wxString msg = wxString::Format( _(
+                "An error occurred attempting to save component library '%s'." ),
+                GetChars( aFileName )
+                );
+            DisplayError( aEditFrame, msg );
+            return false;
+        }
+    }
+    catch( ... /* IO_ERROR ioe */ )
+    {
+        wxString msg = wxString::Format( _(
+            "Failed to create component library file '%s'" ),
+            GetChars( aFileName )
+            );
+        DisplayError( aEditFrame, msg );
+        return false;
+    }
+
+    return true;
+}
+
+
+/**
+ * Function insert_library
+ * inserts a library into the project and refreshes libraries.
+ *
+ * @param aProject - project that will be modified
+ * @param aLibrary - PART_LIB to add
+ * @param aIndex - index in the list at which the library is to be inserted
+ *
+ * @return true on success, false on failure
+ */
+static bool insert_library( PROJECT *aProject, PART_LIB *aLibrary, size_t aIndex ) throw( boost::bad_pointer )
+{
+    wxArrayString libNames;
+    wxString libPaths;
+
+    wxString libName = aLibrary->GetName();
+    PART_LIBS *libs = dynamic_cast<PART_LIBS*>( aProject->GetElem( PROJECT::ELEM_SCH_PART_LIBS ) );
+    if( !libs )
+    {
+        libs = new PART_LIBS();
+        aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, libs );
+    }
+
+    PART_LIBS::LibNamesAndPaths( aProject, false, &libPaths, &libNames );
+
+    // Make sure the library is not already in the list
+    while( libNames.Index( libName ) != wxNOT_FOUND )
+        libNames.Remove( libName );
+
+    // Add the library to the list and save
+    libNames.Insert( libName, aIndex );
+    PART_LIBS::LibNamesAndPaths( aProject, true, &libPaths, &libNames );
+
+    // Save the old libraries in case there is a problem after clear(). We'll
+    // put them back in.
+    boost::ptr_vector<PART_LIB> libsSave;
+    libsSave.transfer( libsSave.end(), libs->begin(), libs->end(), *libs );
+
+    aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, NULL );
+
+    libs = new PART_LIBS();
+    try
+    {
+        libs->LoadAllLibraries( aProject );
+    }
+    catch( const PARSE_ERROR& e )
+    {
+        // Some libraries were not found. There's no point in showing the error,
+        // because it was already shown. Just don't do anything.
+    }
+    catch( const IO_ERROR& e )
+    {
+        // Restore the old list
+        libs->clear();
+        libs->transfer( libs->end(), libsSave.begin(), libsSave.end(), libsSave );
+        return false;
+    }
+    aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, libs );
+
+    return true;
+}
+
+
+/**
+ * Function get_components
+ * Fills a vector with all of the project's components, to ease iterating over them.
+ *
+ * @param aComponents - a vector that will take the components
+ */
+static void get_components( std::vector<SCH_COMPONENT*>& aComponents )
+{
+    SCH_SCREENS screens;
+    for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
+    {
+        for( SCH_ITEM* item = screen->GetDrawItems(); item; item = item->Next() )
+        {
+            if( item->Type() != SCH_COMPONENT_T ) continue;
+            SCH_COMPONENT* component = dynamic_cast<SCH_COMPONENT*>( item );
+            aComponents.push_back( component );
+        }
+    }
+}
+
+
+/**
+ * Function find_component
+ * Search the libraries for the first component with a given name.
+ *
+ * @param aName - name to search for
+ * @param aLibs - the loaded PART_LIBS
+ * @param aCached - whether we are looking for the cached part
+ */
+static LIB_PART* find_component( wxString aName, PART_LIBS* aLibs, bool aCached )
+{
+    LIB_PART *part = NULL;
+
+    BOOST_FOREACH( PART_LIB& each_lib, *aLibs )
+    {
+        if( aCached && !each_lib.IsCache() )
+            continue;
+
+        if( !aCached && each_lib.IsCache() )
+            continue;
+
+        part = each_lib.FindPart( aName );
+        if( part )
+            break;
+    }
+
+    return part;
+}
+
+
+void RESCUER::RemoveDuplicates()
+{
+    std::vector<wxString> names_seen;
+
+    for( boost::ptr_vector<RESCUE_CANDIDATE>::iterator it = m_all_candidates.begin();
+            it != m_all_candidates.end(); )
+    {
+        bool seen_already = false;
+        BOOST_FOREACH( wxString& name_seen, names_seen )
+        {
+            if( name_seen == it->GetRequestedName() )
+            {
+                seen_already = true;
+                break;
+            }
+        }
+
+        if( seen_already )
+        {
+            m_all_candidates.erase( it );
+        }
+        else
+        {
+            names_seen.push_back( it->GetRequestedName() );
+            ++it;
+        }
+    }
+}
+
+
+class RESCUE_CASE_CANDIDATE: public RESCUE_CANDIDATE
+{
+    wxString m_requested_name;
+    wxString m_new_name;
+    LIB_PART* m_lib_candidate;
+
+public:
+    /**
+     * Function FindRescues
+     * Grab all possible RESCUE_CASE_CANDIDATES into a vector.
+     * @param aRescuer - the working RESCUER instance.
+     * @param aCandidates - the vector the will hold the candidates.
+     */
+    static void FindRescues( RESCUER& aRescuer, boost::ptr_vector<RESCUE_CANDIDATE>& aCandidates )
+    {
+        typedef std::map<wxString, RESCUE_CASE_CANDIDATE> candidate_map_t;
+        candidate_map_t candidate_map;
+
+        BOOST_FOREACH( SCH_COMPONENT* each_component, *( aRescuer.GetComponents() ) )
+        {
+            wxString part_name( each_component->GetPartName() );
+
+            LIB_PART* case_sensitive_match = find_component( part_name, aRescuer.GetLibs(), /* aCached */ true );
+            std::vector<LIB_ALIAS*> case_insensitive_matches;
+            aRescuer.GetLibs()->FindLibraryNearEntries( case_insensitive_matches, part_name );
+
+            if( case_sensitive_match || !( case_insensitive_matches.size() ) )
+                continue;
+
+            RESCUE_CASE_CANDIDATE candidate(
+                part_name, case_insensitive_matches[0]->GetName(),
+                case_insensitive_matches[0]->GetPart() );
+            candidate_map[part_name] = candidate;
+        }
+
+        // Now, dump the map into aCandidates
+        BOOST_FOREACH( const candidate_map_t::value_type& each_pair, candidate_map )
+        {
+            aCandidates.push_back( new RESCUE_CASE_CANDIDATE( each_pair.second ) );
+        }
+    }
+
+    /**
+     * Constructor RESCUE_CANDIDATE
+     * @param aRequestedName - the name the schematic asks for
+     * @param aNewName - the name we want to change it to
+     * @param aLibCandidate - the part that will give us
+     */
+    RESCUE_CASE_CANDIDATE( const wxString& aRequestedName, const wxString& aNewName,
+            LIB_PART* aLibCandidate )
+        : m_requested_name( aRequestedName ),
+          m_new_name( aNewName ),
+          m_lib_candidate( aLibCandidate ) { }
+
+    RESCUE_CASE_CANDIDATE() {}
+
+    virtual wxString GetRequestedName() const { return m_requested_name; }
+    virtual wxString GetNewName() const { return m_new_name; }
+    virtual LIB_PART* GetLibCandidate() const { return m_lib_candidate; }
+    virtual wxString GetActionDescription() const
+    {
+        wxString action;
+        action.Printf( _( "Rename to %s" ), m_new_name );
+        return action;
+    }
+
+    virtual bool PerformAction( RESCUER* aRescuer )
+    {
+        BOOST_FOREACH( SCH_COMPONENT* each_component, *aRescuer->GetComponents() )
+        {
+            if( each_component->GetPartName() != m_requested_name ) continue;
+            each_component->SetPartName( m_new_name );
+            aRescuer->LogRescue( each_component, m_requested_name, m_new_name );
+        }
+        return true;
+    }
+};
+
+
+class RESCUE_CACHE_CANDIDATE: public RESCUE_CANDIDATE
+{
+    wxString m_requested_name;
+    wxString m_new_name;
+    LIB_PART* m_cache_candidate;
+    LIB_PART* m_lib_candidate;
+
+    static std::auto_ptr<PART_LIB> m_rescue_lib;
+    static wxFileName m_library_fn;
+
+public:
+    /**
+     * Function FindRescues
+     * Grab all possible RESCUE_CACHE_CANDIDATEs into a vector.
+     * @param aRescuer - the working RESCUER instance.
+     * @param aCandidates - the vector the will hold the candidates.
+     */
+    static void FindRescues( RESCUER& aRescuer, boost::ptr_vector<RESCUE_CANDIDATE>& aCandidates )
+    {
+        typedef std::map<wxString, RESCUE_CACHE_CANDIDATE> candidate_map_t;
+        candidate_map_t candidate_map;
+
+        wxString part_name_suffix = wxT( "-RESCUE-" ) + aRescuer.GetPrj()->GetProjectName();
+
+        BOOST_FOREACH( SCH_COMPONENT* each_component, *( aRescuer.GetComponents() ) )
+        {
+            wxString part_name( each_component->GetPartName() );
+
+            LIB_PART* cache_match = find_component( part_name, aRescuer.GetLibs(), /* aCached */ true );
+            LIB_PART* lib_match = find_component( part_name, aRescuer.GetLibs(), /* aCached */ false );
+
+            // Test whether there is a conflict
+            if( !cache_match || !lib_match )
+                continue;
+            if( !cache_match->PinsConflictWith( *lib_match, /* aTestNums */ true,
+                    /* aTestNames */ true, /* aTestType */ true, /* aTestOrientation */ true,
+                    /* aTestLength */ false ))
+                continue;
+
+            RESCUE_CACHE_CANDIDATE candidate(
+                part_name, part_name + part_name_suffix,
+                cache_match, lib_match );
+            candidate_map[part_name] = candidate;
+        }
+
+        // Now, dump the map into aCandidates
+        BOOST_FOREACH( const candidate_map_t::value_type& each_pair, candidate_map )
+        {
+            aCandidates.push_back( new RESCUE_CACHE_CANDIDATE( each_pair.second ) );
+        }
+    }
+
+    /**
+     * Constructor RESCUE_CACHE_CANDIDATE
+     * @param aRequestedName - the name the schematic asks for
+     * @param aNewName - the name we want to change it to
+     * @param aCacheCandidate - the part from the cache
+     * @param aLibCandidate - the part that would be loaded from the library
+     */
+    RESCUE_CACHE_CANDIDATE( const wxString& aRequestedName, const wxString& aNewName,
+            LIB_PART* aCacheCandidate, LIB_PART* aLibCandidate )
+        : m_requested_name( aRequestedName ),
+          m_new_name( aNewName ),
+          m_cache_candidate( aCacheCandidate ),
+          m_lib_candidate( aLibCandidate ) { }
+
+    RESCUE_CACHE_CANDIDATE() {}
+
+    virtual wxString GetRequestedName() const { return m_requested_name; }
+    virtual wxString GetNewName() const { return m_new_name; }
+    virtual LIB_PART* GetCacheCandidate() const { return m_cache_candidate; }
+    virtual LIB_PART* GetLibCandidate() const { return m_lib_candidate; }
+    virtual wxString GetActionDescription() const
+    {
+        wxString action;
+        action.Printf( _( "Rescue %s as %s" ), m_requested_name, m_new_name );
+        return action;
+    }
+
+    /**
+     * Function OpenRescueLibrary
+     * Creates the new rescue library. Must be called before calling any PerformAction()s.
+     */
+    static void OpenRescueLibrary()
+    {
+        wxFileName fn( g_RootSheet->GetScreen()->GetFileName() );
+        fn.SetName( fn.GetName() + wxT( "-rescue" ) );
+        fn.SetExt( SchematicLibraryFileExtension );
+        m_library_fn.SetPath( fn.GetPath() );
+        m_library_fn.SetName( fn.GetName() );
+        m_library_fn.SetExt( wxT( "lib" ) );
+
+        std::auto_ptr<PART_LIB> rescue_lib( new PART_LIB( LIBRARY_TYPE_EESCHEMA,
+                        fn.GetFullPath() ) );
+
+        m_rescue_lib = rescue_lib;
+    }
+
+    virtual bool PerformAction( RESCUER* aRescuer )
+    {
+        LIB_PART new_part( *m_cache_candidate, m_rescue_lib.get() );
+        new_part.SetName( m_new_name );
+        new_part.RemoveAllAliases();
+        RESCUE_CACHE_CANDIDATE::m_rescue_lib.get()->AddPart( &new_part );
+
+        BOOST_FOREACH( SCH_COMPONENT* each_component, *aRescuer->GetComponents() )
+        {
+            if( each_component->GetPartName() != m_requested_name ) continue;
+            each_component->SetPartName( m_new_name );
+            aRescuer->LogRescue( each_component, m_requested_name, m_new_name );
+        }
+        return true;
+    }
+
+    /**
+     * Function WriteRescueLibrary
+     * Writes out the rescue library. Called after successful PerformAction()s. If this fails,
+     * undo the actions.
+     * @return True on success.
+     */
+    static bool WriteRescueLibrary( SCH_EDIT_FRAME *aEditFrame, PROJECT* aProject )
+    {
+
+        if( !save_library( m_library_fn.GetFullPath(), m_rescue_lib.get(), aEditFrame ) )
+            return false;
+        return insert_library( aProject, m_rescue_lib.get(), 0 );
+    }
+};
+
+std::auto_ptr<PART_LIB> RESCUE_CACHE_CANDIDATE::m_rescue_lib;
+wxFileName RESCUE_CACHE_CANDIDATE::m_library_fn;
+
+RESCUER::RESCUER( SCH_EDIT_FRAME& aEditFrame, PROJECT& aProject )
+{
+    get_components( m_components );
+    m_prj = &aProject;
+    m_libs = m_prj->SchLibs();
+    m_edit_frame = &aEditFrame;
+}
+
+
+void RESCUER::FindCandidates()
+{
+    RESCUE_CASE_CANDIDATE::FindRescues( *this, m_all_candidates );
+    RESCUE_CACHE_CANDIDATE::FindRescues( *this, m_all_candidates );
+}
+
+
+void RESCUER::InvokeDialog( bool aAskShowAgain )
+{
+    InvokeDialogRescueEach( m_edit_frame, *this, aAskShowAgain );
+}
+
+
+void RESCUER::LogRescue( SCH_COMPONENT *aComponent, const wxString &aOldName,
+        const wxString &aNewName )
+{
+    RESCUE_LOG logitem;
+    logitem.component = aComponent;
+    logitem.old_name = aOldName;
+    logitem.new_name = aNewName;
+    m_rescue_log.push_back( logitem );
+}
+
+
+bool RESCUER::DoRescues()
+{
+    BOOST_FOREACH( RESCUE_CANDIDATE* each_candidate, m_chosen_candidates )
+    {
+        if( ! each_candidate->PerformAction( this ) )
+            return false;
+    }
+    return true;
+}
+
+
+void RESCUER::UndoRescues()
+{
+    BOOST_FOREACH( RESCUE_LOG& each_logitem, m_rescue_log )
+    {
+        each_logitem.component->SetPartName( each_logitem.old_name );
+    }
+}
+
+
+bool SCH_EDIT_FRAME::RescueProject( bool aRunningOnDemand )
+{
+    // Data that will be used throughout the operation
+    std::vector<RESCUE_CANDIDATE>   candidates;
+    wxString                part_name_suffix;
+
+    RESCUER rescuer( *this, Prj() );
+
+    rescuer.FindCandidates();
+
+    if( ! rescuer.GetCandidateCount() )
+    {
+        if( aRunningOnDemand )
+        {
+            wxMessageDialog dlg( this, _( "This project has nothing to rescue." ) );
+            dlg.ShowModal();
+        }
+        return true;
+    }
+
+    rescuer.RemoveDuplicates();
+
+    rescuer.InvokeDialog( !aRunningOnDemand );
+
+    // If no components were rescued, let the user know what's going on. He might
+    // have clicked cancel by mistake, and should have some indication of that.
+    if( !rescuer.GetChosenCandidateCount() )
+    {
+        wxMessageDialog dlg( this, _( "No symbols were rescued." ) );
+        dlg.ShowModal();
+        return true;
+    }
+
+    RESCUE_CACHE_CANDIDATE::OpenRescueLibrary();
+
+    if( !rescuer.DoRescues() )
+    {
+        rescuer.UndoRescues();
+        return false;
+    }
+
+    RESCUE_CACHE_CANDIDATE::WriteRescueLibrary( this, &Prj() );
+
+    Prj().SetElem( PROJECT::ELEM_SCH_PART_LIBS, NULL );
+
+    // Clean up wire ends
+    INSTALL_UNBUFFERED_DC( dc, m_canvas );
+    GetScreen()->SchematicCleanUp( NULL, &dc );
+    m_canvas->Refresh( true );
+    OnModify();
+
+    return true;
+}
diff --git a/eeschema/project_rescue.h b/eeschema/project_rescue.h
new file mode 100644
index 0000000..bc961da
--- /dev/null
+++ b/eeschema/project_rescue.h
@@ -0,0 +1,190 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2015 Chris Pavlina <pavlina.chris@xxxxxxxxx>
+ * Copyright (C) 2015 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef _LIB_CACHE_RESCUE_H_
+#define _LIB_CACHE_RESCUE_H_
+
+/* This code handles the case where an old schematic was made before
+ * various changes were made, either to KiCad or to the libraries, and
+ * the project needs to be recovered. The function of note is a member
+ * of SCH_EDIT_FRAME, defined thus:
+ *
+ * bool SCH_EDIT_FRAME::RescueProject( bool aSilentIfNone );
+ *
+ * When this is called, a list of problematic components is compiled. If
+ * this list is empty, then the function displays a notification and returns
+ * (if aSilentIfNone is true, the notification is silenced).
+ */
+
+#include <schframe.h>
+
+#include <vector>
+#include <wx/string.h>
+#include <boost/ptr_container/ptr_vector.hpp>
+
+class LIB_PART;
+class SCH_COMPONENT;
+class RESCUER;
+
+enum RESCUE_TYPE
+{
+    RESCUE_CONFLICT,
+    RESCUE_CASE,
+};
+
+class RESCUE_CANDIDATE
+{
+public:
+    /**
+     * Function GetRequestedName
+     * Get the name that was originally requested in the schematic
+     */
+    virtual wxString GetRequestedName() const = 0;
+
+    /**
+     * Function GetNewName
+     * Get the name we're proposing changing it to
+     */
+    virtual wxString GetNewName() const = 0;
+
+    /**
+     * Function GetCacheCandidate
+     * Get the part that can be loaded from the project cache, if possible, or
+     * else NULL.
+     */
+    virtual LIB_PART* GetCacheCandidate() const { return NULL; }
+
+    /**
+     * Function GetLibCandidate
+     * Get the part the would be loaded from the libraries, if possible, or else
+     * NULL.
+     */
+    virtual LIB_PART* GetLibCandidate() const { return NULL; }
+
+    /**
+     * Function GetActionDescription
+     * Get a description of the action proposed, for displaying in the UI.
+     */
+    virtual wxString GetActionDescription() const = 0;
+
+    /**
+     * Function PerformAction
+     * Perform the actual rescue action. If successful, this must log the rescue using
+     * RESCUER::LogRescue to allow it to be reversed.
+     * @return True on success.
+     */
+    virtual bool PerformAction( RESCUER* aRescuer ) = 0;
+};
+
+class RESCUE_LOG
+{
+public:
+    SCH_COMPONENT*  component;
+    wxString        old_name;
+    wxString        new_name;
+};
+
+class RESCUER
+{
+    friend class DIALOG_RESCUE_EACH;
+
+    std::vector<SCH_COMPONENT*> m_components;
+    PART_LIBS* m_libs;
+    PROJECT* m_prj;
+    SCH_EDIT_FRAME* m_edit_frame;
+
+    boost::ptr_vector<RESCUE_CANDIDATE> m_all_candidates;
+    std::vector<RESCUE_CANDIDATE*> m_chosen_candidates;
+
+    std::vector<RESCUE_LOG> m_rescue_log;
+
+public:
+    RESCUER( SCH_EDIT_FRAME& aEditFrame, PROJECT& aProject );
+
+    /**
+     * Function FindCandidates
+     * Populate the RESCUER with all possible candidates.
+     */
+    void FindCandidates();
+
+    /**
+     * Function RemoveDuplicates
+     * Filter out duplicately named rescue candidates.
+     */
+    void RemoveDuplicates();
+
+    /**
+     * Function GetCandidateCount
+     */
+    size_t GetCandidateCount() { return m_all_candidates.size(); }
+
+    /**
+     * Function GetChosenCandidateCount
+     */
+    size_t GetChosenCandidateCount() { return m_chosen_candidates.size(); }
+
+    /**
+     * Function GetComponents
+     */
+    std::vector<SCH_COMPONENT*>* GetComponents() { return &m_components; }
+
+    /**
+     * Function GetLibs
+     */
+    PART_LIBS* GetLibs() { return m_libs; }
+
+    /**
+     * Function GetPrj
+     */
+    PROJECT* GetPrj() { return m_prj; }
+
+    /**
+     * Function InvokeDialog
+     * Display a dialog to allow the user to select rescues.
+     * @param aAskShowAgain - whether the "Never Show Again" button should be visible
+     */
+    void InvokeDialog( bool aAskShowAgain );
+
+    /**
+     * Function LogRescue
+     * Used by individual RESCUE_CANDIDATEs to log a rescue for undoing.
+     */
+    void LogRescue( SCH_COMPONENT *aComponent, const wxString& aOldName,
+            const wxString& aNewName );
+
+    /**
+     * Function DoRescues
+     * Perform all chosen rescue actions, logging them to be undone if necessary.
+     * @return True on success
+     */
+    bool DoRescues();
+
+    /**
+     * Function UndoRescues
+     * Reverse the effects of all rescues on the project.
+     */
+    void UndoRescues();
+};
+
+#endif // _LIB_CACHE_RESCUE_H_
diff --git a/eeschema/schframe.cpp b/eeschema/schframe.cpp
index 769d6cd..9053a56 100644
--- a/eeschema/schframe.cpp
+++ b/eeschema/schframe.cpp
@@ -243,7 +243,7 @@ BEGIN_EVENT_TABLE( SCH_EDIT_FRAME, EDA_DRAW_FRAME )
     EVT_TOOL( ID_RUN_LIBRARY, SCH_EDIT_FRAME::OnOpenLibraryEditor )
     EVT_TOOL( ID_POPUP_SCH_CALL_LIBEDIT_AND_LOAD_CMP, SCH_EDIT_FRAME::OnOpenLibraryEditor )
     EVT_TOOL( ID_TO_LIBVIEW, SCH_EDIT_FRAME::OnOpenLibraryViewer )
-    EVT_TOOL( ID_RESCUE_CACHED, SCH_EDIT_FRAME::OnRescueCached )
+    EVT_TOOL( ID_RESCUE_CACHED, SCH_EDIT_FRAME::OnRescueProject )
 
     EVT_TOOL( ID_RUN_PCB, SCH_EDIT_FRAME::OnOpenPcbnew )
     EVT_TOOL( ID_RUN_PCB_MODULE_EDITOR, SCH_EDIT_FRAME::OnOpenPcbModuleEditor )
@@ -1120,9 +1120,9 @@ void SCH_EDIT_FRAME::OnOpenLibraryEditor( wxCommandEvent& event )
     }
 }
 
-void SCH_EDIT_FRAME::OnRescueCached( wxCommandEvent& event )
+void SCH_EDIT_FRAME::OnRescueProject( wxCommandEvent& event )
 {
-    RescueCacheConflicts( true );
+    RescueProject( true );
 }
 
 void SCH_EDIT_FRAME::OnExit( wxCommandEvent& event )
diff --git a/eeschema/schframe.h b/eeschema/schframe.h
index 9f55bb1..38fb7e5 100644
--- a/eeschema/schframe.h
+++ b/eeschema/schframe.h
@@ -832,7 +832,7 @@ private:
     void OnOpenPcbModuleEditor( wxCommandEvent& event );
     void OnOpenCvpcb( wxCommandEvent& event );
     void OnOpenLibraryEditor( wxCommandEvent& event );
-    void OnRescueCached( wxCommandEvent& event );
+    void OnRescueProject( wxCommandEvent& event );
     void OnPreferencesOptions( wxCommandEvent& event );
     void OnCancelCurrentCommand( wxCommandEvent& aEvent );
 
@@ -1307,10 +1307,13 @@ public:
     bool CreateArchiveLibrary( const wxString& aFileName );
 
     /**
-     * Function RescueCacheConflicts
-     * exports components from the '-cache' library that conflict with parts
-     * in the project libraries to a new library, renaming them to add a suffix
-     * of the root document's name to avoid conflicts.
+     * Function RescueProject
+     * performs rescue operations to recover old projects from before certain
+     * changes were made.
+     *
+     * - Exports cached symbols that conflict with new symbols to a separate
+     *   library
+     * - Renames symbols named before libraries were case sensitive
      *
      * @param aRunningOnDemand - indicates whether the tool has been called up by the user
      *      (as opposed to being run automatically). If true, an information dialog is
@@ -1318,7 +1321,7 @@ public:
      *      if there are no components to rescue, and a "Never Show Again" button is
      *      displayed.
      */
-    bool RescueCacheConflicts( bool aRunningOnDemand );
+    bool RescueProject( bool aRunningOnDemand );
 
     /**
      * Function PrintPage

Follow ups