From be1d6113d6a81bc414e06c5d080cc4917f5de1f1 Mon Sep 17 00:00:00 2001
From: Jeff Young <jeff@rokeby.ie>
Date: Sun, 5 Aug 2018 12:56:02 +0100
Subject: [PATCH] More performance enhancements.

Be more intelligent about sorting lib tree items.  (Footprint
entries, for instance, come out of an already-sorted list.)

Don't recreate menus twice when laoding Footprint Editor.

More pervasive use of WX_FILENAME to avoid expensive calls to
wxFileName::SplitPath() and string concatenation.

For POSIX kernels do all the work on the file-system side so we
don't have to keep converting back and forth between encodings.
---
 common/common.cpp                             | 297 ++++++++++++------
 common/footprint_info.cpp                     |  23 --
 common/gal/stroke_font.cpp                    |   6 +-
 common/lib_tree_model.cpp                     |  44 ++-
 common/lib_tree_model.h                       |   4 +-
 common/lib_tree_model_adapter.cpp             |   6 +-
 common/lib_tree_model_adapter.h               |   8 +-
 common/widgets/color_swatch.cpp               |   3 +-
 eeschema/class_libentry.cpp                   |   8 +-
 eeschema/class_libentry.h                     |   6 +-
 eeschema/getpart.cpp                          |   4 +-
 eeschema/symbol_tree_model_adapter.cpp        |   5 +-
 .../symbol_tree_synchronizing_adapter.cpp     |   3 +-
 include/common.h                              |  52 +--
 include/footprint_info.h                      |  31 +-
 include/lib_tree_item.h                       |   2 +-
 pcbnew/footprint_edit_frame.cpp               |  19 +-
 pcbnew/fp_tree_model_adapter.cpp              |   4 +-
 pcbnew/fp_tree_synchronizing_adapter.cpp      |  11 +-
 pcbnew/generate_footprint_info.cpp            |   4 +-
 pcbnew/gpcb_plugin.cpp                        |  56 +---
 pcbnew/kicad_plugin.cpp                       |  78 ++---
 pcbnew/load_select_footprint.cpp              |   2 +-
 pcbnew/pcb_layer_widget.cpp                   |   1 -
 24 files changed, 356 insertions(+), 321 deletions(-)

diff --git a/common/common.cpp b/common/common.cpp
index deb07d44b2..b3490b2d8f 100644
--- a/common/common.cpp
+++ b/common/common.cpp
@@ -534,121 +534,240 @@ bool std::less<wxPoint>::operator()( const wxPoint& aA, const wxPoint& aB ) cons
 
 
 //
-// A cover of wxFileName::SetFullName() which avoids expensive calls to wxFileName::SplitPath().
+// A wrapper around a wxFileName which avoids expensive calls to wxFileName::SplitPath()
+// and string concatenations by caching the path and filename locally and only resolving
+// the wxFileName when it has to.
 //
+WX_FILENAME::WX_FILENAME( const wxString& aPath, const wxString& aFilename ) :
+        m_fn( aPath, aFilename ),
+        m_path( aPath ),
+        m_fullName( aFilename )
+{ }
+
+
 void WX_FILENAME::SetFullName( const wxString& aFileNameAndExtension )
 {
     m_fullName = aFileNameAndExtension;
+}
 
+
+wxString WX_FILENAME::GetName() const
+{
+    size_t dot = m_fullName.find_last_of( wxT( '.' ) );
+    return m_fullName.substr( 0, dot );
+}
+
+
+wxString WX_FILENAME::GetFullName() const
+{
+    return m_fullName;
+}
+
+
+wxString WX_FILENAME::GetPath() const
+{
+    return m_path;
+}
+
+
+wxString WX_FILENAME::GetFullPath() const
+{
+    return m_path + wxT( '/' ) + m_fullName;
+}
+
+
+// Write locally-cached values to the wxFileName.  MUST be called before using m_fn.
+void WX_FILENAME::resolve()
+{
     size_t dot = m_fullName.find_last_of( wxT( '.' ) );
     m_fn.SetName( m_fullName.substr( 0, dot ) );
     m_fn.SetExt( m_fullName.substr( dot + 1 ) );
 }
 
 
-//
-// An alernative to wxFileName::GetModificationTime() which avoids multiple calls to stat() on
-// POSIX kernels.
-//
 long long WX_FILENAME::GetTimestamp()
 {
-#ifdef __WINDOWS__
+    resolve();
+
     if( m_fn.FileExists() )
         return m_fn.GetModificationTime().GetValue().GetValue();
-#else
-    // By stat-ing the file ourselves we save wxWidgets from doing it three times:
-    // Exists( wxFILE_EXISTS_SYMLINK ), FileExists(), and finally GetModificationTime()
-    struct stat fn_stat;
-    wxLstat( GetFullPath(), &fn_stat );
 
-    // Timestamp the source file, not the symlink
-    if( S_ISLNK( fn_stat.st_mode ) )    // wxFILE_EXISTS_SYMLINK
-    {
-        char buffer[ PATH_MAX + 1 ];
-        ssize_t pathLen = readlink( TO_UTF8( GetFullPath() ), buffer, PATH_MAX );
-
-        if( pathLen > 0 )
-        {
-            buffer[ pathLen ] = '\0';
-            wxString srcPath = m_path + wxT( '/' ) + wxString::FromUTF8( buffer );
-            wxLstat( srcPath, &fn_stat );
-        }
-    }
-
-    if( S_ISREG( fn_stat.st_mode ) )    // wxFileExists()
-        return fn_stat.st_mtime * 1000;
-#endif
     return 0;
 }
 
-#ifndef __WINDOWS__
 
 //
-// A version of wxDir which avoids expensive calls to wxFileName::wxFileName().
+// A copy of wxMatchWild (attributed to <dalewis@cs.Buffalo.EDU>) modified to use
+// POSIX-file-system-encoded inputs.
 //
