From 879a8f4efbd37c5ef2c5f6e0c020759a017f78a0 Mon Sep 17 00:00:00 2001
From: Ian McInerney <Ian.S.McInerney@ieee.org>
Date: Tue, 25 Feb 2020 15:46:56 +0000
Subject: [PATCH] Rework the file history menus to not need references to the
 file history

Keeping a pointer to the actual file history inside a special
file history menu led to many cases of use after free crashes,
so instead rework the actual file history to add the menu
items.

Fixes https://gitlab.com/kicad/code/kicad/issues/3741
---
 common/bin_mod.cpp                    |   2 +-
 common/eda_base_frame.cpp             |   9 ++
 common/filehistory.cpp                | 148 +++++++++++++-------------
 eeschema/menubar.cpp                  |  15 +--
 eeschema/sch_edit_frame.cpp           |  13 +--
 eeschema/sch_edit_frame.h             |   2 +
 gerbview/events_called_functions.cpp  |   4 +
 gerbview/files.cpp                    |  22 ++++
 gerbview/gerbview_frame.cpp           |  21 ++--
 gerbview/gerbview_frame.h             |   5 +
 gerbview/gerbview_id.h                |   3 +
 gerbview/menubar.cpp                  |  38 ++++---
 include/eda_base_frame.h              |   8 ++
 include/filehistory.h                 | 106 ++++++++++--------
 include/id.h                          |   1 +
 kicad/files-io.cpp                    |   6 ++
 kicad/kicad_manager_frame.cpp         |   7 +-
 kicad/kicad_manager_frame.h           |   3 +-
 kicad/menubar.cpp                     |  14 ++-
 pagelayout_editor/files.cpp           |   6 ++
 pagelayout_editor/menubar.cpp         |   9 +-
 pagelayout_editor/pl_editor_frame.cpp |   7 +-
 pagelayout_editor/pl_editor_frame.h   |   1 +
 pcbnew/files.cpp                      |   5 +
 pcbnew/menubar_pcb_editor.cpp         |   7 +-
 pcbnew/pcb_edit_frame.cpp             |   7 +-
 pcbnew/pcb_edit_frame.h               |   1 +
 27 files changed, 283 insertions(+), 187 deletions(-)

diff --git a/common/bin_mod.cpp b/common/bin_mod.cpp
index 84dfc9bff5..a989c1587b 100644
--- a/common/bin_mod.cpp
+++ b/common/bin_mod.cpp
@@ -45,7 +45,7 @@ void BIN_MOD::Init()
     // get file history size from common settings
     int fileHistorySize = Pgm().GetCommonSettings()->m_System.file_history_size;
 
-    m_history = new FILE_HISTORY( (unsigned) std::max( 0, fileHistorySize ), ID_FILE1 );
+    m_history = new FILE_HISTORY( (unsigned) std::max( 0, fileHistorySize ), ID_FILE1, ID_FILE_LIST_CLEAR );
     m_history->Load( *m_config );
 
     // Prepare On Line Help. Use only lower case for help file names, in order to
diff --git a/common/eda_base_frame.cpp b/common/eda_base_frame.cpp
index 5a3404cc71..179517534d 100644
--- a/common/eda_base_frame.cpp
+++ b/common/eda_base_frame.cpp
@@ -640,6 +640,15 @@ wxString EDA_BASE_FRAME::GetFileFromHistory( int cmdId, const wxString& type,
 }
 
 
