mirror of
https://github.com/cookiengineer/audacity
synced 2025-08-08 16:11:14 +02:00
... 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.
421 lines
10 KiB
C++
Executable File
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;
|
|
}
|