-WX_DIR::WX_DIR( const wxString& aDirPath ) :
-    m_dirpath( aDirPath )
+bool matchWild( const char* pat, const char* text, bool dot_special )
 {
-    m_dir = NULL;
-
-    // throw away the trailing slashes
-    size_t n = m_dirpath.length();
-
-    while ( n > 0 && m_dirpath[--n] == '/' )
-        ;
-
-    m_dirpath.Truncate(n + 1);
-
-    m_dir = opendir( m_dirpath.fn_str() );
-}
-
-
-bool WX_DIR::IsOpened() const
-{
-    return m_dir != nullptr;
-}
-
-
-WX_DIR::~WX_DIR()
-{
-    if ( m_dir )
-        closedir( m_dir );
-}
-
-
-bool WX_DIR::GetFirst( wxString *filename, const wxString& filespec )
-{
-    m_filespec = filespec;
-
-    rewinddir( m_dir );
-    return GetNext( filename );
-}
-
-
-bool WX_DIR::GetNext(wxString *filename) const
-{
-    dirent *dirEntry = NULL;
-    wxString dirEntryName;
-    bool matches = false;
-
-    while ( !matches )
+    if ( !*text )
     {
-        dirEntry = readdir( m_dir );
-
-        if ( !dirEntry )
-            return false;
-
-#if wxUSE_UNICODE
-        dirEntryName = wxString( dirEntry->d_name, *wxConvFileName );
-#else
-        dirEntryName = dirEntry->d_name;
-#endif
-
-        matches = wxMatchWild( m_filespec, dirEntryName );
+        /* Match if both are empty. */
+        return !*pat;
     }
 
-    *filename = dirEntryName;
+    const char *m = pat,
+    *n = text,
+    *ma = NULL,
+    *na = NULL;
+    int just = 0,
+    acount = 0,
+    count = 0;
 
-    return true;
+    if (dot_special && (*n == '.'))
+    {
+        /* Never match so that hidden Unix files
+         * are never found. */
+        return false;
+    }
+
+    for (;;)
+    {
+        if (*m == '*')
+        {
+            ma = ++m;
+            na = n;
+            just = 1;
+            acount = count;
+        }
+        else if (*m == '?')
+        {
+            m++;
+            if (!*n++)
+                return false;
+        }
+        else
+        {
+            if (*m == '\\')
+            {
+                m++;
+                /* Quoting "nothing" is a bad thing */
+                if (!*m)
+                    return false;
+            }
+            if (!*m)
+            {
+                /*
+                * If we are out of both strings or we just
+                * saw a wildcard, then we can say we have a
+                * match
+                */
+                if (!*n)
+                    return true;
+                if (just)
+                    return true;
+                just = 0;
+                goto not_matched;
+            }
+            /*
+            * We could check for *n == NULL at this point, but
+            * since it's more common to have a character there,
+            * check to see if they match first (m and n) and
+            * then if they don't match, THEN we can check for
+            * the NULL of n
+            */
+            just = 0;
+            if (*m == *n)
+            {
+                m++;
+                count++;
+                n++;
+            }
+            else
+            {
+
+                not_matched:
+
+                /*
+                * If there are no more characters in the
+                * string, but we still need to find another
+                * character (*m != NULL), then it will be
+                * impossible to match it
+                */
+                if (!*n)
+                    return false;
+
+                if (ma)
+                {
+                    m = ma;
+                    n = ++na;
+                    count = acount;
+                }
+                else
+                    return false;
+            }
+        }
+    }
 }