+void EDA_BASE_FRAME::ClearFileHistory( FILE_HISTORY* aFileHistory )
+{
+    if( !aFileHistory )
+        aFileHistory = &Kiface().GetFileHistory();
+
+    aFileHistory->ClearFileHistory();
+}
+
+
 void EDA_BASE_FRAME::OnKicadAbout( wxCommandEvent& event )
 {
     void ShowAboutDialog(EDA_BASE_FRAME * aParent); // See AboutDialog_main.cpp
diff --git a/common/filehistory.cpp b/common/filehistory.cpp
index 24526b831a..7549e288cc 100644
--- a/common/filehistory.cpp
+++ b/common/filehistory.cpp
@@ -33,8 +33,10 @@
 using namespace std::placeholders;
 
 
-FILE_HISTORY::FILE_HISTORY( size_t aMaxFiles, int aBaseFileId ) :
-        wxFileHistory( std::min( aMaxFiles, (size_t) MAX_FILE_HISTORY_SIZE ) )
+FILE_HISTORY::FILE_HISTORY( size_t aMaxFiles, int aBaseFileId, int aClearId, wxString aClearText )
+        : wxFileHistory( std::min( aMaxFiles, (size_t) MAX_FILE_HISTORY_SIZE ) ),
+          m_clearId( aClearId ),
+          m_clearText( aClearText )
 {
     SetBaseId( aBaseFileId );
 }
@@ -91,21 +93,80 @@ void FILE_HISTORY::SetMaxFiles( size_t aMaxFiles )
 
 void FILE_HISTORY::AddFileToHistory( const wxString &aFile )
 {
-    wxFileHistory::AddFileToHistory( aFile );
-
-    // Iterate over each menu associated with this file history, and if it is one of our
-    // FILE_HISTORY_MENUs, we force it to be refreshed (so that the items are all in the
-    // correct locations).
+    // Iterate over each menu removing our custom items
     for( wxList::compatibility_iterator node = m_fileMenus.GetFirst();
             node; node = node->GetNext() )
     {
         wxMenu* menu = static_cast<wxMenu*>( node->GetData() );
-
-        FILE_HISTORY_MENU* fileMenu = dynamic_cast<FILE_HISTORY_MENU*>( menu );
-
-        if( fileMenu )
-            fileMenu->RefreshMenu();
+        doRemoveClearitem( menu );
     }
+
+    // Let wx add the items in the file history
+    wxFileHistory::AddFileToHistory( aFile );
+
+    // Add our custom items back
+    for( wxList::compatibility_iterator node = m_fileMenus.GetFirst();
+            node; node = node->GetNext() )
+    {
+        wxMenu* menu = static_cast<wxMenu*>( node->GetData() );
+        doAddClearItem( menu );
+    }
+}
+
+
+void FILE_HISTORY::AddFilesToMenu( wxMenu* aMenu )
+{
+    doRemoveClearitem( aMenu );
+    wxFileHistory::AddFilesToMenu( aMenu );
+    doAddClearItem( aMenu );
+}
+
+
+void FILE_HISTORY::doRemoveClearitem( wxMenu* aMenu )
+{
+    size_t      itemPos;
+    wxMenuItem* clearItem = aMenu->FindChildItem( m_clearId, &itemPos );
+
+    // Remove the separator if there is one
+    if( clearItem && itemPos > 1 )
+    {
+        wxMenuItem* sepItem = aMenu->FindItemByPosition( itemPos - 1 );
+
+        if( sepItem )
+            aMenu->Destroy( sepItem );
+    }
+
+    // Remove the clear and placeholder menu items
+    if( clearItem )
+        aMenu->Destroy( m_clearId );
+
+    if( aMenu->FindChildItem( ID_FILE_LIST_EMPTY ) )
+        aMenu->Destroy( ID_FILE_LIST_EMPTY );
+}
+
+
+void FILE_HISTORY::doAddClearItem( wxMenu* aMenu )
+{
+    if( GetCount() == 0 )
+    {
+        // If the history is empty, we create an item to say there are no files
+        wxMenuItem* item = new wxMenuItem( nullptr, ID_FILE_LIST_EMPTY, _( "No Files" ) );
+
+        aMenu->Append( item );
+        aMenu->Enable( item->GetId(), false );
+    }
+
+    wxMenuItem* clearItem = new wxMenuItem( nullptr, m_clearId, m_clearText );
+
+    aMenu->AppendSeparator();
+    aMenu->Append( clearItem );
+}
+
+
+void FILE_HISTORY::ClearFileHistory()
+{
+    while( GetCount() > 0 )
+        RemoveFileFromHistory( 0 );
 }
 
 
@@ -119,66 +180,3 @@ bool FILE_HISTORY::isHistoryNotEmpty( const SELECTION& aSelection, const FILE_HI
 {
     return aHistory.GetCount() != 0;
 }
-
-
-FILE_HISTORY_MENU::FILE_HISTORY_MENU( FILE_HISTORY& aHistory, wxString aClearText ) :
-    ACTION_MENU( false ),
-    m_fileHistory( aHistory ),
-    m_clearText( aClearText )
-{
-    m_fileHistory.UseMenu( this );
-    buildMenu();
-}
-
-
-FILE_HISTORY_MENU::~FILE_HISTORY_MENU()
-{
-    m_fileHistory.RemoveMenu( this );
-}
-
-
-void FILE_HISTORY_MENU::RefreshMenu()
-{
-    // We have to manually delete all menu items before we rebuild the menu
-    for( int i = GetMenuItemCount() - 1; i >= 0; --i )
-        Destroy( FindItemByPosition( i ) );
-
-    buildMenu();
-}
-
-
-void FILE_HISTORY_MENU::buildMenu()
-{
-    if( m_fileHistory.GetCount() == 0 )
-    {
-        // If the history is empty, we create an item to say there are no files
-        wxMenuItem* item = new wxMenuItem( this, wxID_ANY, _( "No Files" ) );
-
-        Append( item );
-        Enable( item->GetId(), false );
-    }
-    else
-        m_fileHistory.AddFilesToMenu( this );
-
-    wxMenuItem* clearItem = new wxMenuItem( this, ID_FILE_LIST_CLEAR, m_clearText );
-
-    AppendSeparator();
-    Append( clearItem );
-    Connect( ID_FILE_LIST_CLEAR, wxEVT_COMMAND_MENU_SELECTED,
-            wxMenuEventHandler( FILE_HISTORY_MENU::onClearEntries ), NULL, this );
-}
-
-
-void FILE_HISTORY_MENU::onClearEntries( wxMenuEvent& aEvent )
-{
-    while( m_fileHistory.GetCount() > 0 )
-        m_fileHistory.RemoveFileFromHistory( 0 );
-
-    RefreshMenu();
-}
-
-
-ACTION_MENU* FILE_HISTORY_MENU::create() const
-{
-    return new FILE_HISTORY_MENU( m_fileHistory, m_clearText );
-}
diff --git a/eeschema/menubar.cpp b/eeschema/menubar.cpp
index 49cca1e561..57af3bb498 100644
--- a/eeschema/menubar.cpp
+++ b/eeschema/menubar.cpp
@@ -53,21 +53,24 @@ void SCH_EDIT_FRAME::ReCreateMenuBar()
 
     //-- File menu -----------------------------------------------------------
     //
-    CONDITIONAL_MENU*         fileMenu = new CONDITIONAL_MENU( false, selTool );
-    static FILE_HISTORY_MENU* openRecentMenu;
+    CONDITIONAL_MENU*   fileMenu = new CONDITIONAL_MENU( false, selTool );
+    static ACTION_MENU* openRecentMenu;
 
-    if( Kiface().IsSingle() )   // not when under a project mgr
+    if( Kiface().IsSingle() )   // When not under a project mgr
     {
         FILE_HISTORY& fileHistory = Kiface().GetFileHistory();
 
-        // Create the menu if it does not exist. Adding a file to/from the history
-        // will automatically refresh the menu.
+        // Add this menu to the list of menus managed by the file history
+        // (the file history will be updated when adding/removing files in history)
         if( !openRecentMenu )
         {
-            openRecentMenu = new FILE_HISTORY_MENU( fileHistory );
+            openRecentMenu = new ACTION_MENU( false );
             openRecentMenu->SetTool( selTool );
             openRecentMenu->SetTitle( _( "Open Recent" ) );
             openRecentMenu->SetIcon( recent_xpm );
+
+            fileHistory.UseMenu( openRecentMenu );
+            fileHistory.AddFilesToMenu( openRecentMenu );
         }
 
         fileMenu->AddItem( ACTIONS::doNew,         EE_CONDITIONS::ShowAlways );
diff --git a/eeschema/sch_edit_frame.cpp b/eeschema/sch_edit_frame.cpp
index 047b8f663e..4f4834649a 100644
--- a/eeschema/sch_edit_frame.cpp
+++ b/eeschema/sch_edit_frame.cpp
@@ -191,6 +191,7 @@ BEGIN_EVENT_TABLE( SCH_EDIT_FRAME, EDA_DRAW_FRAME )
     EVT_SIZE( SCH_EDIT_FRAME::OnSize )
 
     EVT_MENU_RANGE( ID_FILE1, ID_FILEMAX, SCH_EDIT_FRAME::OnLoadFile )
+    EVT_MENU( ID_FILE_LIST_CLEAR, SCH_EDIT_FRAME::OnClearFileHistory )
 
     EVT_MENU( ID_APPEND_PROJECT, SCH_EDIT_FRAME::OnAppendProject )
     EVT_MENU( ID_IMPORT_NON_KICAD_SCH, SCH_EDIT_FRAME::OnImportProject )
@@ -306,12 +307,6 @@ SCH_EDIT_FRAME::~SCH_EDIT_FRAME()
     g_CurrentSheet = nullptr;
     g_ConnectionGraph = nullptr;
     g_RootSheet = NULL;
-
-    // Since the file menu contains file history menus, we must ensure that the menu
-    // destructor is called before the file history objects are deleted since their destructor
-    // unregisters the menu from the history.
-    wxMenu* fileMenu = GetMenuBar()->Remove( 0 );
-    delete fileMenu;
 }
 
 
@@ -752,6 +747,12 @@ void SCH_EDIT_FRAME::OnLoadFile( wxCommandEvent& event )
 }
 
 
+void SCH_EDIT_FRAME::OnClearFileHistory( wxCommandEvent& aEvent )
+{
+    ClearFileHistory();
+}
+
+
 void SCH_EDIT_FRAME::NewProject()
 {
     wxString pro_dir = m_mruPath;
diff --git a/eeschema/sch_edit_frame.h b/eeschema/sch_edit_frame.h
index c4e4519546..4576278697 100644
--- a/eeschema/sch_edit_frame.h
+++ b/eeschema/sch_edit_frame.h
@@ -716,6 +716,8 @@ private:
     void OnAppendProject( wxCommandEvent& event );
     void OnImportProject( wxCommandEvent& event );
 
+    void OnClearFileHistory( wxCommandEvent& aEvent );
+
     /**
      * Set the main window title bar text.
      *
diff --git a/gerbview/events_called_functions.cpp b/gerbview/events_called_functions.cpp
index 55e3e40b7b..ca83dac9ca 100644
--- a/gerbview/events_called_functions.cpp
+++ b/gerbview/events_called_functions.cpp
@@ -62,15 +62,19 @@ BEGIN_EVENT_TABLE( GERBVIEW_FRAME, EDA_DRAW_FRAME )
     EVT_MENU( ID_GERBVIEW_EXPORT_TO_PCBNEW, GERBVIEW_FRAME::ExportDataInPcbnewFormat )
 
     EVT_MENU_RANGE( ID_FILE1, ID_FILEMAX, GERBVIEW_FRAME::OnGbrFileHistory )
+    EVT_MENU( ID_FILE_LIST_CLEAR, GERBVIEW_FRAME::OnClearGbrFileHistory )
 
     EVT_MENU_RANGE( ID_GERBVIEW_DRILL_FILE1, ID_GERBVIEW_DRILL_FILEMAX,
                     GERBVIEW_FRAME::OnDrlFileHistory )
+    EVT_MENU( ID_GERBVIEW_DRILL_FILE_LIST_CLEAR, GERBVIEW_FRAME::OnClearDrlFileHistory )
 
     EVT_MENU_RANGE( ID_GERBVIEW_ZIP_FILE1, ID_GERBVIEW_ZIP_FILEMAX,
                     GERBVIEW_FRAME::OnZipFileHistory )
+    EVT_MENU( ID_GERBVIEW_ZIP_FILE_LIST_CLEAR, GERBVIEW_FRAME::OnClearZipFileHistory )
 
     EVT_MENU_RANGE( ID_GERBVIEW_JOB_FILE1, ID_GERBVIEW_JOB_FILEMAX,
                     GERBVIEW_FRAME::OnJobFileHistory )
+    EVT_MENU( ID_GERBVIEW_JOB_FILE_LIST_CLEAR, GERBVIEW_FRAME::OnClearJobFileHistory )
 
     EVT_MENU( wxID_EXIT, GERBVIEW_FRAME::OnQuit )
 
diff --git a/gerbview/files.cpp b/gerbview/files.cpp
index 5b1f051f6f..ae9f9674e1 100644
--- a/gerbview/files.cpp
+++ b/gerbview/files.cpp
@@ -57,6 +57,11 @@ void GERBVIEW_FRAME::OnGbrFileHistory( wxCommandEvent& event )
     }
 }
 
+void GERBVIEW_FRAME::OnClearGbrFileHistory( wxCommandEvent& aEvent )
+{
+    ClearFileHistory();
+}
+
 
 void GERBVIEW_FRAME::OnDrlFileHistory( wxCommandEvent& event )
 {
@@ -72,6 +77,12 @@ void GERBVIEW_FRAME::OnDrlFileHistory( wxCommandEvent& event )
 }
 
 
+void GERBVIEW_FRAME::OnClearDrlFileHistory( wxCommandEvent& aEvent )
+{
+    ClearFileHistory( &m_drillFileHistory );
+}
+
+
 void GERBVIEW_FRAME::OnZipFileHistory( wxCommandEvent& event )
 {
     wxString filename;
@@ -85,6 +96,12 @@ void GERBVIEW_FRAME::OnZipFileHistory( wxCommandEvent& event )
 }
 
 
+void GERBVIEW_FRAME::OnClearZipFileHistory( wxCommandEvent& aEvent )
+{
+    ClearFileHistory( &m_zipFileHistory );
+}
+
+
 void GERBVIEW_FRAME::OnJobFileHistory( wxCommandEvent& event )
 {
     wxString filename = GetFileFromHistory( event.GetId(), _( "Job files" ), &m_jobFileHistory );
@@ -94,6 +111,11 @@ void GERBVIEW_FRAME::OnJobFileHistory( wxCommandEvent& event )
 }
 
 
+void GERBVIEW_FRAME::OnClearJobFileHistory( wxCommandEvent& aEvent )
+{
+    ClearFileHistory( &m_jobFileHistory );
+}
+
 /* File commands. */
 void GERBVIEW_FRAME::Files_io( wxCommandEvent& event )
 {
diff --git a/gerbview/gerbview_frame.cpp b/gerbview/gerbview_frame.cpp
index a89243510c..3081f21c1c 100644
--- a/gerbview/gerbview_frame.cpp
+++ b/gerbview/gerbview_frame.cpp
@@ -58,12 +58,15 @@
 #include <panel_hotkeys_editor.h>
 
 
-GERBVIEW_FRAME::GERBVIEW_FRAME( KIWAY* aKiway, wxWindow* aParent ):
-    EDA_DRAW_FRAME( aKiway, aParent, FRAME_GERBER, wxT( "GerbView" ),
-        wxDefaultPosition, wxDefaultSize, KICAD_DEFAULT_DRAWFRAME_STYLE, GERBVIEW_FRAME_NAME ),
-    m_zipFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_ZIP_FILE1 ),
-    m_drillFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_DRILL_FILE1 ),
-    m_jobFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_JOB_FILE1 )
+GERBVIEW_FRAME::GERBVIEW_FRAME( KIWAY* aKiway, wxWindow* aParent )
+        : EDA_DRAW_FRAME( aKiway, aParent, FRAME_GERBER, wxT( "GerbView" ), wxDefaultPosition,
+                wxDefaultSize, KICAD_DEFAULT_DRAWFRAME_STYLE, GERBVIEW_FRAME_NAME ),
+          m_zipFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_ZIP_FILE1,
+                  ID_GERBVIEW_ZIP_FILE_LIST_CLEAR, _( "Clear Recent Zip Files" ) ),
+          m_drillFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_DRILL_FILE1,
+                  ID_GERBVIEW_DRILL_FILE_LIST_CLEAR, _( "Clear Recent Drill Files" ) ),
+          m_jobFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_JOB_FILE1,
+                  ID_GERBVIEW_JOB_FILE_LIST_CLEAR, _( "Clear Recent Job Files" ) )
 {
     m_gerberLayout = NULL;
     m_zoomLevelCoeff = ZOOM_FACTOR( 110 );   // Adjusted to roughly displays zoom level = 1
@@ -225,12 +228,6 @@ GERBVIEW_FRAME::~GERBVIEW_FRAME()
 
     GetGerberLayout()->GetImagesList()->DeleteAllImages();
     delete m_gerberLayout;
-
-    // Since the file menu contains file history menus, we must ensure that the menu
-    // destructor is called before the file history objects are deleted since their destructor
-    // unregisters the menu from the history.
-    wxMenu* fileMenu = GetMenuBar()->Remove( 0 );
-    delete fileMenu;
 }
 
 
