1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-10-20 17:41:13 +02:00

New library for preferences...

... It mentions some wxWidgets types in its interface, but these are in the
acceptable utility subset of wxBase that we still consider GUI toolkit-neutral.
This commit is contained in:
Paul Licameli
2021-02-18 09:08:38 -05:00
parent a2f109de2e
commit 7e50e9b5af
114 changed files with 158 additions and 137 deletions

View File

@@ -10,6 +10,7 @@ set( LIBRARIES
lib-components
lib-basic-ui
lib-exceptions
lib-preferences
)
if ( ${_OPT}has_networking )

View File

@@ -0,0 +1,32 @@
#[[
Classes managing settings that persist between runs of the program.
Settings objects for various types (boolean, numbers, strings, enumerations)
that package a preference key path and a default value (so that literals are
not duplicated in widely separated parts) and cache values in memory.
PreferenceListener which is a callback notified of certain changes of
preferences.
PreferenceInitializer is a callback for the reinitialization of preferences.
FileConfig decorates wxFileConfig, with a (pure virtual) member function to
alert the user of failure to initialize it (as when the file is read-only).
]]#
set( SOURCES
FileConfig.cpp
FileConfig.h
Prefs.cpp
Prefs.h
)
set( LIBRARIES
lib-utility-interface
lib-basic-ui-interface
lib-components-interface
PRIVATE
wxBase
)
audacity_library( lib-preferences "${SOURCES}" "${LIBRARIES}"
"" ""
)

View File