+
+
+long long TimestampDir( const wxString& aDirPath, const wxString& aFilespec )
+{
+    long long timestamp = 0;
+
+#ifdef __WINDOWS__
+    // wxFileName construction is egregiously slow.  Construct it once and just swap out
+    // the filename thereafter.
+    WX_FILENAME fn( aDirPath, wxT( "dummyName" ) );
+    wxDir       dir( aDirPath );
+    wxString    fullname;
+
+    if( dir.IsOpened() )
+    {
+        if( dir.GetFirst( &fullname, filespec ) )
+        {
+            do
+            {
+                fn.SetFullName( fullname );
+                timestamp += fn.GetTimestamp();
+            }
+            while( dir.GetNext( &fullname ) );
+        }
+    }
+#else
+    // POSIX version.  Save time by not converting between encodings -- do everything on
+    // the file-system side.
+    std::string filespec( aFilespec.fn_str() );
+    std::string dir_path( aDirPath.fn_str() );
+
+    DIR* dir = opendir( dir_path.c_str() );
+
+    if( dir )
+    {
+        for( dirent* dir_entry = readdir( dir ); dir_entry; dir_entry = readdir( dir ) )
+        {
+            if( !matchWild( filespec.c_str(), dir_entry->d_name, true ) )
+                continue;
+
+            std::string entry_path = dir_path + '/' + dir_entry->d_name;
+            struct stat entry_stat;
+
+            wxCRT_Lstat( entry_path.c_str(), &entry_stat );
+
+            // Timestamp the source file, not the symlink
+            if( S_ISLNK( entry_stat.st_mode ) )    // wxFILE_EXISTS_SYMLINK
+            {
+                char buffer[ PATH_MAX + 1 ];
+                ssize_t pathLen = readlink( entry_path.c_str(), buffer, PATH_MAX );
+
+                if( pathLen > 0 )
+                {
+                    buffer[ pathLen ] = '\0';
+                    entry_path = dir_path + buffer;
+
+                    wxCRT_Lstat( entry_path.c_str(), &entry_stat );
+                }
+            }
+
+            if( S_ISREG( entry_stat.st_mode ) )    // wxFileExists()
+                timestamp += entry_stat.st_mtime * 1000;
+        }
+    }
 #endif
+
+    return timestamp;
+}
+
+
diff --git a/common/footprint_info.cpp b/common/footprint_info.cpp
index f094819a9c..2e2128dcb5 100644
--- a/common/footprint_info.cpp
+++ b/common/footprint_info.cpp
@@ -23,12 +23,6 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
-
-/**
- * @file footprint_info.cpp
- */
-
-
 /*
  * Functions to read footprint libraries and fill m_footprints by available footprints names
  * and their documentation (comments and keywords)
@@ -156,7 +150,6 @@ FOOTPRINT_LIST* FOOTPRINT_LIST::GetInstance( KIWAY& aKiway )
 
 FOOTPRINT_ASYNC_LOADER::FOOTPRINT_ASYNC_LOADER() : m_list( nullptr )
 {
-    m_started = false;
     m_total_libs = 0;
 }
 
@@ -177,8 +170,6 @@ void FOOTPRINT_ASYNC_LOADER::SetList( FOOTPRINT_LIST* aList )
 void FOOTPRINT_ASYNC_LOADER::Start(
         FP_LIB_TABLE* aTable, wxString const* aNickname, unsigned aNThreads )
 {
-    m_started = true;
-
     // 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;
@@ -210,17 +201,3 @@ void FOOTPRINT_ASYNC_LOADER::Abort()
         m_list = nullptr;
     }
 }
-
-
-void FOOTPRINT_ASYNC_LOADER::SetCompletionCallback( std::function<void()> aCallback )
-{
-    m_completion_cb = std::move(aCallback);
-}
-
-
-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/gal/stroke_font.cpp b/common/gal/stroke_font.cpp
index 1bda0204d8..ec7f64ea78 100644
--- a/common/gal/stroke_font.cpp
+++ b/common/gal/stroke_font.cpp
@@ -142,15 +142,15 @@ BOX2D STROKE_FONT::computeBoundingBox( const GLYPH& aGLYPH, const VECTOR2D& aGLY
 
     std::deque<VECTOR2D> boundingPoints;
 
-    boundingPoints.push_back( VECTOR2D( aGLYPHBoundingX.x, 0 ) );
-    boundingPoints.push_back( VECTOR2D( aGLYPHBoundingX.y, 0 ) );
+    boundingPoints.emplace_back( VECTOR2D( aGLYPHBoundingX.x, 0 ) );
+    boundingPoints.emplace_back( VECTOR2D( aGLYPHBoundingX.y, 0 ) );
 
     for( GLYPH::const_iterator pointListIt = aGLYPH.begin(); pointListIt != aGLYPH.end(); ++pointListIt )
     {
         for( std::deque<VECTOR2D>::const_iterator pointIt = pointListIt->begin();
                 pointIt != pointListIt->end(); ++pointIt )
         {
-            boundingPoints.push_back( VECTOR2D( aGLYPHBoundingX.x, pointIt->y ) );
+            boundingPoints.emplace_back( VECTOR2D( aGLYPHBoundingX.x, pointIt->y ) );
         }
     }
 
diff --git a/common/lib_tree_model.cpp b/common/lib_tree_model.cpp
index 68fe2bb75d..5356295c1e 100644
--- a/common/lib_tree_model.cpp
+++ b/common/lib_tree_model.cpp
@@ -26,7 +26,7 @@
 #include <make_unique.h>
 #include <utility>
 #include <pgm_base.h>
-
+#include <kicad_string.h>
 
 // Each node gets this lowest score initially, without any matches applied.
 // Matches will then increase this score depending on match quality.  This way,
@@ -59,19 +59,29 @@ void LIB_TREE_NODE::ResetScore()
 }
 
 
-void LIB_TREE_NODE::AssignIntrinsicRanks()
+void LIB_TREE_NODE::AssignIntrinsicRanks( bool presorted )
 {
     std::vector<LIB_TREE_NODE*> sort_buf;
 
-    for( auto const& node: Children )
-        sort_buf.push_back( &*node );
+    if( presorted )
+    {
+        int max = Children.size() - 1;
 
-    std::sort( sort_buf.begin(), sort_buf.end(),
-            []( LIB_TREE_NODE* a, LIB_TREE_NODE* b ) -> bool
-                { return a->MatchName > b->MatchName; } );
+        for( int i = 0; i <= max; ++i )
+            Children[i]->IntrinsicRank = max - i;
+    }
+    else
+    {
+        for( auto const& node: Children )
+            sort_buf.push_back( &*node );
 
-    for( int i = 0; i < (int) sort_buf.size(); ++i )
-        sort_buf[i]->IntrinsicRank = i;
+        std::sort( sort_buf.begin(), sort_buf.end(),
+                []( LIB_TREE_NODE* a, LIB_TREE_NODE* b ) -> bool
+                    { return StrNumCmp( a->Name, b->Name, INT_MAX, true ) > 0; } );
+
+        for( int i = 0; i < (int) sort_buf.size(); ++i )
+            sort_buf[i]->IntrinsicRank = i;
+    }
 }
 
 
@@ -108,7 +118,7 @@ LIB_TREE_NODE::LIB_TREE_NODE()
       Type( INVALID ),
       IntrinsicRank( 0 ),
       Score( kLowestDefaultScore ),
-      SearchTextNormalized( false ),
+      Normalized( false ),
       Unit( 0 ),
       IsRoot( false )
 {}
@@ -146,14 +156,15 @@ LIB_TREE_NODE_LIB_ID::LIB_TREE_NODE_LIB_ID( LIB_TREE_NODE* aParent, LIB_TREE_ITE
     Type = LIBID;
     Parent = aParent;
 
-    LibId = aItem->GetLibId();
+    LibId.SetLibNickname( aItem->GetLibNickname() );
+    LibId.SetLibItemName( aItem->GetName () );
 
     Name = aItem->GetName();
     Desc = aItem->GetDescription();
 
-    MatchName = aItem->GetName().Lower();
+    MatchName = aItem->GetName();
     SearchText = aItem->GetSearchText();
-    SearchTextNormalized = false;
+    Normalized = false;
 
     IsRoot = aItem->IsRoot();
 
@@ -182,7 +193,7 @@ void LIB_TREE_NODE_LIB_ID::Update( LIB_TREE_ITEM* aItem )
     Desc = aItem->GetDescription();
 
     SearchText = aItem->GetSearchText();
-    SearchTextNormalized = false;
+    Normalized = false;
 
     IsRoot = aItem->IsRoot();
     Children.clear();
@@ -197,10 +208,11 @@ void LIB_TREE_NODE_LIB_ID::UpdateScore( EDA_COMBINED_MATCHER& aMatcher )
     if( Score <= 0 )
         return; // Leaf nodes without scores are out of the game.
 
-    if( !SearchTextNormalized )
+    if( !Normalized )
     {
+        MatchName = MatchName.Lower();
         SearchText = SearchText.Lower();
-        SearchTextNormalized = true;
+        Normalized = true;
     }
 
     // Keywords and description we only count if the match string is at
diff --git a/common/lib_tree_model.h b/common/lib_tree_model.h
index ee9c724f24..970cc0dfa5 100644
--- a/common/lib_tree_model.h
+++ b/common/lib_tree_model.h
@@ -98,7 +98,7 @@ public:
     wxString    Desc;        ///< Description to be displayed
     wxString    MatchName;   ///< Normalized name for matching
     wxString    SearchText;  ///< Descriptive text to search
-    bool        SearchTextNormalized;  ///< Support for lazy normalization.
+    bool        Normalized;  ///< Support for lazy normalization.
 
 
     LIB_ID      LibId;       ///< LIB_ID determined by the parent library nickname and alias name.
@@ -122,7 +122,7 @@ public:
      * Store intrinsic ranks on all children of this node. See IntrinsicRank
      * member doc for more information.
      */