diff --git a/gerbview/gerbview_frame.h b/gerbview/gerbview_frame.h
index 9f66822da3..625c91495a 100644
--- a/gerbview/gerbview_frame.h
+++ b/gerbview/gerbview_frame.h
@@ -187,6 +187,11 @@ private:
     void            updateZoomSelectBox();
     void            unitsChangeRefresh() override;      // See class EDA_DRAW_FRAME
 
+    void OnClearJobFileHistory( wxCommandEvent& aEvent );
+    void OnClearZipFileHistory( wxCommandEvent& aEvent );
+    void OnClearDrlFileHistory( wxCommandEvent& aEvent );
+    void OnClearGbrFileHistory( wxCommandEvent& aEvent );
+
     // The Tool Framework initalization
     void setupTools();
 
diff --git a/gerbview/gerbview_id.h b/gerbview/gerbview_id.h
index 7a0cde6cce..56dcbdf716 100644
--- a/gerbview/gerbview_id.h
+++ b/gerbview/gerbview_id.h
@@ -72,16 +72,19 @@ enum gerbview_ids
     ID_GERBVIEW_DRILL_FILE,
     ID_GERBVIEW_DRILL_FILE1,
     ID_GERBVIEW_DRILL_FILEMAX = ID_GERBVIEW_DRILL_FILE + MAX_FILE_HISTORY_SIZE,
