← Back to team overview

kicad-developers team mailing list archive

Re: [PATCH] Add footprint select dropdown to component chooser, serious refactoring

 

Hi again,

Updated patch unless this doesn't apply cleanly anymore after b47a6e4


On Wed, Mar 22, 2017 at 09:09:01PM -0400, Chris Pavlina wrote:
> Hi,
> 
> Footprint selection in the component chooser is now working - here is a
> patch. I'd like to merge this, but it required serious refactoring to
> make everything work cleanly, so I'm posting to the list. Wayne, please
> have a look when you get a chance.
> 
> There are a couple known issues, but IMO they aren't merge-stoppers. The
> sooner I get this merged the sooner I can get actual feedback, and the
> smoother integration of any changes that are made can be.
> 
> Here's a summary (yes, the *summary* is big):
> 
> - DIALOG_CHOOSE_COMPONENT changes:
>    + Add FOOTPRINT_SELECT_WIDGET.
>    + Add support for DIALOG_CHOOSE_COMPONENT to pass arbitrary field value
>      overrides to the caller. This is of course to allow setting the footprint;
>      in the future it could be used to allow more field edits.
>    + Add an option to hide everything that can edit fields, for use when
>      that doesn't make sense (libedit, etc).
> 
> - Add FOOTPRINT_SELECT_WIDGET
>   This is an adapter widget that combines into one FOOTPRINT_CHOICE view:
>     + Footprint listings from FOOTPRINT_LIST
>     + Filtering from FOOTPRINT_FILTER
>     + Loading progress display from FOOTPRINT_ASYNC_LOADER and wxGauge
> 
>   It presents as a status progress bar that transforms in-place to a selection
>   dropdown when the footprints finish loading. The GUI remains fully interactive
>   as the footprints load; the user can even exit and reopen the dialog and it
>   will continue to load in the background.
> 
> - Add FOOTPRINT_CHOICE widget
>   This is a customized wxComboCtrl with some extra features:
>     + Greying out of library name for readability
>     + List separators
> 
> - Add FOOTPRINT_FILTER class
>   Provides a reusable filtered view of a FOOTPRINT_LIST, fully iterable
> 
>    + Make cvpcb use FOOTPRINT_FILTER instead of providing its own filter
>      code.
> 
> - Seriously rework FOOTPRINT_INFO
>    + Add partially asynchronous loading via the new FP_LIB_TABLE::PrefetchLib.
>      A FOOTPRINT_ASYNC_LOADER class is added that can spawn loader threads and
>      provide progress updates to the GUI while they work.
>    + Completely rewrite footprint loader worker threads. They are now a
>      queue-driven pool of workers rather than each loading a fixed number
>      of libs (more efficient, a bit faster) and the main thread does no
>      work, so it can return.
>    + Make FOOTPRINT_INFO available to the world, by making them virtual base
>      classes, putting the real implementation in
>      pcbnew/footprint_info_impl.h/cpp, and adding a factory function to
>      create an instance from anywhere via Kiface.
> 
> - Add FP_LIB_TABLE::PrefetchLib
>   This pulls everything that is async-safe (download from github, but not
>   parsing due to a threadsafety issue) into a separate loader so the user
>   can continue interacting as footprints download.
> 
>   Parsing itself remains synchronous, but the time it takes is tiny
>   compared to downloading.
> 
> - Allow access to the global fp_lib_table from anywhere via kiface
>   (IfaceOrAddress()). Most methods to manipulate the table are still not
>   compiled in everywhere (they have seriously large dependencies), but the
>   table can be fetched as an opaque object.
> 
> - Add a SYNC_QUEUE template class providing a std::queue wrapper with locking,
>   for ease of passing things to and from worker threads.
> 
> - Minor changes:
>    + Add EDA_PATTERN_MATCH::GetPattern()
>    + Create a type to represent component history items instead of just storing
>      a list of strings and a unit. We'll need to track footprints too.
>    + Make DIALOG_CHOOSE_COMPONENT quasimodal so it can summon the footprint
>      picker.
>    + Add kiface_ids.h for storing arbitrary IDs used in kiface. This is used
>      for KifaceOrAddress(), as I'm the first person to actually use that method
>    + Remove KICAD_FOOTPRINT_SELECTOR build option, no reason for it to be
>      optional now.
> 
> ===========
> Known issues, planned improvements:
> 
> - History items don't remember their selected footprints.
>   There are some implementation issues and UI/UX issues I still have to work
>   out.
> 
> - eeschema and cvpcb/pcbnew have separate footprint caches, so the first time
>   you open one of the latter, it'll fetch the footprints even if eeschema
>   already did.
> 
> - FOOTPRINT_ASYNC_LOADER can also be used in cvpcb so cvpcb doesn't freeze
>   as the footprints load! :)
> 
> - Footprint picker isn't hidden in standalone mode, but it's useless.
> 
> -- 
> Chris

>From f35678eb5d8f969a69c95ee60a360b7674678185 Mon Sep 17 00:00:00 2001
From: Chris Pavlina <pavlina.chris@xxxxxxxxx>
Date: Wed, 22 Mar 2017 20:59:25 -0400
Subject: [PATCH] Add footprint select dropdown to component chooser, serious
 refactoring

- DIALOG_CHOOSE_COMPONENT has footprint select widget
- FOOTPRINT_SELECT_WIDGET
- FOOTPRINT_CHOICE widget (customized wxComboCtrl)
- FOOTPRINT_FILTER class
- FOOTPRINT_INFO rework:
    - FOOTPRINT_ASYNC_LOADER to load without freezing UI
    - Rewrite loader threads as queue-driven thread pool
    - Make FOOTPRINT_INFO available via kiway
- FP_LIB_TABLE::PrefetchLib
- Access to global fp-lib-table via kiway
- SYNC_QUEUE threadsafe queue template
- Remove KICAD_FOOTPRINT_SELECTOR build option
---
 CMakeLists.txt                                     |   6 -
 common/CMakeLists.txt                              |   5 +-
 common/eda_pattern_match.cpp                       |  20 ++
 common/footprint_filter.cpp                        | 230 ++++++++++++++
 common/footprint_info.cpp                          | 330 +++++++--------------
 common/fp_lib_table.cpp                            |  10 +-
 common/project.cpp                                 |  45 ++-
 common/widgets/footprint_choice.cpp                | 262 ++++++++++++++++
 common/widgets/footprint_select_widget.cpp         | 304 +++++++++++++++++++
 cvpcb/autosel.cpp                                  |   6 +-
 cvpcb/class_DisplayFootprintsFrame.cpp             |   6 +-
 cvpcb/class_footprints_listbox.cpp                 |  69 ++---
 cvpcb/cvpcb_mainframe.cpp                          |  25 +-
 cvpcb/cvpcb_mainframe.h                            |   5 +-
 cvpcb/listview_classes.h                           |  12 +-
 cvpcb/readwrite_dlgs.cpp                           |   5 +-
 eeschema/dialogs/dialog_choose_component.cpp       | 247 +++++++++------
 eeschema/dialogs/dialog_choose_component.h         |  55 +++-
 .../dialogs/dialog_edit_component_in_schematic.cpp |  12 +-
 eeschema/getpart.cpp                               | 127 ++++----
 eeschema/libedit.cpp                               |  20 +-
 eeschema/onleftclick.cpp                           |  13 +-
 eeschema/sch_base_frame.h                          |  63 ++--
 eeschema/schframe.h                                |  12 +-
 eeschema/viewlibs.cpp                              |   4 +-
 include/eda_pattern_match.h                        |  14 +-
 include/footprint_filter.h                         | 148 +++++++++
 include/footprint_info.h                           | 288 +++++++++++++-----
 include/fp_lib_table.h                             |  15 +-
 include/kiface_ids.h                               |  49 +++
 include/project.h                                  |  14 +-
 include/sync_queue.h                               | 113 +++++++
 include/widgets/footprint_choice.h                 | 113 +++++++
 include/widgets/footprint_select_widget.h          | 161 ++++++++++
 pcbnew/CMakeLists.txt                              |   1 +
 pcbnew/footprint_info_impl.cpp                     | 230 ++++++++++++++
 pcbnew/footprint_info_impl.h                       |  94 ++++++
 pcbnew/github/github_plugin.cpp                    |  24 +-
 pcbnew/github/github_plugin.h                      |   8 +-
 pcbnew/io_mgr.h                                    |  24 +-
 pcbnew/loadcmp.cpp                                 |   7 +-
 pcbnew/modview_frame.cpp                           |  15 +-
 pcbnew/pcb_netlist.cpp                             |  32 --
 pcbnew/pcb_netlist.h                               |   8 -
 pcbnew/pcbnew.cpp                                  |  15 +-
 pcbnew/plugin.cpp                                  |   9 +-
 46 files changed, 2622 insertions(+), 653 deletions(-)
 create mode 100644 common/footprint_filter.cpp
 create mode 100644 common/widgets/footprint_choice.cpp
 create mode 100644 common/widgets/footprint_select_widget.cpp
 create mode 100644 include/footprint_filter.h
 create mode 100644 include/kiface_ids.h
 create mode 100644 include/sync_queue.h
 create mode 100644 include/widgets/footprint_choice.h
 create mode 100644 include/widgets/footprint_select_widget.h
 create mode 100644 pcbnew/footprint_info_impl.cpp
 create mode 100644 pcbnew/footprint_info_impl.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 18f2052e1..0d72dbef5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -101,8 +101,6 @@ option( BUILD_GITHUB_PLUGIN "Build the GITHUB_PLUGIN for pcbnew." ON )
 
 option( KICAD_SPICE "Build Kicad with internal Spice simulator." OFF )
 
-option( KICAD_FOOTPRINT_SELECTOR "Build experimental eeschema footprint selector." OFF )
-
 # Global setting: exports are explicit
 set( CMAKE_CXX_VISIBILITY_PRESET "hidden" )
 set( CMAKE_VISIBILITY_INLINES_HIDDEN ON )
@@ -321,10 +319,6 @@ if( KICAD_SPICE )
     add_definitions( -DKICAD_SPICE )
 endif()
 
-if( KICAD_FOOTPRINT_SELECTOR )
-    add_definitions( -DKICAD_FOOTPRINT_SELECTOR )
-endif()
-
 if( KICAD_USE_SCH_IO_MANAGER )
     add_definitions( -DKICAD_USE_SCH_IO_MANAGER )
 endif()
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index c9744915c..ad1cd5dee 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -171,6 +171,8 @@ set( COMMON_WIDGET_SRCS
     widgets/widget_hotkey_list.cpp
     widgets/two_column_tree_list.cpp
     widgets/footprint_preview_widget.cpp
+    widgets/footprint_select_widget.cpp
+    widgets/footprint_choice.cpp
     widgets/indicator_icon.cpp
     )
 