-    void AssignIntrinsicRanks();
+    void AssignIntrinsicRanks( bool presorted = false );
 
     /**
      * Sort child nodes quickly and recursively (IntrinsicRanks must have been set).
diff --git a/common/lib_tree_model_adapter.cpp b/common/lib_tree_model_adapter.cpp
index b568538ddd..098a27dd57 100644
--- a/common/lib_tree_model_adapter.cpp
+++ b/common/lib_tree_model_adapter.cpp
@@ -107,15 +107,15 @@ void LIB_TREE_MODEL_ADAPTER::SetPreselectNode( LIB_ID const& aLibId, int aUnit )
 
 
 void LIB_TREE_MODEL_ADAPTER::DoAddLibrary( wxString const& aNodeName, wxString const& aDesc,
-                                           std::vector<LIB_TREE_ITEM*> const& aItemList )
+                                           std::vector<LIB_TREE_ITEM*> const& aItemList,
+                                           bool presorted )
 {
     auto& lib_node = m_tree.AddLib( aNodeName, aDesc );
 
     for( auto item: aItemList )
         lib_node.AddItem( item );
 
-    lib_node.AssignIntrinsicRanks();
-    m_tree.AssignIntrinsicRanks();
+    lib_node.AssignIntrinsicRanks( presorted );
 }
 
 
diff --git a/common/lib_tree_model_adapter.h b/common/lib_tree_model_adapter.h
index e3253c2975..fc1b74bed8 100644
--- a/common/lib_tree_model_adapter.h
+++ b/common/lib_tree_model_adapter.h
@@ -151,7 +151,13 @@ public:
      * @param aItemList    list of components
      */
     void DoAddLibrary( wxString const& aNodeName, wxString const& aDesc,
-                       std::vector<LIB_TREE_ITEM*> const& aItemList );
+                       std::vector<LIB_TREE_ITEM*> const& aItemList, bool presorted );
+
+
+    /**
+     * Sort the tree and assign ranks after adding libraries.
+     */
+    void AssignIntrinsicRanks() { m_tree.AssignIntrinsicRanks(); }
 
     /**
      * Set the search string provided by the user.
diff --git a/common/widgets/color_swatch.cpp b/common/widgets/color_swatch.cpp
index 8f8c3aeaf7..7890f13614 100644
--- a/common/widgets/color_swatch.cpp
+++ b/common/widgets/color_swatch.cpp
@@ -70,7 +70,8 @@ wxBitmap COLOR_SWATCH::MakeBitmap( COLOR4D aColor, COLOR4D aBackground, wxSize a
 static std::unique_ptr<wxStaticBitmap> makeColorSwatch( wxWindow* aParent, COLOR4D aColor,
                                                         COLOR4D aBackground, int aID )
 {
-    wxSize size = aParent->ConvertDialogToPixels( SWATCH_SIZE_DU );
+    static wxSize size = aParent->ConvertDialogToPixels( SWATCH_SIZE_DU );
+
     wxBitmap bitmap = COLOR_SWATCH::MakeBitmap( aColor, aBackground, size );
     auto ret = std::make_unique<wxStaticBitmap>( aParent, aID, bitmap );
 
diff --git a/eeschema/class_libentry.cpp b/eeschema/class_libentry.cpp
index ff23a48cfb..5ac865ca36 100644
--- a/eeschema/class_libentry.cpp
+++ b/eeschema/class_libentry.cpp
@@ -145,7 +145,11 @@ wxString LIB_ALIAS::GetUnitReference( int aUnit )
 
 wxString LIB_ALIAS::GetSearchText()
 {
-    wxString text = GetKeyWords() + wxT( "        " ) + GetDescription();
+    // Matches are scored by offset from front of string, so inclusion of this spacer
+    // discounts matches found after it.
+    static const wxString discount( wxT( "        " ) );
+
+    wxString text = GetKeyWords() + discount + GetDescription();
 
     // If a footprint is defined for the part, add it to the serach string
     if( shared )
@@ -153,7 +157,7 @@ wxString LIB_ALIAS::GetSearchText()
         wxString footprint = shared->GetFootprintField().GetText();
 
         if( !footprint.IsEmpty() )
-            text += wxT( "        " ) + footprint;
+            text += discount + footprint;
     }
 
     return text;
diff --git a/eeschema/class_libentry.h b/eeschema/class_libentry.h
index c7238ea92e..42c0bfb7a0 100644
--- a/eeschema/class_libentry.h
+++ b/eeschema/class_libentry.h
@@ -123,21 +123,21 @@ public:
         description = aDescription;
     }
 
-    wxString GetDescription() override { return description; }
+    const wxString& GetDescription() override { return description; }
 
     void SetKeyWords( const wxString& aKeyWords )
     {
         keyWords = aKeyWords;
     }
 
-    wxString GetKeyWords() const { return keyWords; }
+    const wxString& GetKeyWords() const { return keyWords; }
 
     void SetDocFileName( const wxString& aDocFileName )
     {
         docFileName = aDocFileName;
     }
 
-    wxString GetDocFileName() const { return docFileName; }
+    const wxString& GetDocFileName() const { return docFileName; }
 
     wxString GetSearchText() override;
 
diff --git a/eeschema/getpart.cpp b/eeschema/getpart.cpp
index 96476ed1f3..09e5b9ee85 100644
--- a/eeschema/getpart.cpp
+++ b/eeschema/getpart.cpp
@@ -133,6 +133,8 @@ SCH_BASE_FRAME::COMPONENT_SELECTION SCH_BASE_FRAME::SelectComponentFromLibTree(
             }
         }
 
+        adapter->AssignIntrinsicRanks();
+
         if( aFilter->GetFilterPowerParts() )
             adapter->SetFilter( SYMBOL_TREE_MODEL_ADAPTER::CMP_FILTER_POWER );
 
@@ -150,7 +152,7 @@ SCH_BASE_FRAME::COMPONENT_SELECTION SCH_BASE_FRAME::SelectComponentFromLibTree(
                 history_list.push_back( alias );
         }
 
-        adapter->DoAddLibrary( "-- " + _( "Recently Used" ) + " --", wxEmptyString, history_list );
+        adapter->DoAddLibrary( "-- " + _( "Recently Used" ) + " --", wxEmptyString, history_list, true );
         adapter->SetPreselectNode( aHistoryList[0].LibId, aHistoryList[0].Unit );
     }
 
diff --git a/eeschema/symbol_tree_model_adapter.cpp b/eeschema/symbol_tree_model_adapter.cpp
index 853e4dc335..c39b53add3 100644
--- a/eeschema/symbol_tree_model_adapter.cpp
+++ b/eeschema/symbol_tree_model_adapter.cpp
@@ -76,6 +76,8 @@ void SYMBOL_TREE_MODEL_ADAPTER::AddLibraries( const std::vector<wxString>& aNick
         ii++;
     }
 
+    m_tree.AssignIntrinsicRanks();
+
     if( prg )
     {
         prg->Destroy();
@@ -105,8 +107,7 @@ void SYMBOL_TREE_MODEL_ADAPTER::AddLibrary( wxString const& aLibNickname )
     if( alias_list.size() > 0 )
     {
         comp_list.assign( alias_list.begin(), alias_list.end() );
-        DoAddLibrary( aLibNickname, m_libs->GetDescription( aLibNickname ), comp_list );
-        m_tree.AssignIntrinsicRanks();
+        DoAddLibrary( aLibNickname, m_libs->GetDescription( aLibNickname ), comp_list, false );
     }
 }
 
diff --git a/eeschema/symbol_tree_synchronizing_adapter.cpp b/eeschema/symbol_tree_synchronizing_adapter.cpp
index 9161ffb4d6..13e72674dd 100644
--- a/eeschema/symbol_tree_synchronizing_adapter.cpp
+++ b/eeschema/symbol_tree_synchronizing_adapter.cpp
@@ -102,9 +102,10 @@ void SYMBOL_TREE_SYNCHRONIZING_ADAPTER::Sync( bool aForce, std::function<void(in
 
             auto& lib_node = m_tree.AddLib( libName, library->GetDescr() );
             updateLibrary( lib_node );
-            m_tree.AssignIntrinsicRanks();
         }
     }
+
+    m_tree.AssignIntrinsicRanks();
 }
 
 
diff --git a/include/common.h b/include/common.h
index eabedc0ccb..56f1a19b94 100644
--- a/include/common.h
+++ b/include/common.h
@@ -383,65 +383,35 @@ namespace std
 
 
 /**
- * A wrapper around a wxFileName which is much more performant with a (very) limited API.
+ * A wrapper around a wxFileName which is much more performant with a subset of the API.
  */
 class WX_FILENAME
 {
 public:
-    WX_FILENAME( const wxString& aPath, const wxString& aFilename ) :
-        m_fn( aPath, aFilename ),
-        m_path( aPath ),
-        m_fullName( aFilename )
-    { }
+    WX_FILENAME( const wxString& aPath, const wxString& aFilename );
 
-    // Avoid wxFileName's expensive path concatenation.
-    wxString GetFullPath() { return m_path + wxT( '/' ) + m_fullName; }
-
-    wxString GetName() { return m_fn.GetName(); }
-
-    // Avoid wxFileName's expensive calls to wxFileName::SplitPath().
     void SetFullName( const wxString& aFileNameAndExtension );
 
+    wxString GetName() const;
+    wxString GetFullName() const;
+    wxString GetPath() const;
+    wxString GetFullPath() const;
+
     // Avoid multiple calls to stat() on POSIX kernels.
     long long GetTimestamp();
 
-    operator wxFileName() const { return m_fn; }
-
 private:
+    // Write cached values to the wrapped wxFileName.  MUST be called before using m_fn.
+    void resolve();
+
     wxFileName m_fn;
     wxString   m_path;
     wxString   m_fullName;
 };
 
 