+    ID_GERBVIEW_DRILL_FILE_LIST_CLEAR,
 
     // IDs for job file history (ID_FILEnn is already in use)
     ID_GERBVIEW_JOB_FILE,
     ID_GERBVIEW_JOB_FILE1,
     ID_GERBVIEW_JOB_FILEMAX = ID_GERBVIEW_JOB_FILE + MAX_FILE_HISTORY_SIZE,
+    ID_GERBVIEW_JOB_FILE_LIST_CLEAR,
 
     // IDs for zip file history (ID_FILEnn is already in use)
     ID_GERBVIEW_ZIP_FILE,
     ID_GERBVIEW_ZIP_FILE1,
     ID_GERBVIEW_ZIP_FILEMAX = ID_GERBVIEW_ZIP_FILE + MAX_FILE_HISTORY_SIZE,
+    ID_GERBVIEW_ZIP_FILE_LIST_CLEAR,
 
     ID_GERBER_END_LIST
 };
diff --git a/gerbview/menubar.cpp b/gerbview/menubar.cpp
index 926573dd94..b177284b24 100644
--- a/gerbview/menubar.cpp
+++ b/gerbview/menubar.cpp
@@ -46,56 +46,66 @@ void GERBVIEW_FRAME::ReCreateMenuBar()
 
     //-- File menu -------------------------------------------------------
     //