@@ -243,6 +245,8 @@ set( COMMON_SRCS
     exceptions.cpp
     executable_names.cpp
     filter_reader.cpp
+    footprint_info.cpp
+    footprint_filter.cpp
     lib_id.cpp
     lib_table_keywords.cpp
 #    findkicadhelppath.cpp.notused      deprecated, use searchhelpfilefullpath.cpp
@@ -351,7 +355,6 @@ set( PCB_COMMON_SRCS
     eda_text.cpp
     class_page_info.cpp
     lset.cpp
-    footprint_info.cpp
     ../pcbnew/basepcbframe.cpp
     ../pcbnew/class_board.cpp
     ../pcbnew/class_board_connected_item.cpp
diff --git a/common/eda_pattern_match.cpp b/common/eda_pattern_match.cpp
index b09094e0d..f2d8bced0 100644
--- a/common/eda_pattern_match.cpp
+++ b/common/eda_pattern_match.cpp
@@ -34,6 +34,12 @@ bool EDA_PATTERN_MATCH_SUBSTR::SetPattern( const wxString& aPattern )
 }
 
 
+wxString const& EDA_PATTERN_MATCH_SUBSTR::GetPattern() const
+{
+    return m_pattern;
+}
+
+
 int EDA_PATTERN_MATCH_SUBSTR::Find( const wxString& aCandidate ) const
 {
     int loc = aCandidate.Find( m_pattern );
@@ -75,6 +81,12 @@ bool EDA_PATTERN_MATCH_REGEX::SetPattern( const wxString& aPattern )
 }
 
 
+wxString const& EDA_PATTERN_MATCH_REGEX::GetPattern() const
+{
+    return m_pattern;
+}
+
+
 int EDA_PATTERN_MATCH_REGEX::Find( const wxString& aCandidate ) const
 {
     if( m_regex.IsValid() )
@@ -100,6 +112,8 @@ int EDA_PATTERN_MATCH_REGEX::Find( const wxString& aCandidate ) const
 
 bool EDA_PATTERN_MATCH_WILDCARD::SetPattern( const wxString& aPattern )
 {
+    m_wildcard_pattern = aPattern;
+
     // Compile the wildcard string to a regular expression
     wxString regex;
     regex.Alloc( 2 * aPattern.Length() );   // no need to keep resizing, we know the size roughly
@@ -132,6 +146,12 @@ bool EDA_PATTERN_MATCH_WILDCARD::SetPattern( const wxString& aPattern )
 }
 
 
+wxString const& EDA_PATTERN_MATCH_WILDCARD::GetPattern() const
+{
+    return m_wildcard_pattern;
+}
+
+
 int EDA_PATTERN_MATCH_WILDCARD::Find( const wxString& aCandidate ) const
 {
     return EDA_PATTERN_MATCH_REGEX::Find( aCandidate );
diff --git a/common/footprint_filter.cpp b/common/footprint_filter.cpp
new file mode 100644
index 000000000..c6929758e
--- /dev/null
+++ b/common/footprint_filter.cpp
@@ -0,0 +1,230 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2016 Jean-Pierre Charras, jp.charras at wanadoo.fr
+ * Copyright (C) 1992-2017 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 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 <footprint_filter.h>
+#include <make_unique.h>
+#include <stdexcept>
+
+using FOOTPRINT_FILTER_IT = FOOTPRINT_FILTER::ITERATOR;
+
+
+FOOTPRINT_FILTER::ITERATOR::ITERATOR() : m_pos( 0 ), m_filter( nullptr )
+{
+}
+
+
+FOOTPRINT_FILTER::ITERATOR::ITERATOR( FOOTPRINT_FILTER_IT const& aOther )
+        : m_pos( aOther.m_pos ), m_filter( aOther.m_filter )
+{
+}
+
+
+FOOTPRINT_FILTER::ITERATOR::ITERATOR( FOOTPRINT_FILTER& aFilter )
+        : m_pos( (size_t) -1 ), m_filter( &aFilter )
+{
+    increment();
+}
+
+
+void FOOTPRINT_FILTER_IT::increment()
+{
+    bool found = false;
+
+    if( !m_filter || !m_filter->m_list || m_filter->m_list->GetCount() == 0 )
+    {
+        m_pos = 0;
+        return;
+    }
+
+    auto  filter_type       = m_filter->m_filter_type;
+    auto  list              = m_filter->m_list;
+    auto& lib_name          = m_filter->m_lib_name;
+    auto& filter_pattern    = m_filter->m_filter_pattern;
+    auto& filter            = m_filter->m_filter;
+
+    for( ++m_pos; m_pos < list->GetCount() && !found; ++m_pos )
+    {
+        found = true;
+
+        if( ( filter_type & FOOTPRINT_FILTER::FILTERING_BY_LIBRARY ) && !lib_name.IsEmpty()
+                && !list->GetItem( m_pos ).InLibrary( lib_name ) )
+            found = false;
+
+        if( ( filter_type & FOOTPRINT_FILTER::FILTERING_BY_COMPONENT_KEYWORD )
+                && !FootprintFilterMatch( list->GetItem( m_pos ) ) )
+            found = false;
+
+        if( ( filter_type & FOOTPRINT_FILTER::FILTERING_BY_PIN_COUNT )
+                && !PinCountMatch( list->GetItem( m_pos ) ) )
+            found = false;
+
+        if( ( filter_type & FOOTPRINT_FILTER::FILTERING_BY_NAME ) && !filter_pattern.IsEmpty() )
+        {
+            wxString currname;
+
+            // If the search string contains a ':' character,
+            // include the library name in the search string
+            // e.g. LibName:FootprintName
+            if( filter_pattern.Contains( ":" ) )
+                currname = list->GetItem( m_pos ).GetNickname().Lower() + ":";
+
+            currname += list->GetItem( m_pos ).GetFootprintName().Lower();
+
+            if( filter.Find( currname ) == EDA_PATTERN_NOT_FOUND )
+                found = false;
+        }
+
+        if( filter_type == FOOTPRINT_FILTER::UNFILTERED_FP_LIST )
+        {
+            // override
+            found = true;
+        }
+    }
+
+    // for loop will stop one past the correct item
+    if( found )
+        --m_pos;
+}
+
+
+bool FOOTPRINT_FILTER_IT::equal( FOOTPRINT_FILTER_IT const& aOther ) const
+{
+    // Invalid iterators are always equal
+    return ( m_pos == aOther.m_pos ) && ( m_filter == aOther.m_filter || m_pos == (size_t) -1 );
+}
+
+
+FOOTPRINT_INFO& FOOTPRINT_FILTER_IT::dereference() const
+{
+    if( m_filter && m_filter->m_list && m_pos < m_filter->m_list->GetCount() )
+        return m_filter->m_list->GetItem( m_pos );
+    else
+        throw std::out_of_range( "Attempt to dereference past FOOTPRINT_FILTER::end()" );
+}
+
+
+bool FOOTPRINT_FILTER_IT::FootprintFilterMatch( FOOTPRINT_INFO& aItem )
+{
+    if( m_filter->m_footprint_filters.empty() )
+        return true;
+
+    // The matching is case insensitive
+    wxString name = "";
+
+    EDA_PATTERN_MATCH_WILDCARD patternFilter;
+
+    for( auto const& each_filter : m_filter->m_footprint_filters )
+    {
+        // If the filter contains a ':' character, include the library name in the pattern
+        if( each_filter->GetPattern().Contains( ":" ) )
+        {
+            name = aItem.GetNickname().Lower() + ":";
+        }
+
+        name += aItem.GetFootprintName().Lower();
+
+        if( each_filter->Find( name ) != EDA_PATTERN_NOT_FOUND )
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+
+bool FOOTPRINT_FILTER_IT::PinCountMatch( FOOTPRINT_INFO& aItem )
+{
+    return (unsigned) m_filter->m_pin_count == aItem.GetUniquePadCount();
+}
+
+
+FOOTPRINT_FILTER::FOOTPRINT_FILTER( FOOTPRINT_LIST& aList ) : FOOTPRINT_FILTER()
+{
+    SetList( aList );
+}
+
+
+FOOTPRINT_FILTER::FOOTPRINT_FILTER()
+        : m_list( nullptr ), m_pin_count( -1 ), m_filter_type( UNFILTERED_FP_LIST )
+{
+}
+
+
+void FOOTPRINT_FILTER::SetList( FOOTPRINT_LIST& aList )
+{
+    m_list = &aList;
+}
+
+
+void FOOTPRINT_FILTER::ClearFilters()
+{
+    m_filter_type = UNFILTERED_FP_LIST;
+}
+
+
+void FOOTPRINT_FILTER::FilterByLibrary( wxString const& aLibName )
+{
+    m_lib_name = aLibName;
+    m_filter_type |= FILTERING_BY_LIBRARY;
+}
+
+
+void FOOTPRINT_FILTER::FilterByPinCount( int aPinCount )
+{
+    m_pin_count = aPinCount;
+    m_filter_type |= FILTERING_BY_PIN_COUNT;
+}
+
+
+void FOOTPRINT_FILTER::FilterByFootprintFilters( wxArrayString const& aFilters )
+{
+    m_footprint_filters.clear();
+
+    for( auto const& each_pattern : aFilters )
+    {
+        m_footprint_filters.push_back( std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
+        wxASSERT( m_footprint_filters.back()->SetPattern( each_pattern.Lower() ) );
+    }
+
+    m_filter_type |= FILTERING_BY_COMPONENT_KEYWORD;
+}
+
+
+void FOOTPRINT_FILTER::FilterByPattern( wxString const& aPattern )
+{
+    m_filter_pattern = aPattern;
+    m_filter.SetPattern( aPattern.Lower() );
+    m_filter_type |= FILTERING_BY_NAME;
+}
+
+
+FOOTPRINT_FILTER_IT FOOTPRINT_FILTER::begin()
+{
+    return FOOTPRINT_FILTER_IT( *this );
+}
+
+
+FOOTPRINT_FILTER_IT FOOTPRINT_FILTER::end()
+{
+    FOOTPRINT_FILTER_IT end_it( *this );
+    end_it.m_pos = m_list->GetCount();
+    return end_it;
+}
diff --git a/common/footprint_info.cpp b/common/footprint_info.cpp
index ddf79508a..8ec1163a0 100644
--- a/common/footprint_info.cpp
+++ b/common/footprint_info.cpp
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2011 Jean-Pierre Charras, <jp.charras@xxxxxxxxxx>
  * Copyright (C) 2013-2016 SoftPLC Corporation, Dick Hollenbeck <dick@xxxxxxxxxxx>
- * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 1992-2017 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
@@ -23,293 +23,189 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
+
 /**
  * @file footprint_info.cpp
  */
 
 
-/**
-    No. concurrent threads doing "http(s) GET". More than 6 is not significantly
-    faster, less than 6 is likely slower. Main thread is in this count, so if
-    set to 1 then no temp threads are created.
-*/
-#define READER_THREADS      6
-
 /*
  * Functions to read footprint libraries and fill m_footprints by available footprints names
  * and their documentation (comments and keywords)
  */
 
-#include <fctsys.h>
+#include <class_module.h>
 #include <common.h>
-#include <macros.h>
-#include <pgm_base.h>
-#include <wildcards_and_files_ext.h>
+#include <fctsys.h>
 #include <footprint_info.h>
-#include <io_mgr.h>
 #include <fp_lib_table.h>
+#include <html_messagebox.h>
+#include <io_mgr.h>
+#include <kiface_ids.h>
+#include <kiway.h>
 #include <lib_id.h>
-#include <class_module.h>
+#include <macros.h>
+#include <pgm_base.h>
 #include <thread>
-#include <html_messagebox.h>
+#include <wildcards_and_files_ext.h>
 
 
-/*
-static wxString ToHTMLFragment( const IO_ERROR* aDerivative )
+FOOTPRINT_INFO* FOOTPRINT_LIST::GetModuleInfo( const wxString& aFootprintName )
 {
-    @todo
+    if( aFootprintName.IsEmpty() )
+        return NULL;
+
+    for( auto& fp : m_list )
+    {
+        LIB_ID fpid;
 
-    1)  change up IO_ERROR so it keeps linenumbers, source file name and
-        error message in separate strings.
+        wxCHECK_MSG( fpid.Parse( aFootprintName ) < 0, NULL,
+                wxString::Format(
+                        wxT( "'%s' is not a valid LIB_ID." ), GetChars( aFootprintName ) ) );
 
-    2)  Add a summarizing virtual member like
-            virtual wxString What()
-        to combine all portions of an IO_ERROR's text into a single wxString.
+        wxString libNickname = fpid.GetLibNickname();
+        wxString footprintName = fpid.GetLibItemName();
 
-    3)  Do same for PARSE_ERROR.
+        if( libNickname == fp->GetNickname() && footprintName == fp->GetFootprintName() )
+            return &*fp;
+    }
 
-    4)  Add a "reason or error category" to IO_ERROR and thereby also PARSE_ERROR?
+    return NULL;
+}
 
-    msg += "
 
-    for( int i=0; i<aCount; ++i )
-    {
+bool FOOTPRINT_INFO::InLibrary( const wxString& aLibrary ) const
+{
+    return aLibrary == m_nickname;
+}
 
 
-        wxArrayString* sl = wxStringSplit( aList, wxChar( '\n' ) );
+void FOOTPRINT_LIST::DisplayErrors( wxTopLevelWindow* aWindow )
+{
+    // @todo: go to a more HTML !<table>! ? centric output, possibly with
+    // recommendations for remedy of errors.  Add numeric error codes
+    // to PARSE_ERROR, and switch on them for remedies, etc.  Full
+    // access is provided to everything in every exception!
 
+    HTML_MESSAGE_BOX dlg( aWindow, _( "Load Error" ) );
 
-        delete sl;
-    }
+    dlg.MessageSet( _( "Errors were encountered loading footprints:" ) );
 
-    wxString msg = wxT( "<ul>" );
+    wxString msg;
 
-    for ( unsigned ii = 0; ii < strings_list->GetCount(); ii++ )
+    while( auto error = PopError() )
     {
-        msg += wxT( "<li>" );
-        msg += strings_list->Item( ii ) + wxT( "</li>" );
+        msg += wxT( "<p>" ) + error->Problem() + wxT( "</p>" );
     }
 
-    msg += wxT( "</ul>" );
-
-    m_htmlWindow->AppendToPage( msg );
+    dlg.AddHTML_Text( msg );
 
-    delete strings_list;
+    dlg.ShowModal();
 }
-*/
 
 
-void FOOTPRINT_INFO::load()
+static std::unique_ptr<FOOTPRINT_LIST> get_instance_from_id( KIWAY& aKiway, int aId )
 {
-    FP_LIB_TABLE*   fptable = m_owner->GetTable();
+    void* ptr = nullptr;
 
-    wxASSERT( fptable );
+    try
+    {
+        KIFACE* kiface = aKiway.KiFACE( KIWAY::FACE_PCB );
 
-    std::unique_ptr<MODULE> footprint( fptable->FootprintLoad( m_nickname, m_fpname ) );
+        if( !kiface )
+            return nullptr;
 
-    if( footprint.get() == NULL )    // Should happen only with malformed/broken libraries
-    {
-        m_pad_count = 0;
-        m_unique_pad_count = 0;
+        ptr = kiface->IfaceOrAddress( aId );
+
+        if( !ptr )
+            return nullptr;
     }
-    else
+    catch( ... )
     {
-        m_pad_count = footprint->GetPadCount( DO_NOT_INCLUDE_NPTH );
-        m_unique_pad_count = footprint->GetUniquePadCount( DO_NOT_INCLUDE_NPTH );
-        m_keywords  = footprint->GetKeywords();
-        m_doc       = footprint->GetDescription();
-
-        // tell ensure_loaded() I'm loaded.
-        m_loaded = true;
+        return nullptr;
     }
+
+    return std::unique_ptr<FOOTPRINT_LIST>( (FOOTPRINT_LIST*) ( ptr ) );
 }
 
 
-void FOOTPRINT_LIST::loader_job( const wxString* aNicknameList, int aJobZ )
+std::unique_ptr<FOOTPRINT_LIST> FOOTPRINT_LIST::GetInstance( KIWAY& aKiway )
 {
-    for( int i=0; i<aJobZ; ++i )
-    {
-        const wxString& nickname = aNicknameList[i];
-
-        try
-        {
-            wxArrayString fpnames = m_lib_table->FootprintEnumerate( nickname );
-
-            for( unsigned ni=0;  ni<fpnames.GetCount();  ++ni )
-            {
-                FOOTPRINT_INFO* fpinfo = new FOOTPRINT_INFO( this, nickname, fpnames[ni] );
-
-                addItem( fpinfo );
-            }
-        }
-        catch( const PARSE_ERROR& pe )
-        {
-            // m_errors.push_back is not thread safe, lock its MUTEX.
-            MUTLOCK lock( m_errors_lock );
-
-            ++m_error_count;        // modify only under lock
-            m_errors.push_back( new IO_ERROR( pe ) );
-        }
-        catch( const IO_ERROR& ioe )
-        {
-            MUTLOCK lock( m_errors_lock );
-
-            ++m_error_count;
-            m_errors.push_back( new IO_ERROR( ioe ) );
-        }
-
-        // Catch anything unexpected and map it into the expected.
-        // Likely even more important since this function runs on GUI-less
-        // worker threads.
-        catch( const std::exception& se )
-        {
-            // This is a round about way to do this, but who knows what THROW_IO_ERROR()
-            // may be tricked out to do someday, keep it in the game.
-            try
-            {
-                THROW_IO_ERROR( se.what() );
-            }
-            catch( const IO_ERROR& ioe )
-            {
-                MUTLOCK lock( m_errors_lock );
-
-                ++m_error_count;
-                m_errors.push_back( new IO_ERROR( ioe ) );
-            }
-        }
-    }
+    return get_instance_from_id( aKiway, KIFACE_NEW_FOOTPRINT_LIST );
 }
 
 
-bool FOOTPRINT_LIST::ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname )
+FOOTPRINT_ASYNC_LOADER::FOOTPRINT_ASYNC_LOADER() : m_list( nullptr )
 {
-    bool retv = true;
-
-    m_lib_table = aTable;
-
-    // Clear data before reading files
-    m_error_count = 0;
-    m_errors.clear();
-    m_list.clear();
-
-    if( aNickname )
-        // single footprint
-        loader_job( aNickname, 1 );
-    else
-    {
-        std::vector< wxString > nicknames;
-
-        // do all of them
-        nicknames = aTable->GetLogicalLibs();
-
-        // Even though the PLUGIN API implementation is the place for the
-        // locale toggling, in order to keep LOCAL_IO::C_count at 1 or greater
-        // for the duration of all helper threads, we increment by one here via instantiation.
-        // Only done here because of the multi-threaded nature of this code.
-        // Without this C_count skips in and out of "equal to zero" and causes
-        // needless locale toggling among the threads, based on which of them
-        // are in a PLUGIN::FootprintLoad() function.  And that is occasionally
-        // none of them.
-        LOCALE_IO   top_most_nesting;
-
-        // Something which will not invoke a thread copy constructor, one of many ways obviously:
-        typedef std::vector< std::thread >  MYTHREADS;
-
-        MYTHREADS threads;
-
-        unsigned jobz = (nicknames.size() + READER_THREADS - 1) / READER_THREADS;
-
-        // Give each thread JOBZ nicknames to process.  The last portion of, or if the entire
-        // size() is small, I'll do myself.
-        for( unsigned i=0; i<nicknames.size();  )
-        {
-            if( i + jobz >= nicknames.size() )  // on the last iteration of this for(;;)
-            {
-                jobz = nicknames.size() - i;
-
-                // Only a little bit to do, I'll do it myself on current thread.
-                // I am part of the READER_THREADS count.
-                loader_job( &nicknames[i], jobz );
-            }
-            else
-            {
-                // Delegate the job to a temporary thread created here.
-                threads.push_back( std::thread( &FOOTPRINT_LIST::loader_job,
-                        this, &nicknames[i], jobz ) );
-            }
-
-            i += jobz;
-        }
-
-        // Wait for all the worker threads to complete, it does not matter in what order
-        // we wait for them as long as a full sweep is made.  Think of the great race,
-        // everyone must finish.
-        for( unsigned i=0;  i<threads.size();  ++i )
-        {
-            threads[i].join();
-        }
-
-        m_list.sort();
-    }
+}
 
-    // The result of this function can be a blend of successes and failures, whose
-    // mix is given by the Count()s of the two lists.  The return value indicates whether
-    // an abort occurred, even true does not necessarily mean full success, although
-    // false definitely means failure.
 
-    return retv;
+void FOOTPRINT_ASYNC_LOADER::SetList( FOOTPRINT_LIST* aList )
+{
+    m_list = aList;
 }
 
 
-FOOTPRINT_INFO* FOOTPRINT_LIST::GetModuleInfo( const wxString& aFootprintName )
+void FOOTPRINT_ASYNC_LOADER::Start(
+        FP_LIB_TABLE* aTable, wxString const* aNickname, unsigned aNThreads )
 {
-    if( aFootprintName.IsEmpty() )
-        return NULL;
+    m_started = true;
 
-    for( FOOTPRINT_INFO& fp : m_list )
-    {
-        LIB_ID fpid;
+    // Capture the FP_LIB_TABLE into m_last_table. Formatting it as a string instead of storing the
+    // raw data avoids having to pull in the FP-specific parts.
+    STRING_FORMATTER sof;
+    aTable->Format( &sof, 0 );
+    m_last_table = sof.GetString();
 
-        wxCHECK_MSG( fpid.Parse( aFootprintName ) < 0, NULL,
-                     wxString::Format( wxT( "'%s' is not a valid LIB_ID." ),
-                                       GetChars( aFootprintName ) ) );
+    m_list->StartWorkers( aTable, aNickname, this, aNThreads );
+}
 
-        wxString libNickname   = fpid.GetLibNickname();
-        wxString footprintName = fpid.GetLibItemName();
 
-        if( libNickname == fp.GetNickname() && footprintName == fp.GetFootprintName() )
-            return &fp;
+bool FOOTPRINT_ASYNC_LOADER::Join()
+{
+    if( m_list )
+    {
+        bool rv = m_list->JoinWorkers();
+        m_list = nullptr;
+        return rv;
     }
-
-    return NULL;
+    else
+        return true;
 }
 
 
-bool FOOTPRINT_INFO::InLibrary( const wxString& aLibrary ) const
+int FOOTPRINT_ASYNC_LOADER::GetProgress() const
 {
-    return aLibrary == m_nickname;
+    if( !m_started )
+        return 0;
+    else if( m_total_libs == 0 || !m_list )
+        return 100;
+    else
+    {
+        int loaded = m_list->CountFinished();
+        int prog = ( 100 * loaded ) / m_total_libs;
+
+        if( loaded == m_total_libs )
+            return 100;
+        else if( loaded < m_total_libs && prog >= 100 )
+            return 99;
+        else if( prog <= 0 )
+            return 1;
+        else
+            return prog;
+    }
 }
 
 
-void FOOTPRINT_LIST::DisplayErrors( wxTopLevelWindow* aWindow )
+void FOOTPRINT_ASYNC_LOADER::SetCompletionCallback( std::function<void()> aCallback )
 {
-    // @todo: go to a more HTML !<table>! ? centric output, possibly with
-    // recommendations for remedy of errors.  Add numeric error codes
-    // to PARSE_ERROR, and switch on them for remedies, etc.  Full
-    // access is provided to everything in every exception!
-
-    HTML_MESSAGE_BOX dlg( aWindow, _( "Load Error" ) );
-
-    dlg.MessageSet( _( "Errors were encountered loading footprints:" ) );
-
-    wxString msg;
-
-    for( unsigned i = 0; i<m_errors.size();  ++i )
-    {
-        msg += wxT( "<p>" ) + m_errors[i].Problem() + wxT( "</p>" );
-    }
+    m_completion_cb = aCallback;
+}
 
-    dlg.AddHTML_Text( msg );
 
-    dlg.ShowModal();
+bool FOOTPRINT_ASYNC_LOADER::IsSameTable( FP_LIB_TABLE* aOther )
+{
+    STRING_FORMATTER sof;
+    aOther->Format( &sof, 0 );
+    return m_last_table == sof.GetString();
 }
diff --git a/common/fp_lib_table.cpp b/common/fp_lib_table.cpp
index 4c4f6e203..504496024 100644
--- a/common/fp_lib_table.cpp
+++ b/common/fp_lib_table.cpp
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2010-2012 SoftPLC Corporation, Dick Hollenbeck <dick@xxxxxxxxxxx>
  * Copyright (C) 2012-2016 Wayne Stambaugh <stambaughw@xxxxxxxxx>
- * Copyright (C) 2012-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2012-2017 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
@@ -204,6 +204,14 @@ wxArrayString FP_LIB_TABLE::FootprintEnumerate( const wxString& aNickname )
 }
 
 
+void FP_LIB_TABLE::PrefetchLib( const wxString& aNickname )
+{
+    const FP_LIB_TABLE_ROW* row = FindRow( aNickname );
+    wxASSERT( (PLUGIN*) row->plugin );
+    row->plugin->PrefetchLib( row->GetFullURI( true ), row->GetProperties() );
+}
+
+
 const FP_LIB_TABLE_ROW* FP_LIB_TABLE::FindRow( const wxString& aNickname )
     throw( IO_ERROR )
 {
diff --git a/common/project.cpp b/common/project.cpp
index e55ed94b0..12e36473e 100644
--- a/common/project.cpp
+++ b/common/project.cpp
@@ -1,8 +1,7 @@
-
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 2014 KiCad Developers, see CHANGELOG.TXT for contributors.
+ * Copyright (C) 2014-2017 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
@@ -22,7 +21,6 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
-
 #include <wx/stdpaths.h>
 
 #include <fctsys.h>
@@ -34,6 +32,9 @@
 #include <kicad_string.h>
 #include <config_params.h>
 #include <wildcards_and_files_ext.h>
+#include <fp_lib_table.h>
+#include <kiway.h>
+#include <kiface_ids.h>
 
 
 PROJECT::PROJECT()
@@ -379,3 +380,41 @@ const wxString PROJECT::AbsolutePath( const wxString& aFileName ) const
 
     return fn.GetFullPath();
 }
+
+
+FP_LIB_TABLE* PROJECT::PcbFootprintLibs( KIWAY& aKiway )
+{
+    // This is a lazy loading function, it loads the project specific table when
+    // that table is asked for, not before.
+
+    FP_LIB_TABLE*   tbl = (FP_LIB_TABLE*) GetElem( ELEM_FPTBL );
+
+    // its gotta be NULL or a FP_LIB_TABLE, or a bug.
+    wxASSERT( !tbl || dynamic_cast<FP_LIB_TABLE*>( tbl ) );
+
+    if( !tbl )
+    {
+        // Stack the project specific FP_LIB_TABLE overlay on top of the global table.
+        // ~FP_LIB_TABLE() will not touch the fallback table, so multiple projects may
+        // stack this way, all using the same global fallback table.
+        KIFACE* kiface = aKiway.KiFACE( KIWAY::FACE_PCB );
+        if( kiface )
+            tbl = (FP_LIB_TABLE*) kiface->IfaceOrAddress( KIFACE_G_FOOTPRINT_TABLE );
+
+        wxASSERT( tbl );
+        SetElem( ELEM_FPTBL, tbl );
+
+        wxString projectFpLibTableFileName = FootprintLibTblName();
+
+        try
+        {
+            tbl->Load( projectFpLibTableFileName );
+        }
+        catch( const IO_ERROR& ioe )
+        {
+            DisplayError( NULL, ioe.What() );
+        }
+    }
+
+    return tbl;
+}
diff --git a/common/widgets/footprint_choice.cpp b/common/widgets/footprint_choice.cpp
new file mode 100644
index 000000000..ec547282b
--- /dev/null
+++ b/common/widgets/footprint_choice.cpp
@@ -0,0 +1,262 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 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 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 <functional>
+#include <widgets/footprint_choice.h>
+#include <wx/dc.h>
+#include <wx/pen.h>
+
+wxDEFINE_EVENT( EVT_INTERACTIVE_CHOICE, wxCommandEvent );
+
+
+wxColour FOOTPRINT_CHOICE::m_grey( 0x808080 );
+
+
+FOOTPRINT_CHOICE::FOOTPRINT_CHOICE( wxWindow* aParent, int aId )
+        : wxOwnerDrawnComboBox( aParent, aId, wxEmptyString, wxDefaultPosition, wxDefaultSize,
+                  /* n */ 0, /* choices */ nullptr, wxCB_READONLY ),
+          m_last_selection( 0 )
+{
+}
+
+
+FOOTPRINT_CHOICE::~FOOTPRINT_CHOICE()
+{
+}
+
+
+void FOOTPRINT_CHOICE::DoSetPopupControl( wxComboPopup* aPopup )
+{
+    using namespace std::placeholders;
+    wxOwnerDrawnComboBox::DoSetPopupControl( aPopup );
+
+    // Bind events to intercept selections, so the separator can be made nonselectable.
+
+    GetVListBoxComboPopup()->Bind( wxEVT_MOTION, &FOOTPRINT_CHOICE::TryVetoMouse, this );
+    GetVListBoxComboPopup()->Bind( wxEVT_LEFT_DOWN, &FOOTPRINT_CHOICE::TryVetoMouse, this );
+    GetVListBoxComboPopup()->Bind( wxEVT_LEFT_UP, &FOOTPRINT_CHOICE::TryVetoMouse, this );
+    GetVListBoxComboPopup()->Bind( wxEVT_LEFT_UP, &FOOTPRINT_CHOICE::OnMouseUp, this );
+    GetVListBoxComboPopup()->Bind( wxEVT_LEFT_DCLICK, &FOOTPRINT_CHOICE::TryVetoMouse, this );
+    GetVListBoxComboPopup()->Bind(
+            wxEVT_LISTBOX, std::bind( &FOOTPRINT_CHOICE::TryVetoSelect, this, _1, true ) );
+    Bind( wxEVT_COMBOBOX, std::bind( &FOOTPRINT_CHOICE::TryVetoSelect, this, _1, false ) );
+    GetVListBoxComboPopup()->Bind(
+            wxEVT_CHAR_HOOK, std::bind( &FOOTPRINT_CHOICE::TrySkipSeparator, this, _1, true ) );
+    GetVListBoxComboPopup()->Bind( wxEVT_CHAR_HOOK, &FOOTPRINT_CHOICE::OnKeyUp, this );
+    Bind( wxEVT_KEY_DOWN, std::bind( &FOOTPRINT_CHOICE::TrySkipSeparator, this, _1, false ) );
+}
+
+
+void FOOTPRINT_CHOICE::OnDrawItem( wxDC& aDC, wxRect const& aRect, int aItem, int aFlags ) const
+{
+    wxString text = SafeGetString( aItem );
+
+    if( text == wxEmptyString )
+    {
+        wxPen pen( m_grey, 1, wxPENSTYLE_SOLID );
+
+        aDC.SetPen( pen );
+        aDC.DrawLine( aRect.x, aRect.y + aRect.height / 2, aRect.x + aRect.width,
+                aRect.y + aRect.height / 2 );
+    }
+    else
+    {
+        wxCoord x, y;
+
+        if( aFlags & wxODCB_PAINTING_CONTROL )
+        {
+            x = aRect.x + GetMargins().x;
+            y = ( aRect.height - aDC.GetCharHeight() ) / 2 + aRect.y;
+        }
+        else
+        {
+            x = aRect.x + 2;
+            y = aRect.y;
+        }
+
+        // If this item has a footprint and that footprint has a ":" delimiter, find the
+        // library component, then find that in the display string and grey it out.
+
+        size_t start_grey = 0;
+        size_t end_grey = 0;
+
+        wxString lib = static_cast<wxStringClientData*>( GetClientObject( aItem ) )->GetData();
+        size_t   colon_index = lib.rfind( ':' );
+
+        if( colon_index != wxString::npos )
+        {
+            wxString library_part = lib.SubString( 0, colon_index );
+            size_t   library_index = text.rfind( library_part );
+
+            if( library_index != wxString::npos )
+            {
+                start_grey = library_index;
+                end_grey = start_grey + library_part.Length();
+            }
+        }
+
+        if( start_grey != end_grey && !( aFlags & wxODCB_PAINTING_SELECTED ) )
+        {
+            x = DrawTextFragment( aDC, x, y, text.SubString( 0, start_grey - 1 ) );
+
+            wxColour standard_color = aDC.GetTextForeground();
+
+            aDC.SetTextForeground( m_grey );
+            x = DrawTextFragment( aDC, x, y, text.SubString( start_grey, end_grey - 1 ) );
+
+            aDC.SetTextForeground( standard_color );
+            x = DrawTextFragment( aDC, x, y, text.SubString( end_grey, text.Length() - 1 ) );
+        }
+        else
+        {
+            aDC.DrawText( text, x, y );
+        }
+    }
+}
+
+wxCoord FOOTPRINT_CHOICE::OnMeasureItem( size_t aItem ) const
+{
+    if( SafeGetString( aItem ) == "" )
+        return 11;
+    else
+        return wxOwnerDrawnComboBox::OnMeasureItem( aItem );
+}
+
+
+wxCoord FOOTPRINT_CHOICE::OnMeasureItemWidth( size_t aItem ) const
+{
+    if( SafeGetString( aItem ) == "" )
+        return GetTextRect().GetWidth() - 2;
+    else
+        return wxOwnerDrawnComboBox::OnMeasureItemWidth( aItem );
+}
+
+
+wxCoord FOOTPRINT_CHOICE::DrawTextFragment( wxDC& aDC, wxCoord x, wxCoord y, wxString const& aText )
+{
+    aDC.DrawText( aText, x, y );
+    return x + aDC.GetTextExtent( aText ).GetWidth();
+}
+
+
+void FOOTPRINT_CHOICE::TryVetoMouse( wxMouseEvent& aEvent )
+{
+    int item = GetVListBoxComboPopup()->VirtualHitTest( aEvent.GetPosition().y );
+
+    if( SafeGetString( item ) != "" )
+        aEvent.Skip();
+}
+
+
+void FOOTPRINT_CHOICE::OnMouseUp( wxMouseEvent& aEvent )
+{
+    int item = GetVListBoxComboPopup()->VirtualHitTest( aEvent.GetPosition().y );
+
+    wxCommandEvent evt( EVT_INTERACTIVE_CHOICE );
+    evt.SetInt( item );
+    wxPostEvent( this, evt );
+
+    aEvent.Skip();
+}
+
+
+void FOOTPRINT_CHOICE::OnKeyUp( wxKeyEvent& aEvent )
+{
+    int item = GetSelectionEither( true );
+
+    if( aEvent.GetKeyCode() == WXK_RETURN )
+    {
+        wxCommandEvent evt( EVT_INTERACTIVE_CHOICE );
+        evt.SetInt( item );
+        wxPostEvent( this, evt );
+    }
+
+    aEvent.Skip();
+}
+
+
+void FOOTPRINT_CHOICE::TryVetoSelect( wxCommandEvent& aEvent, bool aInner )
+{
+    int sel = GetSelectionEither( aInner );
+
+    if( sel >= 0 && sel < (int) GetCount() )
+    {
+        wxString text = SafeGetString( sel );
+
+        if( text == "" )
+            SetSelectionEither( aInner, m_last_selection );
+        else
+        {
+            m_last_selection = sel;
+            aEvent.Skip();
+        }
+    }
+}
+
+
+void FOOTPRINT_CHOICE::TrySkipSeparator( wxKeyEvent& aEvent, bool aInner )
+{
+    int key = aEvent.GetKeyCode();
+    int sel = GetSelectionEither( aInner );
+    int new_sel = sel;
+
+    if( key == WXK_UP && SafeGetString( sel - 1 ) == wxEmptyString )
+    {
+        new_sel = sel - 2;
+    }
+    else if( key == WXK_DOWN && SafeGetString( sel + 1 ) == wxEmptyString )
+    {
+        new_sel = sel + 2;
+    }
+
+    if( new_sel != sel )
+        SetSelectionEither( aInner, new_sel );
+    else
+        aEvent.Skip();
+}
+
+
+wxString FOOTPRINT_CHOICE::SafeGetString( int aItem ) const
+{
+    if( aItem >= 0 && aItem < (int) GetCount() )
+        return GetVListBoxComboPopup()->GetString( aItem );
+    else
+        return wxEmptyString;
+}
+
+
+int FOOTPRINT_CHOICE::GetSelectionEither( bool aInner ) const
+{
+    if( aInner )
+        return GetVListBoxComboPopup()->wxVListBox::GetSelection();
+    else
+        return GetSelection();
+}
+
+
+void FOOTPRINT_CHOICE::SetSelectionEither( bool aInner, int aSel )
+{
+    if( aSel >= 0 && aSel < (int) GetCount() )
+    {
+        if( aInner )
+            return GetVListBoxComboPopup()->wxVListBox::SetSelection( aSel );
+        else
+            return SetSelection( aSel );
+    }
+}
diff --git a/common/widgets/footprint_select_widget.cpp b/common/widgets/footprint_select_widget.cpp
new file mode 100644
index 000000000..430c0849c
--- /dev/null
+++ b/common/widgets/footprint_select_widget.cpp
@@ -0,0 +1,304 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 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 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 <dialog_shim.h>
+#include <kiway.h>
+#include <kiway_player.h>
+#include <make_unique.h>
+#include <project.h>
+#include <widgets/footprint_choice.h>
+#include <widgets/footprint_select_widget.h>
+
+#include <functional>
+#include <wx/combo.h>
+#include <wx/gauge.h>
+#include <wx/odcombo.h>
+#include <wx/simplebook.h>
+#include <wx/sizer.h>
+#include <wx/timer.h>
+#include <wx/utils.h>
+#include <wx/wupdlock.h>
+
+
+/**
+ * Fixed positions for standard items in the list
+ */
+enum
+{
+    POS_DEFAULT,
+    POS_OTHER,
+    POS_SEPARATOR
+};
+
+
+/**
+ * Page numbers in the wxSimplebook
+ */
+enum
+{
+    PAGE_PROGRESS,
+    PAGE_SELECT
+};
+
+
+wxDEFINE_EVENT( EVT_FOOTPRINT_SELECTED, wxCommandEvent );
+
+
+FOOTPRINT_SELECT_WIDGET::FOOTPRINT_SELECT_WIDGET( wxWindow* aParent,
+        FOOTPRINT_ASYNC_LOADER& aLoader, std::unique_ptr<FOOTPRINT_LIST>& aFpList, bool aUpdate,
+        int aMaxItems )
+        : wxPanel( aParent ),
+          m_kiway( nullptr ),
+          m_update( aUpdate ),
+          m_finished_loading( false ),
+          m_max_items( aMaxItems ),
+          m_last_item( 0 ),
+          m_fp_loader( aLoader ),
+          m_fp_list( aFpList )
+{
+    m_sizer = new wxBoxSizer( wxVERTICAL );
+    m_progress_timer = std::make_unique<wxTimer>( this );
+    m_book = new wxSimplebook( this, wxID_ANY );
+    m_progress_ctrl = new wxGauge( m_book, wxID_ANY, 100 );
+    m_fp_sel_ctrl = new FOOTPRINT_CHOICE( m_book, wxID_ANY );
+
+    m_book->SetEffect( wxSHOW_EFFECT_BLEND );
+    m_book->AddPage( m_progress_ctrl, "", true );
+    m_book->AddPage( m_fp_sel_ctrl, "", false );
+    m_sizer->Add( m_book, 1, wxEXPAND | wxALL, 5 );
+
+    SetSizer( m_sizer );
+    Layout();
+    m_sizer->Fit( this );
+
+    Bind( wxEVT_TIMER, &FOOTPRINT_SELECT_WIDGET::OnProgressTimer, this, m_progress_timer->GetId() );
+    m_fp_sel_ctrl->Bind( wxEVT_COMBOBOX, &FOOTPRINT_SELECT_WIDGET::OnComboBox, this );
+    m_fp_sel_ctrl->Bind(
+            EVT_INTERACTIVE_CHOICE, &FOOTPRINT_SELECT_WIDGET::OnComboInteractive, this );
+}
+
+
+void FOOTPRINT_SELECT_WIDGET::Load( KIWAY& aKiway, PROJECT& aProject )
+{
+    m_kiway = &aKiway;
+    auto fp_lib_table = aProject.PcbFootprintLibs( aKiway );
+
+    if( m_fp_loader.GetProgress() == 0 || !m_fp_loader.IsSameTable( fp_lib_table ) )
+    {
+        m_fp_list = FOOTPRINT_LIST::GetInstance( aKiway );
+        m_fp_loader.SetList( &*m_fp_list );
+        m_fp_loader.Start( fp_lib_table );
+    }
+
+    m_progress_timer->Start( 200 );
+}
+
+
+void FOOTPRINT_SELECT_WIDGET::OnProgressTimer( wxTimerEvent& aEvent )
+{
+    int prog = m_fp_loader.GetProgress();
+    m_progress_ctrl->SetValue( prog );
+
+    if( prog == 100 )
+    {
+        wxBusyCursor busy;
+
+        m_fp_loader.Join();
+        m_fp_filter.SetList( *m_fp_list );
+        m_progress_timer->Stop();
+
+        m_book->SetSelection( PAGE_SELECT );
+        m_finished_loading = true;
+
+        if( m_update )
+            UpdateList();
+    }
+}
+
+
+void FOOTPRINT_SELECT_WIDGET::OnComboBox( wxCommandEvent& aEvent )
+{
+    wxCommandEvent evt( EVT_FOOTPRINT_SELECTED );
+    int            sel = m_fp_sel_ctrl->GetSelection();
+
+    switch( sel )
+    {
+    case wxNOT_FOUND: return;
+
+    case POS_SEPARATOR:
+        // User somehow managed to select the separator. This should not be
+        // possible, but just in case... deselect it
+        m_fp_sel_ctrl->SetSelection( m_last_item );
+        break;
+
+    case POS_OTHER:
+        // When POS_OTHER is selected, a dialog should be shown. However, we don't want to
+        // do this ALL the time, as some times (e.g. when moving around with the arrow keys)
+        // it could be very annoying. Therefore showing the picker is done from the custom
+        // "interactive select" event on FOOTPRINT_CHOICE, which only fires for more direct
+        // choice actions.
+        break;
+
+    default:
+    {
+        wxStringClientData* clientdata =
+                static_cast<wxStringClientData*>( m_fp_sel_ctrl->GetClientObject( sel ) );
+        wxASSERT( clientdata );
+
+        evt.SetString( clientdata->GetData() );
+        wxPostEvent( this, evt );
+    }
+    }
+}
+
+
+void FOOTPRINT_SELECT_WIDGET::OnComboInteractive( wxCommandEvent& aEvent )
+{
+    if( aEvent.GetInt() == POS_OTHER && !m_fp_sel_ctrl->IsPopupShown() )
+    {
+        DoOther();
+    }
+}
+
+
+void FOOTPRINT_SELECT_WIDGET::DoOther()
+{
+    wxCommandEvent evt( EVT_FOOTPRINT_SELECTED );
+
+    wxString fpname = ShowPicker();
+    m_other_footprint = fpname;
+    UpdateList();
+    m_fp_sel_ctrl->SetSelection( POS_OTHER );
+    m_last_item = POS_OTHER;
+
+    evt.SetString( m_other_footprint );
+    wxPostEvent( this, evt );
+}
+
+
+wxString FOOTPRINT_SELECT_WIDGET::ShowPicker()
+{
+    wxString     fpname;
+    wxWindow*    parent = ::wxGetTopLevelParent( this );
+    DIALOG_SHIM* dsparent = dynamic_cast<DIALOG_SHIM*>( parent );
+
+    // Only quasimodal dialogs can launch modal kiface dialogs. Otherwise the
+    // event loop goes all silly.
+    wxASSERT( !dsparent || dsparent->IsQuasiModal() );
+
+    auto frame = m_kiway->Player( FRAME_PCB_MODULE_VIEWER_MODAL, true );
+
+    if( !frame->ShowModal( &fpname, parent ) )
+    {
+        fpname = wxEmptyString;
+    }
+
+    frame->Destroy();
+
+    return fpname;
+}
+
+
+void FOOTPRINT_SELECT_WIDGET::ClearFilters()
+{
+    m_fp_filter.ClearFilters();
+    m_default_footprint.Clear();
+    m_other_footprint.Clear();
+    m_zero_filter = false;
+}
+
+
+void FOOTPRINT_SELECT_WIDGET::FilterByPinCount( int aPinCount )
+{
+    m_fp_filter.FilterByPinCount( aPinCount );
+}
+
+
+void FOOTPRINT_SELECT_WIDGET::FilterByFootprintFilters(
+        wxArrayString const& aFilters, bool aZeroFilters )
+{
+    if( aZeroFilters && aFilters.size() == 0 )
+        m_zero_filter = true;
+    else
+        m_zero_filter = false;
+
+    m_fp_filter.FilterByFootprintFilters( aFilters );
+}
+
+
+void FOOTPRINT_SELECT_WIDGET::SetDefaultFootprint( wxString const& aFp )
+{
+    m_default_footprint = aFp;
+}
+
+
+bool FOOTPRINT_SELECT_WIDGET::UpdateList()
+{
+    int n_items = 0;
+
+    if( !m_fp_list || !m_finished_loading )
+        return false;
+
+    wxWindowUpdateLocker lock( m_fp_sel_ctrl );
+    m_fp_sel_ctrl->Clear();
+
+    // Be careful adding items! "Default" must occupy POS_DEFAULT,
+    // "Other" must occupy POS_OTHER, and the separator must occupy POS_SEPARATOR.
+
+    m_fp_sel_ctrl->Append( m_default_footprint.IsEmpty() ?
+                                   _( "No default footprint" ) :
+                                   "[" + _( "Default" ) + "] " + m_default_footprint,
+            new wxStringClientData( m_default_footprint ) );
+
+    m_fp_sel_ctrl->Append( m_other_footprint.IsEmpty() ?
+                                   _( "Other..." ) :
+                                   "[" + _( "Other..." ) + "] " + m_other_footprint,
+            new wxStringClientData( m_other_footprint ) );
+
+    m_fp_sel_ctrl->Append( "", new wxStringClientData( "" ) );
+
+    if( !m_zero_filter )
+    {
+        for( auto& fpinfo : m_fp_filter )
+        {
+            wxString display_name( fpinfo.GetNickname() + ":" + fpinfo.GetFootprintName() );
+
+            m_fp_sel_ctrl->Append( display_name, new wxStringClientData( display_name ) );
+            ++n_items;
+
+            if( n_items >= m_max_items )
+                break;
+        }
+    }
+
+    SelectDefault();
+    return true;
+}
+
+
+void FOOTPRINT_SELECT_WIDGET::SelectDefault()
+{
+    m_fp_sel_ctrl->SetSelection( POS_DEFAULT );
+}
+
+
+bool FOOTPRINT_SELECT_WIDGET::Enable( bool aEnable )
+{
+    return m_fp_sel_ctrl->Enable( aEnable );
+}
diff --git a/cvpcb/autosel.cpp b/cvpcb/autosel.cpp
index a60583cd5..1646106f8 100644
--- a/cvpcb/autosel.cpp
+++ b/cvpcb/autosel.cpp
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 1992-2017 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
@@ -215,7 +215,7 @@ void CVPCB_MAINFRAME::AutomaticFootprintMatching( wxCommandEvent& event )
             if( equivItem.m_ComponentValue.CmpNoCase( component->GetValue() ) != 0 )
                 continue;
 
-            const FOOTPRINT_INFO *module = m_FootprintsList.GetModuleInfo( equivItem.m_FootprintFPID );
+            const FOOTPRINT_INFO *module = m_FootprintsList->GetModuleInfo( equivItem.m_FootprintFPID );
 
             bool equ_is_unique = true;
             unsigned next = idx+1;
@@ -277,7 +277,7 @@ void CVPCB_MAINFRAME::AutomaticFootprintMatching( wxCommandEvent& event )
         {
             // we do not need to analyze wildcards: single footprint do not
             // contain them and if there are wildcards it just will not match any
-            const FOOTPRINT_INFO* module = m_FootprintsList.GetModuleInfo( component->GetFootprintFilters()[0] );
+            const FOOTPRINT_INFO* module = m_FootprintsList->GetModuleInfo( component->GetFootprintFilters()[0] );
 
             if( module )
                 SetNewPkg( component->GetFootprintFilters()[0] );
diff --git a/cvpcb/class_DisplayFootprintsFrame.cpp b/cvpcb/class_DisplayFootprintsFrame.cpp
index 892f5e90b..5a484b3b6 100644
--- a/cvpcb/class_DisplayFootprintsFrame.cpp
+++ b/cvpcb/class_DisplayFootprintsFrame.cpp
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2016 Jean-Pierre Charras, jp.charras at wanadoo.fr
  * Copyright (C) 2015-2016 Wayne Stambaugh <stambaughw@xxxxxxxxxxx>
- * Copyright (C) 2007-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2007-2017 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
@@ -459,7 +459,7 @@ MODULE* DISPLAY_FOOTPRINTS_FRAME::Get_Module( const wxString& aFootprintName )
         wxLogDebug( wxT( "Load footprint <%s> from library <%s>." ),
                     fpname.c_str(), nickname.c_str()  );
 
-        footprint = Prj().PcbFootprintLibs()->FootprintLoad(
+        footprint = Prj().PcbFootprintLibs( Kiway() )->FootprintLoad(
                 FROM_UTF8( nickname.c_str() ), FROM_UTF8( fpname.c_str() ) );
     }
     catch( const IO_ERROR& ioe )
@@ -495,7 +495,7 @@ void DISPLAY_FOOTPRINTS_FRAME::InitDisplay()
 
         SetTitle( msg );
         const FOOTPRINT_INFO* module_info =
-                parentframe->m_FootprintsList.GetModuleInfo( footprintName );
+                parentframe->m_FootprintsList->GetModuleInfo( footprintName );
 
         const wxChar* libname;
 
diff --git a/cvpcb/class_footprints_listbox.cpp b/cvpcb/class_footprints_listbox.cpp
index 4d3f5b0b5..1b0d0320c 100644
--- a/cvpcb/class_footprints_listbox.cpp
+++ b/cvpcb/class_footprints_listbox.cpp
@@ -29,12 +29,14 @@
 
 #include <fctsys.h>
 #include <wxstruct.h>
+#include <wx/wupdlock.h>
 
 #include <cvpcb.h>
 #include <cvpcb_mainframe.h>
 #include <listview_classes.h>
 #include <cvpcb_id.h>
 #include <eda_pattern_match.h>
+#include <footprint_filter.h>
 
 
 FOOTPRINTS_LISTBOX::FOOTPRINTS_LISTBOX( CVPCB_MAINFRAME* parent,
@@ -133,62 +135,28 @@ void FOOTPRINTS_LISTBOX::SetFootprints( FOOTPRINT_LIST& aList, const wxString& a
     wxString        msg;
     wxString        oldSelection;
 
-    EDA_PATTERN_MATCH_WILDCARD patternFilter;
-    patternFilter.SetPattern( aFootPrintFilterPattern.Lower() );    // Use case insensitive search
+    FOOTPRINT_FILTER filter( aList );
 
-    if( GetSelection() >= 0 && GetSelection() < (int)m_footprintList.GetCount() )
-        oldSelection = m_footprintList[ GetSelection() ];
-
-    for( unsigned ii = 0; ii < aList.GetCount(); ii++ )
-    {
-        if( aFilterType == UNFILTERED_FP_LIST )
-        {
-            msg.Printf( wxT( "%3d %s:%s" ), int( newList.GetCount() + 1 ),
-                        GetChars( aList.GetItem( ii ).GetNickname() ),
-                        GetChars( aList.GetItem( ii ).GetFootprintName() ) );
-            newList.Add( msg );
-            continue;
-        }
+    if( aFilterType & FILTERING_BY_COMPONENT_KEYWORD )
+        filter.FilterByFootprintFilters( aComponent->GetFootprintFilters() );
 
-        // Filter footprints by selected library
-        if( (aFilterType & FILTERING_BY_LIBRARY) && !aLibName.IsEmpty()
-            && !aList.GetItem( ii ).InLibrary( aLibName ) )
-            continue;
+    if( aFilterType & FILTERING_BY_PIN_COUNT )
+        filter.FilterByPinCount( aComponent->GetNetCount() );
 
-        // Filter footprints by symbol fp-filters
-        if( (aFilterType & FILTERING_BY_COMPONENT_KEYWORD) && aComponent
-            && !aComponent->MatchesFootprintFilters( aList.GetItem( ii ).GetNickname(), aList.GetItem( ii ).GetFootprintName() ) )
-            continue;
+    if( aFilterType & FILTERING_BY_LIBRARY )
+        filter.FilterByLibrary( aLibName );
 
-        // Filter footprints by symbol pin-count
-        if( (aFilterType & FILTERING_BY_PIN_COUNT) && aComponent
-            && aComponent->GetNetCount() != aList.GetItem( ii ).GetUniquePadCount() )
-            continue;
+    if( aFilterType & FILTERING_BY_NAME )
+        filter.FilterByPattern( aFootPrintFilterPattern );
 
-        // Filter footprints by text-input
-        if( (aFilterType & FILTERING_BY_NAME ) && !aFootPrintFilterPattern.IsEmpty() )
-        {
-            wxString currname = "";
-
-            // If the search string contains a ':' character,
-            // include the library name in the search string
-            // e.g. LibName:FootprintName
-            if( aFootPrintFilterPattern.Contains( ":" ) )
-            {
-                currname = aList.GetItem( ii ).GetNickname().Lower() + ":";
-            }
-
-            currname += aList.GetItem( ii ).GetFootprintName().Lower();
-
-            if( patternFilter.Find( currname ) == EDA_PATTERN_NOT_FOUND )
-            {
-                continue;
-            }
-        }
+    if( GetSelection() >= 0 && GetSelection() < (int)m_footprintList.GetCount() )
+        oldSelection = m_footprintList[ GetSelection() ];
 
-        msg.Printf( wxT( "%3d %s:%s" ), int( newList.GetCount() + 1 ),
-                    GetChars( aList.GetItem( ii ).GetNickname() ),
-                    GetChars( aList.GetItem( ii ).GetFootprintName() ) );
+    for( auto& i: filter )
+    {
+        msg.Printf( "%3d %s:%s", int( newList.GetCount() + 1 ),
+                    GetChars( i.GetNickname() ),
+                    GetChars( i.GetFootprintName() ) );
         newList.Add( msg );
     }
 
@@ -202,6 +170,7 @@ void FOOTPRINTS_LISTBOX::SetFootprints( FOOTPRINT_LIST& aList, const wxString& a
     if( selection == wxNOT_FOUND )
         selection = 0;
 
+    wxWindowUpdateLocker freeze( this );
     DeleteAllItems();
 
     if( m_footprintList.GetCount() )
diff --git a/cvpcb/cvpcb_mainframe.cpp b/cvpcb/cvpcb_mainframe.cpp
index af25c93c2..fb622768c 100644
--- a/cvpcb/cvpcb_mainframe.cpp
+++ b/cvpcb/cvpcb_mainframe.cpp
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2016 Jean-Pierre Charras, jp.charras at wanadoo.fr
  * Copyright (C) 2011-2016 Wayne Stambaugh <stambaughw@xxxxxxxxxxx>
- * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 1992-2017 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
@@ -127,6 +127,7 @@ CVPCB_MAINFRAME::CVPCB_MAINFRAME( KIWAY* aKiway, wxWindow* aParent ) :
     m_skipComponentSelect   = false;
     m_filteringOptions      = 0;
     m_tcFilterString        = NULL;
+    m_FootprintsList        = FOOTPRINT_LIST::GetInstance( Kiway() );
 
     /* Name of the document footprint list
      * usually located in share/modules/footprints_doc
@@ -409,7 +410,7 @@ bool CVPCB_MAINFRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, i
 void CVPCB_MAINFRAME::OnEditFootprintLibraryTable( wxCommandEvent& aEvent )
 {
     bool    tableChanged = false;
-    int     r = InvokePcbLibTableEditor( this, &GFootprintTable, Prj().PcbFootprintLibs() );
+    int     r = InvokePcbLibTableEditor( this, &GFootprintTable, Prj().PcbFootprintLibs( Kiway() ) );
 
     if( r & 1 )
     {
@@ -437,7 +438,7 @@ void CVPCB_MAINFRAME::OnEditFootprintLibraryTable( wxCommandEvent& aEvent )
 
         try
         {
-            Prj().PcbFootprintLibs()->Save( fileName );
+            Prj().PcbFootprintLibs( Kiway() )->Save( fileName );
             tableChanged = true;
         }
         catch( const IO_ERROR& ioe )
@@ -455,7 +456,7 @@ void CVPCB_MAINFRAME::OnEditFootprintLibraryTable( wxCommandEvent& aEvent )
     {
         wxBusyCursor dummy;
         BuildLIBRARY_LISTBOX();
-        m_FootprintsList.ReadFootprintFiles( Prj().PcbFootprintLibs() );
+        m_FootprintsList->ReadFootprintFiles( Prj().PcbFootprintLibs( Kiway() ) );
     }
 }
 
@@ -482,7 +483,7 @@ void CVPCB_MAINFRAME::OnSelectComponent( wxListEvent& event )
     COMPONENT* component = GetSelectedComponent();
     libraryName = m_libListBox->GetSelectedLibrary();
 
-    m_footprintListBox->SetFootprints( m_FootprintsList, libraryName, component,
+    m_footprintListBox->SetFootprints( *m_FootprintsList, libraryName, component,
                                        m_currentSearchPattern, m_filteringOptions);
 
     refreshAfterComponentSearch (component);
@@ -657,7 +658,7 @@ void CVPCB_MAINFRAME::DisplayStatus()
     {
         wxString footprintName = GetSelectedFootprint();
 
-        FOOTPRINT_INFO* module = m_FootprintsList.GetModuleInfo( footprintName );
+        FOOTPRINT_INFO* module = m_FootprintsList->GetModuleInfo( footprintName );
 
         if( module )    // can be NULL if no netlist loaded
         {
@@ -715,7 +716,7 @@ void CVPCB_MAINFRAME::DisplayStatus()
 
 bool CVPCB_MAINFRAME::LoadFootprintFiles()
 {
-    FP_LIB_TABLE* fptbl = Prj().PcbFootprintLibs();
+    FP_LIB_TABLE* fptbl = Prj().PcbFootprintLibs( Kiway() );
 
     // Check if there are footprint libraries in the footprint library table.
     if( !fptbl || !fptbl->GetLogicalLibs().size() )
@@ -728,12 +729,12 @@ bool CVPCB_MAINFRAME::LoadFootprintFiles()
     {
     wxBusyCursor dummy;  // Let the user know something is happening.
 
-    m_FootprintsList.ReadFootprintFiles( fptbl );
+    m_FootprintsList->ReadFootprintFiles( fptbl );
     }
 
-    if( m_FootprintsList.GetErrorCount() )
+    if( m_FootprintsList->GetErrorCount() )
     {
-        m_FootprintsList.DisplayErrors( this );
+        m_FootprintsList->DisplayErrors( this );
     }
 
     return true;
@@ -862,7 +863,7 @@ void CVPCB_MAINFRAME::BuildFOOTPRINTS_LISTBOX()
                                              wxFONTWEIGHT_NORMAL ) );
     }
 
-    m_footprintListBox->SetFootprints( m_FootprintsList, wxEmptyString, NULL,
+    m_footprintListBox->SetFootprints( *m_FootprintsList, wxEmptyString, NULL,
                     wxEmptyString, FOOTPRINTS_LISTBOX::UNFILTERED_FP_LIST );
     DisplayStatus();
 }
@@ -921,7 +922,7 @@ void CVPCB_MAINFRAME::BuildLIBRARY_LISTBOX()
                                        wxFONTWEIGHT_NORMAL ) );
     }
 
-    FP_LIB_TABLE* tbl = Prj().PcbFootprintLibs();
+    FP_LIB_TABLE* tbl = Prj().PcbFootprintLibs( Kiway() );
 
     if( tbl )
     {
diff --git a/cvpcb/cvpcb_mainframe.h b/cvpcb/cvpcb_mainframe.h
index 5471aa8a1..2218478ba 100644
--- a/cvpcb/cvpcb_mainframe.h
+++ b/cvpcb/cvpcb_mainframe.h
@@ -2,7 +2,7 @@
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
  * Copyright (C) 2016 Jean-Pierre Charras, jp.charras at wanadoo.fr
- * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 1992-2017 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
@@ -37,6 +37,7 @@
 #include <wxBasePcbFrame.h>
 #include <config_params.h>
 #include <autosel.h>
+#include <memory>
 
 
 /*  Forward declarations of all top-level window classes. */
@@ -72,7 +73,7 @@ public:
     wxArrayString             m_ModuleLibNames;
     wxArrayString             m_EquFilesNames;
     wxString                  m_DocModulesFileName;
-    FOOTPRINT_LIST            m_FootprintsList;
+    std::unique_ptr<FOOTPRINT_LIST> m_FootprintsList;
 
 protected:
     int             m_undefinedComponentCnt;
diff --git a/cvpcb/listview_classes.h b/cvpcb/listview_classes.h
index 0cb429037..1ef078caa 100644
--- a/cvpcb/listview_classes.h
+++ b/cvpcb/listview_classes.h
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 1992-2012 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 1992-2017 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
@@ -29,7 +29,7 @@
 #define CVSTRUCT_H
 
 #include <wx/listctrl.h>
-
+#include <footprint_filter.h>
 
 /*  Forward declarations of all top-level window classes. */
 class CVPCB_MAINFRAME;
@@ -90,8 +90,12 @@ private:
     wxArrayString  m_footprintList;
 
 public:
-    // OR'ed mask to manage footprint filtering options
-    enum FP_FILTER_T
+
+    /**
+     * Filter setting constants. The filter type is a bitwise OR of these flags,
+     * and only footprints matching all selected filter types are shown.
+     */
+    enum FP_FILTER_T: int
     {
         UNFILTERED_FP_LIST              = 0,
         FILTERING_BY_COMPONENT_KEYWORD  = 0x0001,
diff --git a/cvpcb/readwrite_dlgs.cpp b/cvpcb/readwrite_dlgs.cpp
index 4eadc9a1f..137b89121 100644
--- a/cvpcb/readwrite_dlgs.cpp
+++ b/cvpcb/readwrite_dlgs.cpp
@@ -7,7 +7,7 @@
  *
  * Copyright (C) 2015 Jean-Pierre Charras, jean-pierre.charras
  * Copyright (C) 2011-2016 Wayne Stambaugh <stambaughw@xxxxxxxxxxx>
- * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 1992-2017 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
@@ -26,7 +26,6 @@
  * or you may write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
-
 #include <fctsys.h>
 #include <kiway.h>
 #include <common.h>
@@ -212,7 +211,7 @@ bool CVPCB_MAINFRAME::ReadNetListAndLinkFiles( const std::string& aNetlist )
                     if( component->GetFPID().IsLegacy() )
                     {
                         // get this first here, it's possibly obsoleted if we get it too soon.
-                        FP_LIB_TABLE*   tbl = Prj().PcbFootprintLibs();
+                        FP_LIB_TABLE*   tbl = Prj().PcbFootprintLibs( Kiway() );
 
                         int guess = guessNickname( tbl, (LIB_ID*) &component->GetFPID() );
 
diff --git a/eeschema/dialogs/dialog_choose_component.cpp b/eeschema/dialogs/dialog_choose_component.cpp
index dab4e2de2..7940a2cbd 100644
--- a/eeschema/dialogs/dialog_choose_component.cpp
+++ b/eeschema/dialogs/dialog_choose_component.cpp
@@ -25,30 +25,31 @@
 
 #include <dialog_choose_component.h>
 
+#include <algorithm>
 #include <set>
 #include <wx/tokenzr.h>
 #include <wx/utils.h>
 
 #include <wx/artprov.h>
 #include <wx/bitmap.h>
-#include <wx/statbmp.h>
-#include <wx/textctrl.h>
-#include <wx/sizer.h>
+#include <wx/button.h>
+#include <wx/choice.h>
 #include <wx/dataview.h>
 #include <wx/html/htmlwin.h>
 #include <wx/panel.h>
-#include <wx/choice.h>
+#include <wx/sizer.h>
 #include <wx/splitter.h>
-#include <wx/button.h>
+#include <wx/statbmp.h>
+#include <wx/textctrl.h>
 #include <wx/timer.h>
 #include <wx/utils.h>
 
 #include <class_library.h>
+#include <generate_alias_info.h>
 #include <sch_base_frame.h>
-#include <widgets/footprint_preview_widget.h>
-#include <widgets/two_column_tree_list.h>
 #include <template_fieldnames.h>
-#include <generate_alias_info.h>
+#include <widgets/footprint_preview_widget.h>
+#include <widgets/footprint_select_widget.h>
 
 // Tree navigation helpers.
 static wxDataViewItem GetPrevItem( const wxDataViewCtrl& ctrl, const wxDataViewItem& item );
@@ -56,21 +57,25 @@ static wxDataViewItem GetNextItem( const wxDataViewCtrl& ctrl, const wxDataViewI
 static wxDataViewItem GetPrevSibling( const wxDataViewCtrl& ctrl, const wxDataViewItem& item );
 static wxDataViewItem GetNextSibling( const wxDataViewCtrl& ctrl, const wxDataViewItem& item );
 
-DIALOG_CHOOSE_COMPONENT::DIALOG_CHOOSE_COMPONENT(
-        SCH_BASE_FRAME* aParent, const wxString& aTitle,
-        CMP_TREE_MODEL_ADAPTER::PTR& aAdapter, int aDeMorganConvert ):
-    DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition,
-                 wxSize( 800, 650 ), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
-    m_parent( aParent ),
-    m_adapter( aAdapter ),
-    m_deMorganConvert( aDeMorganConvert >= 0 ? aDeMorganConvert : 0 ),
-    m_external_browser_requested( false )
+FOOTPRINT_ASYNC_LOADER          DIALOG_CHOOSE_COMPONENT::m_fp_loader;
+std::unique_ptr<FOOTPRINT_LIST> DIALOG_CHOOSE_COMPONENT::m_fp_list;
+
+DIALOG_CHOOSE_COMPONENT::DIALOG_CHOOSE_COMPONENT( SCH_BASE_FRAME* aParent, const wxString& aTitle,
+        CMP_TREE_MODEL_ADAPTER::PTR& aAdapter, int aDeMorganConvert, bool aAllowFieldEdits )
+        : DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, wxSize( 800, 650 ),
+                  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
+          m_parent( aParent ),
+          m_adapter( aAdapter ),
+          m_deMorganConvert( aDeMorganConvert >= 0 ? aDeMorganConvert : 0 ),
+          m_allow_field_edits( aAllowFieldEdits ),
+          m_external_browser_requested( false )
 {
     wxBusyCursor busy_while_loading;
 
     auto sizer = new wxBoxSizer( wxVERTICAL );
 
-    auto splitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE );
+    auto splitter = new wxSplitterWindow(
+            this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE );
     auto left_panel = ConstructLeftPanel( splitter );
     auto right_panel = ConstructRightPanel( splitter );
     auto buttons = new wxStdDialogButtonSizer();
@@ -84,22 +89,28 @@ DIALOG_CHOOSE_COMPONENT::DIALOG_CHOOSE_COMPONENT(
     buttons->AddButton( new wxButton( this, wxID_CANCEL ) );
     buttons->Realize();
 
-    sizer->Add( splitter,   1, wxEXPAND | wxALL,     5 );
-    sizer->Add( buttons,    0, wxEXPAND | wxBOTTOM, 10 );
+    sizer->Add( splitter, 1, wxEXPAND | wxALL, 5 );
+    sizer->Add( buttons, 0, wxEXPAND | wxBOTTOM, 10 );
     SetSizer( sizer );
 
     Bind( wxEVT_INIT_DIALOG, &DIALOG_CHOOSE_COMPONENT::OnInitDialog, this );
-    Bind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this );
-
-    m_query_ctrl->Bind( wxEVT_TEXT,         &DIALOG_CHOOSE_COMPONENT::OnQueryText, this );
-    m_query_ctrl->Bind( wxEVT_TEXT_ENTER,   &DIALOG_CHOOSE_COMPONENT::OnQueryEnter, this );
-    m_query_ctrl->Bind( wxEVT_CHAR_HOOK,    &DIALOG_CHOOSE_COMPONENT::OnQueryCharHook, this );
-    m_tree_ctrl->Bind( wxEVT_DATAVIEW_ITEM_ACTIVATED,   &DIALOG_CHOOSE_COMPONENT::OnTreeActivate, this );
-    m_tree_ctrl->Bind( wxEVT_DATAVIEW_SELECTION_CHANGED, &DIALOG_CHOOSE_COMPONENT::OnTreeSelect, this );
+    Bind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this, m_dbl_click_timer->GetId() );
+
+    m_query_ctrl->Bind( wxEVT_TEXT, &DIALOG_CHOOSE_COMPONENT::OnQueryText, this );
+    m_query_ctrl->Bind( wxEVT_TEXT_ENTER, &DIALOG_CHOOSE_COMPONENT::OnQueryEnter, this );
+    m_query_ctrl->Bind( wxEVT_CHAR_HOOK, &DIALOG_CHOOSE_COMPONENT::OnQueryCharHook, this );
+    m_tree_ctrl->Bind(
+            wxEVT_DATAVIEW_ITEM_ACTIVATED, &DIALOG_CHOOSE_COMPONENT::OnTreeActivate, this );
+    m_tree_ctrl->Bind(
+            wxEVT_DATAVIEW_SELECTION_CHANGED, &DIALOG_CHOOSE_COMPONENT::OnTreeSelect, this );
     m_details_ctrl->Bind( wxEVT_HTML_LINK_CLICKED, &DIALOG_CHOOSE_COMPONENT::OnDetailsLink, this );
     m_sch_view_ctrl->Bind( wxEVT_LEFT_DCLICK, &DIALOG_CHOOSE_COMPONENT::OnSchViewDClick, this );
     m_sch_view_ctrl->Bind( wxEVT_PAINT, &DIALOG_CHOOSE_COMPONENT::OnSchViewPaint, this );
 
+    if( m_fp_sel_ctrl )
+        m_fp_sel_ctrl->Bind(
+                EVT_FOOTPRINT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnFootprintSelected, this );
+
     Layout();
 }
 
@@ -121,31 +132,27 @@ wxPanel* DIALOG_CHOOSE_COMPONENT::ConstructLeftPanel( wxWindow* aParent )
     auto sizer = new wxBoxSizer( wxVERTICAL );
     auto search_sizer = new wxBoxSizer( wxHORIZONTAL );
 
-    m_query_ctrl = new wxTextCtrl( panel, wxID_ANY,
-            wxEmptyString, wxDefaultPosition, wxDefaultSize,
-            wxTE_PROCESS_ENTER );
+    m_query_ctrl = new wxTextCtrl(
+            panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER );
 
-    m_tree_ctrl = new wxDataViewCtrl( panel, wxID_ANY,
-            wxDefaultPosition, wxDefaultSize,
-            wxDV_SINGLE );
+    m_tree_ctrl =
+            new wxDataViewCtrl( panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_SINGLE );
     m_adapter->AttachTo( m_tree_ctrl );
 
-    m_details_ctrl = new wxHtmlWindow( panel, wxID_ANY,
-            wxDefaultPosition, wxSize( 320,240 ),
+    m_details_ctrl = new wxHtmlWindow( panel, wxID_ANY, wxDefaultPosition, wxSize( 320, 240 ),
             wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER );
 
-    // Additional visual cue for GTK, which hides the placeholder text on focus
+// Additional visual cue for GTK, which hides the placeholder text on focus
 #ifdef __WXGTK__
-    search_sizer->Add(
-        new wxStaticBitmap(
-            panel, wxID_ANY, wxArtProvider::GetBitmap( wxART_FIND, wxART_FRAME_ICON ) ),
-        0, wxALIGN_CENTER | wxALL, 5 );
+    search_sizer->Add( new wxStaticBitmap( panel, wxID_ANY,
+                               wxArtProvider::GetBitmap( wxART_FIND, wxART_FRAME_ICON ) ),
+            0, wxALIGN_CENTER | wxALL, 5 );
 #endif
 
     search_sizer->Add( m_query_ctrl, 1, wxALIGN_CENTER | wxALL | wxEXPAND, 5 );
 
-    sizer->Add( search_sizer,   0, wxEXPAND,         5 );
-    sizer->Add( m_tree_ctrl,    1, wxALL | wxEXPAND, 5 );
+    sizer->Add( search_sizer, 0, wxEXPAND, 5 );
+    sizer->Add( m_tree_ctrl, 1, wxALL | wxEXPAND, 5 );
     sizer->Add( m_details_ctrl, 1, wxALL | wxEXPAND, 5 );
 
     panel->SetSizer( sizer );
@@ -160,23 +167,25 @@ wxPanel* DIALOG_CHOOSE_COMPONENT::ConstructRightPanel( wxWindow* aParent )
     auto panel = new wxPanel( aParent );
     auto sizer = new wxBoxSizer( wxVERTICAL );
 
-    m_sch_view_ctrl = new wxPanel( panel, wxID_ANY,
-            wxDefaultPosition, wxSize( -1, -1 ),
+    m_sch_view_ctrl = new wxPanel( panel, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ),
             wxFULL_REPAINT_ON_RESIZE | wxSUNKEN_BORDER | wxTAB_TRAVERSAL );
     m_sch_view_ctrl->SetLayoutDirection( wxLayout_LeftToRight );
 
-    m_fp_sel_ctrl = new wxChoice( panel, wxID_ANY );
-    m_fp_sel_ctrl->SetSelection( 0 );
+    if( m_allow_field_edits )
+        m_fp_sel_ctrl = new FOOTPRINT_SELECT_WIDGET( panel, m_fp_loader, m_fp_list, true );
+    else
+        m_fp_sel_ctrl = nullptr;
 
     m_fp_view_ctrl = new FOOTPRINT_PREVIEW_WIDGET( panel, Kiway() );
 
-    sizer->Add( m_sch_view_ctrl,    1, wxEXPAND | wxALL,    5 );
-    sizer->Add( m_fp_sel_ctrl,      0, wxEXPAND | wxALL,    5 );
-    sizer->Add( m_fp_view_ctrl,     1, wxEXPAND | wxALL,    5 );
 
-#ifndef KICAD_FOOTPRINT_SELECTOR
-    m_fp_sel_ctrl->Hide();
-#endif
+    sizer->Add( m_sch_view_ctrl, 1, wxEXPAND | wxALL, 5 );
+
+    if( m_fp_sel_ctrl )
+        sizer->Add( m_fp_sel_ctrl, 0, wxEXPAND | wxALL, 5 );
+
+    sizer->Add( m_fp_view_ctrl, 1, wxEXPAND | wxALL, 5 );
+
 
     panel->SetSizer( sizer );
     panel->Layout();
@@ -199,6 +208,12 @@ void DIALOG_CHOOSE_COMPONENT::OnInitDialog( wxInitDialogEvent& aEvent )
         // This hides the GAL panel and shows the status label
         m_fp_view_ctrl->SetStatusText( wxEmptyString );
     }
+
+    if( m_fp_sel_ctrl )
+        m_fp_sel_ctrl->Load( Kiway(), Prj() );
+
+    // There may be a part preselected in the model. Make sure it is displayed.
+    PostSelectEvent();
 }
 
 
@@ -206,13 +221,19 @@ LIB_ALIAS* DIALOG_CHOOSE_COMPONENT::GetSelectedAlias( int* aUnit ) const
 {
     auto sel = m_tree_ctrl->GetSelection();
 
-    if( aUnit && m_adapter->GetUnitFor( sel ) )
+    if( aUnit )
         *aUnit = m_adapter->GetUnitFor( sel );
 
     return m_adapter->GetAliasFor( sel );
 }
 
 
+std::vector<std::pair<int, wxString>> DIALOG_CHOOSE_COMPONENT::GetFields() const
+{
+    return m_field_edits;
+}
+
+
 void DIALOG_CHOOSE_COMPONENT::OnQueryText( wxCommandEvent& aEvent )
 {
     m_adapter->UpdateSearchString( m_query_ctrl->GetLineText( 0 ) );
@@ -254,16 +275,12 @@ void DIALOG_CHOOSE_COMPONENT::OnQueryCharHook( wxKeyEvent& aKeyStroke )
 
     switch( aKeyStroke.GetKeyCode() )
     {
-    case WXK_UP:
-        SelectIfValid( GetPrevItem( *m_tree_ctrl, sel ) );
-        break;
+    case WXK_UP: SelectIfValid( GetPrevItem( *m_tree_ctrl, sel ) ); break;
 
-    case WXK_DOWN:
-        SelectIfValid( GetNextItem( *m_tree_ctrl, sel ) );
-        break;
+    case WXK_DOWN: SelectIfValid( GetNextItem( *m_tree_ctrl, sel ) ); break;
 
     default:
-        aKeyStroke.Skip();  // Any other key: pass on to search box directly.
+        aKeyStroke.Skip(); // Any other key: pass on to search box directly.
         break;
     }
 }
@@ -271,8 +288,8 @@ void DIALOG_CHOOSE_COMPONENT::OnQueryCharHook( wxKeyEvent& aKeyStroke )
 
 void DIALOG_CHOOSE_COMPONENT::OnTreeSelect( wxDataViewEvent& aEvent )
 {
-    auto sel = m_tree_ctrl->GetSelection();
-    int unit = m_adapter->GetUnitFor( sel );
+    auto       sel = m_tree_ctrl->GetSelection();
+    int        unit = m_adapter->GetUnitFor( sel );
     LIB_ALIAS* alias = m_adapter->GetAliasFor( sel );
 
     m_sch_view_ctrl->Refresh();
@@ -281,6 +298,7 @@ void DIALOG_CHOOSE_COMPONENT::OnTreeSelect( wxDataViewEvent& aEvent )
     {
         m_details_ctrl->SetPage( GenerateAliasInfo( alias, unit ) );
         ShowFootprintFor( alias );
+        PopulateFootprintSelector( alias );
     }
     else
     {
@@ -288,6 +306,8 @@ void DIALOG_CHOOSE_COMPONENT::OnTreeSelect( wxDataViewEvent& aEvent )
 
         if( m_fp_view_ctrl->IsInitialized() )
             m_fp_view_ctrl->SetStatusText( wxEmptyString );
+
+        PopulateFootprintSelector( nullptr );
     }
 }
 
@@ -314,7 +334,7 @@ void DIALOG_CHOOSE_COMPONENT::OnCloseTimer( wxTimerEvent& aEvent )
     }
     else
     {
-        EndModal( wxID_OK );
+        EndQuasiModal( wxID_OK );
     }
 }
 
@@ -322,7 +342,7 @@ void DIALOG_CHOOSE_COMPONENT::OnCloseTimer( wxTimerEvent& aEvent )
 void DIALOG_CHOOSE_COMPONENT::OnSchViewDClick( wxMouseEvent& aEvent )
 {
     m_external_browser_requested = true;
-    EndModal( wxID_OK );
+    EndQuasiModal( wxID_OK );
 }
 
 
@@ -331,32 +351,62 @@ void DIALOG_CHOOSE_COMPONENT::ShowFootprintFor( LIB_ALIAS* aAlias )
     if( !m_fp_view_ctrl->IsInitialized() )
         return;
 
-    LIB_FIELDS fields;
-    aAlias->GetPart()->GetFields( fields );
+    LIB_FIELD* fp_field = aAlias->GetPart()->GetField( FOOTPRINT );
+    wxString   fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
+
+    ShowFootprint( fp_name );
+}
 
-    for( auto const & field: fields )
+
+void DIALOG_CHOOSE_COMPONENT::ShowFootprint( wxString const& aName )
+{
+    if( aName == wxEmptyString )
     {
-        if( field.GetId() != FOOTPRINT )
-            continue;
-        wxString fpname = field.GetFullText();
-        if( fpname == wxEmptyString )
-        {
-            m_fp_view_ctrl->SetStatusText( _( "No footprint specified" ) );
-        }
-        else
-        {
-            m_fp_view_ctrl->ClearStatus();
-            m_fp_view_ctrl->CacheFootprint( LIB_ID( fpname ) );
-            m_fp_view_ctrl->DisplayFootprint( LIB_ID( fpname ) );
-        }
-        break;
+        m_fp_view_ctrl->SetStatusText( _( "No footprint specified" ) );
+    }
+    else
+    {
+        LIB_ID lib_id( aName );
+
+        m_fp_view_ctrl->ClearStatus();
+        m_fp_view_ctrl->CacheFootprint( lib_id );
+        m_fp_view_ctrl->DisplayFootprint( lib_id );
+    }
+}
+
+
+void DIALOG_CHOOSE_COMPONENT::PopulateFootprintSelector( LIB_ALIAS* aAlias )
+{
+    if( !m_fp_sel_ctrl )
+        return;
+
+    m_fp_sel_ctrl->ClearFilters();
+
+    if( aAlias )
+    {
+        LIB_PINS   temp_pins;
+        LIB_FIELD* fp_field = aAlias->GetPart()->GetField( FOOTPRINT );
+        wxString   fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
+
+        aAlias->GetPart()->GetPins( temp_pins );
+
+        m_fp_sel_ctrl->FilterByPinCount( temp_pins.size() );
+        m_fp_sel_ctrl->FilterByFootprintFilters( aAlias->GetPart()->GetFootPrints(), true );
+        m_fp_sel_ctrl->SetDefaultFootprint( fp_name );
+        m_fp_sel_ctrl->UpdateList();
+        m_fp_sel_ctrl->Enable();
+    }
+    else
+    {
+        m_fp_sel_ctrl->UpdateList();
+        m_fp_sel_ctrl->Disable();
     }
 }
 
 
 void DIALOG_CHOOSE_COMPONENT::OnDetailsLink( wxHtmlLinkEvent& aEvent )
 {
-    const wxHtmlLinkInfo & info = aEvent.GetLinkInfo();
+    const wxHtmlLinkInfo& info = aEvent.GetLinkInfo();
     ::wxLaunchDefaultBrowser( info.GetHref() );
 }
 
@@ -365,9 +415,9 @@ void DIALOG_CHOOSE_COMPONENT::OnSchViewPaint( wxPaintEvent& aEvent )
 {
     auto sel = m_tree_ctrl->GetSelection();
 
-    int unit = m_adapter->GetUnitFor( sel );
+    int        unit = m_adapter->GetUnitFor( sel );
     LIB_ALIAS* alias = m_adapter->GetAliasFor( sel );
-    LIB_PART*   part = alias ? alias->GetPart() : nullptr;
+    LIB_PART*  part = alias ? alias->GetPart() : nullptr;
 
     // Don't draw anything (not even the background) if we don't have
     // a part to show
@@ -392,6 +442,21 @@ void DIALOG_CHOOSE_COMPONENT::OnSchViewPaint( wxPaintEvent& aEvent )
 }
 
 
+void DIALOG_CHOOSE_COMPONENT::OnFootprintSelected( wxCommandEvent& aEvent )
+{
+    m_fp_override = aEvent.GetString();
+
+    m_field_edits.erase(
+            std::remove_if( m_field_edits.begin(), m_field_edits.end(),
+                    []( std::pair<int, wxString> const& i ) { return i.first == FOOTPRINT; } ),
+            m_field_edits.end() );
+
+    m_field_edits.push_back( std::make_pair( FOOTPRINT, m_fp_override ) );
+
+    ShowFootprint( m_fp_override );
+}
+
+
 void DIALOG_CHOOSE_COMPONENT::RenderPreview( LIB_PART* aComponent, int aUnit )
 {
     wxPaintDC dc( m_sch_view_ctrl );
@@ -418,10 +483,10 @@ void DIALOG_CHOOSE_COMPONENT::RenderPreview( LIB_PART* aComponent, int aUnit )
     dc.SetDeviceOrigin( dc_size.x / 2, dc_size.y / 2 );
 
     // Find joint bounding box for everything we are about to draw.
-    EDA_RECT bBox = aComponent->GetUnitBoundingBox( aUnit, m_deMorganConvert );
+    EDA_RECT     bBox = aComponent->GetUnitBoundingBox( aUnit, m_deMorganConvert );
     const double xscale = (double) dc_size.x / bBox.GetWidth();
     const double yscale = (double) dc_size.y / bBox.GetHeight();
-    const double scale  = std::min( xscale, yscale ) * 0.85;
+    const double scale = std::min( xscale, yscale ) * 0.85;
 
     dc.SetUserScale( scale, scale );
 
@@ -503,7 +568,7 @@ static wxDataViewItem GetNextItem( const wxDataViewCtrl& tree, const wxDataViewI
     else
     {
         // Walk up levels until we find one that has a next sibling.
-        for ( wxDataViewItem walk = item; walk.IsOk(); walk = tree.GetModel()->GetParent( walk ) )
+        for( wxDataViewItem walk = item; walk.IsOk(); walk = tree.GetModel()->GetParent( walk ) )
         {
             nextItem = GetNextSibling( tree, walk );
 
@@ -519,8 +584,8 @@ static wxDataViewItem GetNextItem( const wxDataViewCtrl& tree, const wxDataViewI
 static wxDataViewItem GetPrevSibling( const wxDataViewCtrl& tree, const wxDataViewItem& item )
 {
     wxDataViewItemArray siblings;
-    wxDataViewItem invalid;
-    wxDataViewItem parent = tree.GetModel()->GetParent( item );
+    wxDataViewItem      invalid;
+    wxDataViewItem      parent = tree.GetModel()->GetParent( item );
 
     tree.GetModel()->GetChildren( parent, siblings );
 
@@ -542,8 +607,8 @@ static wxDataViewItem GetPrevSibling( const wxDataViewCtrl& tree, const wxDataVi
 static wxDataViewItem GetNextSibling( const wxDataViewCtrl& tree, const wxDataViewItem& item )
 {
     wxDataViewItemArray siblings;
-    wxDataViewItem invalid;
-    wxDataViewItem parent = tree.GetModel()->GetParent( item );
+    wxDataViewItem      invalid;
+    wxDataViewItem      parent = tree.GetModel()->GetParent( item );
 
     tree.GetModel()->GetChildren( parent, siblings );
 
diff --git a/eeschema/dialogs/dialog_choose_component.h b/eeschema/dialogs/dialog_choose_component.h
index 6af8869ac..211f97f29 100644
--- a/eeschema/dialogs/dialog_choose_component.h
+++ b/eeschema/dialogs/dialog_choose_component.h
@@ -27,6 +27,7 @@
 
 #include "dialog_shim.h"
 #include <cmp_tree_model_adapter.h>
+#include <footprint_info.h>
 
 class wxStaticBitmap;
 class wxTextCtrl;
@@ -40,6 +41,7 @@ class wxButton;
 class wxTimer;
 
 class FOOTPRINT_PREVIEW_WIDGET;
+class FOOTPRINT_SELECT_WIDGET;
 class LIB_ALIAS;
 class LIB_PART;
 class SCH_BASE_FRAME;
@@ -82,7 +84,6 @@ class SCH_BASE_FRAME;
 class DIALOG_CHOOSE_COMPONENT : public DIALOG_SHIM
 {
 public:
-
     /**
      * Create dialog to choose component.
      *
@@ -92,30 +93,46 @@ public:
      *                  for documentation.
      * @param aDeMorganConvert  preferred deMorgan conversion
      *                          (TODO: should happen in dialog)
+     * @param aAllowFieldEdits  if false, all functions that allow the user to edit
+     *      fields (currently just footprint selection) will not be available.
      */
     DIALOG_CHOOSE_COMPONENT( SCH_BASE_FRAME* aParent, const wxString& aTitle,
-                             CMP_TREE_MODEL_ADAPTER::PTR& aAdapter,
-                             int aDeMorganConvert );
+            CMP_TREE_MODEL_ADAPTER::PTR& aAdapter, int aDeMorganConvert, bool aAllowFieldEdits );
+
+    ~DIALOG_CHOOSE_COMPONENT();
 
     ~DIALOG_CHOOSE_COMPONENT();
 
     /** Function GetSelectedAlias
      * To be called after this dialog returns from ShowModal().
      *
+     * For multi-unit components, if the user selects the component itself
+     * rather than picking an individual unit, 0 will be returned in aUnit.
+     * Beware that this is an invalid unit number - this should be replaced
+     * with whatever default is desired (usually 1).
+     *
      * @param aUnit if not NULL, the selected unit is filled in here.
      * @return the alias that has been selected, or NULL if there is none.
      */
     LIB_ALIAS* GetSelectedAlias( int* aUnit ) const;
 
+    /**
+     * Get a list of fields edited by the user.
+     * @return vector of pairs; each.first = field ID, each.second = new value
+     */
+    std::vector<std::pair<int, wxString>> GetFields() const;
+
     /** Function IsExternalBrowserSelected
      *
      * @return true, iff the user pressed the thumbnail view of the component to
      *               launch the component browser.
      */
-    bool IsExternalBrowserSelected() const { return m_external_browser_requested; }
+    bool IsExternalBrowserSelected() const
+    {
+        return m_external_browser_requested;
+    }
 
 protected:
-
     static constexpr int DblClickDelay = 100; // milliseconds
 
     wxPanel* ConstructLeftPanel( wxWindow* aParent );
@@ -123,6 +140,7 @@ protected:
 
     void OnInitDialog( wxInitDialogEvent& aEvent );
     void OnCloseTimer( wxTimerEvent& aEvent );
+    void OnProgressTimer( wxTimerEvent& aEvent );
 
     void OnQueryText( wxCommandEvent& aEvent );
     void OnQueryEnter( wxCommandEvent& aEvent );
@@ -136,12 +154,26 @@ protected:
     void OnSchViewDClick( wxMouseEvent& aEvent );
     void OnSchViewPaint( wxPaintEvent& aEvent );
 
+    void OnFootprintSelected( wxCommandEvent& aEvent );
+
     /**
      * Look up the footprint for a given alias and display it.
      */
     void ShowFootprintFor( LIB_ALIAS* aAlias );
 
     /**
+     * Display the given footprint by name.
+     */
+    void ShowFootprint( wxString const& aName );
+
+    /**
+     * Populate the footprint selector for a given alias.
+     *
+     * @param aAlias alias, or null to clear
+     */
+    void PopulateFootprintSelector( LIB_ALIAS* aAlias );
+
+    /**
      * If a wxDataViewitem is valid, select it and post a selection event.
      */
     void SelectIfValid( const wxDataViewItem& aTreeId );
@@ -170,15 +202,20 @@ protected:
     wxDataViewCtrl* m_tree_ctrl;
     wxHtmlWindow*   m_details_ctrl;
     wxPanel*        m_sch_view_ctrl;
-    wxChoice*       m_fp_sel_ctrl;
 
+    FOOTPRINT_SELECT_WIDGET*  m_fp_sel_ctrl;
     FOOTPRINT_PREVIEW_WIDGET* m_fp_view_ctrl;
 
     SCH_BASE_FRAME*             m_parent;
     CMP_TREE_MODEL_ADAPTER::PTR m_adapter;
-    int             m_deMorganConvert;
-    bool            m_external_browser_requested;
-
+    int                         m_deMorganConvert;
+    bool                        m_allow_field_edits;
+    bool                        m_external_browser_requested;
+    wxString                    m_fp_override;
+
+    static FOOTPRINT_ASYNC_LOADER          m_fp_loader;
+    static std::unique_ptr<FOOTPRINT_LIST> m_fp_list;
+    std::vector<std::pair<int, wxString>> m_field_edits;
 };
 
 #endif /* DIALOG_CHOOSE_COMPONENT_H */
diff --git a/eeschema/dialogs/dialog_edit_component_in_schematic.cpp b/eeschema/dialogs/dialog_edit_component_in_schematic.cpp
index e1a25a872..381640da5 100644
--- a/eeschema/dialogs/dialog_edit_component_in_schematic.cpp
+++ b/eeschema/dialogs/dialog_edit_component_in_schematic.cpp
@@ -281,14 +281,14 @@ void DIALOG_EDIT_COMPONENT_IN_SCHEMATIC::OnTestChipName( wxCommandEvent& event )
 
 void DIALOG_EDIT_COMPONENT_IN_SCHEMATIC::OnSelectChipName( wxCommandEvent& event )
 {
-    wxArrayString dummy;
-    int dummyunit = 1;
-    wxString chipname = m_parent->SelectComponentFromLibrary( NULL, dummy, dummyunit,
-                                                              true, NULL, NULL );
-    if( chipname.IsEmpty() )
+    SCH_BASE_FRAME::HISTORY_LIST dummy;
+
+    auto sel = m_parent->SelectComponentFromLibrary( NULL, dummy, true, 0, 0 );
+
+    if( sel.Name.IsEmpty() )
         return;
 
-    chipnameTextCtrl->SetValue( chipname );
+    chipnameTextCtrl->SetValue( sel.Name );
 }
 
 
diff --git a/eeschema/getpart.cpp b/eeschema/getpart.cpp
index 22a9eaa52..42f1962cc 100644
--- a/eeschema/getpart.cpp
+++ b/eeschema/getpart.cpp
@@ -28,6 +28,7 @@
  * @brief functions to get and place library components.
  */
 
+#include <algorithm>
 #include <fctsys.h>
 #include <pgm_base.h>
 #include <kiway.h>
@@ -50,9 +51,10 @@
 #include <dialog_get_component.h>
 
 
-wxString SCH_BASE_FRAME::SelectComponentFromLibBrowser( const SCHLIB_FILTER* aFilter,
-                                                        LIB_ALIAS* aPreselectedAlias,
-                                                        int* aUnit, int* aConvert )
+SCH_BASE_FRAME::COMPONENT_SELECTION SCH_BASE_FRAME::SelectComponentFromLibBrowser(
+        const SCHLIB_FILTER* aFilter,
+        LIB_ALIAS* aPreselectedAlias,
+        int aUnit, int aConvert )
 {
     // Close any open non-modal Lib browser, and open a new one, in "modal" mode:
     LIB_VIEW_FRAME* viewlibFrame = (LIB_VIEW_FRAME*) Kiway().Player( FRAME_SCH_VIEWER, false );
@@ -71,38 +73,36 @@ wxString SCH_BASE_FRAME::SelectComponentFromLibBrowser( const SCHLIB_FILTER* aFi
         viewlibFrame->SetSelectedComponent( aPreselectedAlias->GetName() );
     }
 
-    if( aUnit && *aUnit > 0 )
-        viewlibFrame->SetUnit( *aUnit );
+    if( aUnit > 0 )
+        viewlibFrame->SetUnit( aUnit );
 
-    if( aConvert && *aConvert > 0 )
-        viewlibFrame->SetConvert( *aConvert );
+    if( aConvert > 0 )
+        viewlibFrame->SetConvert( aConvert );
 
     viewlibFrame->Refresh();
 
-    wxString cmpname;
+    COMPONENT_SELECTION sel;
 
-    if( viewlibFrame->ShowModal( &cmpname, this ) )
+    if( viewlibFrame->ShowModal( &sel.Name, this ) )
     {
-        if( aUnit )
-            *aUnit = viewlibFrame->GetUnit();
-
-        if( aConvert )
-            *aConvert = viewlibFrame->GetConvert();
+        sel.Unit = viewlibFrame->GetUnit();
+        sel.Convert = viewlibFrame->GetConvert();
     }
 
     viewlibFrame->Destroy();
 
-    return cmpname;
+    return sel;
 }
 
 
-wxString SCH_BASE_FRAME::SelectComponentFromLibrary( const SCHLIB_FILTER* aFilter,
-                                                     wxArrayString&  aHistoryList,
-                                                     int&            aHistoryLastUnit,
-                                                     bool            aUseLibBrowser,
-                                                     int*            aUnit,
-                                                     int*            aConvert,
-                                                     const wxString& aHighlight )
+SCH_BASE_FRAME::COMPONENT_SELECTION SCH_BASE_FRAME::SelectComponentFromLibrary(
+        const SCHLIB_FILTER*                aFilter,
+        std::vector<COMPONENT_SELECTION>&   aHistoryList,
+        bool                                aUseLibBrowser,
+        int                                 aUnit,
+        int                                 aConvert,
+        const wxString&                     aHighlight,
+        bool                                aAllowFields )
 {
     wxString        dialogTitle;
     PART_LIBS*      libs = Prj().SchLibs();
@@ -141,56 +141,68 @@ wxString SCH_BASE_FRAME::SelectComponentFromLibrary( const SCHLIB_FILTER* aFilte
 
     if( !aHistoryList.empty() )
     {
-        adapter->AddAliasList( "-- " + _( "History" ) + " --", aHistoryList, NULL );
-        adapter->SetPreselectNode( aHistoryList[0], aHistoryLastUnit );
+        wxArrayString history_list;
+
+        for( auto const& i : aHistoryList )
+            history_list.push_back( i.Name );
+
+        adapter->AddAliasList( "-- " + _( "History" ) + " --", history_list, NULL );
+        adapter->SetPreselectNode( aHistoryList[0].Name, aHistoryList[0].Unit );
     }
 
     if( !aHighlight.IsEmpty() )
         adapter->SetPreselectNode( aHighlight, /* aUnit */ 0 );
 
-    const int deMorgan = aConvert ? *aConvert : 1;
     dialogTitle.Printf( _( "Choose Component (%d items loaded)" ),
                         adapter->GetComponentsCount() );
-    DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, adapter, deMorgan );
+    DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, adapter, aConvert, aAllowFields );
+
+    if( dlg.ShowQuasiModal() == wxID_CANCEL )
+        return COMPONENT_SELECTION();
+
+    COMPONENT_SELECTION sel;
+    LIB_ALIAS* const alias = dlg.GetSelectedAlias( &sel.Unit );
+
+    if( alias->GetPart()->IsMulti() && sel.Unit == 0 )
+        sel.Unit = 1;
 
-    if( dlg.ShowModal() == wxID_CANCEL )
-        return wxEmptyString;
+    sel.Fields = dlg.GetFields();
 
-    wxString cmpName;
-    LIB_ALIAS* const alias = dlg.GetSelectedAlias( aUnit );
     if ( alias )
-        cmpName = alias->GetName();
+        sel.Name = alias->GetName();
 
     if( dlg.IsExternalBrowserSelected() )   // User requested component browser.
-        cmpName = SelectComponentFromLibBrowser( aFilter, alias, aUnit, aConvert);
+        sel = SelectComponentFromLibBrowser( aFilter, alias, sel.Unit, sel.Convert );
 
-    if( !cmpName.empty() )
+    if( !sel.Name.empty() )
     {
-        AddHistoryComponentName( aHistoryList, cmpName );
-
-        if ( aUnit )
-            aHistoryLastUnit = *aUnit;
+        aHistoryList.erase(
+            std::remove_if(
+                aHistoryList.begin(),
+                aHistoryList.end(),
+                [ &sel ]( COMPONENT_SELECTION const& i ) { return i.Name == sel.Name; } ),
+            aHistoryList.end() );
+
+        aHistoryList.insert( aHistoryList.begin(), sel );
     }
 
-    return cmpName;
+    return sel;
 }
 
 
-SCH_COMPONENT* SCH_EDIT_FRAME::Load_Component( wxDC*           aDC,
-                                               const SCHLIB_FILTER* aFilter,
-                                               wxArrayString&  aHistoryList,
-                                               int&            aHistoryLastUnit,
-                                               bool            aUseLibBrowser )
+SCH_COMPONENT* SCH_EDIT_FRAME::Load_Component(
+                                wxDC*                            aDC,
+                                const SCHLIB_FILTER*             aFilter,
+                                SCH_BASE_FRAME::HISTORY_LIST&    aHistoryList,
+                                bool                             aUseLibBrowser )
 {
-    int unit    = 1;
-    int convert = 1;
     SetRepeatItem( NULL );
     m_canvas->SetIgnoreMouseEvents( true );
 
-    wxString name = SelectComponentFromLibrary( aFilter, aHistoryList, aHistoryLastUnit,
-                                                aUseLibBrowser, &unit, &convert );
+    auto sel = SelectComponentFromLibrary( aFilter, aHistoryList,
+                                                aUseLibBrowser, 1, 1 );
 
-    if( name.IsEmpty() )
+    if( sel.Name.IsEmpty() )
     {
         m_canvas->SetIgnoreMouseEvents( false );
         m_canvas->MoveCursorToCrossHair();
@@ -205,19 +217,19 @@ SCH_COMPONENT* SCH_EDIT_FRAME::Load_Component( wxDC*           aDC,
     if( aFilter )
         libsource = aFilter->GetLibSource();
 
-    LIB_PART* part = Prj().SchLibs()->FindLibPart( LIB_ID( wxEmptyString, name ), libsource );
+    LIB_PART* part = Prj().SchLibs()->FindLibPart( LIB_ID( wxEmptyString, sel.Name ), libsource );
 
     if( !part )
     {
         wxString msg = wxString::Format( _(
             "Failed to find part '%s' in library" ),
-            GetChars( name )
+            GetChars( sel.Name )
             );
         wxMessageBox( msg );
         return NULL;
     }
 
-    SCH_COMPONENT* component = new SCH_COMPONENT( *part, m_CurrentSheet, unit, convert,
+    SCH_COMPONENT* component = new SCH_COMPONENT( *part, m_CurrentSheet, sel.Unit, sel.Convert,
                                                   GetCrossHairPosition(), true );
 
     // Set the m_ChipName value, from component name in lib, for aliases
@@ -225,14 +237,23 @@ SCH_COMPONENT* SCH_EDIT_FRAME::Load_Component( wxDC*           aDC,
     // alias exists because its root component was found
     LIB_ID libId;
 
-    libId.SetLibItemName( name, false );
+    libId.SetLibItemName( sel.Name, false );
     component->SetLibId( libId );
 
     // Be sure the link to the corresponding LIB_PART is OK:
     component->Resolve( Prj().SchLibs() );
 
+    // Set any fields that have been modified
+    for( auto const& i : sel.Fields )
+    {
+        auto field = component->GetField( i.first );
+
+        if( field )
+            field->SetText( i.second );
+    }
+
     // Set the component value that can differ from component name in lib, for aliases
-    component->GetField( VALUE )->SetText( name );
+    component->GetField( VALUE )->SetText( sel.Name );
 
     MSG_PANEL_ITEMS items;
 
diff --git a/eeschema/libedit.cpp b/eeschema/libedit.cpp
index 2f454c3ac..fc9aa63e6 100644
--- a/eeschema/libedit.cpp
+++ b/eeschema/libedit.cpp
@@ -105,7 +105,6 @@ bool LIB_EDIT_FRAME::LoadComponentFromCurrentLib( LIB_ALIAS* aLibEntry )
 
 void LIB_EDIT_FRAME::LoadOneLibraryPart( wxCommandEvent& event )
 {
-    wxString   cmp_name;
     LIB_ALIAS* libEntry = NULL;
 
     m_canvas->EndMouseCapture( ID_NO_TOOL_SELECTED, m_canvas->GetDefaultCursor() );
@@ -130,14 +129,13 @@ void LIB_EDIT_FRAME::LoadOneLibraryPart( wxCommandEvent& event )
     LIB_PART* current_part = GetCurPart();
     wxString part_name = current_part ? current_part->GetName() : wxString( wxEmptyString );
 
-    wxArrayString dummyHistoryList;
-    int dummyLastUnit;
+    SCH_BASE_FRAME::HISTORY_LIST dummyHistoryList;
     SCHLIB_FILTER filter;
     filter.LoadFrom( lib->GetName() );
-    cmp_name = SelectComponentFromLibrary( &filter, dummyHistoryList, dummyLastUnit,
-                                          true, NULL, NULL, part_name );
+    auto sel = SelectComponentFromLibrary( &filter, dummyHistoryList,
+                                          true, 0, 0, part_name, false );
 
-    if( cmp_name.IsEmpty() )
+    if( sel.Name.IsEmpty() )
         return;
 
     GetScreen()->ClrModify();
@@ -148,14 +146,14 @@ void LIB_EDIT_FRAME::LoadOneLibraryPart( wxCommandEvent& event )
     m_aliasName.Empty();
 
     // Load the new library component
-    libEntry = lib->FindAlias( cmp_name );
+    libEntry = lib->FindAlias( sel.Name );
     PART_LIB* searchLib = lib;
 
     if( !libEntry )
     {
         // Not found in the active library: search inside the full list
         // (can happen when using Viewlib to load a component)
-        libEntry = Prj().SchLibs()->FindLibraryAlias( LIB_ID( wxEmptyString, cmp_name ) );
+        libEntry = Prj().SchLibs()->FindLibraryAlias( LIB_ID( wxEmptyString, sel.Name ) );
 
         if( libEntry )
         {
@@ -175,7 +173,7 @@ void LIB_EDIT_FRAME::LoadOneLibraryPart( wxCommandEvent& event )
     if( !libEntry )
     {
         wxString msg = wxString::Format( _( "Part name '%s' not found in library '%s'" ),
-                                         GetChars( cmp_name ),
+                                         GetChars( sel.Name ),
                                          GetChars( searchLib->GetName() )  );
         DisplayError( this, msg );
         return;
@@ -540,9 +538,9 @@ void LIB_EDIT_FRAME::DeleteOnePart( wxCommandEvent& event )
     wxString dialogTitle;
     dialogTitle.Printf( _( "Delete Component (%u items loaded)" ), adapter->GetComponentsCount() );
 
-    DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, adapter, m_convert );
+    DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, adapter, m_convert, false );
 
-    if( dlg.ShowModal() == wxID_CANCEL )
+    if( dlg.ShowQuasiModal() == wxID_CANCEL )
     {
         return;
     }
diff --git a/eeschema/onleftclick.cpp b/eeschema/onleftclick.cpp
index 9aec70733..5721d6c4d 100644
--- a/eeschema/onleftclick.cpp
+++ b/eeschema/onleftclick.cpp
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
  * Copyright (C) 2011 Wayne Stambaugh <stambaughw@xxxxxxxxxxx>
- * Copyright (C) 1992-2015 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 1992-2017 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
@@ -53,11 +53,8 @@
 
 // TODO(hzeller): These pairs of elmenets should be represented by an object, but don't want
 // to refactor too much right now to not get in the way with other code changes.
-static wxArrayString s_CmpNameList;
-static int s_CmpLastUnit;
-
-static wxArrayString s_PowerNameList;
-static int s_LastPowerUnit;
+static SCH_BASE_FRAME::HISTORY_LIST s_CmpNameList;
+static SCH_BASE_FRAME::HISTORY_LIST s_PowerNameList;
 
 
 void SCH_EDIT_FRAME::OnLeftClick( wxDC* aDC, const wxPoint& aPosition )
@@ -306,7 +303,7 @@ void SCH_EDIT_FRAME::OnLeftClick( wxDC* aDC, const wxPoint& aPosition )
         if( (item == NULL) || (item->GetFlags() == 0) )
         {
             GetScreen()->SetCurItem( Load_Component( aDC, NULL,
-                                                     s_CmpNameList, s_CmpLastUnit, true ) );
+                                                     s_CmpNameList, true ) );
             m_canvas->SetAutoPanRequest( true );
         }
         else
@@ -321,7 +318,7 @@ void SCH_EDIT_FRAME::OnLeftClick( wxDC* aDC, const wxPoint& aPosition )
             SCHLIB_FILTER filter;
             filter.FilterPowerParts( true );
             GetScreen()->SetCurItem( Load_Component( aDC, &filter,
-                                                     s_PowerNameList, s_LastPowerUnit, false ) );
+                                                     s_PowerNameList, false ) );
             m_canvas->SetAutoPanRequest( true );
         }
         else
diff --git a/eeschema/sch_base_frame.h b/eeschema/sch_base_frame.h
index b5a5accbf..7757f48f7 100644
--- a/eeschema/sch_base_frame.h
+++ b/eeschema/sch_base_frame.h
@@ -122,31 +122,55 @@ public:
 
     void UpdateStatusBar() override;
 
+
+    struct COMPONENT_SELECTION
+    {
+        wxString    Name;
+        int         Unit;
+        int         Convert;
+
+        std::vector<std::pair<int, wxString>>   Fields;
+
+        COMPONENT_SELECTION():
+            Name(""),
+            Unit(1),
+            Convert(1)
+        {}
+    };
+
+    typedef std::vector<COMPONENT_SELECTION> HISTORY_LIST;
+
     /**
      * Function SelectComponentFromLib
      * Calls the library viewer to select component to import into schematic.
      * if the library viewer is currently running, it is closed and reopened
      * in modal mode.
+     *
+     * aAllowFields chooses whether or not features that permit the user to edit
+     * fields (e.g. footprint selection) should be enabled. This should be false
+     * when they would have no effect, for example loading a part into libedit.
+     *
      * @param aFilter is a SCHLIB_FILTER filter to pass the allowed library names
      *  and/or the library name to load the component from and/or some other filter
      *          if NULL, no filtering.
-     * @param aHistoryList       list of previously loaded components
-     * @param aHistoryLastUnit   remembering last unit in last component.
+     * @param aHistoryList       list of previously loaded components - will be edited
      * @param aUseLibBrowser     bool to call the library viewer to select the component
-     * @param aUnit              a pointer to int to return the selected unit (if any)
-     * @param aConvert           a pointer to int to return the selected De Morgan shape (if any)
+     * @param aUnit              preselected unit
+     * @param aConvert           preselected De Morgan shape
      * @param aHighlight         name of component to highlight in the list.
      *                           highlights none if there isn't one by that name
+     * @param aAllowFields       whether to allow field editing in the dialog
      *
-     * @return the component name
+     * @return the selected component
      */
-    wxString SelectComponentFromLibrary( const SCHLIB_FILTER* aFilter,
-                                         wxArrayString&  aHistoryList,
-                                         int&            aHistoryLastUnit,
-                                         bool            aUseLibBrowser,
-                                         int*            aUnit,
-                                         int*            aConvert,
-                                         const wxString& aHighlight = wxEmptyString );
+    COMPONENT_SELECTION SelectComponentFromLibrary(
+            const SCHLIB_FILTER*                aFilter,
+            std::vector<COMPONENT_SELECTION>&   aHistoryList,
+            bool                                aUseLibBrowser,
+            int                                 aUnit,
+            int                                 aConvert,
+            const wxString& aHighlight = wxEmptyString,
+            bool                                aAllowFields = true );
 
 protected:
 
@@ -158,15 +182,14 @@ protected:
      * @param aFilter is a filter to pass the allowed library names
      *          and/or some other filter
      * @param aPreselectedAlias Preselected component alias. NULL if none.
-     * @param aUnit            Pointer to Unit-number. Input is the pre-selected unit, output
-     *                         is the finally selected unit by the user. Can be NULL.
-     * @param aConvert         Pointer to deMorgan conversion. Input is what is pre-selected,
-     *                         output is the finally selected deMorgan type by the user.
-     * @return the component name
+     * @param aUnit             preselected unit
+     * @param aConvert          preselected deMorgan conversion
+     * @return the selected component
      */
-    wxString SelectComponentFromLibBrowser( const SCHLIB_FILTER* aFilter,
-                                            LIB_ALIAS* aPreselectedAlias,
-                                            int* aUnit, int* aConvert );
+    COMPONENT_SELECTION SelectComponentFromLibBrowser(
+            const SCHLIB_FILTER* aFilter,
+            LIB_ALIAS* aPreselectedAlias,
+            int aUnit, int aConvert );
 
     /**
      * Function OnOpenLibraryViewer
diff --git a/eeschema/schframe.h b/eeschema/schframe.h
index 356290899..4fe4a6546 100644
--- a/eeschema/schframe.h
+++ b/eeschema/schframe.h
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2015 Jean-Pierre Charras, jp.charras wanadoo.fr
  * Copyright (C) 2008-2015 Wayne Stambaugh <stambaughw@xxxxxxxxxxx>
- * Copyright (C) 2004-2015 KiCad Developers, see change_log.txt for contributors.
+ * Copyright (C) 2004-2017 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
@@ -1093,17 +1093,15 @@ private:
      * to load the component from and/or some other filters
      *          if NULL, no filtering.
      * @param aHistoryList     list remembering recently used component names.
-     * @param aHistoryLastUnit remembering last unit in last component.
      * @param aUseLibBrowser is the flag to determine if the library browser should be launched.
      * @return a pointer the SCH_COMPONENT object selected or NULL if no component was selected.
      * (TODO(hzeller): This really should be a class doing history, but didn't
      *  want to change too much while other refactoring is going on)
      */
-    SCH_COMPONENT* Load_Component( wxDC*           aDC,
-                                   const SCHLIB_FILTER*  aFilter,
-                                   wxArrayString&  aHistoryList,
-                                   int&            aHistoryLastUnit,
-                                   bool            aUseLibBrowser );
+    SCH_COMPONENT* Load_Component( wxDC*                            aDC,
+                                   const SCHLIB_FILTER*             aFilter,
+                                   SCH_BASE_FRAME::HISTORY_LIST&    aHistoryList,
+                                   bool                             aUseLibBrowser );
 
     /**
      * Function EditComponent
diff --git a/eeschema/viewlibs.cpp b/eeschema/viewlibs.cpp
index 06ded6082..4badd6b3a 100644
--- a/eeschema/viewlibs.cpp
+++ b/eeschema/viewlibs.cpp
@@ -59,9 +59,9 @@ void LIB_VIEW_FRAME::OnSelectSymbol( wxCommandEvent& aEvent )
 
     dialogTitle.Printf( _( "Choose Component (%d items loaded)" ),
                         adapter->GetComponentsCount() );
-    DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, adapter, m_convert );
+    DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, adapter, m_convert, false );
 
-    if( dlg.ShowModal() == wxID_CANCEL )
+    if( dlg.ShowQuasiModal() == wxID_CANCEL )
         return;
 
     /// @todo: The unit selection gets reset to 1 by SetSelectedComponent() so the unit
diff --git a/include/eda_pattern_match.h b/include/eda_pattern_match.h
index 6bca8acd9..367c5602b 100644
--- a/include/eda_pattern_match.h
+++ b/include/eda_pattern_match.h
@@ -1,8 +1,7 @@
 /*
  * 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.
+ * Copyright (C) 2015-2017 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
@@ -53,6 +52,11 @@ public:
     virtual bool SetPattern( const wxString& aPattern ) = 0;
 
     /**
+     * Return the pattern passed to SetPattern().
+     */
+    virtual wxString const& GetPattern() const = 0;
+
+    /**
      * Return the location of a match iff a given candidate string matches the set pattern.
      * Otherwise, return EDA_PATTERN_NOT_FOUND.
      */
@@ -67,6 +71,7 @@ class EDA_PATTERN_MATCH_SUBSTR : public EDA_PATTERN_MATCH
 {
 public:
     virtual bool SetPattern( const wxString& aPattern ) override;
+    virtual wxString const& GetPattern() const override;
     virtual int Find( const wxString& aCandidate ) const override;
 
 protected:
@@ -81,6 +86,7 @@ class EDA_PATTERN_MATCH_REGEX : public EDA_PATTERN_MATCH
 {
 public:
     virtual bool SetPattern( const wxString& aPattern ) override;
+    virtual wxString const& GetPattern() const override;
     virtual int Find( const wxString& aCandidate ) const override;
 
 protected:
@@ -93,7 +99,11 @@ class EDA_PATTERN_MATCH_WILDCARD : public EDA_PATTERN_MATCH_REGEX
 {
 public:
     virtual bool SetPattern( const wxString& aPattern ) override;
+    virtual wxString const& GetPattern() const override;
     virtual int Find( const wxString& aCandidate ) const override;
+
+protected:
+    wxString m_wildcard_pattern;
 };
 
 
diff --git a/include/footprint_filter.h b/include/footprint_filter.h
new file mode 100644
index 000000000..7a08dfa7b
--- /dev/null
+++ b/include/footprint_filter.h
@@ -0,0 +1,148 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 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 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/>.
+ */
+
+#ifndef FOOTPRINT_FILTER_H
+#define FOOTPRINT_FILTER_H
+
+#include <boost/iterator/iterator_facade.hpp>
+#include <eda_pattern_match.h>
+#include <footprint_info.h>
+
+
+/**
+ * Footprint display filter. Takes a list of footprints and filtering settings,
+ * and provides an iterable view of the filtered data.
+ */
+class FOOTPRINT_FILTER
+{
+public:
+    /**
+     * Construct a filter.
+     *
+     * @param aList         - unfiltered list of footprints
+     */
+    FOOTPRINT_FILTER( FOOTPRINT_LIST& aList );
+
+    /**
+     * Construct a filter without assigning a footprint list. The filter MUST NOT
+     * be iterated over until SetList() is called.
+     */
+    FOOTPRINT_FILTER();
+
+    /**
+     * Set the list to filter.
+     */
+    void SetList( FOOTPRINT_LIST& aList );
+
+    /**
+     * Clear all filter criteria.
+     */
+    void ClearFilters();
+
+    /**
+     * Add library name to filter criteria.
+     */
+    void FilterByLibrary( wxString const& aLibName );
+
+    /**
+     * Set a pin count to filter by.
+     */
+    void FilterByPinCount( int aPinCount );
+
+    /**
+     * Set a list of footprint filters to filter by.
+     */
+    void FilterByFootprintFilters( wxArrayString const& aFilters );
+
+    /**
+     * Add a pattern to filter by name, including wildcards and optionally a colon-delimited
+     * library name.
+     */
+    void FilterByPattern( wxString const& aPattern );
+
+    /**
+     * Inner iterator class returned by begin() and end().
+     */
+    class ITERATOR
+            : public boost::iterator_facade<ITERATOR, FOOTPRINT_INFO, boost::forward_traversal_tag>
+    {
+    public:
+        ITERATOR();
+        ITERATOR( ITERATOR const& aOther );
+        ITERATOR( FOOTPRINT_FILTER& aFilter );
+
+    private:
+        friend class boost::iterator_core_access;
+        friend class FOOTPRINT_FILTER;
+
+        void increment();
+        bool equal( ITERATOR const& aOther ) const;
+        FOOTPRINT_INFO& dereference() const;
+
+        size_t            m_pos;
+        FOOTPRINT_FILTER* m_filter;
+
+        /**
+         * Check if the stored component matches an item by footprint filter.
+         */
+        bool FootprintFilterMatch( FOOTPRINT_INFO& aItem );
+
+        /**
+         * Check if the stored component matches an item by pin count.
+         */
+        bool PinCountMatch( FOOTPRINT_INFO& aItem );
+    };
+
+    /**
+     * Get an iterator to the beginning of the filtered view.
+     */
+    ITERATOR begin();
+
+    /**
+     * Get an iterator to the end of the filtered view. The end iterator is
+     * invalid and may not be dereferenced, only compared against.
+     */
+    ITERATOR end();
+
+private:
+    /**
+     * Filter setting constants. The filter type is a bitwise OR of these flags,
+     * and only footprints matching all selected filter types are shown.
+     */
+    enum FP_FILTER_T : int
+    {
+        UNFILTERED_FP_LIST              = 0,
+        FILTERING_BY_COMPONENT_KEYWORD  = 0x0001,
+        FILTERING_BY_PIN_COUNT          = 0x0002,
+        FILTERING_BY_LIBRARY            = 0x0004,
+        FILTERING_BY_NAME               = 0x0008
+    };
+
+    FOOTPRINT_LIST* m_list;
+
+    wxString                   m_lib_name;
+    wxString                   m_filter_pattern;
+    int                        m_pin_count;
+    int                        m_filter_type;
+    EDA_PATTERN_MATCH_WILDCARD m_filter;
+
+    std::vector<std::unique_ptr<EDA_PATTERN_MATCH>> m_footprint_filters;
+};
+
+#endif // FOOTPRINT_FILTER_H
diff --git a/include/footprint_info.h b/include/footprint_info.h
index 2559752f0..ecdaaaa0e 100644
--- a/include/footprint_info.h
+++ b/include/footprint_info.h
@@ -2,7 +2,7 @@
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
  * Copyright (C) 2011 Jean-Pierre Charras, <jp.charras@xxxxxxxxxx>
- * Copyright (C) 1992-2011 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 1992-2017 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
@@ -32,47 +32,55 @@
 
 #include <boost/ptr_container/ptr_vector.hpp>
 
+#include <import_export.h>
+#include <ki_exception.h>
 #include <ki_mutex.h>
 #include <kicad_string.h>
+#include <sync_queue.h>
 
+#include <atomic>
+#include <functional>
+#include <memory>
 
-#define USE_FPI_LAZY            0   // 1:yes lazy,  0:no early
+
+#define USE_FPI_LAZY 0 // 1:yes lazy,  0:no early
 
 
 class FP_LIB_TABLE;
 class FOOTPRINT_LIST;
+class FOOTPRINT_LIST_IMPL;
+class FOOTPRINT_ASYNC_LOADER;
 class wxTopLevelWindow;
+class KIWAY;
 
 
 /*
- * Class FOOTPRINT_INFO
- * is a helper class to handle the list of footprints available in libraries. It stores
- * footprint names, doc and keywords
+ * Helper class to handle the list of footprints available in libraries. It stores
+ * footprint names, doc and keywords.
+ *
+ * This is a virtual class; its implementation lives in pcbnew/footprint_info_impl.cpp.
+ * To get instances of these classes, see FOOTPRINT_LIST::GetInstance().
  */
-class FOOTPRINT_INFO
+class APIEXPORT FOOTPRINT_INFO
 {
     friend bool operator<( const FOOTPRINT_INFO& item1, const FOOTPRINT_INFO& item2 );
 
 public:
+    virtual ~FOOTPRINT_INFO()
+    {
+    }
 
     // These two accessors do not have to call ensure_loaded(), because constructor
     // fills in these fields:
 
-    const wxString& GetFootprintName() const            { return m_fpname; }
-    const wxString& GetNickname() const                 { return m_nickname; }
+    const wxString& GetFootprintName() const
+    {
+        return m_fpname;
+    }
 
-    FOOTPRINT_INFO( FOOTPRINT_LIST* aOwner, const wxString& aNickname, const wxString& aFootprintName ) :
-        m_owner( aOwner ),
-        m_loaded( false ),
-        m_nickname( aNickname ),
-        m_fpname( aFootprintName ),
-        m_num( 0 ),
-        m_pad_count( 0 ),
-        m_unique_pad_count( 0 )
+    const wxString& GetNickname() const
     {
-#if !USE_FPI_LAZY
-        load();
-#endif
+        return m_nickname;
     }
 
     const wxString& GetDoc()
@@ -106,8 +114,7 @@ public:
     }
 
     /**
-     * Function InLibrary
-     * tests if the #FOOTPRINT_INFO object was loaded from \a aLibrary.
+     * Test if the #FOOTPRINT_INFO object was loaded from \a aLibrary.
      *
      * @param aLibrary is the nickname of the library to test.
      *
@@ -116,8 +123,7 @@ public:
      */
     bool InLibrary( const wxString& aLibrary ) const;
 
-private:
-
+protected:
     void ensure_loaded()
     {
         if( !m_loaded )
@@ -125,19 +131,19 @@ private:
     }
 
     /// lazily load stuff not filled in by constructor.  This may throw IO_ERRORS.
-    void load();
+    virtual void load() = 0;
 
-    FOOTPRINT_LIST* m_owner;            ///< provides access to FP_LIB_TABLE
+    FOOTPRINT_LIST* m_owner; ///< provides access to FP_LIB_TABLE
 
-    bool        m_loaded;
+    bool m_loaded;
 
-    wxString    m_nickname;             ///< library as known in FP_LIB_TABLE
-    wxString    m_fpname;               ///< Module name.
-    int         m_num;                  ///< Order number in the display list.
-    int         m_pad_count;            ///< Number of pads
-    int         m_unique_pad_count;     ///< Number of unique pads
-    wxString    m_doc;                  ///< Footprint description.
-    wxString    m_keywords;             ///< Footprint keywords.
+    wxString m_nickname;         ///< library as known in FP_LIB_TABLE
+    wxString m_fpname;           ///< Module name.
+    int      m_num;              ///< Order number in the display list.
+    int      m_pad_count;        ///< Number of pads
+    int      m_unique_pad_count; ///< Number of unique pads
+    wxString m_doc;              ///< Footprint description.
+    wxString m_keywords;         ///< Footprint keywords.
 };
 
 
@@ -154,89 +160,91 @@ inline bool operator<( const FOOTPRINT_INFO& item1, const FOOTPRINT_INFO& item2
 
 
 /**
- * Class FOOTPRINT_LIST
- * holds a list of FOOTPRINT_INFO objects, along with a list of IO_ERRORs or
+ * Holds a list of FOOTPRINT_INFO objects, along with a list of IO_ERRORs or
  * PARSE_ERRORs that were thrown acquiring the FOOTPRINT_INFOs.
+ *
+ * This is a virtual class; its implementation lives in pcbnew/footprint_info_impl.cpp.
+ * To get instances of these classes, see FOOTPRINT_LIST::GetInstance().
  */
-class FOOTPRINT_LIST
+class APIEXPORT FOOTPRINT_LIST
 {
-    FP_LIB_TABLE*   m_lib_table;        ///< no ownership
-    volatile int    m_error_count;      ///< thread safe to read.
+    friend class FOOTPRINT_ASYNC_LOADER;
+
+protected:
+    FP_LIB_TABLE* m_lib_table; ///< no ownership
 
-    typedef boost::ptr_vector< FOOTPRINT_INFO >         FPILIST;
-    typedef boost::ptr_vector< IO_ERROR >               ERRLIST;
+    typedef std::vector<std::unique_ptr<FOOTPRINT_INFO>> FPILIST;
+    typedef SYNC_QUEUE<std::unique_ptr<IO_ERROR>>        ERRLIST;
 
     FPILIST m_list;
-    ERRLIST m_errors;                   ///< some can be PARSE_ERRORs also
+    ERRLIST m_errors; ///< some can be PARSE_ERRORs also
 
-    MUTEX   m_errors_lock;
-    MUTEX   m_list_lock;
+    MUTEX m_list_lock;
 
-    /**
-     * Function loader_job
-     * loads footprints from @a aNicknameList and calls AddItem() on to help fill
-     * m_list.
-     *
-     * @param aNicknameList is a wxString[] holding libraries to load all footprints from.
-     * @param aJobZ is the size of the job, i.e. the count of nicknames.
-     */
-    void loader_job( const wxString* aNicknameList, int aJobZ );
 
-    void addItem( FOOTPRINT_INFO* aItem )
+public:
+    FOOTPRINT_LIST() : m_lib_table( 0 )
     {
-        // m_list is not thread safe, and this function is called from
-        // worker threads, lock m_list.
-        MUTLOCK lock( m_list_lock );
-
-        m_list.push_back( aItem );
     }
 
-
-public:
-
-    FOOTPRINT_LIST() :
-        m_lib_table( 0 ),
-        m_error_count( 0 )
+    virtual ~FOOTPRINT_LIST()
     {
     }
 
     /**
-     * Function GetCount
      * @return the number of items stored in list
      */
-    unsigned GetCount() const { return m_list.size(); }
+    unsigned GetCount() const
+    {
+        return m_list.size();
+    }
 
     /// Was forced to add this by modview_frame.cpp
-    const FPILIST& GetList() const { return m_list; }
+    const FPILIST& GetList() const
+    {
+        return m_list;
+    }
 
     /**
-     * Function GetModuleInfo
+     * Get info for a module by name.
      * @param aFootprintName = the footprint name inside the FOOTPRINT_INFO of interest.
      * @return FOOTPRINT_INF* - the item stored in list if found
      */
     FOOTPRINT_INFO* GetModuleInfo( const wxString& aFootprintName );
 
     /**
-     * Function GetItem
+     * Get info for a module by index.
      * @param aIdx = index of the given item
      * @return the aIdx item in list
      */
-    FOOTPRINT_INFO& GetItem( unsigned aIdx )            { return m_list[aIdx]; }
+    FOOTPRINT_INFO& GetItem( unsigned aIdx )
+    {
+        return *m_list[aIdx];
+    }
 
     /**
-     * Function AddItem
-     * add aItem in list
+     * Add aItem to list
      * @param aItem = item to add
      */
     void AddItem( FOOTPRINT_INFO* aItem );
 
-    unsigned GetErrorCount() const  { return m_errors.size(); }
+    unsigned GetErrorCount() const
+    {
+        return m_errors.size();
+    }
+
+    std::unique_ptr<IO_ERROR> PopError()
+    {
+        auto item = m_errors.pop();
 
-    const IO_ERROR* GetError( unsigned aIdx ) const     { return &m_errors[aIdx]; }
+        if( item )
+            return std::move( *item );
+        else
+            return std::unique_ptr<IO_ERROR>();
+    }
 
     /**
-     * Function ReadFootprintFiles
-     * reads all the footprints provided by the combination of aTable and aNickname.
+     * Read all the footprints provided by the combination of aTable and aNickname.
      *
      * @param aTable defines all the libraries.
      * @param aNickname is the library to read from, or if NULL means read all
@@ -245,11 +253,133 @@ public:
      *  some number of errors.  If true, it does not mean there were no errors, check
      *  GetErrorCount() for that, should be zero to indicate success.
      */
-    bool ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname = NULL );
+    virtual bool ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname = NULL ) = 0;
 
     void DisplayErrors( wxTopLevelWindow* aCaller = NULL );
 
-    FP_LIB_TABLE* GetTable() const { return m_lib_table; }
+    FP_LIB_TABLE* GetTable() const
+    {
+        return m_lib_table;
+    }
+
+    /**
+     * Factory function to return a new FOOTPRINT_LIST via Kiway. NOT guaranteed
+     * to succeed; will return null if the kiface is not available.
+     *
+     * @param aKiway - active kiway instance
+     */
+    static std::unique_ptr<FOOTPRINT_LIST> GetInstance( KIWAY& aKiway );
+
+protected:
+    /**
+     * Launch worker threads to load footprints. Part of the
+     * FOOTPRINT_ASYNC_LOADER implementation.
+     */
+    virtual void StartWorkers( FP_LIB_TABLE* aTable, wxString const* aNickname,
+            FOOTPRINT_ASYNC_LOADER* aLoader, unsigned aNThreads ) = 0;
+
+    /**
+     * Join worker threads. Part of the FOOTPRINT_ASYNC_LOADER implementation.
+     */
+    virtual bool JoinWorkers() = 0;
+
+
+    /**
+     * Return the number of libraries finished (successfully or otherwise).
+     */
+    virtual size_t CountFinished() = 0;
 };
 
-#endif  // FOOTPRINT_INFO_H_
+
+/**
+ * This class can be used to populate a FOOTPRINT_LIST asynchronously.
+ * Constructing one, calling .Start(), then waiting until it reports completion
+ * is equivalent to calling FOOTPRINT_LIST::ReadFootprintFiles().
+ */
+class APIEXPORT FOOTPRINT_ASYNC_LOADER
+{
+    friend class FOOTPRINT_LIST;
+    friend class FOOTPRINT_LIST_IMPL;
+
+    FOOTPRINT_LIST*       m_list;
+    std::function<void()> m_completion_cb;
+    std::string           m_last_table;
+
+    bool m_started; ///< True if Start() has been called - does not reset
+    int  m_total_libs;
+
+public:
+    /**
+     * Construct an asynchronous loader.
+     */
+    FOOTPRINT_ASYNC_LOADER();
+
+    /**
+     * Assign a FOOTPRINT_LIST to the loader. This does not take ownership of
+     * the list.
+     */
+    void SetList( FOOTPRINT_LIST* aList );
+
+    /**
+     * Launch the worker threads.
+     * @param aTable defines all the libraries.
+     * @param aNickname is the library to read from, or if NULL means read all
+     *         footprints from all known libraries in aTable.
+     * @param aNThreads is the number of worker threads.
+     */
+    void Start( FP_LIB_TABLE* aTable, wxString const* aNickname = nullptr,
+            unsigned aNThreads = DEFAULT_THREADS );
+
+    /**
+     * Wait until the worker threads are finished, and then perform any required
+     * single-threaded finishing on the list. This must be called before using
+     * the list, even if the completion callback was used!
+     *
+     * It is safe to call this method from a thread, but it is not safe to use
+     * the list from ANY thread until it completes. It is recommended to call
+     * this from the main thread because of this.
+     *
+     * It is safe to call this multiple times, but after the first it will
+     * always return true.
+     *
+     * @return true if no errors occurred
+     */
+    bool Join();
+
+    /**
+     * Get the current completion percentage. 0 and 100 are reserved values:
+     * 0 will only be returned if Start() has not yet been called, and 100
+     * will only be returned if totally complete (i.e. rounding errors will
+     * never cause a 100% progress despite not being complete).
+     *
+     * If there are no libraries at all, returns 100 (as loading zero libraries
+     * is always complete).
+     *
+     * Threadsafe.
+     */
+    int GetProgress() const;
+
+    /**
+     * Set a callback to receive notice when loading is complete.
+     *
+     * Callback MUST be threadsafe, and must be set before calling Start
+     * if you want to use it (it is safe not to set it at all).
+     */
+    void SetCompletionCallback( std::function<void()> aCallback );
+
+    /**
+     * Return true if the given table is the same as the last table loaded.
+     * Useful for checking if the table has been modified and needs to be
+     * reloaded.
+     */
+    bool IsSameTable( FP_LIB_TABLE* aOther );
+
+    /**
+     * Default number of worker threads. Determined empirically (by dickelbeck):
+     * More than 6 is not significantly faster, less than 6 is likely slower.
+     */
+    static constexpr unsigned DEFAULT_THREADS = 6;
+};
+
+
+#endif // FOOTPRINT_INFO_H_
diff --git a/include/fp_lib_table.h b/include/fp_lib_table.h
index 9162aae4f..bb87ed4bc 100644
--- a/include/fp_lib_table.h
+++ b/include/fp_lib_table.h
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2010-2012 SoftPLC Corporation, Dick Hollenbeck <dick@xxxxxxxxxxx>
  * Copyright (C) 2012-2016 Wayne Stambaugh <stambaughw@xxxxxxxxxxx>
- * Copyright (C) 2012-2016 KiCad Developers, see change_log.txt for contributors.
+ * Copyright (C) 2012-2017 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
@@ -146,6 +146,19 @@ public:
     wxArrayString FootprintEnumerate( const wxString& aNickname );
 
     /**
+     * Function PrefetchLib
+     * If possible, prefetches the specified library (e.g. performing downloads). Does not parse.
+     * Threadsafe.
+     *
+     * This is a no-op for libraries that cannot be prefetched.
+     *
+     * @param aNickname is a locator for the library; it is a name in LIB_TABLE_ROW.
+     *
+     * @throw IO_ERROR if there is an error prefetching the library.
+     */
+    void PrefetchLib( const wxString& aNickname );
+
+    /**
      * Function FootprintLoad
      *
      * loads a footprint having @a aFootprintName from the library given by @a aNickname.
diff --git a/include/kiface_ids.h b/include/kiface_ids.h
new file mode 100644
index 000000000..bea912943
--- /dev/null
+++ b/include/kiface_ids.h
@@ -0,0 +1,49 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef KIFACE_IDS_H
+#define KIFACE_IDS_H
+
+/**
+ * IDs of objects that may be returned by KIFACE::IfaceOrAddress.
+ */
+enum KIFACE_ADDR_ID : int
+{
+    INVALID,
+
+    /**
+     * Return a new instance of FOOTPRINT_LIST from pcbnew.
+     * Type is FOOTPRINT_LIST*
+     * Caller takes ownership
+     */
+    KIFACE_NEW_FOOTPRINT_LIST,
+
+    /**
+     * Return a new FP_LIB_TABLE copying the global table.
+     * Type is FP_LIB_TABLE*
+     * Caller takes ownership
+     */
+    KIFACE_G_FOOTPRINT_TABLE, ///<
+};
+
+#endif // KIFACE_IDS
diff --git a/include/project.h b/include/project.h
index e3fd29230..491abe4a2 100644
--- a/include/project.h
+++ b/include/project.h
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 2014 KiCad Developers, see CHANGELOG.TXT for contributors.
+ * Copyright (C) 2014-2017 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
@@ -20,7 +20,6 @@
  * or you may write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
-
 #ifndef PROJECT_H_
 #define PROJECT_H_
 
@@ -39,6 +38,7 @@ class FP_LIB_TABLE;
 class PART_LIBS;
 class SEARCH_STACK;
 class S3D_CACHE;
+class KIWAY;
 
 #define VTBL_ENTRY      virtual
 
@@ -235,6 +235,11 @@ public:
      */
     VTBL_ENTRY const wxString AbsolutePath( const wxString& aFileName ) const;
 
+    /**
+     * Return the table of footprint libraries. Requires an active Kiway as
+     * this is fetched from pcbnew.
+     */
+    VTBL_ENTRY FP_LIB_TABLE* PcbFootprintLibs( KIWAY& aKiway );
 
     //-----</Cross Module API>---------------------------------------------------
 
@@ -250,7 +255,10 @@ public:
     // functions can get linked into the KIFACE that needs them, and only there.
     // In fact, the other KIFACEs don't even know they exist.
 #if defined(PCBNEW) || defined(CVPCB)
-    // These are all prefaced with "Pcb"
+    /**
+     * Return the table of footprint libraries without Kiway, only from within
+     * pcbnew.
+     */
     FP_LIB_TABLE* PcbFootprintLibs();
 
     /**
diff --git a/include/sync_queue.h b/include/sync_queue.h
new file mode 100644
index 000000000..55772f6e4
--- /dev/null
+++ b/include/sync_queue.h
@@ -0,0 +1,113 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 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 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/>.
+ */
+
+#ifndef SYNC_QUEUE_H
+#define SYNC_QUEUE_H
+
+#include <boost/optional.hpp>
+#include <mutex>
+#include <queue>
+
+/**
+ * Synchronized, locking queue. Safe for multiple producer/multiple consumer environments with
+ * nontrivial data (though bear in mind data needs to be copied in and out).
+ */
+template <typename T> class SYNC_QUEUE
+{
+    typedef std::lock_guard<std::mutex> GUARD;
+
+    std::queue<T>      m_queue;
+    mutable std::mutex m_mutex;
+
+public:
+    SYNC_QUEUE()
+    {
+    }
+
+    /**
+     * Push a value onto the queue.
+     */
+    void push( T const& aValue )
+    {
+        GUARD guard( m_mutex );
+        m_queue.push( aValue );
+    }
+
+    /**
+     * Move a value onto the queue. Useful for e.g. unique_ptr.
+     */
+    void move_push( T&& aValue )
+    {
+        GUARD guard( m_mutex );
+        m_queue.push( std::move( aValue ) );
+    }
+
+    /**
+     * Pop a value off the queue if there is one, returning it. If the queue is empty,
+     * return boost::none instead.
+     */
+    boost::optional<T> pop()
+    {
+        GUARD guard( m_mutex );
+
+        if( m_queue.empty() )
+        {
+            return boost::none;
+        }
+        else
+        {
+            T val = std::move( m_queue.front() );
+            m_queue.pop();
+            return std::move( val );
+        }
+    }
+
+    /**
+     * Return true iff the queue is empty.
+     */
+    bool empty() const
+    {
+        GUARD guard( m_mutex );
+        return m_queue.empty();
+    }
+
+    /**
+     * Return the size of the queue.
+     */
+    size_t size() const
+    {
+        GUARD guard( m_mutex );
+        return m_queue.size();
+    }
+
+    /**
+     * Clear the queue.
+     */
+    void clear()
+    {
+        GUARD guard( m_mutex );
+
+        while( !m_queue.empty() )
+        {
+            m_queue.pop();
+        }
+    }
+};
+
+#endif // SYNC_QUEUE_H
diff --git a/include/widgets/footprint_choice.h b/include/widgets/footprint_choice.h
new file mode 100644
index 000000000..a2427f6fc
--- /dev/null
+++ b/include/widgets/footprint_choice.h
@@ -0,0 +1,113 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 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 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/>.
+ */
+
+#ifndef FOOTPRINT_CHOICE_H
+#define FOOTPRINT_CHOICE_H
+
+#include <wx/odcombo.h>
+
+/**
+ * Event thrown when an item is selected "interactively". This includes direct clicks
+ * and presses of the Enter key, but not arrow key motion. Integer data will be the
+ * item selected.
+ */
+wxDECLARE_EVENT( EVT_INTERACTIVE_CHOICE, wxCommandEvent );
+
+
+/**
+ * Customized combo box for footprint selection. This provides the following features:
+ *
+ * - library name is greyed out for readability when lib:footprint format is found in
+ *   the item text
+ * - empty items are displayed as nonselectable separators
+ *
+ * Multiple separators in a row is undefined behavior; it is likely to result in errors
+ * such as the ability to select separators. Separators ARE valid at the top and bottom.
+ *
+ * For any items containing footprints, the "lib:footprint" name should be attached to
+ * the item as a wxStringClientData.
+ */
+class FOOTPRINT_CHOICE : public wxOwnerDrawnComboBox
+{
+public:
+    FOOTPRINT_CHOICE( wxWindow* aParent, int aId );
+
+    virtual ~FOOTPRINT_CHOICE();
+
+protected:
+    virtual void DoSetPopupControl( wxComboPopup* aPopup ) override;
+    virtual void OnDrawItem( wxDC& aDC, wxRect const& aRect, int aItem, int aFlags ) const override;
+    virtual wxCoord OnMeasureItem( size_t aItem ) const override;
+    virtual wxCoord OnMeasureItemWidth( size_t aItem ) const override;
+
+    /**
+     * Draw a fragment of text, then return the next x coordinate to continue drawing.
+     */
+    static wxCoord DrawTextFragment( wxDC& aDC, wxCoord x, wxCoord y, wxString const& aText );
+
+    /// Veto a mouseover event if in the separator
+    void TryVetoMouse( wxMouseEvent& aEvent );
+
+    /**
+     * Veto a select event for the separator
+     *
+     * @param aInner - true if event was called for the inner list (ie the popup)
+     */
+    void TryVetoSelect( wxCommandEvent& aEvent, bool aInner );
+
+    /**
+     * Mouse up on an item in the list.
+     */
+    void OnMouseUp( wxMouseEvent& aEvent );
+
+    /**
+     * Key up on an item in the list.
+     */
+    void OnKeyUp( wxKeyEvent& aEvent );
+
+    /**
+     * For arrow key events, skip over separators.
+     *
+     * @param aInner - true if event was called for the inner list (ie the popup)
+     */
+    void TrySkipSeparator( wxKeyEvent& aEvent, bool aInner );
+
+    /**
+     * Safely get a string for an item, returning wxEmptyString if the item doesn't exist.
+     */
+    wxString SafeGetString( int aItem ) const;
+
+    /**
+     * Get selection from either the outer (combo box) or inner (popup) list.
+     */
+    int GetSelectionEither( bool aInner ) const;
+
+    /**
+     * Safely set selection for either the outer (combo box) or inner (popup) list, doing nothing
+     * for invalid selections.
+     */
+    void SetSelectionEither( bool aInner, int aSel );
+
+    static wxColour m_grey;
+
+private:
+    int m_last_selection;
+};
+
+#endif // FOOTPRINT_CHOICE_H
diff --git a/include/widgets/footprint_select_widget.h b/include/widgets/footprint_select_widget.h
new file mode 100644
index 000000000..e5bbbaa3f
--- /dev/null
+++ b/include/widgets/footprint_select_widget.h
@@ -0,0 +1,161 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 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 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/>.
+ */
+
+#ifndef FOOTPRINT_SELECT_WIDGET_H
+#define FOOTPRINT_SELECT_WIDGET_H
+
+#include <footprint_filter.h>
+#include <footprint_info.h>
+#include <vector>
+#include <wx/panel.h>
+#include <wx/wx.h>
+
+class KIWAY;
+class PROJECT;
+class FOOTPRINT_CHOICE;
+class wxGauge;
+class wxMenu;
+class wxTimer;
+class wxTimerEvent;
+class wxWindow;
+class wxSimplebook;
+
+/**
+ * This event is fired when a footprint is selected. The string data of the
+ * event will contain the footprint name.
+ */
+wxDECLARE_EVENT( EVT_FOOTPRINT_SELECTED, wxCommandEvent );
+
+class FOOTPRINT_SELECT_WIDGET : public wxPanel
+{
+public:
+    /**
+     * Construct a footprint selector widget.
+     *
+     * This requires references to an external footprint loader, and an external
+     * unique_ptr-to-FOOTPRINT_LIST. The latter will be populated with a
+     * FOOTPRINT_LIST instance the first time Load() is called.
+     *
+     * The reason for this is that footprint loading tends to be very expensive,
+     * especially when using online libraries. The caller is expected to keep
+     * these objects around (e.g. they may be statics on the dialog this
+     * FOOTPRINT_SELECT_WIDGET is created in) so footprints do not have to be
+     * loaded more than once.
+     *
+     * @param aParent - parent window
+     * @param aLoader - FOOTPRINT_ASYNC_LOADER instance
+     * @param aFpList - FOOTPRINT_LIST container
+     * @param aUpdate - whether to call UpdateList() automatically when finished loading
+     * @param aMaxItems - maximum number of filter items to display, in addition to
+     *  Default and Other
+     */
+    FOOTPRINT_SELECT_WIDGET( wxWindow* aParent, FOOTPRINT_ASYNC_LOADER& aLoader,
+            std::unique_ptr<FOOTPRINT_LIST>& aFpList, bool aUpdate = true, int aMaxItems = 10 );
+
+    virtual ~FOOTPRINT_SELECT_WIDGET()
+    {
+    }
+
+    /**
+     * Start loading. This function returns immediately; footprints will
+     * continue to load in the background.
+     *
+     * @param aKiway - active kiway instance. This is cached for use when "Other"
+     *      is selected.
+     * @param aProject - current project
+     */
+    void Load( KIWAY& aKiway, PROJECT& aProject );
+
+    /**
+     * Clear all filters. Does not update the list.
+     */
+    void ClearFilters();
+
+    /**
+     * Filter by pin count. Does not update the list.
+     */
+    void FilterByPinCount( int aPinCount );
+
+    /**
+     * Filter by footprint filter list. Does not update the list.
+     *
+     * @param aZeroFilters - if true, zero filters = zero footprints. If false, zero filters =
+     *  not filtering.
+     */
+    void FilterByFootprintFilters( wxArrayString const& aFilters, bool aZeroFilters );
+
+    /**
+     * Set the default footprint for a part. This will be listed at the
+     * top. May be an empty string.
+     */
+    void SetDefaultFootprint( wxString const& aFp );
+
+    /**
+     * Update the contents of the list to match the filters. Has no effect if
+     * the footprint list has not been loaded yet. The "default" footprint will be
+     * selected.
+     *
+     * @return true if the footprint list has been loaded (and the list was updated)
+     */
+    bool UpdateList();
+
+    /**
+     * Set current selection to the default footprint
+     */
+    void SelectDefault();
+
+    /**
+     * Enable or disable the control for input
+     */
+    virtual bool Enable( bool aEnable = true ) override;
+
+private:
+    KIWAY*            m_kiway;
+    wxGauge*          m_progress_ctrl;
+    FOOTPRINT_CHOICE* m_fp_sel_ctrl;
+    wxSizer*          m_sizer;
+    wxSimplebook*     m_book;
+
+    std::unique_ptr<wxTimer> m_progress_timer;
+
+    bool     m_update;
+    bool     m_finished_loading;
+    int      m_max_items;
+    wxString m_default_footprint;
+    wxString m_other_footprint;
+    int      m_last_item;
+
+    FOOTPRINT_ASYNC_LOADER&          m_fp_loader;
+    std::unique_ptr<FOOTPRINT_LIST>& m_fp_list;
+    FOOTPRINT_FILTER                 m_fp_filter;
+    bool                             m_zero_filter;
+
+    void OnProgressTimer( wxTimerEvent& aEvent );
+    void OnComboBox( wxCommandEvent& aEvent );
+    void OnComboInteractive( wxCommandEvent& aEvent );
+
+    /// Show the component picker and return the selected component. Used by DoOther()
+    wxString ShowPicker();
+
+    /// Handle activation of the "Other..." item
+    void DoOther();
+};
+
+
+#endif // FOOTPRINT_SELECT_WIDGET
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index c0ce9ad19..20769c6a4 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -238,6 +238,7 @@ set( PCBNEW_CLASS_SRCS
     edtxtmod.cpp
     event_handlers_tracks_vias_sizes.cpp
     files.cpp
+    footprint_info_impl.cpp
     globaleditpad.cpp
     highlight.cpp
     hotkeys.cpp
diff --git a/pcbnew/footprint_info_impl.cpp b/pcbnew/footprint_info_impl.cpp
new file mode 100644
index 000000000..108a191ba
--- /dev/null
+++ b/pcbnew/footprint_info_impl.cpp
@@ -0,0 +1,230 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2011 Jean-Pierre Charras, <jp.charras@xxxxxxxxxx>
+ * Copyright (C) 2013-2016 SoftPLC Corporation, Dick Hollenbeck <dick@xxxxxxxxxxx>
+ * Copyright (C) 1992-2017 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 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 <footprint_info_impl.h>
+
+#include <class_module.h>
+#include <common.h>
+#include <fctsys.h>
+#include <footprint_info.h>
+#include <fp_lib_table.h>
+#include <html_messagebox.h>
+#include <io_mgr.h>
+#include <kiface_ids.h>
+#include <kiway.h>
+#include <lib_id.h>
+#include <macros.h>
+#include <make_unique.h>
+#include <pgm_base.h>
+#include <wildcards_and_files_ext.h>
+
+#include <thread>
+
+
+void FOOTPRINT_INFO_IMPL::load()
+{
+    FP_LIB_TABLE* fptable = m_owner->GetTable();
+
+    wxASSERT( fptable );
+
+    std::unique_ptr<MODULE> footprint( fptable->FootprintLoad( m_nickname, m_fpname ) );
+
+    if( footprint.get() == NULL ) // Should happen only with malformed/broken libraries
+    {
+        m_pad_count = 0;
+        m_unique_pad_count = 0;
+    }
+    else
+    {
+        m_pad_count = footprint->GetPadCount( DO_NOT_INCLUDE_NPTH );
+        m_unique_pad_count = footprint->GetUniquePadCount( DO_NOT_INCLUDE_NPTH );
+        m_keywords = footprint->GetKeywords();
+        m_doc = footprint->GetDescription();
+
+        // tell ensure_loaded() I'm loaded.
+        m_loaded = true;
+    }
+}
+
+
+bool FOOTPRINT_LIST_IMPL::CatchErrors( std::function<void()> aFunc )
+{
+    try
+    {
+        aFunc();
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
+        return false;
+    }
+    catch( const std::exception& se )
+    {
+        // This is a round about way to do this, but who knows what THROW_IO_ERROR()
+        // may be tricked out to do someday, keep it in the game.
+        try
+        {
+            THROW_IO_ERROR( se.what() );
+        }
+        catch( const IO_ERROR& ioe )
+        {
+            m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
+        }
+        return false;
+    }
+
+    return true;
+}
+
+
+void FOOTPRINT_LIST_IMPL::loader_job()
+{
+    while( auto const nickname = m_queue_in.pop() )
+    {
+        CatchErrors( [this, &nickname]() {
+            m_lib_table->PrefetchLib( *nickname );
+            m_queue_out.push( *nickname );
+        } );
+
+        m_count_finished.fetch_add( 1 );
+    }
+
+    if( !m_first_to_finish.exchange( true ) )
+    {
+        // yay, we're first to finish!
+        if( m_loader->m_completion_cb )
+        {
+            m_loader->m_completion_cb();
+        }
+    }
+}
+
+
+bool FOOTPRINT_LIST_IMPL::ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname )
+{
+    FOOTPRINT_ASYNC_LOADER loader;
+
+    loader.SetList( this );
+    loader.Start( aTable, aNickname );
+    return loader.Join();
+}
+
+
+void FOOTPRINT_LIST_IMPL::StartWorkers( FP_LIB_TABLE* aTable, wxString const* aNickname,
+        FOOTPRINT_ASYNC_LOADER* aLoader, unsigned aNThreads )
+{
+    m_loader = aLoader;
+    m_lib_table = aTable;
+
+    // Clear data before reading files
+    m_first_to_finish.store( false );
+    m_count_finished.store( 0 );
+    m_errors.clear();
+    m_list.clear();
+    m_threads.clear();
+    m_queue_in.clear();
+    m_queue_out.clear();
+
+    if( aNickname )
+        m_queue_in.push( *aNickname );
+    else
+    {
+        for( auto const& nickname : aTable->GetLogicalLibs() )
+            m_queue_in.push( nickname );
+    }
+
+    m_loader->m_total_libs = m_queue_in.size();
+
+    for( unsigned i = 0; i < aNThreads; ++i )
+    {
+        m_threads.push_back( std::thread( &FOOTPRINT_LIST_IMPL::loader_job, this ) );
+    }
+}
+
+bool FOOTPRINT_LIST_IMPL::JoinWorkers()
+{
+    for( auto& i : m_threads )
+        i.join();
+
+    m_threads.clear();
+    m_queue_in.clear();
+
+    LOCALE_IO toggle_locale;
+
+    // Parse the footprints in parallel. WARNING! This requires changing the locale, which is
+    // GLOBAL. It is only threadsafe to construct the LOCALE_IO before the threads are created,
+    // destroy it after they finish, and block the main (GUI) thread while they work. Any deviation
+    // from this will cause nasal demons.
+    //
+    // TODO: blast LOCALE_IO into the sun
+
+    SYNC_QUEUE<std::unique_ptr<FOOTPRINT_INFO>> queue_parsed;
+    std::vector<std::thread>                    threads;
+
+    for( size_t i = 0; i < std::thread::hardware_concurrency() + 1; ++i )
+    {
+        threads.push_back( std::thread( [this, &queue_parsed]() {
+            while( auto nickname = this->m_queue_out.pop() )
+            {
+                CatchErrors( [this, &queue_parsed, &nickname]() {
+                    wxArrayString fpnames = this->m_lib_table->FootprintEnumerate( *nickname );
+
+                    for( auto const& fpname : fpnames )
+                    {
+                        FOOTPRINT_INFO* fpinfo = new FOOTPRINT_INFO_IMPL( this, *nickname, fpname );
+                        queue_parsed.move_push( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );
+                    }
+                } );
+            }
+        } ) );
+    }
+
+    for( auto& thr : threads )
+        thr.join();
+
+    while( auto fpi = queue_parsed.pop() )
+        m_list.push_back( std::move( *fpi ) );
+
+    std::sort( m_list.begin(), m_list.end(),
+            []( std::unique_ptr<FOOTPRINT_INFO> const&     lhs,
+                    std::unique_ptr<FOOTPRINT_INFO> const& rhs ) -> bool { return *lhs < *rhs; } );
+
+    return m_errors.empty();
+}
+
+
+size_t FOOTPRINT_LIST_IMPL::CountFinished()
+{
+    return m_count_finished.load();
+}
+
+
+FOOTPRINT_LIST_IMPL::FOOTPRINT_LIST_IMPL() : m_loader( nullptr )
+{
+}
+
+
+FOOTPRINT_LIST_IMPL::~FOOTPRINT_LIST_IMPL()
+{
+    for( auto& i : m_threads )
+        i.join();
+}
diff --git a/pcbnew/footprint_info_impl.h b/pcbnew/footprint_info_impl.h
new file mode 100644
index 000000000..a2ec466b4
--- /dev/null
+++ b/pcbnew/footprint_info_impl.h
@@ -0,0 +1,94 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2011 Jean-Pierre Charras, <jp.charras@xxxxxxxxxx>
+ * Copyright (C) 1992-2017 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 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/>.
+ */
+
+#ifndef FOOTPRINT_INFO_IMPL_H
+#define FOOTPRINT_INFO_IMPL_H
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <thread>
+#include <vector>
+
+#include <footprint_info.h>
+#include <sync_queue.h>
+
+class LOCALE_IO;
+
+class FOOTPRINT_INFO_IMPL : public FOOTPRINT_INFO
+{
+public:
+    FOOTPRINT_INFO_IMPL(
+            FOOTPRINT_LIST* aOwner, const wxString& aNickname, const wxString& aFootprintName )
+    {
+        m_owner = aOwner;
+        m_loaded = false;
+        m_nickname = aNickname;
+        m_fpname = aFootprintName;
+        m_num = 0;
+        m_pad_count = 0;
+        m_unique_pad_count = 0;
+#if !USE_FPI_LAZY
+        load();
+#endif
+    }
+
+protected:
+    virtual void load() override;
+};
+
+
+class FOOTPRINT_LIST_IMPL : public FOOTPRINT_LIST
+{
+    FOOTPRINT_ASYNC_LOADER*  m_loader;
+    std::vector<std::thread> m_threads;
+    SYNC_QUEUE<wxString>     m_queue_in;
+    SYNC_QUEUE<wxString>     m_queue_out;
+    std::atomic_size_t       m_count_finished;
+    std::atomic_bool         m_first_to_finish;
+
+    /**
+     * Call aFunc, pushing any IO_ERRORs and std::exceptions it throws onto m_errors.
+     *
+     * @return true if no error occurred.
+     */
+    bool CatchErrors( std::function<void()> aFunc );
+
+protected:
+    virtual void StartWorkers( FP_LIB_TABLE* aTable, wxString const* aNickname,
+            FOOTPRINT_ASYNC_LOADER* aLoader, unsigned aNThreads ) override;
+    virtual bool   JoinWorkers() override;
+    virtual size_t CountFinished() override;
+
+    /**
+     * Function loader_job
+     * loads footprints from m_queue_in.
+     */
+    void loader_job();
+
+public:
+    FOOTPRINT_LIST_IMPL();
+    virtual ~FOOTPRINT_LIST_IMPL();
+
+    virtual bool ReadFootprintFiles(
+            FP_LIB_TABLE* aTable, const wxString* aNickname = NULL ) override;
+};
+
+#endif // FOOTPRINT_INFO_IMPL_H
diff --git a/pcbnew/github/github_plugin.cpp b/pcbnew/github/github_plugin.cpp
index cefc620e0..f95529f97 100644
--- a/pcbnew/github/github_plugin.cpp
+++ b/pcbnew/github/github_plugin.cpp
@@ -2,7 +2,7 @@
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
  * Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck <dick@xxxxxxxxxxx>
- * Copyright (C) 2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2016-2017 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
@@ -165,6 +165,18 @@ wxArrayString GITHUB_PLUGIN::FootprintEnumerate(
 }
 
 
+void GITHUB_PLUGIN::PrefetchLib(
+        const wxString& aLibraryPath, const PROPERTIES* aProperties )
+{
+    if( m_lib_path != aLibraryPath )
+    {
+        m_zip_image.clear();
+    }
+
+    remoteGetZip( aLibraryPath );
+}
+
+
 MODULE* GITHUB_PLUGIN::FootprintLoad( const wxString& aLibraryPath,
         const wxString& aFootprintName, const PROPERTIES* aProperties )
 {
@@ -370,9 +382,14 @@ void GITHUB_PLUGIN::cacheLib( const wxString& aLibraryPath, const PROPERTIES* aP
     {
         delete m_gh_cache;
         m_gh_cache = 0;
-
         m_pretty_dir.clear();
 
+        if( !m_lib_path.empty() )
+        {
+            // Library path wasn't empty before - it's been changed. Flush out the prefetch cache.
+            m_zip_image.clear();
+        }
+
         if( aProperties )
         {
             UTF8  pretty_dir;
@@ -516,6 +533,9 @@ void GITHUB_PLUGIN::remoteGetZip( const wxString& aRepoURL ) throw( IO_ERROR )
 {
     std::string  zip_url;
 
+    if( !m_zip_image.empty() )
+        return;
+
     if( !repoURL_zipURL( aRepoURL, &zip_url ) )
     {
         wxString msg = wxString::Format( _( "Unable to parse URL:\n'%s'" ), GetChars( aRepoURL ) );
diff --git a/pcbnew/github/github_plugin.h b/pcbnew/github/github_plugin.h
index f795a9f66..3ec8f42d6 100644
--- a/pcbnew/github/github_plugin.h
+++ b/pcbnew/github/github_plugin.h
@@ -2,7 +2,7 @@
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
  * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <dick@xxxxxxxxxxx>
- * Copyright (C) 2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2016-2017 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
@@ -170,6 +170,9 @@ public:
     wxArrayString FootprintEnumerate( const wxString& aLibraryPath,
             const PROPERTIES* aProperties = NULL ) override;
 
+    void PrefetchLib( const wxString& aLibraryPath,
+            const PROPERTIES* aProperties = NULL ) override;
+
     MODULE* FootprintLoad( const wxString& aLibraryPath,
             const wxString& aFootprintName, const PROPERTIES* aProperties ) override;
 
@@ -215,7 +218,8 @@ protected:
     /**
      * Function remoteGetZip
      * fetches a zip file image from a github repo synchronously.  The byte image
-     * is received into the m_input_stream.
+     * is received into the m_input_stream. If the image has already been stored,
+     * do nothing.
      */
     void remoteGetZip( const wxString& aRepoURL ) throw( IO_ERROR );
 
diff --git a/pcbnew/io_mgr.h b/pcbnew/io_mgr.h
index 87e217a04..6dff80ed3 100644
--- a/pcbnew/io_mgr.h
+++ b/pcbnew/io_mgr.h
@@ -5,7 +5,7 @@
  * This program source code file is part of KICAD, a free EDA CAD application.
  *
  * Copyright (C) 2011-2012 SoftPLC Corporation, Dick Hollenbeck <dick@xxxxxxxxxxx>
- * Copyright (C) 2016 Kicad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2016-2017 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
@@ -283,6 +283,28 @@ public:
             const PROPERTIES* aProperties = NULL );
 
     /**
+     * Function PrefetchLib
+     * If possible, prefetches the specified library (e.g. performing downloads). Does not parse.
+     * Threadsafe.
+     *
+     * This is a no-op for libraries that cannot be prefetched.
+     *
+     * Plugins that cannot prefetch need not override this; a default no-op is provided.
+     *
+     * @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.
+     *
+     * @throw IO_ERROR if there is an error prefetching the library.
+     */
+    virtual void PrefetchLib( const wxString& aLibraryPath,
+            const PROPERTIES* aProperties = NULL );
+
+    /**
      * Function FootprintLoad
      * loads a footprint having @a aFootprintName from the @a aLibraryPath containing
      * a library format that this PLUGIN knows about.
diff --git a/pcbnew/loadcmp.cpp b/pcbnew/loadcmp.cpp
index 6dac6db80..f1955c296 100644
--- a/pcbnew/loadcmp.cpp
+++ b/pcbnew/loadcmp.cpp
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2012 Jean-Pierre Charras, jean-pierre.charras@xxxxxxxxxxxxxxx
  * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@xxxxxxxxxxx>
- * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 1992-2017 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
@@ -54,6 +54,7 @@ using namespace std::placeholders;
 #include <pcbnew.h>
 #include <module_editor_frame.h>
 #include <footprint_info.h>
+#include <footprint_info_impl.h>
 #include <dialog_get_component.h>
 #include <modview_frame.h>
 #include <wildcards_and_files_ext.h>
@@ -62,7 +63,9 @@ using namespace std::placeholders;
 
 static void DisplayCmpDoc( wxString& aName, void* aData );
 
-static FOOTPRINT_LIST MList;
+// Use the _IMPL class directly here because this is static - don't want to yank
+// a static through kiface.
+static FOOTPRINT_LIST_IMPL MList;
 
 static void clearModuleItemFlags( BOARD_ITEM* aItem )
 {
diff --git a/pcbnew/modview_frame.cpp b/pcbnew/modview_frame.cpp
index 7cb36959a..3dee456ae 100644
--- a/pcbnew/modview_frame.cpp
+++ b/pcbnew/modview_frame.cpp
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2012-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
  * Copyright (C) 2008-2016 Wayne Stambaugh <stambaughw@xxxxxxxxxxx>
- * Copyright (C) 2004-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2004-2017 KiCad Developers, see AUTHORS.txt for contributors.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -60,6 +60,7 @@
 #include "tools/pcb_actions.h"
 
 #include <functional>
+#include <memory>
 using namespace std::placeholders;
 
 
@@ -387,21 +388,21 @@ void FOOTPRINT_VIEWER_FRAME::ReCreateFootprintList()
         return;
     }
 
-    FOOTPRINT_LIST fp_info_list;
+    auto fp_info_list( FOOTPRINT_LIST::GetInstance( Kiway() ) );
 
     wxString nickname = getCurNickname();
 
-    fp_info_list.ReadFootprintFiles( Prj().PcbFootprintLibs(), !nickname ? NULL : &nickname );
+    fp_info_list->ReadFootprintFiles( Prj().PcbFootprintLibs(), !nickname ? NULL : &nickname );
 
-    if( fp_info_list.GetErrorCount() )
+    if( fp_info_list->GetErrorCount() )
     {
-        fp_info_list.DisplayErrors( this );
+        fp_info_list->DisplayErrors( this );
         return;
     }
 
-    for( const FOOTPRINT_INFO& footprint : fp_info_list.GetList() )
+    for( auto& footprint : fp_info_list->GetList() )
     {
-        m_footprintList->Append( footprint.GetFootprintName() );
+        m_footprintList->Append( footprint->GetFootprintName() );
     }
 
     int index = m_footprintList->FindString( getCurFootprintName() );
diff --git a/pcbnew/pcb_netlist.cpp b/pcbnew/pcb_netlist.cpp
index cb54855df..cc4acc1cc 100644
--- a/pcbnew/pcb_netlist.cpp
+++ b/pcbnew/pcb_netlist.cpp
@@ -73,38 +73,6 @@ const COMPONENT_NET& COMPONENT::GetNet( const wxString& aPinName )
 }
 
 
-bool COMPONENT::MatchesFootprintFilters( const wxString& aLibraryName, const wxString& aFootprintName ) const
-{
-    if( m_footprintFilters.GetCount() == 0 )
-        return true;
-
-    // The matching is case insensitive
-    wxString name = "";
-
-    EDA_PATTERN_MATCH_WILDCARD patternFilter;
-
-    for( unsigned ii = 0; ii < m_footprintFilters.GetCount(); ii++ )
-    {
-        // If the filter contains a ':' character, include the library name in the pattern
-        if( m_footprintFilters[ii].Contains( ":" ) )
-        {
-            name = aLibraryName.Lower() + ":";
-        }
-
-        name += aFootprintName.Lower();
-
-        patternFilter.SetPattern( m_footprintFilters[ii].Lower() );
-
-        if( patternFilter.Find( name ) != EDA_PATTERN_NOT_FOUND )
-        {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-
 void COMPONENT::Format( OUTPUTFORMATTER* aOut, int aNestLevel, int aCtl )
 {
     int nl = aNestLevel;
diff --git a/pcbnew/pcb_netlist.h b/pcbnew/pcb_netlist.h
index 25b79f52c..e6d404685 100644
--- a/pcbnew/pcb_netlist.h
+++ b/pcbnew/pcb_netlist.h
@@ -174,14 +174,6 @@ public:
 
     const wxArrayString& GetFootprintFilters() const { return m_footprintFilters; }
 
-    /**
-     * Function MatchesFootprintFilters
-     *
-     * @return true if \a aFootprintName matches any of the footprint filters or no footprint
-     *         filters are defined.
-     */
-    bool MatchesFootprintFilters( const wxString& aLibraryName, const wxString& aFootprintName ) const;
-
     MODULE* GetModule( bool aRelease = false )
     {
         return ( aRelease ) ? m_footprint.release() : m_footprint.get();
diff --git a/pcbnew/pcbnew.cpp b/pcbnew/pcbnew.cpp
index 65d4a2d95..b17a68ac1 100644
--- a/pcbnew/pcbnew.cpp
+++ b/pcbnew/pcbnew.cpp
@@ -35,8 +35,10 @@
 #include <fctsys.h>
 #include <pgm_base.h>
 #include <kiface_i.h>
+#include <kiface_ids.h>
 #include <confirm.h>
 #include <macros.h>
+#include <make_unique.h>
 #include <class_drawpanel.h>
 #include <wxPcbStruct.h>
 #include <eda_dde.h>
@@ -58,6 +60,7 @@
 #include <modview_frame.h>
 #include <footprint_wizard_frame.h>
 #include <footprint_preview_panel.h>
+#include <footprint_info_impl.h>
 #include <gl_context_mgr.h>
 extern bool IsWxPythonLoaded();
 
@@ -170,7 +173,17 @@ static struct IFACE : public KIFACE_I
      */
     void* IfaceOrAddress( int aDataId ) override
     {
-        return NULL;
+        switch( aDataId )
+        {
+        case KIFACE_NEW_FOOTPRINT_LIST:
+            return (void*) static_cast<FOOTPRINT_LIST*>( new FOOTPRINT_LIST_IMPL() );
+
+        case KIFACE_G_FOOTPRINT_TABLE:
+            return (void*) new FP_LIB_TABLE( &GFootprintTable );
+
+        default:
+            return nullptr;
+        }
     }
 
 } kiface( "pcbnew", KIWAY::FACE_PCB );
diff --git a/pcbnew/plugin.cpp b/pcbnew/plugin.cpp
index 5d9364712..89557da17 100644
--- a/pcbnew/plugin.cpp
+++ b/pcbnew/plugin.cpp
@@ -2,7 +2,7 @@
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
  * Copyright (C) 2011-2012 SoftPLC Corporation, Dick Hollenbeck <dick@xxxxxxxxxxx>
- * Copyright (C) 2016 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 2016-2017 KiCad Developers, see AUTHORS.txt for contributors.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -66,6 +66,13 @@ wxArrayString PLUGIN::FootprintEnumerate( const wxString& aLibraryPath, const PR
 }
 
 
+void PLUGIN::PrefetchLib( const wxString& aLibraryPath, const PROPERTIES* aProperties )
+{
+    (void) aLibraryPath;
+    (void) aProperties;
+}
+
+
 MODULE* PLUGIN::FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName,
                                     const PROPERTIES* aProperties )
 {
-- 
2.12.0


Follow ups

References