← Back to team overview

kicad-developers team mailing list archive

[PATCH/RFC] Pcbnew initial startup user experience

 

Hi,

Here is a (proposed) patchset to modify the "user experience" (to use
a buzzword) of Pcbnew and cvPcb when the user loads it for the first
time with no fp-library-table.

The current behaviour is quite unfriendly, as it doesn't get the user
to consent to an action when initialising the library, it does one of
two actions (cpopy default table, or create empty table - which one
depends on various conditions) which means that users click the OK
button not fully knowing what just happened.

Furthermore, if they click though that dialog without reading it, they
will then be frustrated by the lack of libraries later, which
manifests as an error or an empty footprint chooser. Bear in mind, as
far as users are concerned, the libraries are installed (e.g.
/usr/share/kicad has content), but as far as KiCad is concerned, they
are not necessarily configured (empty or default fp-lib-table in
user's home).

This has caused several people to struggle with getting started with
KiCad, and people asking how to proceed is a semi-regular occurence on
IRC.

This patch changes to the following process:

* If user fp-lib-table is found and it has at least one library,
everything is normal
  and the user isn't disturbed.
* If fp-lib-table is missing:
    * On Pcbnew/Cvpcb start, pop up a dialog saying this and giving
these options:
        * Quit - exit Pcbnew/cvPcb witohut attempting to create
fp-lib-table. Users can then make their own arrangements.
        * Copy default table - attempt to copy the default table. If
this fails, return to the choice dialog.
        * Create empty table - if this fails return to the dialog.
    * In no case did the user get an fp-lib-table they didn't ask for.
    * If a blank or default table is created, a second dialog appears,
informing the user that the library table might need configuration.
This provides three options:
        * Cancel - user doesn't want to configure the table now, table
is unchanged
        * Open Manual - the manual referenced in the dialog message is
opened so the user can read about library configuration
        * Open FP Library Manager - go directly to the library
manager, rather than expecting the user to cancel the dialog and find
it themselves. The wizard is accessible from the manager dialog.
* If fp-lib-table is found, but it's empty when the user goes to use
it (e.g. on a keyword search for footprints), the
Cancel/Manual/Manager dialog is shown again, but it's a warning rather
than informational, as it was before.

The aim here is to ensure that the configuration is "management by
consent" and that the user is:

* Not surprised by "secret" configuration behind the scenes
* Informed of and consenting to specific actions
* Guided though configuration with dialogs that give access to
mentioned tools, rather than a block of text and an OK/Cancel button,
after which they are on their own.

Technically speaking, this is done by moving the global fp-lib-table
loading into common, and invoking it only when the frame is shown
(rather than prior to the kiface load). The reason for this is so that
the followup dialog can access the manager. Also, it reduces special
casing before kiface load.

The on-show invocation is done by a small class subscribed to the
frame's wx "Show" event, and once the event is received, the class
unsubscribes itself (so it's a one-shot), and executes a lits of
startup actions. The class is such that you can add multiple startup
actions, any of which can "veto" startup if needed.

Sorry for the long email: the change is not really that complex, but
the configuration process has several paths. As the first thing a new
user to Pcbnew sees, I think this is a fairly important thing to get
right.

Cheers,

John
From 8e1066a946a2a2fd5138790134ff1e3e7ce195da Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Fri, 3 Mar 2017 22:25:26 +0800
Subject: [PATCH 3/3] Move definition of global library table into fp_lib_table

This puts the definition of the variable in the corresponding position
to the declaration, and also means that the individual targets don't
need to define it themselves.
---
 common/fp_lib_table.cpp | 6 ++++++
 cvpcb/cvpcb.cpp         | 5 -----
 pcbnew/pcbnew.cpp       | 6 ------
 3 files changed, 6 insertions(+), 11 deletions(-)

diff --git a/common/fp_lib_table.cpp b/common/fp_lib_table.cpp
index c7e147221..3d785c8c1 100644
--- a/common/fp_lib_table.cpp
+++ b/common/fp_lib_table.cpp
@@ -41,6 +41,12 @@ using namespace LIB_TABLE_T;
 static const wxChar global_tbl_name[] = wxT( "fp-lib-table" );
 
 
+/// The global footprint library table.  This is not dynamically allocated because
+/// in a multiple project environment we must keep its address constant (since it is
+/// the fallback table for multiple projects).
+FP_LIB_TABLE    GFootprintTable;
+
+
 bool FP_LIB_TABLE_ROW::operator==( const FP_LIB_TABLE_ROW& aRow ) const
 {
     return LIB_TABLE_ROW::operator == ( aRow ) && type == aRow.type;
diff --git a/cvpcb/cvpcb.cpp b/cvpcb/cvpcb.cpp
index a43f43cc9..77cb8da11 100644
--- a/cvpcb/cvpcb.cpp
+++ b/cvpcb/cvpcb.cpp
@@ -131,11 +131,6 @@ PGM_BASE& Pgm()
 }
 
 
-//!!!!!!!!!!!!!!! This code is obsolete because of the merge into pcbnew, don't bother with it.
-
-FP_LIB_TABLE GFootprintTable;
-
-
 // A short lived implementation.  cvpcb will get combine into pcbnew shortly, so
 // we skip setting KISYSMOD here for now.  User should set the environment
 // variable.
diff --git a/pcbnew/pcbnew.cpp b/pcbnew/pcbnew.cpp
index 21fe74264..e8ecd2585 100644
--- a/pcbnew/pcbnew.cpp
+++ b/pcbnew/pcbnew.cpp
@@ -302,12 +302,6 @@ void PythonPluginsReloadBase()
 }
 
 
-/// The global footprint library table.  This is not dynamically allocated because
-/// in a multiple project environment we must keep its address constant (since it is
-/// the fallback table for multiple projects).
-FP_LIB_TABLE    GFootprintTable;
-
-
 bool IFACE::OnKifaceStart( PGM_BASE* aProgram, int aCtlBits )
 {
     // This is process level, not project level, initialization of the DSO.
-- 
2.12.0

From b7edaff790a76891b7ca70e4d469494ad059ec65 Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Tue, 17 Jan 2017 15:33:46 +0800
Subject: [PATCH 2/3] Make fp-library-table init process more interactive

By making the user consent to particular course of action, the library
set up process, which is probably there hardest part of getting started
with Pcbnew, is made a bit clearer.

Currently, the action if no Global Footprint Library Table is found is
to copy a tmeplate one, if that fails, make a blank one, then tell the
user it did one of these (but not say which) and exhort the user to set
it up.

The new behaviour is to give a choice for copy/create, and ask again if
that fails.

Then, if the library is created, the user is presented with a dialog
that informs them that they need to set up the libraries, along with
buttons to cancel and leave the table in its "fresh" state, open the
help file, or open the Manager directly.

Furthermore, when attempting to load the fooprint lib, the current error
when no libs are found is, while technically correct, not as helpful as
it could be. The same dialogs as for lib init are used to allow the use
to Cancel/See help/See manager.
---
 common/CMakeLists.txt            |   2 +
 common/basicframe.cpp            |   4 +
 common/fp_lib_table.cpp          | 102 ++++++++++++++++----
 common/frame_startup_actions.cpp |  62 +++++++++++++
 common/global_fp_lib_loader.cpp  | 196 +++++++++++++++++++++++++++++++++++++++
 cvpcb/cvpcb.cpp                  |  31 -------
 cvpcb/cvpcb_mainframe.cpp        |   7 +-
 include/fp_lib_table.h           |  51 +++++++++-
 include/frame_startup_actions.h  |  82 ++++++++++++++++
 include/global_fp_lib_loader.h   |  72 ++++++++++++++
 include/wxPcbStruct.h            |   2 +-
 include/wxstruct.h               |   5 +-
 pcbnew/loadcmp.cpp               |  15 +--
 pcbnew/pcbframe.cpp              |   5 +
 pcbnew/pcbnew.cpp                |  30 ------
 15 files changed, 569 insertions(+), 97 deletions(-)
 create mode 100644 common/frame_startup_actions.cpp
 create mode 100644 common/global_fp_lib_loader.cpp
 create mode 100644 include/frame_startup_actions.h
 create mode 100644 include/global_fp_lib_loader.h

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 5ef47257a..f060ca382 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -229,12 +229,14 @@ set( COMMON_SRCS
     eda_pattern_match.cpp
     exceptions.cpp
     filter_reader.cpp
+    frame_startup_actions.cpp
     lib_id.cpp
     lib_table_keywords.cpp
 #    findkicadhelppath.cpp.notused      deprecated, use searchhelpfilefullpath.cpp
     gbr_metadata.cpp
     gestfich.cpp
     getrunningmicrosecs.cpp
+    global_fp_lib_loader.cpp
     grid_tricks.cpp
     gr_basic.cpp
     hotkeys_basic.cpp
diff --git a/common/basicframe.cpp b/common/basicframe.cpp
index 9ea253fe7..5a98f522a 100644
--- a/common/basicframe.cpp
+++ b/common/basicframe.cpp
@@ -40,6 +40,7 @@
 #include <wxstruct.h>
 #include <menus_helpers.h>
 #include <bitmaps.h>
+#include <frame_startup_actions.h>
 
 #include <wx/display.h>
 #include <wx/utils.h>
@@ -106,6 +107,9 @@ EDA_BASE_FRAME::EDA_BASE_FRAME( wxWindow* aParent, FRAME_T aFrameType,
     // hook wxEVT_CLOSE_WINDOW so we can call SaveSettings().  This function seems
     // to be called before any other hook for wxCloseEvent, which is necessary.
     Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( EDA_BASE_FRAME::windowClosing ) );
+
+    // construct empty frame startup actions - inheritors can add to this if needed
+    m_startActions = std::make_unique<FRAME_STARTUP_ACTIONS>( *this );
 }
 
 
diff --git a/common/fp_lib_table.cpp b/common/fp_lib_table.cpp
index edd903199..c7e147221 100644
--- a/common/fp_lib_table.cpp
+++ b/common/fp_lib_table.cpp
@@ -357,41 +357,110 @@ const wxString FP_LIB_TABLE::GlobalPathEnvVariableName()
 }
 
 
-bool FP_LIB_TABLE::LoadGlobalTable( FP_LIB_TABLE& aTable )
-    throw (IO_ERROR, PARSE_ERROR, boost::interprocess::lock_exception )
+static bool loadGlobalTable( const wxFileName& fn, FP_LIB_TABLE& aTable,
+                bool& aNeedsInit,
+                FP_LIB_TABLE::SETUP_UI_PROVIDER& aUiProvider )
+        throw (IO_ERROR, PARSE_ERROR, boost::interprocess::lock_exception )
 {
-    bool        tableExists = true;
-    wxFileName  fn = GetGlobalTableFileName();
+    aNeedsInit = false;
 
-    if( !fn.FileExists() )
+    while( !fn.FileExists() )
     {
-        tableExists = false;
+        FP_LIB_TABLE::SETUP_UI_PROVIDER::INIT_ACTION action = aUiProvider.PromptForInitAction();
 
+        // user rejects default init actions
+        if( action == FP_LIB_TABLE::SETUP_UI_PROVIDER::INIT_ACTION::EXIT )
+        {
+            break;
+        }
+
+        // attempt to make the containing directory - needed
+        // for both copy and create actions. if this fails, we won't be able
+        // to do any initialisation
         if( !fn.DirExists() && !fn.Mkdir( 0x777, wxPATH_MKDIR_FULL ) )
         {
-            THROW_IO_ERROR( wxString::Format( _( "Cannot create global library table path '%s'." ),
-                                              GetChars( fn.GetPath() ) ) );
+            aUiProvider.ShowError( wxString::Format( _( "Cannot create global library table path '%s'." ),
+                                                     GetChars( fn.GetPath() ) ) );
+            break;
         }
 
-        // Attempt to copy the default global file table from the KiCad
-        // template folder to the user's home configuration path.
-        wxString fileName = Kiface().KifaceSearch().FindValidPath( global_tbl_name );
+        if ( action == FP_LIB_TABLE::SETUP_UI_PROVIDER::INIT_ACTION::COPY_EXAMPLE )
+        {
+            // Attempt to copy the default global file table from the KiCad
+            // template folder to the user's home configuration path.
+            wxString fileName = Kiface().KifaceSearch().FindValidPath( global_tbl_name );
 
-        // The fallback is to create an empty global footprint table for the user to populate.
-        if( fileName.IsEmpty() || !::wxCopyFile( fileName, fn.GetFullPath(), false ) )
+            if( fileName.IsEmpty() )
+            {
+                aUiProvider.ShowError( _( "Failed to find example footprint library table to copy." ) );
+            }
+            else if (!::wxCopyFile( fileName, fn.GetFullPath(), false ) )
+            {
+                aUiProvider.ShowError( _( "Failed to copy example footprint library table." ) );
+            }
+            else
+            {
+                // success, but the user still needs to configure
+                aNeedsInit = true;
+            }
+        }
+        else
         {
+            // create an empty table
             FP_LIB_TABLE    emptyTable;
-
             emptyTable.Save( fn.GetFullPath() );
+
+            if( !fn.FileExists() )
+            {
+                aUiProvider.ShowError( _( "Failed to create empty footprint library table" ) );
+                // ask user what to do now
+            }
+            else
+            {
+                // success, but the user still needs to configure
+                aNeedsInit = true;
+            }
         }
     }
 
-    aTable.Load( fn.GetFullPath() );
+    bool loadedOk = false;
+
+    if ( fn.FileExists() )
+    {
+        aTable.Load( fn.GetFullPath() ); // IO_ERROR thrown on error
+        loadedOk = true;
+    }
 
-    return tableExists;
+    return loadedOk;
 }
 
 
+bool FP_LIB_TABLE::LoadGlobalTable( FP_LIB_TABLE& aTable,
+                                    bool& aNeedsInit,
+                                    SETUP_UI_PROVIDER& aUiProvider )
+    throw ( boost::interprocess::lock_exception )
+{
+    wxFileName  fn = FP_LIB_TABLE::GetGlobalTableFileName();
+
+    bool loaded = false;
+    try
+    {
+        loaded = loadGlobalTable( fn, aTable, aNeedsInit, aUiProvider );
+    }
+    catch( const IO_ERROR& ioe ) // Also PARSE_ERROR
+    {
+        wxString msg = wxString::Format( _(
+                    "An error occurred attempting to load the global footprint library "
+                    "table:\n\n%s" ),
+                GetChars( ioe.What() )
+        );
+
+        aUiProvider.ShowError( msg );
+    }
+
+    return loaded;
+}
+
 wxString FP_LIB_TABLE::GetGlobalTableFileName()
 {
     wxFileName fn;
@@ -401,3 +470,4 @@ wxString FP_LIB_TABLE::GetGlobalTableFileName()
 
     return fn.GetFullPath();
 }
+;
diff --git a/common/frame_startup_actions.cpp b/common/frame_startup_actions.cpp
new file mode 100644
index 000000000..da917718d
--- /dev/null
+++ b/common/frame_startup_actions.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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
+ */
+
+#include <frame_startup_actions.h>
+
+
+FRAME_STARTUP_ACTIONS::FRAME_STARTUP_ACTIONS( wxWindow& aWindow ) :
+    m_window( aWindow )
+{
+    m_window.Bind( wxEVT_SHOW, &FRAME_STARTUP_ACTIONS::onShow, this );
+}
+
+
+void FRAME_STARTUP_ACTIONS::AddAction( std::unique_ptr<ACTION> aAction )
+{
+    m_actionList.push_back( std::move( aAction ) );
+}
+
+
+void FRAME_STARTUP_ACTIONS::onShow( wxShowEvent& event )
+{
+    m_window.Unbind( wxEVT_SHOW, &FRAME_STARTUP_ACTIONS::onShow, this );
+
+    bool fatalError = false;
+
+    for( auto& check : m_actionList )
+    {
+        if( !check->DoStartupAction() )
+        {
+            fatalError = true;
+            break;
+        }
+    }
+
+    // all checks done, we can clear the list and free the memory
+    m_actionList.clear();
+
+    if( fatalError )
+    {
+        m_window.Close();
+    }
+}
diff --git a/common/global_fp_lib_loader.cpp b/common/global_fp_lib_loader.cpp
new file mode 100644
index 000000000..46f2198a8
--- /dev/null
+++ b/common/global_fp_lib_loader.cpp
@@ -0,0 +1,196 @@
+/*
+ * 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
+ */
+
+#include <global_fp_lib_loader.h>
+
+#include <pcbnew_id.h>
+#include <fp_lib_table.h>
+#include <dialogs/dialog_multibutton.h>
+#include <confirm.h>
+
+
+/**
+ * Function showFootprintLibHelperDialog
+ *
+ * Helper function to pop up a Cancel/Help/FP Manager dialog and
+ * dispatch events to the EDA_BASE_FRAME if needed
+ */
+static void showFootprintLibHelperDialog( EDA_BASE_FRAME& aFrame,
+        bool aWarning, const wxString& aTitle, const wxString& aMsg,
+        int aManagerMenuId )
+{
+    DIALOG_MULTIBUTTON dialog( &aFrame, aTitle );
+
+    dialog.AddText( aMsg );
+
+    dialog.AddButton( wxID_CANCEL, wxEmptyString, true );
+    dialog.AddButton( wxID_HELP, _( "Open Manual" ) );
+    dialog.AddButton( wxID_OK, _( "Footprint Library Manager" ) );
+
+    dialog.SetHintImage( aWarning ? wxART_WARNING : wxART_INFORMATION );
+
+    dialog.AdjustSize();
+
+    int ret = dialog.ShowModal();
+
+    switch( ret )
+    {
+    case wxID_OK:
+        aFrame.PostCommandMenuEvent( aManagerMenuId );
+        break;
+
+    case wxID_HELP:
+        aFrame.PostCommandMenuEvent( wxID_HELP );
+        break;
+
+    default:
+        break;
+    }
+}
+
+
+/**
+ * Class GLOBAL_FP_LOADER_UI_PROVIDER
+ *
+ * Implemention of the SETUP_UI_PROVIDER template class to
+ * provide UI prompts for the current frame
+ */
+class GLOBAL_FP_LOADER_UI_PROVIDER : public FP_LIB_TABLE::SETUP_UI_PROVIDER
+{
+public:
+
+    GLOBAL_FP_LOADER_UI_PROVIDER( EDA_BASE_FRAME& aFrame ) :
+        m_frame( aFrame )
+    {}
+
+    void ShowError( const wxString& msg ) override
+    {
+        DisplayError( &m_frame, msg );
+    }
+
+    virtual INIT_ACTION PromptForInitAction() override
+    {
+        const wxString msg = _(
+                "No global footprint library table was found.\n\n"
+                "Would you like to initialise an empty table, "
+                "copy an example table, or exit and create the table "
+                "yourself?\n\n" );
+
+        DIALOG_MULTIBUTTON dialog( &m_frame,
+                _( "No Global Footprint Library Table" ) );
+
+        dialog.AddText( msg );
+
+        dialog.SetHintImage( wxART_ERROR );
+
+        dialog.AddButton( wxID_EXIT, wxEmptyString, true );
+        dialog.AddButton( (int) INIT_ACTION::COPY_EXAMPLE, _( "Copy example table" ) );
+        dialog.AddButton( (int) INIT_ACTION::CREATE_EMPTY, _( "Create empty table" ) );
+
+        dialog.AdjustSize();
+
+        int ret = dialog.ShowModal();
+
+        if( ret == (int) INIT_ACTION::COPY_EXAMPLE )
+            return INIT_ACTION::COPY_EXAMPLE;
+        else if( ret == (int) INIT_ACTION::CREATE_EMPTY )
+            return INIT_ACTION::CREATE_EMPTY;
+
+        return INIT_ACTION::EXIT;
+    }
+
+private:
+
+    EDA_BASE_FRAME& m_frame;
+};
+
+
+GLOBAL_LIB_LOADER::GLOBAL_LIB_LOADER( EDA_BASE_FRAME& aFrame,
+        int aManagerMenuId ) :
+    m_frame( aFrame ),
+    m_managerMenuId( aManagerMenuId )
+{
+}
+
+
+bool GLOBAL_LIB_LOADER::DoStartupAction()
+{
+    GLOBAL_FP_LOADER_UI_PROVIDER uiProv( m_frame );
+
+    bool needsInit;
+
+    if( !FP_LIB_TABLE::LoadGlobalTable( GFootprintTable, needsInit, uiProv ) )
+    {
+        // no global library found or created
+        return false;
+    }
+
+    // We have a global library, but is there anything in it?
+    // If empty, prompt for config - informational if the user just
+    // created a default table, or more loudly if we would normally
+    // have expected a more configured state
+    if( GFootprintTable.GetLogicalLibs().size() == 0 )
+    {
+        ShowNoFootprintsDialog( m_frame, !needsInit, m_managerMenuId );
+    }
+
+    // whether or not the user configured it, we at least have
+    // a global table now
+    return true;
+}
+
+
+// The globally-accessible dialog function
+void GLOBAL_LIB_LOADER::ShowNoFootprintsDialog( EDA_BASE_FRAME& aFrame,
+        bool aIsWarning, int aManagerMenuId )
+{
+    wxString msg, title;
+
+    if( aIsWarning )
+    {
+        // text to show if this is a error dialog
+        title = _( "No Footprints in Library" );
+
+        msg = _( "No footprints could be loaded from the library.\n\n"
+                 "Use the Footprint Libraries Manager to add libraries "
+                 "or check existing library configuration."
+                );
+    }
+    else
+    {
+        // text to show is this is only an informational dialog
+        msg = _( "You must configure the library table "
+                 "to include all footprint libraries you want to use.\n\n"
+                 "This can be done with the Footprint Library Manager."
+                );
+
+        title = _( "Footprint Library Table Needs Configuration" );
+    }
+
+    msg << "\n\n";
+    msg << _( "Refer to the Pcbnew and Cvpcb manual sections "
+              "\"Managing Footprint Libraries\" "
+              "for more details." );
+
+    showFootprintLibHelperDialog( aFrame, aIsWarning, title, msg, aManagerMenuId );
+}
diff --git a/cvpcb/cvpcb.cpp b/cvpcb/cvpcb.cpp
index c7a98aa03..a43f43cc9 100644
--- a/cvpcb/cvpcb.cpp
+++ b/cvpcb/cvpcb.cpp
@@ -155,37 +155,6 @@ bool IFACE::OnKifaceStart( PGM_BASE* aProgram, int aCtlBits )
     // SetFootprintLibTablePath();
     */
 
-    try
-    {
-        // The global table is not related to a specific project.  All projects
-        // will use the same global table.  So the KIFACE::OnKifaceStart() contract
-        // of avoiding anything project specific is not violated here.
-
-        if( !FP_LIB_TABLE::LoadGlobalTable( GFootprintTable ) )
-        {
-            DisplayInfoMessage( NULL, _(
-                "You have run CvPcb for the first time using the "
-                "new footprint library table method for finding "
-                "footprints.  CvPcb has either copied the default "
-                "table or created an empty table in your home "
-                "folder.  You must first configure the library "
-                "table to include all footprint libraries not "
-                "included with KiCad.  See the \"Footprint Library "
-                "Table\" section of the CvPcb documentation for "
-                "more information." ) );
-        }
-    }
-    catch( const IO_ERROR& ioe )
-    {
-        wxString msg = wxString::Format( _(
-            "An error occurred attempting to load the global footprint library "
-            "table:\n\n%s" ),
-            GetChars( ioe.What() )
-            );
-        DisplayError( NULL, msg );
-        return false;
-    }
-
     return true;
 }
 