-#ifdef __WINDOWS__
-#define WX_DIR wxDir
-#else
-// For POSIX kernels we implement our own version which avoids expensive calls to
-// wxFileName::wxFileName().
-class WX_DIR
-{
-public:
-    WX_DIR( const wxString& dir );
-    ~WX_DIR();
+long long TimestampDir( const wxString& aDirPath, const wxString& aFilespec );
 
-    // returns true if the directory was successfully opened
-    bool IsOpened() const;
-
-    bool GetFirst( wxString *filename, const wxString& filespec );
-
-    // get next file in the enumeration started with GetFirst()
-    bool GetNext( wxString *filename ) const;
-
-private:
-    DIR*      m_dir;
-
-    wxString  m_dirpath;
-    wxString  m_filespec;
-
-wxDECLARE_NO_COPY_CLASS( WX_DIR );
-};
-#endif
 
 
 
diff --git a/include/footprint_info.h b/include/footprint_info.h
index 7425a368c3..9bf70f9168 100644
--- a/include/footprint_info.h
+++ b/include/footprint_info.h
@@ -92,13 +92,13 @@ public:
         return LIB_ID( m_nickname, m_fpname );
     }
 
-    wxString GetDescription() override
+    const wxString& GetDescription() override
     {
         ensure_loaded();
         return m_doc;
     }
 
-    wxString GetKeywords()
+    const wxString& GetKeywords()
     {
         ensure_loaded();
         return m_keywords;
@@ -106,7 +106,11 @@ public:
 
     wxString GetSearchText() override
     {
-        return GetKeywords() + wxT( "        " ) + GetDescription();
+        // Matches are scored by offset from front of string, so inclusion of this spacer
+        // discounts matches found after it.
+        static const wxString discount( wxT( "        " ) );
+
+        return GetKeywords() + discount + GetDescription();
     }
 
     unsigned GetPadCount()
@@ -154,8 +158,8 @@ protected:
     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
+    unsigned m_pad_count;        ///< Number of pads
+    unsigned m_unique_pad_count; ///< Number of unique pads
     wxString m_doc;              ///< Footprint description.
     wxString m_keywords;         ///< Footprint keywords.
 };
@@ -313,10 +317,8 @@ class APIEXPORT FOOTPRINT_ASYNC_LOADER
     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:
@@ -364,21 +366,6 @@ public:
      */
     void Abort();
 
