← Back to team overview

kicad-developers team mailing list archive

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

 

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 041024cf860de9c14149bd6002a2e360905c6a4d 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       | 253 ++++++++++------
 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, 2628 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 98538fdba..ddcd34ee4 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
     )
 
@@ -242,6 +244,8 @@ set( COMMON_SRCS
     eda_pattern_match.cpp
     exceptions.cpp
     filter_reader.cpp
+    footprint_info.cpp
+    footprint_filter.cpp
     lib_id.cpp
     lib_table_keywords.cpp
 #    findkicadhelppath.cpp.notused      deprecated, use searchhelpfilefullpath.cpp
@@ -350,7 +354,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 0f6e4cdae..649ffa1ab 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,57 +89,65 @@ 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();
 }
 
 
+DIALOG_CHOOSE_COMPONENT::~DIALOG_CHOOSE_COMPONENT()
+{
+    delete m_dbl_click_timer;
+}
+
+
 wxPanel* DIALOG_CHOOSE_COMPONENT::ConstructLeftPanel( wxWindow* aParent )
 {
     auto panel = new wxPanel( 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 );
@@ -149,23 +162,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();
@@ -188,6 +203,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();
 }
 
 
@@ -195,13 +216,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 ) );
@@ -243,16 +270,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;
     }
 }
@@ -260,8 +283,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();
@@ -270,6 +293,7 @@ void DIALOG_CHOOSE_COMPONENT::OnTreeSelect( wxDataViewEvent& aEvent )
     {
         m_details_ctrl->SetPage( GenerateAliasInfo( alias, unit ) );
         ShowFootprintFor( alias );
+        PopulateFootprintSelector( alias );
     }
     else
     {
@@ -277,6 +301,8 @@ void DIALOG_CHOOSE_COMPONENT::OnTreeSelect( wxDataViewEvent& aEvent )
 
         if( m_fp_view_ctrl->IsInitialized() )
             m_fp_view_ctrl->SetStatusText( wxEmptyString );
+
+        PopulateFootprintSelector( nullptr );
     }
 }
 
@@ -303,7 +329,7 @@ void DIALOG_CHOOSE_COMPONENT::OnCloseTimer( wxTimerEvent& aEvent )
     }
     else
     {
-        EndModal( wxID_OK );
+        EndQuasiModal( wxID_OK );
     }
 }
 
@@ -311,7 +337,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 );
 }
 
 
@@ -320,32 +346,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( "" );
 
-    for( auto const & field: fields )
+    ShowFootprint( fp_name );
+}
+
+
+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() );
 }
 
@@ -354,9 +410,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
@@ -381,6 +437,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 );
@@ -407,10 +478,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 );
 
@@ -492,7 +563,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 );
 
@@ -508,8 +579,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 );
 
@@ -531,8 +602,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 d0aee6945..84f9f5cf2 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,28 +93,44 @@ 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();
 
     /** 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 );
@@ -121,6 +138,7 @@ protected:
 
     void OnInitDialog( wxInitDialogEvent& aEvent );
     void OnCloseTimer( wxTimerEvent& aEvent );
+    void OnProgressTimer( wxTimerEvent& aEvent );
 
     void OnQueryText( wxCommandEvent& aEvent );
     void OnQueryEnter( wxCommandEvent& aEvent );
@@ -134,12 +152,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 );
@@ -168,15 +200,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