diff --git a/cvpcb/cvpcb_mainframe.cpp b/cvpcb/cvpcb_mainframe.cpp
index af25c93c2..e24c93c88 100644
--- a/cvpcb/cvpcb_mainframe.cpp
+++ b/cvpcb/cvpcb_mainframe.cpp
@@ -40,6 +40,7 @@
 #include <html_messagebox.h>
 #include <wildcards_and_files_ext.h>
 #include <fp_lib_table.h>
+#include <global_fp_lib_loader.h>
 #include <netlist_reader.h>
 #include <bitmaps.h>
 
@@ -200,6 +201,10 @@ CVPCB_MAINFRAME::CVPCB_MAINFRAME( KIWAY* aKiway, wxWindow* aParent ) :
                           Right().BestSize( (int) ( m_FrameSize.x * 0.30 ), m_FrameSize.y ) );
 
     m_auimgr.Update();
+
+    m_startActions->AddAction(
+        std::make_unique<GLOBAL_LIB_LOADER>( *this, ID_CVPCB_LIB_TABLE_EDIT )
+    );
 }
 
 
@@ -720,8 +725,6 @@ bool CVPCB_MAINFRAME::LoadFootprintFiles()
     // Check if there are footprint libraries in the footprint library table.
     if( !fptbl || !fptbl->GetLogicalLibs().size() )
     {
-        wxMessageBox( _( "No PCB footprint libraries are listed in the current footprint "
-                         "library table." ), _( "Configuration Error" ), wxOK | wxICON_ERROR );
         return false;
     }
 