-    CONDITIONAL_MENU*         fileMenu = new CONDITIONAL_MENU( false, selTool );
-    static FILE_HISTORY_MENU* openRecentGbrMenu;
-    static FILE_HISTORY_MENU* openRecentDrlMenu;
-    static FILE_HISTORY_MENU* openRecentJobMenu;
-    static FILE_HISTORY_MENU* openRecentZipMenu;
-    FILE_HISTORY&             recentGbrFiles = Kiface().GetFileHistory();
+    CONDITIONAL_MENU*   fileMenu = new CONDITIONAL_MENU( false, selTool );
+    static ACTION_MENU* openRecentGbrMenu;
+    static ACTION_MENU* openRecentDrlMenu;
+    static ACTION_MENU* openRecentJobMenu;
+    static ACTION_MENU* openRecentZipMenu;
+
+    FILE_HISTORY& recentGbrFiles = Kiface().GetFileHistory();
+    recentGbrFiles.SetClearText( _( "Clear Recent Gerber Files" ) );
 
 
     // Create the gerber file menu if it does not exist. Adding a file to/from the history
     // will automatically refresh the menu.
     if( !openRecentGbrMenu )
     {
-        openRecentGbrMenu =
-                new FILE_HISTORY_MENU( recentGbrFiles, _( "Clear Recent Gerber Files" ) );
+        openRecentGbrMenu = new ACTION_MENU( false );
         openRecentGbrMenu->SetTool( selTool );
         openRecentGbrMenu->SetTitle( _( "Open Recent Gerber File" ) );
         openRecentGbrMenu->SetIcon( recent_xpm );
+
+        recentGbrFiles.UseMenu( openRecentGbrMenu );
+        recentGbrFiles.AddFilesToMenu();
     }
 
     // Create the drill file menu if it does not exist. Adding a file to/from the history
     // will automatically refresh the menu.
     if( !openRecentDrlMenu )
     {
-        openRecentDrlMenu =
-                new FILE_HISTORY_MENU( m_drillFileHistory, _( "Clear Recent Drill Files" ) );
+        openRecentDrlMenu = new ACTION_MENU( false );
         openRecentDrlMenu->SetTool( selTool );
         openRecentDrlMenu->SetTitle( _( "Open Recent Drill File" ) );
         openRecentDrlMenu->SetIcon( recent_xpm );
+
+        m_drillFileHistory.UseMenu( openRecentDrlMenu );
+        m_drillFileHistory.AddFilesToMenu();
     }
 
     // Create the job file menu if it does not exist. Adding a file to/from the history
     // will automatically refresh the menu.
     if( !openRecentJobMenu )
     {
-        openRecentJobMenu =
-                new FILE_HISTORY_MENU( m_jobFileHistory, _( "Clear Recent Job Files" ) );
+        openRecentJobMenu = new ACTION_MENU( false );
         openRecentJobMenu->SetTool( selTool );
         openRecentJobMenu->SetTitle( _( "Open Recent Job File" ) );
         openRecentJobMenu->SetIcon( recent_xpm );
+
+        m_jobFileHistory.UseMenu( openRecentJobMenu );
+        m_jobFileHistory.AddFilesToMenu();
     }
 
     // Create the zip file menu if it does not exist. Adding a file to/from the history
     // will automatically refresh the menu.
     if( !openRecentZipMenu )
     {
-        openRecentZipMenu =
-                new FILE_HISTORY_MENU( m_zipFileHistory, _( "Clear Recent Zip Files" ) );
+        openRecentZipMenu = new ACTION_MENU( false );
         openRecentZipMenu->SetTool( selTool );
         openRecentZipMenu->SetTitle( _( "Open Recent Zip File" ) );
         openRecentZipMenu->SetIcon( recent_xpm );
+
+        m_zipFileHistory.UseMenu( openRecentZipMenu );
+        m_zipFileHistory.AddFilesToMenu();
     }
 
     fileMenu->AddItem( wxID_FILE, _( "Open &Gerber File(s)..." ),
diff --git a/include/eda_base_frame.h b/include/eda_base_frame.h
index c0b62d6e7e..737e5559d7 100644
--- a/include/eda_base_frame.h
+++ b/include/eda_base_frame.h
@@ -415,6 +415,14 @@ public:
     wxString GetFileFromHistory( int cmdId, const wxString& type,
                                  FILE_HISTORY* aFileHistory = NULL );
 
+    /**
+     * Removes all files from the file history.
+     *
+     * @param aFileHistory The FILE_HISTORY in use. If null, the main application file
+     *                     history is used
+     */
+    void ClearFileHistory( FILE_HISTORY* aFileHistory = NULL );
+
     /**
      * Update the list of recently opened files.
      *
diff --git a/include/filehistory.h b/include/filehistory.h
index 643ecf778e..cc22bc51fd 100644
--- a/include/filehistory.h
+++ b/include/filehistory.h
@@ -47,8 +47,11 @@ public:
      *
      * @param aMaxFiles is the number of files to store in the history
      * @param aBaseFileId is the ID to use for the first file menu item
+     * @param aClearId is the ID to use for the clear menu menu item
+     * @param aClearText is the text to use for the menu item that clears the history.
      */
-    FILE_HISTORY( size_t aMaxFiles, int aBaseFileId );
+    FILE_HISTORY( size_t aMaxFiles, int aBaseFileId, int aClearId,
+            wxString aClearText = _( "Clear Recent Files" ) );
 
     /**
      * Loads history from a JSON settings object
@@ -82,13 +85,29 @@ public:
      * Adds a file to the history.
      *
      * This function overrides the default wxWidgets method to iterate through all
-     * menus associated with the file history, and if they are of the FILE_HISTORY_MENU
-     * type, call their RefreshMenu() function to update the menu display.
+     * menus associated with the file history, remove the added menu items, lets wx
+     * add the new files, and then re-adds the clear menu item.
      *
      * @param aFile is the filename of the file to add to the history.
      */
     void AddFileToHistory( const wxString &aFile ) override;
 
+    /**
+     * Add the files to all registered menus.
+     */
+    void AddFilesToMenu() override
+    {
+        // This is needed to ensure that the proper base class function is called
+        wxFileHistory::AddFilesToMenu();
+    }
+
+    /**
+     * Add the files to the specified menu
+     *
+     * @aMenu is the menu to operate on.
+     */
+    void AddFilesToMenu( wxMenu* aMenu ) override;
+
     /**
      * Update the number of files that will be contained inside the file history.
      *
@@ -96,6 +115,21 @@ public:
      */
     void SetMaxFiles( size_t aMaxFiles );
 
+    /**
+     * Set the text displayed on the menu item that clears the entire menu.
+     *
+     * @param aClearText is the text to use for the menu item
+     */
+    void SetClearText( wxString aClearText )
+    {
+        m_clearText = aClearText;
+    }
+
+    /**
+     * Clear all entries from the file history.
+     */
+    void ClearFileHistory();
+
     /**
      * Create a SELECTION_CONDITION that can be used to enable a menu item when the
      * file history has items in it.
@@ -105,51 +139,33 @@ public:
      */
     static SELECTION_CONDITION FileHistoryNotEmpty( const FILE_HISTORY& aHistory );
 
+protected:
+    /**
+     * Remove the clear menu item and the preceding separator from the given menu.
+     *
+     * @param aMenu is the menu to operate on
+     */
+    void doRemoveClearitem( wxMenu* aMenu );
+
+    /**
+     * Add the clear menu item and the preceding separator to the given menu.
+     *
+     * @param aMenu is the menu to operate on
+     */
+    void doAddClearItem( wxMenu* aMenu );
+
 private:
+    /**
+     * Test if the file history is empty. This function is designed to be used with a SELECTION_CONDITION
+     * to enable/disable the file history menu.
+     *
+     * @param aSelection is unused
+     * @param aHistory is the file history to test for items
+     */
     static bool isHistoryNotEmpty( const SELECTION& aSelection, const FILE_HISTORY& aHistory );
-};
 