-    /**
-     * 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.
diff --git a/include/lib_tree_item.h b/include/lib_tree_item.h
index 9786cf3e04..5ad5ef4bd8 100644
--- a/include/lib_tree_item.h
+++ b/include/lib_tree_item.h
@@ -44,7 +44,7 @@ public:
     virtual const wxString& GetName() const = 0;
     virtual wxString GetLibNickname() const = 0;
 
-    virtual wxString GetDescription() { return wxEmptyString; }
+    virtual const wxString& GetDescription() = 0;
 
     virtual wxString GetSearchText() { return wxEmptyString; }
 
diff --git a/pcbnew/footprint_edit_frame.cpp b/pcbnew/footprint_edit_frame.cpp
index cb0ab9599b..2de4d2f527 100644
--- a/pcbnew/footprint_edit_frame.cpp
+++ b/pcbnew/footprint_edit_frame.cpp
@@ -270,12 +270,19 @@ FOOTPRINT_EDIT_FRAME::FOOTPRINT_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent,
     initLibraryTree();
     m_treePane = new FOOTPRINT_TREE_PANE( this );
 
-    ReCreateMenuBar();
+    // ReCreateMenuBar();       // UseGalCanvas() will do this for us.
     ReCreateHToolbar();
     ReCreateAuxiliaryToolbar();
     ReCreateVToolbar();
     ReCreateOptToolbar();
 
+    m_Layers->ReFill();
+    m_Layers->ReFillRender();
+
+    GetScreen()->m_Active_Layer = F_SilkS;
+    m_Layers->SelectLayer( F_SilkS );
+    m_Layers->OnLayerSelected();
+
     if( m_canvas )
         m_canvas->SetEnableBlockCommands( true );
 
@@ -334,16 +341,6 @@ FOOTPRINT_EDIT_FRAME::FOOTPRINT_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent,
     GetGalCanvas()->GetGAL()->SetAxesEnabled( true );
     UseGalCanvas( aBackend != EDA_DRAW_PANEL_GAL::GAL_TYPE_NONE );
 
-    if( m_auimgr.GetPane( "m_LayersManagerToolBar" ).IsShown() )
-    {
-        m_Layers->ReFill();
-        m_Layers->ReFillRender();
-
-        GetScreen()->m_Active_Layer = F_SilkS;
-        m_Layers->SelectLayer( F_SilkS );
-        m_Layers->OnLayerSelected();
-    }
-
     m_auimgr.Update();
     updateTitle();
 
diff --git a/pcbnew/fp_tree_model_adapter.cpp b/pcbnew/fp_tree_model_adapter.cpp
index e494e674c9..e0291c62d2 100644
--- a/pcbnew/fp_tree_model_adapter.cpp
+++ b/pcbnew/fp_tree_model_adapter.cpp
@@ -49,8 +49,10 @@ void FP_TREE_MODEL_ADAPTER::AddLibraries()
     {
         const FP_LIB_TABLE_ROW* library = m_libs->FindRow( libName );
 
-        DoAddLibrary( libName, library->GetDescr(), getFootprints( libName ) );
+        DoAddLibrary( libName, library->GetDescr(), getFootprints( libName ), true );
     }
+
+    m_tree.AssignIntrinsicRanks();
 }
 
 
diff --git a/pcbnew/fp_tree_synchronizing_adapter.cpp b/pcbnew/fp_tree_synchronizing_adapter.cpp
index 14699a96d7..012317c3b3 100644
--- a/pcbnew/fp_tree_synchronizing_adapter.cpp
+++ b/pcbnew/fp_tree_synchronizing_adapter.cpp
@@ -69,16 +69,21 @@ void FP_TREE_SYNCHRONIZING_ADAPTER::Sync()
     }
 
     // Look for new libraries
+    size_t count = m_libMap.size();
+
     for( const auto& libName : m_libs->GetLogicalLibs() )
     {
         if( m_libMap.count( libName ) == 0 )
         {
             const FP_LIB_TABLE_ROW* library = m_libs->FindRow( libName );
 
-            DoAddLibrary( libName, library->GetDescr(), getFootprints( libName ) );
+            DoAddLibrary( libName, library->GetDescr(), getFootprints( libName ), true );
             m_libMap.insert( libName  );
         }
     }
+
+    if( m_libMap.size() > count )
+        m_tree.AssignIntrinsicRanks();
 }
 
 
@@ -92,7 +97,7 @@ void FP_TREE_SYNCHRONIZING_ADAPTER::updateLibrary( LIB_TREE_NODE_LIB& aLibNode )
 {
     std::vector<LIB_TREE_ITEM*> footprints = getFootprints( aLibNode.Name );
 
-    // remove the common part from the aliases list
+    // remove the common part from the footprints list
     for( auto nodeIt = aLibNode.Children.begin(); nodeIt != aLibNode.Children.end();  )
     {
         // Since the list is sorted we can use a binary search to speed up searches within
@@ -119,7 +124,7 @@ void FP_TREE_SYNCHRONIZING_ADAPTER::updateLibrary( LIB_TREE_NODE_LIB& aLibNode )
         }
     }
 
-    // now the aliases list contains only new aliases that need to be added to the tree
+    // now the footprint list contains only new aliases that need to be added to the tree
     for( auto footprint : footprints )
         aLibNode.AddItem( footprint );
 
diff --git a/pcbnew/generate_footprint_info.cpp b/pcbnew/generate_footprint_info.cpp
index 90a4e1b582..d3ef286498 100644
--- a/pcbnew/generate_footprint_info.cpp
+++ b/pcbnew/generate_footprint_info.cpp
@@ -133,9 +133,9 @@ public:
 };
 
 
-wxString GenerateFootprintInfo( FP_LIB_TABLE* aSymLibTable, LIB_ID const& aLibId )
+wxString GenerateFootprintInfo( FP_LIB_TABLE* aFpLibTable, LIB_ID const& aLibId )
 {
-    FOOTPRINT_INFO_GENERATOR gen( aSymLibTable, aLibId );
+    FOOTPRINT_INFO_GENERATOR gen( aFpLibTable, aLibId );
     gen.GenerateHtml();
     return gen.GetHtml();
 }
diff --git a/pcbnew/gpcb_plugin.cpp b/pcbnew/gpcb_plugin.cpp
index 0cabd89b40..781eadc946 100644
--- a/pcbnew/gpcb_plugin.cpp
+++ b/pcbnew/gpcb_plugin.cpp
@@ -108,20 +108,19 @@ static inline long parseInt( const wxString& aValue, double aScalar )
  */
 class GPCB_FPL_CACHE_ITEM
 {
-    wxFileName         m_file_name; ///< The the full file name and path of the footprint to cache.
+    WX_FILENAME             m_filename; ///< The the full file name and path of the footprint to cache.
     std::unique_ptr<MODULE> m_module;
 
 public:
-    GPCB_FPL_CACHE_ITEM( MODULE* aModule, const wxFileName& aFileName );
+    GPCB_FPL_CACHE_ITEM( MODULE* aModule, const WX_FILENAME& aFileName );
 
-    wxString    GetName() const { return m_file_name.GetDirs().Last(); }
-    wxFileName  GetFileName() const { return m_file_name; }
-    MODULE*     GetModule() const { return m_module.get(); }
+    WX_FILENAME  GetFileName() const { return m_filename; }
+    MODULE*      GetModule()   const { return m_module.get(); }
 };
 
 