diff --git a/include/fp_lib_table.h b/include/fp_lib_table.h
index 572185ff9..015da948b 100644
--- a/include/fp_lib_table.h
+++ b/include/fp_lib_table.h
@@ -103,6 +103,44 @@ class FP_LIB_TABLE : public LIB_TABLE
 {
 public:
 
+    /**
+     * Class SETUP_UI_PROVIDER
+     *
+     * Interface class to implement to provide UI prompts to the
+     * user to guide the FP library init process, and report errors
+     *
+     * This decouples the UI from the internal logic.
+     */
+    class SETUP_UI_PROVIDER
+    {
+    public:
+
+        enum class INIT_ACTION
+        {
+            CREATE_EMPTY,       ///< Create a new, blank, FP lib table
+            COPY_EXAMPLE,       ///< Attempt to copy the example table
+            EXIT,               ///< Do no init, just quit and let the user do it themselves
+        };
+
+        /**
+         * Show an error message to the user
+         */
+        virtual void ShowError( const wxString& msg ) = 0;
+
+        /**
+         * Prompt the user to choose an init action
+         *
+         * @return: the relevant INIT_ACTION for the user's choice
+         *          with EXIT being the default in case of no explicit
+         *          choice
+         */
+        virtual INIT_ACTION PromptForInitAction() = 0;
+
+    protected:
+        /* Protected: Not managed by base pointer */
+        virtual ~SETUP_UI_PROVIDER() {}
+    };
+
     virtual void Parse( LIB_TABLE_LEXER* aLexer ) throw() override;
 
     virtual void Format( OUTPUTFORMATTER* out, int nestLevel ) const throw() override;
@@ -245,12 +283,17 @@ public:
      * time being.
      *
      * @param aTable the #FP_LIB_TABLE object to load.
+     * @param aNeedsInit bool flag, set true if this function created
+     *        a new table that the user should configure
+     * @param aUiProvider the UI provider to prompt/alert the user
      * @return true if the global library table exists and is loaded properly.
-     * @throw IO_ERROR if an error occurs attempting to load the footprint library
-     *                 table.
+     *         false if there is an error during load, or the user rejects
+     *         initialising a new table
      */
-    static bool LoadGlobalTable( FP_LIB_TABLE& aTable )
-        throw (IO_ERROR, PARSE_ERROR, boost::interprocess::lock_exception );
+    static bool LoadGlobalTable( FP_LIB_TABLE& aTable,
+                                 bool& aNeedsInit,
+                                 SETUP_UI_PROVIDER& aUiProvider )
+        throw ( boost::interprocess::lock_exception );
 
     /**
      * Function GetGlobalTableFileName
diff --git a/include/frame_startup_actions.h b/include/frame_startup_actions.h
new file mode 100644
index 000000000..ee9c51318
--- /dev/null
+++ b/include/frame_startup_actions.h
@@ -0,0 +1,82 @@
+/*
+ * 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 INCLUDE_FRAME_ON_START_ACTION_H_
+#define INCLUDE_FRAME_ON_START_ACTION_H_
+
+#include <wxstruct.h>
+
+/**
+ * Class FRAME_STARTUP_ACTIONS
+ *
+ * Object that manages startup actions for a wxWindow frame. This is
+ * useful when the actions need user input or produce results that
+ * depends on the frame being shown and active.
+ *
+ * When the frame is first shown, a list of actions is performed.
+ * If an action fails and it reports that a failure is fatal, the
+ * window is closed.
+ */
+class FRAME_STARTUP_ACTIONS
+{
+public:
+
+    /**
+     * Class ACTION
+     *
+     * A class to represent and handle a one-off action to run when a wxWindow is
+     * first shown.
+     */
+    class ACTION
+    {
+    public:
+
+        virtual ~ACTION()
+        {}
+
+        /**
+         * Function DoStartupAction
+         *
+         * Implement this function to perform your startup action.
+         *
+         * @return true if successful, or false if a fatal error
+         * occurred and the window should be closed (for example if
+         * a critical resource could not be acquired).
+         */
+        virtual bool DoStartupAction() = 0;
+    };
+
+    FRAME_STARTUP_ACTIONS( wxWindow& aWindow );
+
+    void AddAction( std::unique_ptr<ACTION> aAction );
+
+private:
+
+    void onShow( wxShowEvent& event );
+
+    wxWindow& m_window;
+    std::vector<std::unique_ptr<ACTION> > m_actionList;
+};
+
+
+#endif /* INCLUDE_FRAME_ON_START_ACTION_H_ */
diff --git a/include/global_fp_lib_loader.h b/include/global_fp_lib_loader.h
new file mode 100644
index 000000000..3b8e8e570
--- /dev/null
+++ b/include/global_fp_lib_loader.h
@@ -0,0 +1,72 @@
+/*
+ * 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 INCLUDE_GLOBAL_FP_LIB_LOADER_H_
+#define INCLUDE_GLOBAL_FP_LIB_LOADER_H_
+
+#include <frame_startup_actions.h>
+#include <wxstruct.h>    // EDA_BASE_FRAME
+
+/**
+ * Class GLOBAL_LIB_LOADER
+ *
+ * A frame start action that will load the global library
+ * table, start the library init prompt if not, and also
+ * prompt the user if the library is empty
+ */
+class GLOBAL_LIB_LOADER : public FRAME_STARTUP_ACTIONS::ACTION
+{
+public:
+    GLOBAL_LIB_LOADER( EDA_BASE_FRAME& aFrame, int aManagerMenuId );
+
+    /*!
+     * Static function to show an information dialog that the footprint
+     * lib needs configuration because there are not footprints found
+     *
+     * Placed in this class more as a namespace than as a member
+     *
+     * @param frame - the EDA_BASE_FRAME that the dialog is associated with
+     * @param aIsWarning - trie if this is a warning dialog, false if
+     *        is just an informational reminder
+     * @param aManagerMenuId - the menu ID for opening the footprint
+     *        library manager
+     */
+    static void ShowNoFootprintsDialog( EDA_BASE_FRAME& aFrame,
+            bool aIsWarning,
+            int aManagerMenuId );
+
+    /**
+     * Attempt to load/create the global FP library,
+     *
+     * @return true if a global library is created, false if not
+     *         (false is fatal and triggers exit)
+     */
+    bool DoStartupAction() override;
+
+private:
+
+    EDA_BASE_FRAME& m_frame;
+    int m_managerMenuId;
+};
+
+#endif /* INCLUDE_GLOBAL_FP_LIB_LOADER_H_ */
diff --git a/include/wxPcbStruct.h b/include/wxPcbStruct.h
index b6c0766cc..5cfe8750e 100644
--- a/include/wxPcbStruct.h
+++ b/include/wxPcbStruct.h
@@ -66,6 +66,7 @@ struct PARSE_ERROR;
 class IO_ERROR;
 class FP_LIB_TABLE;
 struct AUTOROUTER_CONTEXT;
+class FRAME_STARTUP_ACTIONS;
 
 namespace PCB { struct IFACE; }     // KIFACE_I is in pcbnew.cpp
 
@@ -89,7 +90,6 @@ class PCB_EDIT_FRAME : public PCB_BASE_EDIT_FRAME
     /// The auxiliary right vertical tool bar used to access the microwave tools.
     wxAuiToolBar* m_microWaveToolBar;
 
-
 protected:
     PCB_LAYER_WIDGET* m_Layers;
 
diff --git a/include/wxstruct.h b/include/wxstruct.h
index 990adf29e..705036599 100644
--- a/include/wxstruct.h
+++ b/include/wxstruct.h
@@ -84,7 +84,7 @@ class MSG_PANEL_ITEM;
 class TOOL_MANAGER;
 class TOOL_DISPATCHER;
 class ACTIONS;
-
+class FRAME_STARTUP_ACTIONS;
 
 enum id_librarytype {
     LIBRARY_TYPE_EESCHEMA,
@@ -154,6 +154,9 @@ protected:
 
     wxString     m_mruPath;         ///< Most recently used path.
 
+    /// Actions to run once the frame is visible
+    std::unique_ptr<FRAME_STARTUP_ACTIONS> m_startActions;
+
     /**
      * Function onAutoSaveTimer
      * handles the auto save timer event.
diff --git a/pcbnew/loadcmp.cpp b/pcbnew/loadcmp.cpp
index 6dac6db80..257a80942 100644
--- a/pcbnew/loadcmp.cpp
+++ b/pcbnew/loadcmp.cpp
@@ -46,6 +46,7 @@ using namespace std::placeholders;
 #include <macros.h>
 #include <fp_lib_table.h>
 #include <lib_id.h>
+#include <pcbnew_id.h>
 
 #include <class_board.h>
 #include <class_module.h>
@@ -55,6 +56,7 @@ using namespace std::placeholders;
 #include <module_editor_frame.h>
 #include <footprint_info.h>
 #include <dialog_get_component.h>
+#include <global_fp_lib_loader.h>
 #include <modview_frame.h>
 #include <wildcards_and_files_ext.h>
 #include <class_pcb_layer_widget.h>
@@ -358,7 +360,6 @@ wxString PCB_BASE_FRAME::SelectFootprint( EDA_DRAW_FRAME* aWindow,
 
     wxString        fpname;
     wxString        msg;
-    wxArrayString   libraries;
 
     std::vector< wxArrayString > rows;
 
@@ -374,17 +375,7 @@ wxString PCB_BASE_FRAME::SelectFootprint( EDA_DRAW_FRAME* aWindow,
 
     if( MList.GetCount() == 0 )
     {
-        wxString tmp;
-
-        for( unsigned i = 0;  i < libraries.GetCount();  i++ )
-        {
-            tmp += libraries[i] + wxT( "\n" );
-        }
-
-        msg.Printf( _( "No footprints could be read from library file(s):\n\n%s\nin any of "
-                       "the library search paths.  Verify your system is configured properly "
-                       "so the footprint libraries can be found." ), GetChars( tmp ) );
-        DisplayError( aWindow, msg );
+        GLOBAL_LIB_LOADER::ShowNoFootprintsDialog( *aWindow, true, ID_PCB_LIB_TABLE_EDIT );
         return wxEmptyString;
     }
 
diff --git a/pcbnew/pcbframe.cpp b/pcbnew/pcbframe.cpp
index 7abbaa329..57bde9f1a 100644
--- a/pcbnew/pcbframe.cpp
+++ b/pcbnew/pcbframe.cpp
@@ -42,6 +42,7 @@
 #include <msgpanel.h>
 #include <fp_lib_table.h>
 #include <bitmaps.h>
+#include <global_fp_lib_loader.h>
 
 #include <pcbnew.h>
 #include <pcbnew_id.h>
@@ -494,6 +495,10 @@ PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
 
     if( !appK2S.FileExists() )
         GetMenuBar()->FindItem( ID_GEN_EXPORT_FILE_STEP )->Enable( false );
+
+    m_startActions->AddAction(
+        std::make_unique<GLOBAL_LIB_LOADER>( *this, ID_PCB_LIB_TABLE_EDIT )
+    );
 }
 
 
diff --git a/pcbnew/pcbnew.cpp b/pcbnew/pcbnew.cpp
index 1eed6bfec..21fe74264 100644
--- a/pcbnew/pcbnew.cpp
+++ b/pcbnew/pcbnew.cpp
@@ -320,36 +320,6 @@ bool IFACE::OnKifaceStart( PGM_BASE* aProgram, int aCtlBits )
     // display the real hotkeys in menus or tool tips
     ReadHotkeyConfig( PCB_EDIT_FRAME_NAME, g_Board_Editor_Hokeys_Descr );
 
-    try
-    {
-        // The global table is not related to a specific project.  All projects
-        // will use the same global table.  So the KIFACE::OnKifaceStart() contract
-        // of avoiding anything project specific is not violated here.
-
-        if( !FP_LIB_TABLE::LoadGlobalTable( GFootprintTable ) )
-        {
-            DisplayInfoMessage( NULL, _(
-                "You have run Pcbnew for the first time using the "
-                "new footprint library table method for finding footprints.\n"
-                "Pcbnew has either copied the default "
-                "table or created an empty table in the kicad configuration folder.\n"
-                "You must first configure the library "
-                "table to include all footprint libraries you want to use.\n"
-                "See the \"Footprint Library  Table\" section of "
-                "the CvPcb or Pcbnew documentation for more information." ) );
-        }
-    }
-    catch( const IO_ERROR& ioe )
-    {
-        wxString msg = wxString::Format( _(
-            "An error occurred attempting to load the global footprint library "
-            "table:\n\n%s" ),
-            GetChars( ioe.What() )
-            );
-        DisplayError( NULL, msg );
-        return false;
-    }
-
 #if defined(KICAD_SCRIPTING)
     scriptingSetup();
 #endif
-- 
2.12.0

From 8d1aff6a72677e899c8ed2059abb744059f09d92 Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Wed, 18 Jan 2017 14:56:52 +0800
Subject: [PATCH 1/3] Add small multi-button dialog class

This is a dialog that can be used when asking a user for general input.
It is similar to a wxMessageBox, but the buttons and IDs are not limited
to the sets Yes/No, OK/Cancel, etc.

You can also add more than one text string, and other UI items to the
main sizer.

This is not intended to be a totally general dialog base, but it's
probably sufficient for most simple cases.
---
 common/CMakeLists.txt                 |   1 +
 common/dialogs/dialog_multibutton.cpp | 137 ++++++++++++++++++++++++++++++++++
 common/dialogs/dialog_multibutton.h   | 123 ++++++++++++++++++++++++++++++
 3 files changed, 261 insertions(+)
 create mode 100644 common/dialogs/dialog_multibutton.cpp
 create mode 100644 common/dialogs/dialog_multibutton.h

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 325b21def..5ef47257a 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -148,6 +148,7 @@ set( COMMON_ABOUT_DLG_SRCS
     )
 
 set( COMMON_DLG_SRCS
+    dialogs/dialog_multibutton.cpp
     dialogs/dialog_display_info_HTML_base.cpp
     dialogs/dialog_exit_base.cpp
     dialogs/dialog_image_editor.cpp
diff --git a/common/dialogs/dialog_multibutton.cpp b/common/dialogs/dialog_multibutton.cpp
new file mode 100644
index 000000000..4236ea93b
--- /dev/null
+++ b/common/dialogs/dialog_multibutton.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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
+ */
+
+#include <dialog_multibutton.h>
+
+#include <wx/event.h>
+
+
+static const int BORDER_WIDTH = 5;
+
+
+DIALOG_MULTIBUTTON::DIALOG_MULTIBUTTON( wxWindow* aParent,
+        const wxString& aTitle ) :
+    DIALOG_SHIM( aParent, wxID_ANY, aTitle,
+            wxDefaultPosition, wxDefaultSize,
+            wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
+    m_image( nullptr )
+{
+    /* Sizers go something like this:
+     *
+     *  +---------------------------------+
+     *  | +-------+---------------------+ |
+     *  | |  Img  |      Body item      | |
+     *  | |       |      Body item      | |
+     *  | +-------+---------------------+ |
+     *  | +-----------------------------+ |
+     *  | |        Button Button Button | |
+     *  | +-----------------------------+ |
+     *  +---------------------------------+
+     */
+
+    // overall sizer for the whole dialog
+    wxBoxSizer* mainBoxSizer = new wxBoxSizer( wxVERTICAL );
+    SetSizer( mainBoxSizer );
+
+    // L/R size for hint image/body items
+    m_topBoxSizer = new wxBoxSizer( wxHORIZONTAL );
+    mainBoxSizer->Add( m_topBoxSizer, 1, wxALIGN_TOP | wxALL, 0 );
+
+    // Vertical sizer for body items
+    m_itemBoxSizer = new wxBoxSizer( wxVERTICAL );
+    m_topBoxSizer->Add( m_itemBoxSizer, 1, wxALIGN_TOP | wxALL, BORDER_WIDTH );
+
+    // L/R sizer for the dialog buttons
+    m_buttonBoxSizer = new wxBoxSizer( wxHORIZONTAL );
+    mainBoxSizer->Add( m_buttonBoxSizer, 0, wxALIGN_RIGHT | wxALL, BORDER_WIDTH );
+
+    // Relayout on resize (for example, so text items will wrap)
+    Bind( wxEVT_SIZE, [ = ] ( wxSizeEvent& event ) {
+        Layout();
+        event.Skip();
+    });
+}
+
+void DIALOG_MULTIBUTTON::SetHintImage( const wxArtID& aImageId )
+{
+    wxBitmap bmp = wxArtProvider::GetBitmap( aImageId, wxART_MESSAGE_BOX, { 48, 48 } );
+
+    // create or update the image
+    if( !m_image )
+    {
+        m_image = new wxStaticBitmap( this, wxID_ANY, bmp );
+        // image goes on the left of the top sizer
+        m_topBoxSizer->Insert( 0, m_image, 0, wxALIGN_TOP | wxALL, BORDER_WIDTH * 2 );
+    }
+    else
+    {
+        m_image->SetBitmap( bmp );
+    }
+}
+
+
+void DIALOG_MULTIBUTTON::AddText( const wxString aMessage )
+{
+    auto text = new wxStaticText( this, wxID_ANY, aMessage );
+
+    m_itemBoxSizer->Add( text, 0, wxGROW | wxALL, BORDER_WIDTH );
+}
+
+
+void DIALOG_MULTIBUTTON::AddButton( int aId, const wxString& aLabel,
+        bool aIsDefault )
+{
+    std::unique_ptr<wxButton> button( new wxButton( this, aId, aLabel ) );
+
+    AddButton( std::move( button ) );
+}
+
+
+void DIALOG_MULTIBUTTON::AddButton( std::unique_ptr<wxButton> aButton,
+        bool aIsDefault )
+{
+    if( aIsDefault )
+    {
+        aButton->SetDefault();
+    }
+
+    const auto endId = aButton->GetId();
+
+    aButton->Bind( wxEVT_BUTTON, [ = ] ( wxCommandEvent& evt ) {
+        EndModal( endId );
+    } );
+
+    m_buttonBoxSizer->Add( aButton.release(), 0, wxGROW | wxALL, BORDER_WIDTH );
+}
+
+
+void DIALOG_MULTIBUTTON::AddItem( std::unique_ptr<wxWindow> aItem )
+{
+    m_itemBoxSizer->Add( aItem.release(), 0, wxGROW | wxALL, BORDER_WIDTH );
+}
+
+
+void DIALOG_MULTIBUTTON::AdjustSize()
+{
+    FinishDialogSettings();
+}
diff --git a/common/dialogs/dialog_multibutton.h b/common/dialogs/dialog_multibutton.h
new file mode 100644
index 000000000..32b6fbf83
--- /dev/null
+++ b/common/dialogs/dialog_multibutton.h
@@ -0,0 +1,123 @@
+/*
+ * 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 COMMON_DIALOGS_DIALOG_MULTIBUTTON_H_
+#define COMMON_DIALOGS_DIALOG_MULTIBUTTON_H_
+
+#include "dialog_shim.h"
+
+#include <memory>
+
+#include <wx/sizer.h>
+#include <wx/string.h>
+#include <wx/artprov.h>
+
+/**
+ * Class DIALOG_MULTIBUTTON
+ *
+ * A simple dialog class that allows you you add a set of items
+ * and buttons to a wxDialog and display it modally, then get the
+ * button IDs when the modal dialog ends.
+ *
+ * This is designed for multiple-choice questions to the user, but
+ * can be used for any easily-laid-out interaction.
+ *
+ * Anything that requires the dialog be smarter about the layout than
+ * just throwing the items into one sizer probably means that this
+ * class is a bad fit.
+ */
+class DIALOG_MULTIBUTTON : public DIALOG_SHIM
+{
+public:
+
+    /**
+     * Construct an empty dialog with the given title.
+     *
+     * You shoulf then add items like text and buttons bofore showing
+     * to the user
+     */
+    DIALOG_MULTIBUTTON( wxWindow* aParent, const wxString& aTitle );
+
+    /**
+     * Add a block of text to the dialog.
+     */
+    void AddText( const wxString aMessage );
+
+    /*!
+     * Construct and add a button to the dialog, show along the bottom.
+     * A click on this button will end the modal dialog
+     *
+     * If you use a stock ID and empty label, a stock button will be
+     * created and used
+     *
+     * @param aId the button ID, returned on modal end from that button
+     * @param aLabel the button label
+     * @param aIsDefault set this button as the default for the dialog
+     */
+    void AddButton( int aId, const wxString& aLabel,
+            bool aIsDefault = false );
+
+    /**
+     * Add an existing button to the dialog's bottom row. Clicking
+     * the button will call EndModal with the button's ID.
+     *
+     * @param aButton a button to take ownership of and add to the dialog
+     * @param aIsDefault set this button as the default for the dialog
+     */
+    void AddButton( std::unique_ptr<wxButton> aButton,
+                    bool aIsDefault = false );
+
+    /**
+     * Set the hint image of the dialog (defualt is no image)
+     *
+     * @param aImageId the wxARTID ID of the image you want.
+     *        wxART_ERROR, wxART_WARNING, wxART_INFOMRATION are likely
+     *        common values.
+     */
+    void SetHintImage( const wxArtID& aImageId );
+
+    /**
+     * Add an existing control to the dialog.
+     * Ownership will be taken, but you can retain a pointer if you want
+     * to retrieve stare before Destroy/destruction. You can also
+     * bind the item events before passing it in.
+     */
+    void AddItem( std::unique_ptr<wxWindow> aItem );
+
+    /**
+     * Adjust the size of the dialog to the contained items and
+     * centre in the parent window.
+     *
+     * Normally called once all items have been added
+     */
+    void AdjustSize();
+
+private:
+    wxBoxSizer* m_itemBoxSizer;
+    wxBoxSizer* m_buttonBoxSizer;
+    wxBoxSizer* m_topBoxSizer;
+    wxStaticBitmap* m_image;
+};
+
+
+#endif /* COMMON_DIALOGS_DIALOG_MULTIBUTTON_H_ */
-- 
2.12.0


Follow ups