-/**
- * This class implements a menu container for a file history. It adds in the ability to clear
- * the file history through a menu item.
- */
-class FILE_HISTORY_MENU : public ACTION_MENU
-{
-public:
-    /**
-     * Create the file history menu.
-     *
-     * @param aHistory is the file history to use in the menu
-     * @param aClearText is the text to use for the menu item that clears the history.
-     */
-    FILE_HISTORY_MENU( FILE_HISTORY& aHistory, wxString aClearText = _( "Clear Recent Files" ) );
-
-    ~FILE_HISTORY_MENU();
-
-    /**
-     * Refresh the menu. This removes all entries from the menu and readds them, to ensure that the
-     * clear menu item is at the bottom of the menu.
-     */
-    void RefreshMenu();
-
-private:
-    //! @copydoc ACTION_MENU::create()
-    ACTION_MENU* create() const override;
-
-    /**
-     * Construct the menu by adding the file history and menu items.
-     */
-    void buildMenu();
-
-    /**
-     * Event handler for when the clear menu item is activated.
-     *
-     * @param aEvent the menu event
-     */
-    void onClearEntries( wxMenuEvent& aEvent );
-
-    FILE_HISTORY& m_fileHistory;
-    wxString      m_clearText;
+    int      m_clearId;
+    wxString m_clearText;
 };
 
 #endif
diff --git a/include/id.h b/include/id.h
index 1029b7edc5..96d0c416f2 100644
--- a/include/id.h
+++ b/include/id.h
@@ -86,6 +86,7 @@ enum main_id
     ID_FILE,
     ID_FILE1,
     ID_FILEMAX = ID_FILE + MAX_FILE_HISTORY_SIZE,
+    ID_FILE_LIST_EMPTY,
     ID_FILE_LIST_CLEAR,
 
     ID_PREFERENCES_HOTKEY_SHOW_CURRENT_LIST,
diff --git a/kicad/files-io.cpp b/kicad/files-io.cpp
index 803dca24d7..3dfd3a632f 100644
--- a/kicad/files-io.cpp
+++ b/kicad/files-io.cpp
@@ -55,6 +55,12 @@ void KICAD_MANAGER_FRAME::OnFileHistory( wxCommandEvent& event )
 }
 
 
+void KICAD_MANAGER_FRAME::OnClearFileHistory( wxCommandEvent& aEvent )
+{
+    ClearFileHistory( &PgmTop().GetFileHistory() );
+}
+
+
 void KICAD_MANAGER_FRAME::OnUnarchiveFiles( wxCommandEvent& event )
 {
     wxFileName fn = GetProjectFileName();
diff --git a/kicad/kicad_manager_frame.cpp b/kicad/kicad_manager_frame.cpp
index 6e41080458..7ef7f5278d 100644
--- a/kicad/kicad_manager_frame.cpp
+++ b/kicad/kicad_manager_frame.cpp
@@ -83,6 +83,7 @@ BEGIN_EVENT_TABLE( KICAD_MANAGER_FRAME, EDA_BASE_FRAME )
                     KICAD_MANAGER_FRAME::language_change )
 
     EVT_MENU_RANGE( ID_FILE1, ID_FILEMAX, KICAD_MANAGER_FRAME::OnFileHistory )
+    EVT_MENU( ID_FILE_LIST_CLEAR, KICAD_MANAGER_FRAME::OnClearFileHistory )
 
     // Special functions
     EVT_MENU( ID_INIT_WATCHED_PATHS, KICAD_MANAGER_FRAME::OnChangeWatchedPaths )