@@ -0,0 +1,286 @@
/**********************************************************************
Audacity: A Digital Audio Editor
FileConfig.cpp
Leland Lucius
**********************************************************************/
#include <errno.h>
#include <wx/wfstream.h>
#include "FileConfig.h"
#include <cerrno> // for ENOENT
#if !defined(F_OK)
#define F_OK 0x00
#endif
#if !defined(W_OK)
#define W_OK 0x02
#endif
#if !defined(R_OK)
#define R_OK 0x04
#endif
FileConfig::FileConfig(const wxString& appName,
const wxString& vendorName,
const wxString& localFilename,
const wxString& globalFilename,
long style,
const wxMBConv& conv)
: wxConfigBase(appName, vendorName, localFilename, globalFilename, style),
mAppName(appName),
mVendorName(vendorName),
mLocalFilename(localFilename),
mGlobalFilename(globalFilename),
mStyle(style),
mConv(conv),
mDirty(false)
{
}
void FileConfig::Init()
{
while (true)
{
mConfig = std::make_unique<wxFileConfig>
(mAppName, mVendorName, mLocalFilename, mGlobalFilename, mStyle, mConv);
// 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
// flag never gets reset. During deletion, the dirty flag is checked and a Flush()
// performed. This can (and probably will) create bogus temporary files.
mConfig->DisableAutoSave();
bool canRead = false;
bool canWrite = false;
int fd;
fd = wxOpen(mLocalFilename, O_RDONLY, S_IREAD);
if (fd != -1 || errno == ENOENT)
{
canRead = true;
if (fd != -1)
{
wxClose(fd);
}
}
fd = wxOpen(mLocalFilename, O_WRONLY | O_CREAT, S_IWRITE);
if (fd != -1)
{
canWrite = true;
wxClose(fd);
}
if (canRead && canWrite)
{
break;
}
Warn();
}
}
FileConfig::~FileConfig()
{
wxASSERT(mDirty == false);
}
void FileConfig::SetPath(const wxString& strPath)
{
mConfig->SetPath(strPath);
}
const wxString& FileConfig::GetPath() const
{
return mConfig->GetPath();
}
bool FileConfig::GetFirstGroup(wxString& str, long& lIndex) const
{
return mConfig->GetFirstGroup(str, lIndex);
}
bool FileConfig::GetNextGroup(wxString& str, long& lIndex) const
{
return mConfig->GetNextGroup(str, lIndex);
}
bool FileConfig::GetFirstEntry(wxString& str, long& lIndex) const
{
return mConfig->GetFirstEntry(str, lIndex);
}
bool FileConfig::GetNextEntry(wxString& str, long& lIndex) const
{
return mConfig->GetNextEntry(str, lIndex);
}
size_t FileConfig::GetNumberOfEntries(bool bRecursive) const
{
return mConfig->GetNumberOfEntries(bRecursive);
}
size_t FileConfig::GetNumberOfGroups(bool bRecursive) const
{
return mConfig->GetNumberOfGroups(bRecursive);
}
bool FileConfig::HasGroup(const wxString& strName) const
{
return mConfig->HasGroup(strName);
}
bool FileConfig::HasEntry(const wxString& strName) const
{
return mConfig->HasEntry(strName);
}
bool FileConfig::Flush(bool WXUNUSED(bCurrentOnly))
{
if (!mDirty)
{
return true;
}
while (true)
{
FilePath backup = mLocalFilename + ".bkp";
if (!wxFileExists(backup) || (wxRemove(backup) == 0))
{
if (!wxFileExists(mLocalFilename) || (wxRename(mLocalFilename, backup) == 0))
{
wxFileOutputStream stream(mLocalFilename);
if (stream.IsOk())
{
if (mConfig->Save(stream))
{
stream.Sync();
if (stream.IsOk() && stream.Close())
{
if (!wxFileExists(backup) || (wxRemove(backup) == 0))
{
mDirty = false;
return true;
}
}
}
}
if (wxFileExists(backup))
{
wxRemove(mLocalFilename);
wxRename(backup, mLocalFilename);
}
}
}
Warn();
}
return false;
}
bool FileConfig::RenameEntry(const wxString& oldName, const wxString& newName)
{
auto res = mConfig->RenameEntry(oldName, newName);
if (res)
{
mDirty = true;
}
return res;
}
bool FileConfig::RenameGroup(const wxString& oldName, const wxString& newName)
{
auto res = mConfig->RenameGroup(oldName, newName);
if (res)
{
mDirty = true;
}
return res;
}
bool FileConfig::DeleteEntry(const wxString& key, bool bDeleteGroupIfEmpty)
{
auto res = mConfig->DeleteEntry(key, bDeleteGroupIfEmpty);
if (res)
{
mDirty = true;
}
return res;
}
bool FileConfig::DeleteGroup(const wxString& key)
{
auto res = mConfig->DeleteGroup(key);
if (res)
{
mDirty = true;
}
return res;
}
bool FileConfig::DeleteAll()
{
auto res = mConfig->DeleteAll();
if (res)
{
mDirty = true;
}
return res;
}
bool FileConfig::DoReadString(const wxString& key, wxString *pStr) const
{
return mConfig->Read(key, pStr);
}
bool FileConfig::DoReadLong(const wxString& key, long *pl) const
{
return mConfig->Read(key, pl);
}
#if wxUSE_BASE64
bool FileConfig::DoReadBinary(const wxString& key, wxMemoryBuffer* buf) const
{
return mConfig->Read(key, buf);
}
#endif // wxUSE_BASE64
bool FileConfig::DoWriteString(const wxString& key, const wxString& szValue)
{
bool res = mConfig->Write(key, szValue);
if (res)
{
mDirty = true;
}
return res;
}
bool FileConfig::DoWriteLong(const wxString& key, long lValue)
{
bool res = mConfig->Write(key, lValue);
if (res)
{
mDirty = true;
}
return res;
}
#if wxUSE_BASE64
bool FileConfig::DoWriteBinary(const wxString& key, const wxMemoryBuffer& buf)
{
bool res = mConfig->Write(key, buf);
if (res)
{
mDirty = true;
}
return res;
}
#endif // wxUSE_BASE64

View File

