1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-08 16:11:14 +02:00
audacity/src/Prefs.cpp
Paul Licameli 5fef82dccf Define Setting classes, bundling config path with settings value...
... the intention being, that no string literal for a path, or its default
value, shall ever occur twice in the code, relying on long-distance coincidence
of literal values.  Instead, a named Setting object is constructed once, then
read and written.

For now, the Tie... functions in ShuttleGuiBase will take references to
implicitly constructed temporary Setting objects.  But all should later be
made static objects, and the constructors made explicit.
2021-05-17 08:46:08 -04:00

421 lines
10 KiB
C++
Executable File

/**********************************************************************
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"
std::unique_ptr<FileConfig> ugPrefs {};
FileConfig *gPrefs = nullptr;
int gMenusDirty = 0;
wxDEFINE_EVENT(EVT_PREFS_UPDATE, wxCommandEvent);
struct PrefsListener::Impl : wxEvtHandler
{
Impl( PrefsListener &owner );
~Impl();
void OnEvent(wxCommandEvent&);
PrefsListener &mOwner;
};
PrefsListener::Impl::Impl( PrefsListener &owner )
: mOwner{ owner }
{
wxTheApp->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( wxCommandEvent &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 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;
}