@@ -175,12 +176,6 @@ KICAD_MANAGER_FRAME::~KICAD_MANAGER_FRAME()
     delete m_toolManager;
 
     m_auimgr.UnInit();
-
-    // Since the file menu contains file history menus, we must ensure that the menu
-    // destructor is called before the file history objects are deleted since their destructor
-    // unregisters the menu from the history.
-    wxMenu* fileMenu = GetMenuBar()->Remove( 0 );
-    delete fileMenu;
 }
 
 
diff --git a/kicad/kicad_manager_frame.h b/kicad/kicad_manager_frame.h
index 3b1b398dc4..60b3ad0e2c 100644
--- a/kicad/kicad_manager_frame.h
+++ b/kicad/kicad_manager_frame.h
@@ -89,6 +89,7 @@ public:
     void OnBrowseInFileExplorer( wxCommandEvent& event );
 
     void OnFileHistory( wxCommandEvent& event );
+    void OnClearFileHistory( wxCommandEvent& aEvent );
     void OnExit( wxCommandEvent& event );
 
     void ReCreateMenuBar() override;
@@ -119,7 +120,7 @@ public:
 
     void RefreshProjectTree();
 
-    /** 
+    /**
      * Creates a new project by setting up and initial project, schematic, and board files.
      *
      * The project file is copied from the kicad.pro template file if possible.  Otherwise,
diff --git a/kicad/menubar.cpp b/kicad/menubar.cpp
index 2a12cf5853..875cdc187c 100644
--- a/kicad/menubar.cpp
+++ b/kicad/menubar.cpp
@@ -46,18 +46,24 @@ void KICAD_MANAGER_FRAME::ReCreateMenuBar()
 
     //-- File menu -----------------------------------------------------------
     //
-    CONDITIONAL_MENU*         fileMenu = new CONDITIONAL_MENU( false, controlTool );
-    FILE_HISTORY&             fileHistory = PgmTop().GetFileHistory();
-    static FILE_HISTORY_MENU* openRecentMenu;
+    CONDITIONAL_MENU* fileMenu    = new CONDITIONAL_MENU( false, controlTool );
+    FILE_HISTORY&     fileHistory = PgmTop().GetFileHistory();
+
+    fileHistory.SetClearText( _( "Clear Recent Projects" ) );
+
+    static ACTION_MENU* openRecentMenu;
 
     // Create the menu if it does not exist. Adding a file to/from the history
     // will automatically refresh the menu.
     if( !openRecentMenu )
     {
-        openRecentMenu = new FILE_HISTORY_MENU( fileHistory, _( "Clear Recent Projects" ) );
+        openRecentMenu = new ACTION_MENU( false );
         openRecentMenu->SetTool( controlTool );
         openRecentMenu->SetTitle( _( "Open Recent" ) );
         openRecentMenu->SetIcon( recent_xpm );
+
+        fileHistory.UseMenu( openRecentMenu );
+        fileHistory.AddFilesToMenu();
     }
 
     fileMenu->AddItem( KICAD_MANAGER_ACTIONS::newProject,      SELECTION_CONDITIONS::ShowAlways );
diff --git a/pagelayout_editor/files.cpp b/pagelayout_editor/files.cpp
index 2042eb00d2..185eff7c4d 100644
--- a/pagelayout_editor/files.cpp
+++ b/pagelayout_editor/files.cpp
@@ -79,6 +79,12 @@ void PL_EDITOR_FRAME::OnFileHistory( wxCommandEvent& event )
 }
 
 
+void PL_EDITOR_FRAME::OnClearFileHistory( wxCommandEvent& aEvent )
+{
+    ClearFileHistory();
+}
+
+
 /* File commands. */
 void PL_EDITOR_FRAME::Files_io( wxCommandEvent& event )
 {
diff --git a/pagelayout_editor/menubar.cpp b/pagelayout_editor/menubar.cpp
index 3cd4c4ba76..c86d81548b 100644
--- a/pagelayout_editor/menubar.cpp
+++ b/pagelayout_editor/menubar.cpp
@@ -48,17 +48,20 @@ void PL_EDITOR_FRAME::ReCreateMenuBar()
         return IsContentModified();
     };
 
-    static FILE_HISTORY_MENU* openRecentMenu;  // Open Recent submenu, static to remember this menu
-    FILE_HISTORY&             recentFiles = Kiface().GetFileHistory();
+    static ACTION_MENU* openRecentMenu;  // Open Recent submenu, static to remember this menu
+    FILE_HISTORY&       recentFiles = Kiface().GetFileHistory();
 
     // Create the menu if it does not exist. Adding a file to/from the history
     // will automatically refresh the menu.
     if( !openRecentMenu )
     {
-        openRecentMenu = new FILE_HISTORY_MENU( recentFiles );
+        openRecentMenu = new ACTION_MENU( false );
         openRecentMenu->SetTool( selTool );
         openRecentMenu->SetTitle( _( "Open Recent" ) );
         openRecentMenu->SetIcon( recent_xpm );
+
+        recentFiles.UseMenu( openRecentMenu );
+        recentFiles.AddFilesToMenu();
     }
 
     //-- File menu -------------------------------------------------------
diff --git a/pagelayout_editor/pl_editor_frame.cpp b/pagelayout_editor/pl_editor_frame.cpp
index 79dc97c999..ba001ae9a9 100644
--- a/pagelayout_editor/pl_editor_frame.cpp
+++ b/pagelayout_editor/pl_editor_frame.cpp
@@ -65,6 +65,7 @@ BEGIN_EVENT_TABLE( PL_EDITOR_FRAME, EDA_DRAW_FRAME )
     EVT_MENU( wxID_FILE, PL_EDITOR_FRAME::Files_io )
 
     EVT_MENU_RANGE( ID_FILE1, ID_FILEMAX, PL_EDITOR_FRAME::OnFileHistory )
