/********************************************************************** 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 #include #include #include #include #include "Internat.h" #include "MemoryX.h" #include "BasicUI.h" BoolSetting DefaultUpdatesCheckingFlag{ L"/Update/DefaultUpdatesChecking", true }; std::unique_ptr ugPrefs {}; FileConfig *gPrefs = nullptr; int gMenusDirty = 0; struct MyEvent; wxDECLARE_EVENT(EVT_PREFS_UPDATE, MyEvent); struct MyEvent : wxEvent { public: explicit MyEvent(int id) : wxEvent{ 0, EVT_PREFS_UPDATE }, mId{id} {} virtual wxEvent *Clone() const override { return new MyEvent{mId}; } int mId; }; wxDEFINE_EVENT(EVT_PREFS_UPDATE, MyEvent); struct PrefsListener::Impl : wxEvtHandler { Impl( PrefsListener &owner ); ~Impl(); void OnEvent(wxEvent&); PrefsListener &mOwner; }; static wxEvtHandler &hub() { static wxEvtHandler theHub; return theHub; } 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( *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 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 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( *this, std::mem_fn( &EnumValueSymbol::Msgid ) ); return mMsgids; } const wxArrayStringEx &EnumValueSymbols::GetInternals() const { if ( mInternals.empty() ) mInternals = transform_container( *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(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 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 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; }