-GPCB_FPL_CACHE_ITEM::GPCB_FPL_CACHE_ITEM( MODULE* aModule, const wxFileName& aFileName ) :
-    m_file_name( aFileName ),
+GPCB_FPL_CACHE_ITEM::GPCB_FPL_CACHE_ITEM( MODULE* aModule, const WX_FILENAME& aFileName ) :
+    m_filename( aFileName ),
     m_module( aModule )
 {
 }
@@ -225,7 +224,7 @@ void GPCB_FPL_CACHE::Load()
     // Note: like our .pretty footprint libraries, the gpcb footprint libraries are folders,
     // and the footprints are the .fp files inside this folder.
 
-    WX_DIR dir( m_lib_path.GetPath() );
+    wxDir dir( m_lib_path.GetPath() );
 
     if( !dir.IsOpened() )
     {
@@ -233,21 +232,21 @@ void GPCB_FPL_CACHE::Load()
                                           m_lib_path.GetPath().GetData() ) );
     }
 
-    wxString fpFileName;
-    wxString wildcard = wxT( "*." ) + GedaPcbFootprintLibFileExtension;
+    wxString fullName;
+    wxString fileSpec = wxT( "*." ) + GedaPcbFootprintLibFileExtension;
 
     // wxFileName construction is egregiously slow.  Construct it once and just swap out
-    // the filename.
-    WX_FILENAME fn( m_lib_path.GetPath(), wxT( "dummy." ) + GedaPcbFootprintLibFileExtension );
+    // the filename thereafter.
+    WX_FILENAME fn( m_lib_path.GetPath(), wxT( "dummyName" ) );
 
-    if( !dir.GetFirst( &fpFileName, wildcard ) )
+    if( !dir.GetFirst( &fullName, fileSpec ) )
         return;
 
     wxString cacheErrorMsg;
 
     do
     {
-        fn.SetFullName( fpFileName );
+        fn.SetFullName( fullName );
 
         // Queue I/O errors so only files that fail to parse don't get loaded.
         try
@@ -260,7 +259,7 @@ void GPCB_FPL_CACHE::Load()
 
             // The footprint name is the file name without the extension.
             footprint->SetFPID( LIB_ID( wxEmptyString, fn.GetName() ) );
-            m_modules.insert( name, new GPCB_FPL_CACHE_ITEM( footprint, fn.GetName() ) );
+            m_modules.insert( name, new GPCB_FPL_CACHE_ITEM( footprint, fn ) );
         }
         catch( const IO_ERROR& ioe )
         {
@@ -269,7 +268,7 @@ void GPCB_FPL_CACHE::Load()
 
             cacheErrorMsg += ioe.What();
         }
-    } while( dir.GetNext( &fpFileName ) );
+    } while( dir.GetNext( &fullName ) );
 
     if( !cacheErrorMsg.IsEmpty() )
         THROW_IO_ERROR( cacheErrorMsg );
@@ -306,30 +305,9 @@ bool GPCB_FPL_CACHE::IsModified()
 
 long long GPCB_FPL_CACHE::GetTimestamp( const wxString& aLibPath )
 {
-    long long files_timestamp = 0;
+    wxString fileSpec = wxT( "*." ) + GedaPcbFootprintLibFileExtension;
 
-    // wxFileName construction is egregiously slow.  Construct it once and just swap out
-    // the filename.
-    WX_FILENAME fn( aLibPath, wxT( "dummy." ) + GedaPcbFootprintLibFileExtension );
-
-    WX_DIR dir( aLibPath );
-
-    if( dir.IsOpened() )
-    {
-        wxString fpFileName;
-        wxString wildcard = wxT( "*." ) + GedaPcbFootprintLibFileExtension;
-
-        if( dir.GetFirst( &fpFileName, wildcard ) )
-        {
-            do
-            {
-                fn.SetFullName( fpFileName );
-                files_timestamp += fn.GetTimestamp();
-            } while( dir.GetNext( &fpFileName ) );
-        }
-    }
-
-    return files_timestamp;
+    return TimestampDir( aLibPath, fileSpec );
 }
 
 
diff --git a/pcbnew/kicad_plugin.cpp b/pcbnew/kicad_plugin.cpp
index 13bad6dcd9..9b66fb4c6c 100644
--- a/pcbnew/kicad_plugin.cpp
+++ b/pcbnew/kicad_plugin.cpp
@@ -85,24 +85,21 @@ void filterNetClass( const BOARD& aBoard, NETCLASS& aNetClass )
  */
 class FP_CACHE_ITEM
 {
-    wxFileName              m_file_name; ///< The the full file name and path of the footprint to cache.
+    WX_FILENAME             m_filename;
     std::unique_ptr<MODULE> m_module;
 
 public:
-    FP_CACHE_ITEM( MODULE* aModule, const wxFileName& aFileName );
+    FP_CACHE_ITEM( MODULE* aModule, const WX_FILENAME& aFileName );
 
-    const wxString&   GetName() const { return m_file_name.GetDirs().Last(); }
-    const wxFileName& GetFileName() const { return m_file_name; }
-
-    const MODULE*     GetModule() const { return m_module.get(); }
+    const WX_FILENAME& GetFileName() const { return m_filename; }
+    const MODULE*      GetModule()   const { return m_module.get(); }
 };
 
 
-FP_CACHE_ITEM::FP_CACHE_ITEM( MODULE* aModule, const wxFileName& aFileName ) :
+FP_CACHE_ITEM::FP_CACHE_ITEM( MODULE* aModule, const WX_FILENAME& aFileName ) :
+    m_filename( aFileName ),
     m_module( aModule )
-{
-    m_file_name = aFileName;
-}
+{ }
 
 
 typedef boost::ptr_map< wxString, FP_CACHE_ITEM >   MODULE_MAP;
@@ -207,11 +204,11 @@ void FP_CACHE::Save( MODULE* aModule )
         if( aModule && aModule != it->second->GetModule() )
             continue;
 
-        wxFileName fn = it->second->GetFileName();
+        WX_FILENAME fn = it->second->GetFileName();
 
         wxString tempFileName =
 #ifdef USE_TMP_FILE
-        fn.CreateTempFileName( fn.GetPath() );
+        wxFileName::CreateTempFileName( fn.GetPath() );
 #else
         fn.GetFullPath();
 #endif
@@ -244,7 +241,7 @@ void FP_CACHE::Save( MODULE* aModule )
             THROW_IO_ERROR( msg );
         }
 #endif
-        m_cache_timestamp += fn.GetModificationTime().GetValue().GetValue();
+        m_cache_timestamp += fn.GetTimestamp();
     }
 
     m_cache_timestamp += m_lib_path.GetModificationTime().GetValue().GetValue();