+    EVT_MENU( ID_FILE_LIST_CLEAR, PL_EDITOR_FRAME::OnClearFileHistory )
 
     EVT_TOOL( ID_SHOW_REAL_MODE, PL_EDITOR_FRAME::OnSelectTitleBlockDisplayMode )
     EVT_TOOL( ID_SHOW_PL_EDITOR_MODE, PL_EDITOR_FRAME::OnSelectTitleBlockDisplayMode )
@@ -204,12 +205,6 @@ PL_EDITOR_FRAME::~PL_EDITOR_FRAME()
     // Shutdown all running tools
     if( m_toolManager )
         m_toolManager->ShutdownAllTools();
-
-    // Since the file menu contains file history menus, we must ensure that the menu
-    // destructor is called before the file history objects are deleted since their destructor
-    // unregisters the menu from the history.
-    wxMenu* fileMenu = GetMenuBar()->Remove( 0 );
-    delete fileMenu;
 }
 
 void PL_EDITOR_FRAME::setupTools()
diff --git a/pagelayout_editor/pl_editor_frame.h b/pagelayout_editor/pl_editor_frame.h
index 33c64e1bad..629a77227f 100644
--- a/pagelayout_editor/pl_editor_frame.h
+++ b/pagelayout_editor/pl_editor_frame.h
@@ -252,6 +252,7 @@ public:
     virtual void PrintPage( wxDC* aDC ) override;
 
     void OnFileHistory( wxCommandEvent& event );
+    void OnClearFileHistory( wxCommandEvent& aEvent );
 
     /**
      * @return the filename of the current layout descr file
diff --git a/pcbnew/files.cpp b/pcbnew/files.cpp
index 8a91dcc9ae..7b66af2abb 100644
--- a/pcbnew/files.cpp
+++ b/pcbnew/files.cpp
@@ -202,6 +202,11 @@ void PCB_EDIT_FRAME::OnFileHistory( wxCommandEvent& event )
     }
 }
 
+void PCB_EDIT_FRAME::OnClearFileHistory( wxCommandEvent& aEvent )
+{
+    ClearFileHistory();
+}
+
 
 void PCB_EDIT_FRAME::Files_io( wxCommandEvent& event )
 {
diff --git a/pcbnew/menubar_pcb_editor.cpp b/pcbnew/menubar_pcb_editor.cpp
index ed5271d60c..d01f0c3afe 100644
--- a/pcbnew/menubar_pcb_editor.cpp
+++ b/pcbnew/menubar_pcb_editor.cpp
@@ -59,7 +59,7 @@ void PCB_EDIT_FRAME::ReCreateMenuBar()
     //-- File menu -----------------------------------------------------------
     //
     CONDITIONAL_MENU*   fileMenu = new CONDITIONAL_MENU( false, selTool );
-    static FILE_HISTORY_MENU* openRecentMenu;
+    static ACTION_MENU* openRecentMenu;
     auto& disp_opt = GetDisplayOptions();
 
     if( Kiface().IsSingle() )   // not when under a project mgr
@@ -70,10 +70,13 @@ void PCB_EDIT_FRAME::ReCreateMenuBar()
         // will automatically refresh the menu.
         if( !openRecentMenu )
         {
-            openRecentMenu = new FILE_HISTORY_MENU( fileHistory );
+            openRecentMenu = new ACTION_MENU( false );
             openRecentMenu->SetTool( selTool );
             openRecentMenu->SetTitle( _( "Open Recent" ) );
             openRecentMenu->SetIcon( recent_xpm );
+
+            fileHistory.UseMenu( openRecentMenu );
+            fileHistory.AddFilesToMenu();
         }
 
         fileMenu->AddItem( ACTIONS::doNew,           SELECTION_CONDITIONS::ShowAlways );
diff --git a/pcbnew/pcb_edit_frame.cpp b/pcbnew/pcb_edit_frame.cpp
index fb7bd71842..5b5c4424fd 100644
--- a/pcbnew/pcb_edit_frame.cpp
+++ b/pcbnew/pcb_edit_frame.cpp
@@ -114,6 +114,7 @@ BEGIN_EVENT_TABLE( PCB_EDIT_FRAME, PCB_BASE_FRAME )
 
     EVT_MENU( ID_IMPORT_NON_KICAD_BOARD, PCB_EDIT_FRAME::Files_io )
     EVT_MENU_RANGE( ID_FILE1, ID_FILEMAX, PCB_EDIT_FRAME::OnFileHistory )
+    EVT_MENU( ID_FILE_LIST_CLEAR, PCB_EDIT_FRAME::OnClearFileHistory )
 
     EVT_MENU( ID_GEN_EXPORT_FILE_GENCADFORMAT, PCB_EDIT_FRAME::ExportToGenCAD )
     EVT_MENU( ID_GEN_EXPORT_FILE_VRML, PCB_EDIT_FRAME::OnExportVRML )
@@ -333,12 +334,6 @@ PCB_EDIT_FRAME::~PCB_EDIT_FRAME()
     // Shutdown all running tools
     if( m_toolManager )
         m_toolManager->ShutdownAllTools();
-
-    // Since the file menu contains file history menus, we must ensure that the menu
-    // destructor is called before the file history objects are deleted since their destructor
-    // unregisters the menu from the history.
-    wxMenu* fileMenu = GetMenuBar()->Remove( 0 );
-    delete fileMenu;
 }
 
 
diff --git a/pcbnew/pcb_edit_frame.h b/pcbnew/pcb_edit_frame.h
index 0765406240..cef85b27ef 100644
--- a/pcbnew/pcb_edit_frame.h
+++ b/pcbnew/pcb_edit_frame.h
@@ -578,6 +578,7 @@ public:
     void GenD356File( wxCommandEvent& event );
 
     void OnFileHistory( wxCommandEvent& event );
+    void OnClearFileHistory( wxCommandEvent& aEvent );
 
     /**
      * Function Files_io