rpcs3/rpcs3/rpcs3qt/emu_settings.cpp
Megamouse 171367fe88 Qt: fix localization in change_microphone_type
Don't rely on localized text at all. Use the setting's index and formatted string instead
2020-04-17 13:30:10 +02:00

766 lines
23 KiB
C++

#include "emu_settings.h"
#include "Utilities/Config.h"
#include <QMessageBox>
#include <QLineEdit>
#include "Emu/System.h"
#include "Emu/system_config.h"
LOG_CHANNEL(cfg_log, "CFG");
extern std::string g_cfg_defaults; //! Default settings grabbed from Utilities/Config.h
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
inline std::string sstr(const QVariant& _in) { return sstr(_in.toString()); }
// Emit sorted YAML
namespace
{
static NEVER_INLINE void emitData(YAML::Emitter& out, const YAML::Node& node)
{
// TODO
out << node;
}
// Incrementally load YAML
static NEVER_INLINE void operator +=(YAML::Node& left, const YAML::Node& node)
{
if (node && !node.IsNull())
{
if (node.IsMap())
{
for (const auto& pair : node)
{
if (pair.first.IsScalar())
{
auto&& lhs = left[pair.first.Scalar()];
lhs += pair.second;
}
else
{
// Exotic case (TODO: probably doesn't work)
auto&& lhs = left[YAML::Clone(pair.first)];
lhs += pair.second;
}
}
}
else if (node.IsScalar() || node.IsSequence())
{
// Scalars and sequences are replaced completely, but this may change in future.
// This logic may be overwritten by custom demands of every specific cfg:: node.
left = node;
}
}
}
}
// Helper methods to interact with YAML and the config settings.
namespace cfg_adapter
{
static cfg::_base& get_cfg(cfg::_base& root, const std::string& name)
{
if (root.get_type() == cfg::type::node)
{
for (const auto& pair : static_cast<cfg::node&>(root).get_nodes())
{
if (pair.first == name)
{
return *pair.second;
}
}
}
fmt::throw_exception("Node not found: %s", name);
}
static cfg::_base& get_cfg(cfg::_base& root, cfg_location::const_iterator begin, cfg_location::const_iterator end)
{
return begin == end ? root : get_cfg(get_cfg(root, *begin), begin + 1, end);
}
static YAML::Node get_node(const YAML::Node& node, cfg_location::const_iterator begin, cfg_location::const_iterator end)
{
return begin == end ? node : get_node(node[*begin], begin + 1, end); // TODO
}
/** Syntactic sugar to get a setting at a given config location. */
static YAML::Node get_node(const YAML::Node& node, cfg_location loc)
{
return get_node(node, loc.cbegin(), loc.cend());
}
}
/** Returns possible options for values for some particular setting.*/
static QStringList get_options(cfg_location location)
{
QStringList values;
auto begin = location.cbegin();
auto end = location.cend();
for (const auto& v : cfg_adapter::get_cfg(g_cfg, begin, end).to_list())
{
values.append(qstr(v));
}
return values;
}
/** Returns dynamic property for some particular setting.*/
static bool get_is_dynamic(cfg_location location)
{
auto begin = location.cbegin();
auto end = location.cend();
return cfg_adapter::get_cfg(g_cfg, begin, end).get_is_dynamic();
}
emu_settings::emu_settings()
: QObject()
, m_render_creator(new render_creator(this))
{
}
emu_settings::~emu_settings()
{
}
void emu_settings::LoadSettings(const std::string& title_id)
{
m_title_id = title_id;
// Create config path if necessary
fs::create_path(title_id.empty() ? fs::get_config_dir() : Emulator::GetCustomConfigDir());
// Load default config
m_defaultSettings = YAML::Load(g_cfg_defaults);
m_currentSettings = YAML::Load(g_cfg_defaults);
// Add global config
fs::file config(fs::get_config_dir() + "/config.yml", fs::read + fs::write + fs::create);
m_currentSettings += YAML::Load(config.to_string());
config.close();
// Add game config
if (!title_id.empty())
{
const std::string config_path_new = Emulator::GetCustomConfigPath(m_title_id);
const std::string config_path_old = Emulator::GetCustomConfigPath(m_title_id, true);
if (fs::is_file(config_path_new))
{
config = fs::file(config_path_new, fs::read + fs::write);
m_currentSettings += YAML::Load(config.to_string());
config.close();
}
else if (fs::is_file(config_path_old))
{
config = fs::file(config_path_old, fs::read + fs::write);
m_currentSettings += YAML::Load(config.to_string());
config.close();
}
}
}
void emu_settings::SaveSettings()
{
fs::file config;
YAML::Emitter out;
emitData(out, m_currentSettings);
std::string config_name;
if (m_title_id.empty())
{
config_name = fs::get_config_dir() + "/config.yml";
}
else
{
config_name = Emulator::GetCustomConfigPath(m_title_id);
}
config = fs::file(config_name, fs::read + fs::write + fs::create);
// Save config
config.seek(0);
config.trunc(0);
config.write(out.c_str(), out.size());
// Check if the running config/title is the same as the edited config/title.
if (config_name == g_cfg.name || m_title_id == Emu.GetTitleID())
{
// Update current config
g_cfg.from_string(config.to_string(), !Emu.IsStopped());
if (!Emu.IsStopped()) // Don't spam the log while emulation is stopped. The config will be logged on boot anyway.
{
cfg_log.notice("Updated configuration:\n%s\n", g_cfg.to_string());
}
}
config.close();
}
void emu_settings::EnhanceComboBox(QComboBox* combobox, emu_settings_type type, bool is_ranged, bool use_max, int max, bool sorted)
{
if (!combobox)
{
cfg_log.fatal("EnhanceComboBox '%s' was used with an invalid object", GetSettingName(type));
return;
}
if (is_ranged)
{
if (sorted)
{
cfg_log.warning("EnhanceCombobox '%s': ignoring sorting request on ranged combo box", GetSettingName(type));
}
const QStringList range = GetSettingOptions(type);
const int max_item = use_max ? max : range.last().toInt();
for (int i = range.first().toInt(); i <= max_item; i++)
{
combobox->addItem(QString::number(i), i);
}
}
else
{
const QStringList settings = GetSettingOptions(type);
for (int i = 0; i < settings.count(); i++)
{
const QString localized_setting = GetLocalizedSetting(settings[i], type, combobox->count());
combobox->addItem(localized_setting, QVariant({settings[i], i}));
}
if (sorted)
{
combobox->model()->sort(0, Qt::AscendingOrder);
}
}
const std::string selected = GetSetting(type);
const QString selected_q = qstr(selected);
int index = -1;
if (is_ranged)
{
index = combobox->findData(selected_q);
}
else
{
for (int i = 0; i < combobox->count(); i++)
{
const QVariantList var_list = combobox->itemData(i).toList();
ASSERT(var_list.size() == 2 && var_list[0].canConvert<QString>());
if (selected_q == var_list[0].toString())
{
index = i;
break;
}
}
}
if (index == -1)
{
const std::string def = GetSettingDefault(type);
cfg_log.fatal("EnhanceComboBox '%s' tried to set an invalid value: %s. Setting to default: %s", GetSettingName(type), selected, def);
combobox->setCurrentIndex(combobox->findData(qstr(def)));
m_broken_types.insert(type);
}
else
{
combobox->setCurrentIndex(index);
}
connect(combobox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=, this](int index)
{
if (is_ranged)
{
SetSetting(type, sstr(combobox->itemData(index)));
}
else
{
const QVariantList var_list = combobox->itemData(index).toList();
ASSERT(var_list.size() == 2 && var_list[0].canConvert<QString>());
SetSetting(type, sstr(var_list[0]));
}
});
}
void emu_settings::EnhanceCheckBox(QCheckBox* checkbox, emu_settings_type type)
{
if (!checkbox)
{
cfg_log.fatal("EnhanceCheckBox '%s' was used with an invalid object", GetSettingName(type));
return;
}
std::string def = GetSettingDefault(type);
std::transform(def.begin(), def.end(), def.begin(), ::tolower);
if (def != "true" && def != "false")
{
cfg_log.fatal("EnhanceCheckBox '%s' was used with an invalid emu_settings_type", GetSettingName(type));
return;
}
std::string selected = GetSetting(type);
std::transform(selected.begin(), selected.end(), selected.begin(), ::tolower);
if (selected == "true")
{
checkbox->setChecked(true);
}
else if (selected != "false")
{
cfg_log.fatal("EnhanceCheckBox '%s' tried to set an invalid value: %s. Setting to default: %s", GetSettingName(type), selected, def);
checkbox->setChecked(def == "true");
m_broken_types.insert(type);
}
connect(checkbox, &QCheckBox::stateChanged, [=, this](int val)
{
const std::string str = val != 0 ? "true" : "false";
SetSetting(type, str);
});
}
void emu_settings::EnhanceSlider(QSlider* slider, emu_settings_type type)
{
if (!slider)
{
cfg_log.fatal("EnhanceSlider '%s' was used with an invalid object", GetSettingName(type));
return;
}
const QStringList range = GetSettingOptions(type);
bool ok_def, ok_sel, ok_min, ok_max;
const int def = qstr(GetSettingDefault(type)).toInt(&ok_def);
const int min = range.first().toInt(&ok_min);
const int max = range.last().toInt(&ok_max);
if (!ok_def || !ok_min || !ok_max)
{
cfg_log.fatal("EnhanceSlider '%s' was used with an invalid emu_settings_type", GetSettingName(type));
return;
}
const QString selected = qstr(GetSetting(type));
int val = selected.toInt(&ok_sel);
if (!ok_sel || val < min || val > max)
{
cfg_log.fatal("EnhanceSlider '%s' tried to set an invalid value: %d. Setting to default: %d. Allowed range: [%d, %d]", GetSettingName(type), val, def, min, max);
val = def;
m_broken_types.insert(type);
}
slider->setRange(min, max);
slider->setValue(val);
connect(slider, &QSlider::valueChanged, [=, this](int value)
{
SetSetting(type, sstr(value));
});
}
void emu_settings::EnhanceSpinBox(QSpinBox* spinbox, emu_settings_type type, const QString& prefix, const QString& suffix)
{
if (!spinbox)
{
cfg_log.fatal("EnhanceSpinBox '%s' was used with an invalid object", GetSettingName(type));
return;
}
const QStringList range = GetSettingOptions(type);
bool ok_def, ok_sel, ok_min, ok_max;
const int def = qstr(GetSettingDefault(type)).toInt(&ok_def);
const int min = range.first().toInt(&ok_min);
const int max = range.last().toInt(&ok_max);
if (!ok_def || !ok_min || !ok_max)
{
cfg_log.fatal("EnhanceSpinBox '%s' was used with an invalid type", GetSettingName(type));
return;
}
const std::string selected = GetSetting(type);
int val = qstr(selected).toInt(&ok_sel);
if (!ok_sel || val < min || val > max)
{
cfg_log.fatal("EnhanceSpinBox '%s' tried to set an invalid value: %d. Setting to default: %d. Allowed range: [%d, %d]", GetSettingName(type), selected, def, min, max);
val = def;
m_broken_types.insert(type);
}
spinbox->setPrefix(prefix);
spinbox->setSuffix(suffix);
spinbox->setRange(min, max);
spinbox->setValue(val);
connect(spinbox, &QSpinBox::textChanged, [=, this](const QString&/* text*/)
{
SetSetting(type, sstr(spinbox->cleanText()));
});
}
void emu_settings::EnhanceDoubleSpinBox(QDoubleSpinBox* spinbox, emu_settings_type type, const QString& prefix, const QString& suffix)
{
if (!spinbox)
{
cfg_log.fatal("EnhanceDoubleSpinBox '%s' was used with an invalid object", GetSettingName(type));
return;
}
const QStringList range = GetSettingOptions(type);
bool ok_def, ok_sel, ok_min, ok_max;
const double def = qstr(GetSettingDefault(type)).toDouble(&ok_def);
const double min = range.first().toDouble(&ok_min);
const double max = range.last().toDouble(&ok_max);
if (!ok_def || !ok_min || !ok_max)
{
cfg_log.fatal("EnhanceDoubleSpinBox '%s' was used with an invalid type", GetSettingName(type));
return;
}
const std::string selected = GetSetting(type);
double val = qstr(selected).toDouble(&ok_sel);
if (!ok_sel || val < min || val > max)
{
cfg_log.fatal("EnhanceDoubleSpinBox '%s' tried to set an invalid value: %f. Setting to default: %f. Allowed range: [%f, %f]", GetSettingName(type), val, def, min, max);
val = def;
m_broken_types.insert(type);
}
spinbox->setPrefix(prefix);
spinbox->setSuffix(suffix);
spinbox->setRange(min, max);
spinbox->setValue(val);
connect(spinbox, &QDoubleSpinBox::textChanged, [=, this](const QString&/* text*/)
{
SetSetting(type, sstr(spinbox->cleanText()));
});
}
void emu_settings::EnhanceLineEdit(QLineEdit* edit, emu_settings_type type)
{
if (!edit)
{
cfg_log.fatal("EnhanceEdit '%s' was used with an invalid object", GetSettingName(type));
return;
}
const std::string set_text = GetSetting(type);
edit->setText(qstr(set_text));
connect(edit, &QLineEdit::textChanged, [=, this](const QString &text)
{
SetSetting(type, sstr(text));
});
}
void emu_settings::EnhanceRadioButton(QButtonGroup* button_group, emu_settings_type type)
{
if (!button_group)
{
cfg_log.fatal("EnhanceRadioButton '%s' was used with an invalid object", GetSettingName(type));
return;
}
const QString selected = qstr(GetSetting(type));
const QStringList options = GetSettingOptions(type);
if (button_group->buttons().count() < options.size())
{
cfg_log.fatal("EnhanceRadioButton '%s': wrong button count", GetSettingName(type));
return;
}
for (int i = 0; i < options.count(); i++)
{
const QString localized_setting = GetLocalizedSetting(options[i], type, i);
button_group->button(i)->setText(localized_setting);
if (options[i] == selected)
{
button_group->button(i)->setChecked(true);
}
connect(button_group->button(i), &QAbstractButton::clicked, [=, this]()
{
SetSetting(type, sstr(options[i]));
});
}
}
std::vector<std::string> emu_settings::GetLoadedLibraries()
{
return m_currentSettings["Core"]["Load libraries"].as<std::vector<std::string>, std::initializer_list<std::string>>({});
}
void emu_settings::SaveSelectedLibraries(const std::vector<std::string>& libs)
{
m_currentSettings["Core"]["Load libraries"] = libs;
}
QStringList emu_settings::GetSettingOptions(emu_settings_type type) const
{
return get_options(const_cast<cfg_location&&>(m_settings_location[type]));
}
std::string emu_settings::GetSettingName(emu_settings_type type) const
{
const cfg_location loc = m_settings_location[type];
return loc[loc.size() - 1];
}
std::string emu_settings::GetSettingDefault(emu_settings_type type) const
{
return cfg_adapter::get_node(m_defaultSettings, m_settings_location[type]).Scalar();
}
std::string emu_settings::GetSetting(emu_settings_type type) const
{
return cfg_adapter::get_node(m_currentSettings, m_settings_location[type]).Scalar();
}
void emu_settings::SetSetting(emu_settings_type type, const std::string& val)
{
cfg_adapter::get_node(m_currentSettings, m_settings_location[type]) = val;
}
void emu_settings::OpenCorrectionDialog(QWidget* parent)
{
if (!m_broken_types.empty() && QMessageBox::question(parent, tr("Fix invalid settings?"),
tr(
"Your config file contained one or more unrecognized values for settings.\n"
"Their default value will be used until they are corrected.\n"
"Consider that a correction might render them invalid for other versions of RPCS3.\n"
"\n"
"Do you wish to let the program correct them for you?\n"
"This change will only be final when you save the config."
),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes)
{
for (const auto& type : m_broken_types)
{
const std::string def = GetSettingDefault(type);
const std::string old = GetSetting(type);
SetSetting(type, def);
cfg_log.success("The config entry '%s' was corrected from '%s' to '%s'", GetSettingName(type), old, def);
}
m_broken_types.clear();
cfg_log.success("You need to save the settings in order to make these changes permanent!");
}
}
QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_type type, int index) const
{
switch (type)
{
case emu_settings_type::SPUBlockSize:
switch (static_cast<spu_block_size_type>(index))
{
case spu_block_size_type::safe: return tr("Safe", "SPU block size");
case spu_block_size_type::mega: return tr("Mega", "SPU block size");
case spu_block_size_type::giga: return tr("Giga", "SPU block size");
}
break;
case emu_settings_type::EnableTSX:
switch (static_cast<tsx_usage>(index))
{
case tsx_usage::disabled: return tr("Disabled", "Enable TSX");
case tsx_usage::enabled: return tr("Enabled", "Enable TSX");
case tsx_usage::forced: return tr("Forced", "Enable TSX");
}
break;
case emu_settings_type::Renderer:
switch (static_cast<video_renderer>(index))
{
case video_renderer::null: return tr("Disable Video Output", "Video renderer");
case video_renderer::opengl: return tr("OpenGL", "Video renderer");
case video_renderer::vulkan: return tr("Vulkan", "Video renderer");
}
break;
case emu_settings_type::FrameLimit:
switch (static_cast<frame_limit_type>(index))
{
case frame_limit_type::none: return tr("Off", "Frame limit");
case frame_limit_type::_59_94: return tr("59.94", "Frame limit");
case frame_limit_type::_50: return tr("50", "Frame limit");
case frame_limit_type::_60: return tr("60", "Frame limit");
case frame_limit_type::_30: return tr("30", "Frame limit");
case frame_limit_type::_auto: return tr("Auto", "Frame limit");
}
break;
case emu_settings_type::MSAA:
switch (static_cast<msaa_level>(index))
{
case msaa_level::none: return tr("Disabled", "MSAA");
case msaa_level::_auto: return tr("Auto", "MSAA");
}
break;
case emu_settings_type::AudioRenderer:
switch (static_cast<audio_renderer>(index))
{
case audio_renderer::null: return tr("Disable Audio Output", "Audio renderer");
#ifdef _WIN32
case audio_renderer::xaudio: return tr("XAudio2", "Audio renderer");
#endif
#ifdef HAVE_ALSA
case audio_renderer::alsa: return tr("ALSA", "Audio renderer");
#endif
#ifdef HAVE_PULSE
case audio_renderer::pulse: return tr("PulseAudio", "Audio renderer");
#endif
case audio_renderer::openal: return tr("OpenAL", "Audio renderer");
#ifdef HAVE_FAUDIO
case audio_renderer::faudio: return tr("FAudio", "Audio renderer");
#endif
}
break;
case emu_settings_type::MicrophoneType:
switch (static_cast<microphone_handler>(index))
{
case microphone_handler::null: return tr("Disabled", "Microphone handler");
case microphone_handler::standard: return tr("Standard", "Microphone handler");
case microphone_handler::singstar: return tr("SingStar", "Microphone handler");
case microphone_handler::real_singstar: return tr("Real SingStar", "Microphone handler");
case microphone_handler::rocksmith: return tr("Rocksmith", "Microphone handler");
}
break;
case emu_settings_type::KeyboardHandler:
switch (static_cast<keyboard_handler>(index))
{
case keyboard_handler::null: return tr("Null", "Keyboard handler");
case keyboard_handler::basic: return tr("Basic", "Keyboard handler");
}
break;
case emu_settings_type::MouseHandler:
switch (static_cast<mouse_handler>(index))
{
case mouse_handler::null: return tr("Null", "Mouse handler");
case mouse_handler::basic: return tr("Basic", "Mouse handler");
}
break;
case emu_settings_type::CameraType:
switch (static_cast<fake_camera_type>(index))
{
case fake_camera_type::unknown: return tr("Unknown", "Camera type");
case fake_camera_type::eyetoy: return tr("EyeToy", "Camera type");
case fake_camera_type::eyetoy2: return tr("PS Eye", "Camera type");
case fake_camera_type::uvc1_1: return tr("UVC 1.1", "Camera type");
}
break;
case emu_settings_type::Camera:
switch (static_cast<camera_handler>(index))
{
case camera_handler::null: return tr("Null", "Camera handler");
case camera_handler::fake: return tr("Fake", "Camera handler");
}
break;
case emu_settings_type::Move:
switch (static_cast<move_handler>(index))
{
case move_handler::null: return tr("Null", "Move handler");
case move_handler::fake: return tr("Fake", "Move handler");
case move_handler::mouse: return tr("Mouse", "Move handler");
}
break;
case emu_settings_type::InternetStatus:
switch (static_cast<np_internet_status>(index))
{
case np_internet_status::disabled: return tr("Disconnected", "Internet Status");
case np_internet_status::enabled: return tr("Connected", "Internet Status");
}
break;
case emu_settings_type::PSNStatus:
switch (static_cast<np_psn_status>(index))
{
case np_psn_status::disabled: return tr("Disconnected", "PSN Status");
case np_psn_status::fake: return tr("Simulated", "PSN Status");
}
break;
case emu_settings_type::SleepTimersAccuracy:
switch (static_cast<sleep_timers_accuracy_level>(index))
{
case sleep_timers_accuracy_level::_as_host: return tr("As Host", "Sleep timers accuracy");
case sleep_timers_accuracy_level::_usleep: return tr("Usleep Only", "Sleep timers accuracy");
case sleep_timers_accuracy_level::_all_timers: return tr("All Timers", "Sleep timers accuracy");
}
break;
case emu_settings_type::PerfOverlayDetailLevel:
switch (static_cast<detail_level>(index))
{
case detail_level::minimal: return tr("Minimal", "Detail Level");
case detail_level::low: return tr("Low", "Detail Level");
case detail_level::medium: return tr("Medium", "Detail Level");
case detail_level::high: return tr("High", "Detail Level");
}
break;
case emu_settings_type::PerfOverlayPosition:
switch (static_cast<screen_quadrant>(index))
{
case screen_quadrant::top_left: return tr("Top Left", "Performance overlay position");
case screen_quadrant::top_right: return tr("Top Right", "Performance overlay position");
case screen_quadrant::bottom_left: return tr("Bottom Left", "Performance overlay position");
case screen_quadrant::bottom_right: return tr("Bottom Right", "Performance overlay position");
}
break;
case emu_settings_type::LibLoadOptions:
switch (static_cast<lib_loading_type>(index))
{
case lib_loading_type::manual: return tr("Manually load selected libraries", "Libraries");
case lib_loading_type::hybrid: return tr("Load automatic and manual selection", "Libraries");
case lib_loading_type::liblv2only: return tr("Load liblv2.sprx only", "Libraries");
case lib_loading_type::liblv2both: return tr("Load liblv2.sprx and manual selection", "Libraries");
case lib_loading_type::liblv2list: return tr("Load liblv2.sprx and strict selection", "Libraries");
}
break;
case emu_settings_type::PPUDecoder:
switch (static_cast<ppu_decoder_type>(index))
{
case ppu_decoder_type::precise: return tr("Interpreter (precise)", "PPU decoder");
case ppu_decoder_type::fast: return tr("Interpreter (fast)", "PPU decoder");
case ppu_decoder_type::llvm: return tr("Recompiler (LLVM)", "PPU decoder");
}
break;
case emu_settings_type::SPUDecoder:
switch (static_cast<spu_decoder_type>(index))
{
case spu_decoder_type::precise: return tr("Interpreter (precise)", "SPU decoder");
case spu_decoder_type::fast: return tr("Interpreter (fast)", "SPU decoder");
case spu_decoder_type::asmjit: return tr("Recompiler (ASMJIT)", "SPU decoder");
case spu_decoder_type::llvm: return tr("Recompiler (LLVM)", "SPU decoder");
}
break;
case emu_settings_type::EnterButtonAssignment:
switch (static_cast<enter_button_assign>(index))
{
case enter_button_assign::circle: return tr("Enter with circle", "Enter button assignment");
case enter_button_assign::cross: return tr("Enter with cross", "Enter button assignment");
}
break;
default:
break;
}
return original;
}
bool emu_settings::GetIsDynamicConfig(emu_settings_type type)
{
const cfg_location loc = m_settings_location[type];
return get_is_dynamic(loc);
}