@@ -0,0 +1,103 @@
/**********************************************************************
Audacity: A Digital Audio Editor
FileConfig.h
Leland Lucius
**********************************************************************/
#ifndef __AUDACITY_WIDGETS_FILECONFIG__
#define __AUDACITY_WIDGETS_FILECONFIG__
#include <memory>
#include <wx/defs.h>
#include <wx/fileconf.h>
#include "Identifier.h"
class PREFERENCES_API FileConfig : public wxConfigBase
{
public:
FileConfig(const wxString& appName = wxEmptyString,
const wxString& vendorName = wxEmptyString,
const wxString& localFilename = wxEmptyString,
const wxString& globalFilename = wxEmptyString,
long style = wxCONFIG_USE_LOCAL_FILE | wxCONFIG_USE_GLOBAL_FILE,
const wxMBConv& conv = wxConvAuto());
void Init();
virtual ~FileConfig();
virtual void SetPath(const wxString& strPath) wxOVERRIDE;
virtual const wxString& GetPath() const wxOVERRIDE;
virtual bool GetFirstGroup(wxString& str, long& lIndex) const wxOVERRIDE;
virtual bool GetNextGroup(wxString& str, long& lIndex) const wxOVERRIDE;
virtual bool GetFirstEntry(wxString& str, long& lIndex) const wxOVERRIDE;
virtual bool GetNextEntry(wxString& str, long& lIndex) const wxOVERRIDE;
virtual size_t GetNumberOfEntries(bool bRecursive = false) const wxOVERRIDE;
virtual size_t GetNumberOfGroups(bool bRecursive = false) const wxOVERRIDE;
virtual bool HasGroup(const wxString& strName) const wxOVERRIDE;
virtual bool HasEntry(const wxString& strName) const wxOVERRIDE;
virtual bool Flush(bool bCurrentOnly = false) wxOVERRIDE;
virtual bool RenameEntry(const wxString& oldName, const wxString& newName) wxOVERRIDE;
virtual bool RenameGroup(const wxString& oldName, const wxString& newName) wxOVERRIDE;
virtual bool DeleteEntry(const wxString& key, bool bDeleteGroupIfEmpty = true) wxOVERRIDE;
virtual bool DeleteGroup(const wxString& key) wxOVERRIDE;
virtual bool DeleteAll() 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 DoReadString(const wxString& key, wxString *pStr) const wxOVERRIDE;
virtual bool DoReadLong(const wxString& key, long *pl) const wxOVERRIDE;
#if wxUSE_BASE64
virtual bool DoReadBinary(const wxString& key, wxMemoryBuffer* buf) const wxOVERRIDE;
#endif // wxUSE_BASE64
virtual bool DoWriteString(const wxString& key, const wxString& szValue) wxOVERRIDE;
virtual bool DoWriteLong(const wxString& key, long lValue) wxOVERRIDE;
#if wxUSE_BASE64
virtual bool DoWriteBinary(const wxString& key, const wxMemoryBuffer& buf) wxOVERRIDE;
#endif // wxUSE_BASE64
protected:
//! Override to notify the user of error conditions involving writability of config files
virtual void Warn() = 0;
const FilePath &GetFilePath() const { return mLocalFilename; }
private:
const wxString mAppName;
const wxString mVendorName;
const wxString mLocalFilename;
const wxString mGlobalFilename;
const long mStyle;
const wxMBConv & mConv;
std::unique_ptr<wxFileConfig> mConfig;
// values of the version major/minor/micro keys in audacity.cfg
// when Audacity first opens
int mVersionMajorKeyInit{};
int mVersionMinorKeyInit{};
int mVersionMicroKeyInit{};
bool mDirty;
};
#endif

View File