@@ -260,7 +257,7 @@ void FP_CACHE::Load()
     m_cache_dirty = false;
     m_cache_timestamp = 0;
 
-    WX_DIR dir( m_lib_raw_path );
+    wxDir dir( m_lib_raw_path );
 
     if( !dir.IsOpened() )
     {
@@ -269,20 +266,20 @@ void FP_CACHE::Load()
         THROW_IO_ERROR( msg );
     }
 
-    wxString fpFileName;
-    wxString wildcard = wxT( "*." ) + KiCadFootprintFileExtension;
+    wxString fullName;
+    wxString fileSpec = wxT( "*." ) + KiCadFootprintFileExtension;
 
     // wxFileName construction is egregiously slow.  Construct it once and just swap out
-    // the filename.
-    WX_FILENAME fn( m_lib_raw_path, wxT( "dummy." ) + KiCadFootprintFileExtension );
+    // the filename thereafter.
+    WX_FILENAME fn( m_lib_raw_path, wxT( "dummyName" ) );
 
-    if( dir.GetFirst( &fpFileName, wildcard ) )
+    if( dir.GetFirst( &fullName, fileSpec ) )
     {
         wxString cacheError;
 
         do
         {
-            fn.SetFullName( fpFileName );
+            fn.SetFullName( fullName );
 
             // Queue I/O errors so only files that fail to parse don't get loaded.
             try
@@ -292,8 +289,6 @@ void FP_CACHE::Load()
                 m_owner->m_parser->SetLineReader( &reader );
 
                 MODULE*     footprint = (MODULE*) m_owner->m_parser->Parse();
-
-                // The footprint name is the file name without the extension.
                 wxString    fpName = fn.GetName();
 
                 footprint->SetFPID( LIB_ID( wxEmptyString, fpName ) );
@@ -308,7 +303,7 @@ void FP_CACHE::Load()
 
                 cacheError += ioe.What();
             }
-        } while( dir.GetNext( &fpFileName ) );
+        } while( dir.GetNext( &fullName ) );
 
         if( !cacheError.IsEmpty() )
             THROW_IO_ERROR( cacheError );
@@ -351,30 +346,9 @@ bool FP_CACHE::IsModified()
 
 long long FP_CACHE::GetTimestamp( const wxString& aLibPath )
 {
-    long long files_timestamp = 0;
+    wxString fileSpec = wxT( "*." ) + KiCadFootprintFileExtension;
 
-    // wxFileName construction is egregiously slow.  Construct it once and just
-    // swap out the filename for each file.
-    WX_FILENAME fn( aLibPath, wxT( "dummy." ) + KiCadFootprintFileExtension );
-
-    WX_DIR dir( aLibPath );
-
-    if( dir.IsOpened() )
-    {
-        wxString fpFileName;
-        wxString wildcard = wxT( "*." ) + KiCadFootprintFileExtension;
-
-        if( dir.GetFirst( &fpFileName, wildcard ) )
-        {
-            do
-            {
-                fn.SetFullName( fpFileName );
-                files_timestamp += fn.GetTimestamp();
-            } while( dir.GetNext( &fpFileName ) );
-        }
-    }
-
-    return files_timestamp;
+    return TimestampDir( aLibPath, fileSpec );
 }
 
 
@@ -2120,14 +2094,15 @@ void PCB_IO::FootprintSave( const wxString& aLibraryPath, const MODULE* aFootpri
                                           fn.GetFullPath() ) );
     }
 
+    wxString fullPath = fn.GetFullPath();
+    wxString fullName = fn.GetFullName();
     MODULE_CITER it = mods.find( footprintName );
 
     if( it != mods.end() )
     {
-        wxLogTrace( traceKicadPcbPlugin, wxT( "Removing footprint library file '%s'." ),
-                    fn.GetFullPath().GetData() );
+        wxLogTrace( traceKicadPcbPlugin, wxT( "Removing footprint file '%s'." ), fullPath );
         mods.erase( footprintName );
-        wxRemoveFile( fn.GetFullPath() );
+        wxRemoveFile( fullPath );
     }
 
     // I need my own copy for the cache
@@ -2142,9 +2117,8 @@ void PCB_IO::FootprintSave( const wxString& aLibraryPath, const MODULE* aFootpri
     if( module->GetLayer() != F_Cu )
         module->Flip( module->GetPosition() );
 
-    wxLogTrace( traceKicadPcbPlugin, wxT( "Creating s-expression footprint file: %s." ),
-                fn.GetFullPath().GetData() );
-    mods.insert( footprintName, new FP_CACHE_ITEM( module, fn ) );
+    wxLogTrace( traceKicadPcbPlugin, wxT( "Creating s-expr footprint file '%s'." ), fullPath );
+    mods.insert( footprintName, new FP_CACHE_ITEM( module, WX_FILENAME( fullPath, fullName ) ) );
     m_cache->Save( module );
 }
 
diff --git a/pcbnew/load_select_footprint.cpp b/pcbnew/load_select_footprint.cpp
index 455f79009b..ce9e481059 100644
--- a/pcbnew/load_select_footprint.cpp
+++ b/pcbnew/load_select_footprint.cpp
@@ -207,7 +207,7 @@ MODULE* PCB_BASE_FRAME::SelectFootprintFromLibTree( bool aAllowBrowser )
     for( auto const& item : s_ModuleHistoryList )
         historyInfos.push_back( GFootprintList.GetModuleInfo( item ) );
 
-    adapter->DoAddLibrary( "-- " + _( "Recently Used" ) + " --", wxEmptyString, historyInfos );
+    adapter->DoAddLibrary( "-- " + _( "Recently Used" ) + " --", wxEmptyString, historyInfos, true );
 
     if( !historyInfos.empty() )
         adapter->SetPreselectNode( historyInfos[0]->GetLibId(), 0 );
diff --git a/pcbnew/pcb_layer_widget.cpp b/pcbnew/pcb_layer_widget.cpp
index 70a97cc190..826b1aa6d5 100644
--- a/pcbnew/pcb_layer_widget.cpp
+++ b/pcbnew/pcb_layer_widget.cpp
@@ -113,7 +113,6 @@ PCB_LAYER_WIDGET::PCB_LAYER_WIDGET( PCB_BASE_FRAME* aParent, wxWindow* aFocusOwn
 {
     m_alwaysShowActiveCopperLayer = false;
     m_fp_editor_mode = aFpEditorMode;
-    ReFillRender();
 
     // Update default tabs labels
     SetLayersManagerTabsText();