diff --git a/src/AudacityApp.cpp b/src/AudacityApp.cpp index 86e456126..94a8bba91 100644 --- a/src/AudacityApp.cpp +++ b/src/AudacityApp.cpp @@ -71,6 +71,7 @@ It handles initialization and termination by subclassing wxApp. #include "AudacityLogger.h" #include "AboutDialog.h" #include "AColor.h" +#include "AudacityFileConfig.h" #include "AudioIO.h" #include "Benchmark.h" #include "Clipboard.h" @@ -188,11 +189,10 @@ void PopulatePreferences() { const wxString fullPath{fn.GetFullPath()}; - FileConfig ini(wxEmptyString, - wxEmptyString, - fullPath, - wxEmptyString, - wxCONFIG_USE_LOCAL_FILE); + auto pIni = + AudacityFileConfig::Create({}, {}, fullPath, {}, + wxCONFIG_USE_LOCAL_FILE); + auto &ini = *pIni; wxString lang; if (ini.Read(wxT("/FromInno/Language"), &lang)) @@ -1245,9 +1245,15 @@ bool AudacityApp::OnInit() #endif // Initialize preferences and language - wxFileName configFileName(FileNames::DataDir(), wxT("audacity.cfg")); - InitPreferences( configFileName ); - PopulatePreferences(); + { + wxFileName configFileName(FileNames::DataDir(), wxT("audacity.cfg")); + auto appName = wxTheApp->GetAppName(); + InitPreferences( AudacityFileConfig::Create( + appName, wxEmptyString, + configFileName.GetFullPath(), + wxEmptyString, wxCONFIG_USE_LOCAL_FILE) ); + PopulatePreferences(); + } #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) && !defined(__CYGWIN__) this->AssociateFileTypes(); diff --git a/src/AudacityFileConfig.cpp b/src/AudacityFileConfig.cpp new file mode 100644 index 000000000..dca72b8aa --- /dev/null +++ b/src/AudacityFileConfig.cpp @@ -0,0 +1,150 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +AudacityFileConfig.cpp + +Paul Licameli split from Prefs.cpp + +**********************************************************************/ + +#include "Audacity.h" +#include "AudacityFileConfig.h" + +#include "widgets/HelpSystem.h" +#include "widgets/wxPanelWrapper.h" +#include "ShuttleGui.h" +#include "../images/Help.xpm" + +#include +#include +#include + +AudacityFileConfig::AudacityFileConfig( + const wxString& appName, + const wxString& vendorName, + const wxString& localFilename, + const wxString& globalFilename, + long style, + const wxMBConv& conv +) +: FileConfig{ appName, vendorName, localFilename, globalFilename, style, conv } +{} + +AudacityFileConfig::~AudacityFileConfig() = default; + +std::unique_ptr AudacityFileConfig::Create( + const wxString& appName, + const wxString& vendorName, + const wxString& localFilename, + const wxString& globalFilename, + long style, + const wxMBConv& conv +) +{ + // Private ctor means make_unique can't compile, so this verbosity: + auto result = std::unique_ptr{ + safenew AudacityFileConfig{ + appName, vendorName, localFilename, globalFilename, style, conv } }; + result->Init(); + return result; +} + +void AudacityFileConfig::Warn(bool canRetry) +{ + wxDialogWrapper dlg(nullptr, wxID_ANY, XO("Audacity Configuration Error")); + + ShuttleGui S(&dlg, eIsCreating); + + wxButton *retryButton; + wxButton *quitButton; + + S.SetBorder(5); + S.StartVerticalLay(wxEXPAND, 1); + { + S.SetBorder(15); + S.StartHorizontalLay(wxALIGN_RIGHT, 0); + { + auto cause = canRetry + ? XO("The following configuration file could not be written:") + : XO("The following configuration file could not be read:"); + + auto retryMsg = canRetry + ? XO("You can attempt to correct the issue and then click \"Retry\" to coninue.\n\n") + : XO(""); + + S.AddFixedText( + XO("%s:\n\n" + "\t%s\n\n" + "This could be caused by many reasons, but the most likely are that " + "the disk is full or you do not have write permissions to the file. " + "More information can be obtained by clicking the help button below.\n\n" + "%s" + "If you choose to \"Quit Audacity\", your project may be left in an unsaved " + "state which will be recovered the next time you open it.") + .Format(cause, GetFilePath(), retryMsg), + false, + 500); + } + S.EndHorizontalLay(); + + S.SetBorder(5); + S.StartHorizontalLay(wxALIGN_RIGHT, 0); + { + // Can't use themed bitmap since the theme manager might not be + // initialized yet and it requires a configuration file. + wxButton *b = S.Id(wxID_HELP).AddBitmapButton(wxBitmap(Help_xpm)); + b->SetToolTip( XO("Help").Translation() ); + b->SetLabel(XO("Help").Translation()); // for screen readers + + b = S.Id(wxID_CANCEL).AddButton(XXO("&Quit Audacity")); + + if (canRetry) + { + b = S.Id(wxID_OK).AddButton(XXO("&Retry")); + dlg.SetAffirmativeId(wxID_OK); + } + + b->SetDefault(); + b->SetFocus(); + } + S.EndHorizontalLay(); + } + S.EndVerticalLay(); + + dlg.Layout(); + dlg.GetSizer()->Fit(&dlg); + dlg.SetMinSize(dlg.GetSize()); + dlg.Center(); + + auto onButton = [&](wxCommandEvent &e) + { + dlg.EndModal(e.GetId()); + }; + + dlg.Bind(wxEVT_BUTTON, onButton); + + switch (dlg.ShowModal()) + { + case wxID_HELP: + // Can't use the HelpSystem since the theme manager may not + // yet be initialized and it requires a configuration file. + OpenInDefaultBrowser("https://" + + HelpSystem::HelpHostname + + HelpSystem::HelpServerHomeDir + + "Error:_Audacity_settings_file_unwritable"); + break; + + case wxID_CANCEL: + // This REALLY needs to use an exception with decent cleanup and program exit + wxExit(); +#if defined(__WXGTK__) + wxAbort(); +#else + exit(-1); +#endif + break; + } + + dlg.Unbind(wxEVT_BUTTON, onButton); +} diff --git a/src/AudacityFileConfig.h b/src/AudacityFileConfig.h new file mode 100644 index 000000000..c5a007161 --- /dev/null +++ b/src/AudacityFileConfig.h @@ -0,0 +1,48 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +@file AudacityFileConfig.h +@brief Extend FileConfig with application-specific behavior + +Paul Licameli split from Prefs.h + +**********************************************************************/ + +#ifndef __AUDACITY_FILE_CONFIG__ +#define __AUDACITY_FILE_CONFIG__ + +#include +#include "widgets/FileConfig.h" // to inherit + +/// \brief Our own specialisation of FileConfig. +class AUDACITY_DLL_API AudacityFileConfig final : public FileConfig +{ +public: + //! Require a call to this factory, to guarantee proper two-phase initialization + static std::unique_ptr Create( + const wxString& appName = {}, + const wxString& vendorName = {}, + const wxString& localFilename = {}, + const wxString& globalFilename = {}, + long style = wxCONFIG_USE_LOCAL_FILE | wxCONFIG_USE_GLOBAL_FILE, + const wxMBConv& conv = wxConvAuto() + ); + + ~AudacityFileConfig() override; + +protected: + void Warn(bool canRetry) override; + +private: + //! Disallow direct constructor call, because a two-phase initialization is required + AudacityFileConfig( + const wxString& appName, + const wxString& vendorName, + const wxString& localFilename, + const wxString& globalFilename, + long style, + const wxMBConv& conv + ); +}; +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7c8e1c84f..cfcc2cb23 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -78,6 +78,8 @@ list( APPEND SOURCES $<$:AudacityApp.mm> AudacityException.cpp AudacityException.h + AudacityFileConfig.cpp + AudacityFileConfig.h AudacityHeaders.cpp AudacityHeaders.h AudacityLogger.cpp diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index 3000df355..be1542d88 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -42,6 +42,7 @@ for shared and private configs - which need to move out. #include "audacity/EffectInterface.h" #include "audacity/ModuleInterface.h" +#include "AudacityFileConfig.h" #include "FileNames.h" #include "ModuleManager.h" #include "PlatformCompatibility.h" @@ -1929,7 +1930,9 @@ bool PluginManager::DropFile(const wxString &fileName) void PluginManager::Load() { // Create/Open the registry - FileConfig registry(wxEmptyString, wxEmptyString, FileNames::PluginRegistry()); + auto pRegistry = AudacityFileConfig::Create( + {}, {}, FileNames::PluginRegistry()); + auto ®istry = *pRegistry; // If this group doesn't exist then we have something that's not a registry. // We should probably warn the user, but it's pretty unlikely that this will happen. @@ -2270,7 +2273,9 @@ void PluginManager::LoadGroup(FileConfig *pRegistry, PluginType type) void PluginManager::Save() { // Create/Open the registry - FileConfig registry(wxEmptyString, wxEmptyString, FileNames::PluginRegistry()); + auto pRegistry = AudacityFileConfig::Create( + {}, {}, FileNames::PluginRegistry()); + auto ®istry = *pRegistry; // Clear it out registry.DeleteAll(); @@ -2777,7 +2782,8 @@ FileConfig *PluginManager::GetSettings() { if (!mSettings) { - mSettings = std::make_unique(wxEmptyString, wxEmptyString, FileNames::PluginSettings()); + mSettings = + AudacityFileConfig::Create({}, {}, FileNames::PluginSettings()); // Check for a settings version that we can understand if (mSettings->HasEntry(SETVERKEY)) @@ -2805,7 +2811,7 @@ FileConfig *PluginManager::GetSettings() bool PluginManager::HasGroup(const RegistryPath & group) { - FileConfig *settings = GetSettings(); + auto settings = GetSettings(); bool res = settings->HasGroup(group); if (res) diff --git a/src/Prefs.cpp b/src/Prefs.cpp index 18572a13e..6f02f668d 100755 --- a/src/Prefs.cpp +++ b/src/Prefs.cpp @@ -62,9 +62,9 @@ #include "Internat.h" #include "MemoryX.h" -std::unique_ptr ugPrefs {}; +std::unique_ptr ugPrefs {}; -AudacityPrefs *gPrefs = NULL; +FileConfig *gPrefs = nullptr; int gMenusDirty = 0; wxDEFINE_EVENT(EVT_PREFS_UPDATE, wxCommandEvent); @@ -171,33 +171,10 @@ static void CopyEntriesRecursive(wxString path, wxConfigBase *src, wxConfigBase } #endif -AudacityPrefs::AudacityPrefs(const wxString& appName, - const wxString& vendorName, - const wxString& localFilename, - const wxString& globalFilename, - long style, - const wxMBConv& conv) : - FileConfig(appName, - vendorName, - localFilename, - globalFilename, - style, - conv) +void InitPreferences( std::unique_ptr uPrefs ) { -} - - - -void InitPreferences( const wxFileName &configFileName ) -{ - wxString appName = wxTheApp->GetAppName(); - - ugPrefs = std::make_unique - (appName, wxEmptyString, - configFileName.GetFullPath(), - wxEmptyString, wxCONFIG_USE_LOCAL_FILE); - gPrefs = ugPrefs.get(); - + gPrefs = uPrefs.get(); + ugPrefs = std::move(uPrefs); wxConfigBase::Set(gPrefs); } diff --git a/src/Prefs.h b/src/Prefs.h index d16a65fb8..985670aad 100644 --- a/src/Prefs.h +++ b/src/Prefs.h @@ -40,50 +40,13 @@ class wxFileName; -void InitPreferences( const wxFileName &configFileName ); +void InitPreferences( std::unique_ptr uPrefs ); void FinishPreferences(); -class AudacityPrefs; - - -extern AUDACITY_DLL_API AudacityPrefs *gPrefs; +extern AUDACITY_DLL_API FileConfig *gPrefs; extern int gMenusDirty; -/// \brief Our own specialisation of wxFileConfig. It is essentially a renaming, -/// though it does provide one new access function. Most of the prefs work -/// is actually done by the InitPreferences() function. -class AUDACITY_DLL_API AudacityPrefs : public FileConfig -{ -public: - AudacityPrefs(const wxString& appName = {}, - const wxString& vendorName = {}, - const wxString& localFilename = {}, - const wxString& globalFilename = {}, - long style = wxCONFIG_USE_LOCAL_FILE | wxCONFIG_USE_GLOBAL_FILE, - const wxMBConv& conv = wxConvAuto()); - - // Set and Get values of the version major/minor/micro keys in audacity.cfg when Audacity first opens - void SetVersionKeysInit( int major, int minor, int micro) - { - mVersionMajorKeyInit = major; - mVersionMinorKeyInit = minor; - mVersionMicroKeyInit = micro; - } - void GetVersionKeysInit( int& major, int& minor, int& micro) const - { - major = mVersionMajorKeyInit; - minor = mVersionMinorKeyInit; - micro = mVersionMicroKeyInit; - } - - // values of the version major/minor/micro keys in audacity.cfg - // when Audacity first opens - int mVersionMajorKeyInit{}; - int mVersionMinorKeyInit{}; - int mVersionMicroKeyInit{}; -}; - struct ByColumns_t{}; extern ByColumns_t ByColumns; diff --git a/src/widgets/FileConfig.cpp b/src/widgets/FileConfig.cpp index be8339d4e..d54d9dc56 100644 --- a/src/widgets/FileConfig.cpp +++ b/src/widgets/FileConfig.cpp @@ -20,11 +20,6 @@ #include #include "FileConfig.h" -#include "HelpSystem.h" -#include "wxPanelWrapper.h" -#include "../ShuttleGui.h" - -#include "../../images/Help.xpm" #if !defined(F_OK) #define F_OK 0x00 @@ -45,6 +40,10 @@ FileConfig::FileConfig(const wxString& appName, : wxFileConfig(appName, vendorName, localFilename, globalFilename, style, conv), mConfigPath(localFilename), mDirty(false) +{ +} + +void FileConfig::Init() { // Prevent wxFileConfig from attempting a Flush() during object deletion. This happens // because we don't use the wxFileConfig::Flush() method and so the wxFileConfig dirty @@ -87,6 +86,8 @@ FileConfig::FileConfig(const wxString& appName, // // If the wxFileConfig class allowed us to call wxFileConfig::Init(), we wouldn't // have to do all this mess. + // (Note that invocation of virtual Warn() can't be done in the ctor, + // which is why this is two-phase construction.) Warn(canRead == true); } } @@ -173,102 +174,3 @@ bool FileConfig::DoWriteBinary(const wxString& key, const wxMemoryBuffer& buf) return res; } #endif // wxUSE_BASE64 - -void FileConfig::Warn(bool canRetry) -{ - wxDialogWrapper dlg(nullptr, wxID_ANY, XO("Audacity Configuration Error")); - - ShuttleGui S(&dlg, eIsCreating); - - wxButton *retryButton; - wxButton *quitButton; - - S.SetBorder(5); - S.StartVerticalLay(wxEXPAND, 1); - { - S.SetBorder(15); - S.StartHorizontalLay(wxALIGN_RIGHT, 0); - { - auto cause = canRetry - ? XO("The following configuration file could not be written:") - : XO("The following configuration file could not be read:"); - - auto retryMsg = canRetry - ? XO("You can attempt to correct the issue and then click \"Retry\" to coninue.\n\n") - : XO(""); - - S.AddFixedText( - XO("%s:\n\n" - "\t%s\n\n" - "This could be caused by many reasons, but the most likely are that " - "the disk is full or you do not have write permissions to the file. " - "More information can be obtained by clicking the help button below.\n\n" - "%s" - "If you choose to \"Quit Audacity\", your project may be left in an unsaved " - "state which will be recovered the next time you open it.") - .Format(cause, mConfigPath, retryMsg), - false, - 500); - } - S.EndHorizontalLay(); - - S.SetBorder(5); - S.StartHorizontalLay(wxALIGN_RIGHT, 0); - { - // Can't use themed bitmap since the theme manager might not be - // initialized yet and it requires a configuration file. - wxButton *b = S.Id(wxID_HELP).AddBitmapButton(wxBitmap(Help_xpm)); - b->SetToolTip( XO("Help").Translation() ); - b->SetLabel(XO("Help").Translation()); // for screen readers - - b = S.Id(wxID_CANCEL).AddButton(XXO("&Quit Audacity")); - - if (canRetry) - { - b = S.Id(wxID_OK).AddButton(XXO("&Retry")); - dlg.SetAffirmativeId(wxID_OK); - } - - b->SetDefault(); - b->SetFocus(); - } - S.EndHorizontalLay(); - } - S.EndVerticalLay(); - - dlg.Layout(); - dlg.GetSizer()->Fit(&dlg); - dlg.SetMinSize(dlg.GetSize()); - dlg.Center(); - - auto onButton = [&](wxCommandEvent &e) - { - dlg.EndModal(e.GetId()); - }; - - dlg.Bind(wxEVT_BUTTON, onButton); - - switch (dlg.ShowModal()) - { - case wxID_HELP: - // Can't use the HelpSystem since the theme manager may not - // yet be initialized and it requires a configuration file. - OpenInDefaultBrowser("https://" + - HelpSystem::HelpHostname + - HelpSystem::HelpServerHomeDir + - "Error:_Audacity_settings_file_unwritable"); - break; - - case wxID_CANCEL: - // This REALLY needs to use an exception with decent cleanup and program exit - wxExit(); -#if defined(__WXGTK__) - wxAbort(); -#else - exit(-1); -#endif - break; - } - - dlg.Unbind(wxEVT_BUTTON, onButton); -} diff --git a/src/widgets/FileConfig.h b/src/widgets/FileConfig.h index 7d830fbc4..cb64d89c7 100644 --- a/src/widgets/FileConfig.h +++ b/src/widgets/FileConfig.h @@ -25,10 +25,25 @@ public: const wxString& globalFilename = wxEmptyString, long style = wxCONFIG_USE_LOCAL_FILE | wxCONFIG_USE_GLOBAL_FILE, const wxMBConv& conv = wxConvAuto()); + void Init(); virtual ~FileConfig(); virtual bool Flush(bool bCurrentOnly = false) wxOVERRIDE; + // Set and Get values of the version major/minor/micro keys in audacity.cfg when Audacity first opens + void SetVersionKeysInit( int major, int minor, int micro) + { + mVersionMajorKeyInit = major; + mVersionMinorKeyInit = minor; + mVersionMicroKeyInit = micro; + } + void GetVersionKeysInit( int& major, int& minor, int& micro) const + { + major = mVersionMajorKeyInit; + minor = mVersionMinorKeyInit; + micro = mVersionMicroKeyInit; + } + protected: virtual bool DoWriteString(const wxString& key, const wxString& szValue) wxOVERRIDE; virtual bool DoWriteLong(const wxString& key, long lValue) wxOVERRIDE; @@ -36,10 +51,21 @@ protected: virtual bool DoWriteBinary(const wxString& key, const wxMemoryBuffer& buf) wxOVERRIDE; #endif // wxUSE_BASE64 -private: - void Warn(bool canRetry = true); +protected: + //! Override to notify the user of error conditions involving writability of config files + virtual void Warn(bool canRetry = true) = 0; + + const FilePath &GetFilePath() const { return mConfigPath; } + +private: + const FilePath mConfigPath; + + // values of the version major/minor/micro keys in audacity.cfg + // when Audacity first opens + int mVersionMajorKeyInit{}; + int mVersionMinorKeyInit{}; + int mVersionMicroKeyInit{}; - FilePath mConfigPath; bool mDirty; };