@@ -0,0 +1,461 @@
/**********************************************************************
Audacity: A Digital Audio Editor
Prefs.cpp
Dominic Mazzoni
*******************************************************************//*!
\file Prefs.cpp
\brief Utility functions for working with our wxConf (gPrefs)
Audacity uses wxWidgets' wxConfig class to handle preferences.
See Prefs.h for more information on how it works...
\verbatim
Note: The info below is very outdated and incomplete
Preference field specification:
/
Version - Audacity Version that created these prefs
DefaultOpenPath - Default directory for NEW file selector
/FileFormats
CopyOrEditUncompressedData - Copy data from uncompressed files or
[ "copy", "edit"] - edit in place?
ExportFormat_SF1 - Format to export PCM data in
(this number is a libsndfile1.0 format)
/SamplingRate
DefaultProjectSampleRate- New projects will have this rate
[ 8000, 11025, 16000, 22050, 44100, 48000 ]
/AudioIO
PlaybackDevice - device to use for playback
RecordingDevice - device to use for recording
(these are device names understood by PortAudio)
/Display
WaveformColor - 0xRRGGBB --since it will be stored in
ShadowColor - decimal, it will be somewhat
SpectrumLowColor - non-intuitive to edit, but
SpectrumHighColor - much easier to parse.
/Locale
Language - two-letter language code for translations
(*): wxGTK
(+): wxWin
($): wxMac
\endverbatim
*//*******************************************************************/
#include "Prefs.h"
#include <wx/defs.h>
#include <wx/app.h>
#include <wx/intl.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include "Internat.h"
#include "MemoryX.h"
#include "BasicUI.h"
BoolSetting DefaultUpdatesCheckingFlag{
L"/Update/DefaultUpdatesChecking", true };
std::unique_ptr<FileConfig> ugPrefs {};
FileConfig *gPrefs = nullptr;
int gMenusDirty = 0;
struct MyEvent : wxEvent
{
public:
explicit MyEvent(int id) : mId{id} {}
virtual wxEvent *Clone() const override { return new MyEvent{mId}; }
int mId;
};
wxDECLARE_EVENT(EVT_PREFS_UPDATE, MyEvent);
wxDEFINE_EVENT(EVT_PREFS_UPDATE, MyEvent);
struct PrefsListener::Impl : wxEvtHandler
{
Impl( PrefsListener &owner );
~Impl();
void OnEvent(wxEvent&);
PrefsListener &mOwner;
};
static wxEvtHandler hub;
void PrefsListener::Broadcast(int id)
{
BasicUI::CallAfter([id]{
MyEvent event{ id };
hub.ProcessEvent(event);
});
}
PrefsListener::Impl::Impl( PrefsListener &owner )
: mOwner{ owner }
{
hub.Bind(EVT_PREFS_UPDATE, &PrefsListener::Impl::OnEvent, this);
}
PrefsListener::Impl::~Impl()
{
}
PrefsListener::PrefsListener()
: mpImpl{ std::make_unique<Impl>( *this ) }
{
}
PrefsListener::~PrefsListener()
{
}
void PrefsListener::UpdateSelectedPrefs( int )
{
}
void PrefsListener::Impl::OnEvent( wxEvent &evt )
{
evt.Skip();
auto id = evt.GetId();
if (id <= 0)
mOwner.UpdatePrefs();
else
mOwner.UpdateSelectedPrefs( id );
}
#if 0
// Copy one entry from one wxConfig object to another
static void CopyEntry(wxString path, wxConfigBase *src, wxConfigBase *dst, wxString entry)
{
switch(src->GetEntryType(entry)) {
case wxConfigBase::Type_Unknown:
case wxConfigBase::Type_String: {
wxString value = src->Read(entry, wxT(""));
dst->Write(path + entry, value);
break;
}
case wxConfigBase::Type_Boolean: {
bool value = false;
src->Read(entry, &value, value);
dst->Write(path + entry, value);
break;
}
case wxConfigBase::Type_Integer: {
long value = false;
src->Read(entry, &value, value);
dst->Write(path + entry, value);
break;
}
case wxConfigBase::Type_Float: {
double value = false;
src->Read(entry, &value, value);
dst->Write(path + entry, value);
break;
}
}
}
// Recursive routine to copy all groups and entries from one wxConfig object to another
static void CopyEntriesRecursive(wxString path, wxConfigBase *src, wxConfigBase *dst)
{
wxString entryName;
long entryIndex;
bool entryKeepGoing;
entryKeepGoing = src->GetFirstEntry(entryName, entryIndex);
while (entryKeepGoing) {
CopyEntry(path, src, dst, entryName);
entryKeepGoing = src->GetNextEntry(entryName, entryIndex);
}
wxString groupName;
long groupIndex;
bool groupKeepGoing;
groupKeepGoing = src->GetFirstGroup(groupName, groupIndex);
while (groupKeepGoing) {
wxString subPath = path+groupName+wxT("/");
src->SetPath(subPath);
CopyEntriesRecursive(subPath, src, dst);
src->SetPath(path);
groupKeepGoing = src->GetNextGroup(groupName, groupIndex);
}
}
#endif
void InitPreferences( std::unique_ptr<FileConfig> uPrefs )
{
gPrefs = uPrefs.get();
ugPrefs = std::move(uPrefs);
wxConfigBase::Set(gPrefs);
}
void ResetPreferences()
{
// Future: make this a static registry table, so the settings objects
// don't need to be defined in this source code file to avoid dependency
// cycles
std::pair<BoolSetting &, bool> stickyBoolSettings[] {
{DefaultUpdatesCheckingFlag, 0},
// ... others?
};
for (auto &pair : stickyBoolSettings)
pair.second = pair.first.Read();
bool savedValue = DefaultUpdatesCheckingFlag.Read();
gPrefs->DeleteAll();
for (auto &pair : stickyBoolSettings)
pair.first.Write(pair.second);
}
void FinishPreferences()
{
if (gPrefs) {
wxConfigBase::Set(NULL);
ugPrefs.reset();
gPrefs = NULL;
}
}
//////////
EnumValueSymbols::EnumValueSymbols(
ByColumns_t,
const TranslatableStrings &msgids,
wxArrayStringEx internals
)
: mInternals( std::move( internals ) )
{
auto size = mInternals.size(), size2 = msgids.size();
if ( size != size2 ) {
wxASSERT( false );
size = std::min( size, size2 );
}
reserve( size );
auto iter1 = mInternals.begin();
auto iter2 = msgids.begin();
while( size-- )
emplace_back( *iter1++, *iter2++ );
}
const TranslatableStrings &EnumValueSymbols::GetMsgids() const
{
if ( mMsgids.empty() )
mMsgids = transform_container<TranslatableStrings>( *this,
std::mem_fn( &EnumValueSymbol::Msgid ) );
return mMsgids;
}
const wxArrayStringEx &EnumValueSymbols::GetInternals() const
{
if ( mInternals.empty() )
mInternals = transform_container<wxArrayStringEx>( *this,
std::mem_fn( &EnumValueSymbol::Internal ) );
return mInternals;
}
//////////
const EnumValueSymbol &ChoiceSetting::Default() const
{
if ( mDefaultSymbol >= 0 && mDefaultSymbol < (long)mSymbols.size() )
return mSymbols[ mDefaultSymbol ];
static EnumValueSymbol empty;
return empty;
}
wxString ChoiceSetting::Read() const
{
const auto &defaultValue = Default().Internal();
return ReadWithDefault( defaultValue );
}
wxString ChoiceSetting::ReadWithDefault( const wxString &defaultValue ) const
{
wxString value;
if ( !gPrefs->Read(mKey, &value, defaultValue) )
if (!mMigrated) {
const_cast<ChoiceSetting*>(this)->Migrate( value );
mMigrated = true;
}
// Remap to default if the string is not known -- this avoids surprises
// in case we try to interpret config files from future versions
auto index = Find( value );
if ( index >= mSymbols.size() )
value = defaultValue;
return value;
}
size_t ChoiceSetting::Find( const wxString &value ) const
{
auto start = GetSymbols().begin();
return size_t(
std::find( start, GetSymbols().end(), EnumValueSymbol{ value, {} } )
- start );
}
void ChoiceSetting::Migrate( wxString &value )
{
(void)value;// Compiler food
}
bool ChoiceSetting::Write( const wxString &value )
{
auto index = Find( value );
if (index >= mSymbols.size())
return false;
auto result = gPrefs->Write( mKey, value );
mMigrated = true;
return result;
}
EnumSettingBase::EnumSettingBase(
const SettingBase &key,
EnumValueSymbols symbols,
long defaultSymbol,
std::vector<int> intValues, // must have same size as symbols
const wxString &oldKey
)
: ChoiceSetting{ key, std::move( symbols ), defaultSymbol }
, mIntValues{ std::move( intValues ) }
, mOldKey{ oldKey }
{
auto size = mSymbols.size();
if( mIntValues.size() != size ) {
wxASSERT( false );
mIntValues.resize( size );
}
}
void ChoiceSetting::SetDefault( long value )
{
if ( value < (long)mSymbols.size() )
mDefaultSymbol = value;
else
wxASSERT( false );
}
int EnumSettingBase::ReadInt() const
{
auto index = Find( Read() );
wxASSERT( index < mIntValues.size() );
return mIntValues[ index ];
}
int EnumSettingBase::ReadIntWithDefault( int defaultValue ) const
{
wxString defaultString;
auto index0 = FindInt( defaultValue );
if ( index0 < mSymbols.size() )
defaultString = mSymbols[ index0 ].Internal();
else
wxASSERT( false );
auto index = Find( ReadWithDefault( defaultString ) );
wxASSERT( index < mSymbols.size() );
return mIntValues[ index ];
}
size_t EnumSettingBase::FindInt( int code ) const
{
const auto start = mIntValues.begin();
return size_t(
std::find( start, mIntValues.end(), code )
- start );
}
void EnumSettingBase::Migrate( wxString &value )
{
int intValue = 0;
if ( !mOldKey.empty() &&
gPrefs->Read(mOldKey, &intValue, 0) ) {
// Make the migration, only once and persistently.
// Do not DELETE the old key -- let that be read if user downgrades
// Audacity. But further changes will be stored only to the NEW key
// and won't be seen then.
auto index = (long) FindInt( intValue );
if ( index >= (long)mSymbols.size() )
index = mDefaultSymbol;
if ( index >= 0 && index < (long)mSymbols.size() ) {
value = mSymbols[index].Internal();
Write(value);
gPrefs->Flush();
}
}
}
bool EnumSettingBase::WriteInt( int code ) // you flush gPrefs afterward
{
auto index = FindInt( code );
if ( index >= mSymbols.size() )
return false;
return Write( mSymbols[index].Internal() );
}
wxString WarningDialogKey(const wxString &internalDialogName)
{
return wxT("/Warnings/") + internalDialogName;
}
ByColumns_t ByColumns{};
#include <set>
namespace {
using PreferenceInitializers = std::set< PreferenceInitializer* >;
PreferenceInitializers &allInitializers()
{
static PreferenceInitializers theSet;
return theSet;
}
}
PreferenceInitializer::PreferenceInitializer()
{
allInitializers().insert( this );
}
PreferenceInitializer::~PreferenceInitializer()
{
allInitializers().erase( this );
}
void PreferenceInitializer::ReinitializeAll()
{
for ( auto pInitializer : allInitializers() )
(*pInitializer)();
}
wxConfigBase *SettingBase::GetConfig() const
{
return gPrefs;
}
bool SettingBase::Delete()
{
auto config = GetConfig();
return config && config->DeleteEntry( GetPath() );
}
bool BoolSetting::Toggle()
{
bool value = Read();
if ( Write( !value ) )
return !value;
else
return value;
}

View File

@@ -0,0 +1,440 @@
/**********************************************************************
Audacity: A Digital Audio Editor
Prefs.h
Dominic Mazzoni
Markus Meyer
Audacity uses wxWidgets' wxFileConfig class to handle preferences.
In Audacity versions prior to 1.3.1, it used wxConfig, which would
store the prefs in a platform-dependent way (e.g. in the registry
on Windows). Now it always stores the settings in a configuration file
in the Audacity Data Directory.
Every time we read a preference, we need to specify the default
value for that preference, to be used if the preference hasn't
been set before.
So, to avoid code duplication, we provide functions in this file
to read and write preferences which have a nonobvious default
value, so that if we later want to change this value, we only
have to change it in one place.
See Prefs.cpp for a (complete?) list of preferences we keep
track of...
**********************************************************************/
#ifndef __AUDACITY_PREFS__
#define __AUDACITY_PREFS__
// Increment this every time the prefs need to be reset
// the first part (before the r) indicates the version the reset took place
// the second part (after the r) indicates the number of times the prefs have been reset within the same version
#define AUDACITY_PREFS_VERSION_STRING "1.1.1r1"
#include <functional>
#include "ComponentInterfaceSymbol.h"
#include "wxArrayStringEx.h"
#include "FileConfig.h"
#include <memory>
#include <wx/event.h> // to declare custom event types
class wxFileName;
PREFERENCES_API void InitPreferences( std::unique_ptr<FileConfig> uPrefs );
//! Call this to reset preferences to an (almost)-"new" default state
/*!
There is at least one exception to that: user preferences we want to make
more "sticky." Notably, whether automatic update checking is preferred.
*/
PREFERENCES_API void ResetPreferences();
PREFERENCES_API void FinishPreferences();
extern PREFERENCES_API FileConfig *gPrefs;
extern int gMenusDirty;
struct ByColumns_t{};
extern PREFERENCES_API ByColumns_t ByColumns;
//! Base class for settings objects. It holds a configuration key path.
/* The constructors are non-explicit for convenience */
class PREFERENCES_API SettingBase
{
public:
SettingBase( const char *path ) : mPath{ path } {}
SettingBase( const wxChar *path ) : mPath{ path } {}
SettingBase( const wxString &path ) : mPath{ path } {}
wxConfigBase *GetConfig() const;
const wxString &GetPath() const { return mPath; }
//! Delete the key if present, and return true iff it was.
bool Delete();
protected:
SettingBase( const SettingBase& ) = default;
const RegistryPath mPath;
};
//! Class template adds an in-memory cache of a value to SettingBase.
template< typename T >
class CachingSettingBase : public SettingBase
{
public:
explicit CachingSettingBase( const SettingBase &path )
: SettingBase{ path } {}
protected:
CachingSettingBase( const CachingSettingBase & ) = default;
mutable T mCurrentValue{};
mutable bool mValid{false};
};
//! Class template adds default value, read, and write methods to CachingSetingBase
template< typename T >
class Setting : public CachingSettingBase< T >
{
public:
using CachingSettingBase< T >::CachingSettingBase;
using DefaultValueFunction = std::function< T() >;
//! Usual overload supplies a default value
Setting( const SettingBase &path, const T &defaultValue )
: CachingSettingBase< T >{ path }
, mDefaultValue{ defaultValue }
{}
//! This overload causes recomputation of the default each time it is needed
Setting( const SettingBase &path, DefaultValueFunction function )
: CachingSettingBase< T >{ path }
, mFunction{ function }
{}
const T& GetDefault() const
{
if ( mFunction )
mDefaultValue = mFunction();
return mDefaultValue;
}
//! overload of Read returning a boolean that is true if the value was previously defined */
bool Read( T *pVar ) const
{
return ReadWithDefault( pVar, GetDefault() );
}
//! overload of ReadWithDefault returning a boolean that is true if the value was previously defined */
bool ReadWithDefault( T *pVar, const T& defaultValue ) const
{
if ( pVar )
*pVar = defaultValue;
if ( pVar && this->mValid ) {
*pVar = this->mCurrentValue;
return true;
}
const auto config = this->GetConfig();
if ( pVar && config ) {
if ((this->mValid = config->Read( this->mPath, &this->mCurrentValue )))
*pVar = this->mCurrentValue;
return this->mValid;
}
return (this->mValid = false);
}
//! overload of Read, always returning a value
/*! The value is the default stored in this in case the key is known to be absent from the config;
but it returns type T's default value if there was failure to read the config */
T Read() const
{
return ReadWithDefault( GetDefault() );
}
//! new direct use is discouraged but it may be needed in legacy code
/*! Use the given default in case the preference is not defined, which may not be the
default-default stored in this object. */
T ReadWithDefault( const T &defaultValue ) const
{
const auto config = this->GetConfig();
return config
? ( this->mValid = true, this->mCurrentValue =
config->ReadObject( this->mPath, defaultValue ) )
: T{};
}
//! Write value to config and return true if successful
bool Write( const T &value )
{
const auto config = this->GetConfig();
if ( config ) {
this->mCurrentValue = value;
return DoWrite();
}
return false;
}
//! Reset to the default value
bool Reset()
{
return Write( GetDefault() );
}
protected:
//! Write cached value to config and return true if successful
/*! (But the config object is not flushed) */
bool DoWrite( )
{
const auto config = this->GetConfig();
return this->mValid =
config ? config->Write( this->mPath, this->mCurrentValue ) : false;
}
mutable T mDefaultValue{};
const DefaultValueFunction mFunction;
};
//! This specialization of Setting for bool adds a Toggle method to negate the saved value
class BoolSetting final : public Setting< bool >
{
public:
using Setting::Setting;
//! Write the negation of the previous value, and then return the current value.
bool Toggle();
};
//! Specialization of Setting for int
class IntSetting final : public Setting< int >
{
public:
using Setting::Setting;
};
//! Specialization of Setting for double
class DoubleSetting final : public Setting< double >
{
public:
using Setting::Setting;
};
//! Specialization of Setting for strings
class StringSetting final : public Setting< wxString >
{
public:
using Setting::Setting;
};
using EnumValueSymbol = ComponentInterfaceSymbol;
/// A table of EnumValueSymbol that you can access by "row" with
/// operator [] but also allowing access to the "columns" of internal or
/// translated strings, and also allowing convenient column-wise construction
class PREFERENCES_API EnumValueSymbols : public std::vector< EnumValueSymbol >
{
public:
EnumValueSymbols() = default;
EnumValueSymbols( std::initializer_list<EnumValueSymbol> symbols )
: vector( symbols )
{}
// columnwise constructor; arguments must have same size
// (Implicit constructor takes initial tag argument to avoid unintended
// overload resolution to the inherited constructor taking
// initializer_list, in the case that each column has exactly two strings)
EnumValueSymbols(
ByColumns_t,
const TranslatableStrings &msgids,
wxArrayStringEx internals
);
const TranslatableStrings &GetMsgids() const;
const wxArrayStringEx &GetInternals() const;
private:
mutable TranslatableStrings mMsgids;
mutable wxArrayStringEx mInternals;
};
/// Packages a table of user-visible choices each with an internal code string,
/// a preference key path, and a default choice
class PREFERENCES_API ChoiceSetting
{
public:
ChoiceSetting(
const SettingBase &key,
EnumValueSymbols symbols,
long defaultSymbol = -1
)
: mKey{ key.GetPath() }
, mSymbols{ std::move( symbols ) }
, mDefaultSymbol{ defaultSymbol }
{
wxASSERT( defaultSymbol < (long)mSymbols.size() );
}
const wxString &Key() const { return mKey; }
const EnumValueSymbol &Default() const;
const EnumValueSymbols &GetSymbols() const { return mSymbols; }
wxString Read() const;
// new direct use is discouraged but it may be needed in legacy code:
// use a default in case the preference is not defined, which may not be
// the default-default stored in this object.
wxString ReadWithDefault( const wxString & ) const;
bool Write( const wxString &value ); // you flush gPrefs afterward
void SetDefault( long value );
protected:
size_t Find( const wxString &value ) const;
virtual void Migrate( wxString& );
const wxString mKey;
const EnumValueSymbols mSymbols;
// stores an internal value
mutable bool mMigrated { false };
long mDefaultSymbol;
};
/// Extends ChoiceSetting with a corresponding table of integer codes
/// (generally not equal to their table positions),
/// and optionally an old preference key path that stored integer codes, to be
/// migrated into one that stores internal string values instead
class PREFERENCES_API EnumSettingBase : public ChoiceSetting
{
public:
EnumSettingBase(
const SettingBase &key,
EnumValueSymbols symbols,
long defaultSymbol,
std::vector<int> intValues, // must have same size as symbols
const wxString &oldKey = {}
);
protected:
// Read and write the encoded values
int ReadInt() const;
// new direct use is discouraged but it may be needed in legacy code:
// use a default in case the preference is not defined, which may not be
// the default-default stored in this object.
int ReadIntWithDefault( int defaultValue ) const;
bool WriteInt( int code ); // you flush gPrefs afterward
size_t FindInt( int code ) const;
void Migrate( wxString& ) override;
private:
std::vector<int> mIntValues;
const wxString mOldKey;
};
/// Adapts EnumSettingBase to a particular enumeration type
template< typename Enum >
class EnumSetting : public EnumSettingBase
{
public:
EnumSetting(
const SettingBase &key,
EnumValueSymbols symbols,
long defaultSymbol,
std::vector< Enum > values, // must have same size as symbols
const wxString &oldKey = {}
)
: EnumSettingBase{
key, symbols, defaultSymbol,
{ values.begin(), values.end() },
oldKey
}
{}
// Wrap ReadInt() and ReadIntWithDefault() and WriteInt()
Enum ReadEnum() const
{ return static_cast<Enum>( ReadInt() ); }
// new direct use is discouraged but it may be needed in legacy code:
// use a default in case the preference is not defined, which may not be
// the default-default stored in this object.
Enum ReadEnumWithDefault( Enum defaultValue ) const
{
auto integer = static_cast<int>(defaultValue);
return static_cast<Enum>( ReadIntWithDefault( integer ) );
}
bool WriteEnum( Enum value )
{ return WriteInt( static_cast<int>( value ) ); }
};
//! A listener notified of changes in preferences
class PREFERENCES_API PrefsListener
{
public:
//! Call this static function to notify all PrefsListener objects
/*!
@param id when positive, passed to UpdateSelectedPrefs() of all listeners,
meant to indicate that only a certain subset of preferences have changed;
else their UpdatePrefs() methods are called. (That is supposed to happen
when the user OK's changes in the Preferences dialog.)
Callbacks are delayed, in the main thread, using BasicUI::CallAfter
*/
static void Broadcast(int id = 0);
PrefsListener();
virtual ~PrefsListener();
// Called when all preferences should be updated.
virtual void UpdatePrefs() = 0;
protected:
// Called when only selected preferences are to be updated.
// id is some value generated by wxNewId() that identifies the portion
// of preferences.
// Default function does nothing.
virtual void UpdateSelectedPrefs( int id );
private:
struct Impl;
std::unique_ptr<Impl> mpImpl;
};
/// Return the config file key associated with a warning dialog identified
/// by internalDialogName. When the box is checked, the value at the key
/// becomes false.
PREFERENCES_API
wxString WarningDialogKey(const wxString &internalDialogName);
/*!
Meant to be statically constructed. A callback to repopulate configuration
files after a reset.
*/
struct PREFERENCES_API PreferenceInitializer {
PreferenceInitializer();
virtual ~PreferenceInitializer();
virtual void operator () () = 0;
static void ReinitializeAll();
};
// Special extra-sticky settings
extern PREFERENCES_API BoolSetting DefaultUpdatesCheckingFlag;
#endif