Qt: Implement per-game controller configuration

This commit is contained in:
Stenzek
2024-08-24 14:10:25 +10:00
parent 9e3507e0f4
commit bda6869084
24 changed files with 427 additions and 179 deletions

View File

@ -23,41 +23,87 @@
static constexpr const std::array<char, 4> s_mtap_slot_names = {{'A', 'B', 'C', 'D'}};
ControllerSettingsWindow::ControllerSettingsWindow() : QWidget()
ControllerSettingsWindow::ControllerSettingsWindow(SettingsInterface* game_sif /* = nullptr */,
QWidget* parent /* = nullptr */)
: QWidget(parent), m_editing_settings_interface(game_sif)
{
m_ui.setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
refreshProfileList();
createWidgets();
m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this,
&ControllerSettingsWindow::onCategoryCurrentRowChanged);
connect(m_ui.currentProfile, &QComboBox::currentIndexChanged, this,
&ControllerSettingsWindow::onCurrentProfileChanged);
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &ControllerSettingsWindow::close);
connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onNewProfileClicked);
connect(m_ui.applyProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onApplyProfileClicked);
connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onDeleteProfileClicked);
connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &ControllerSettingsWindow::onRestoreDefaultsClicked);
connect(g_emu_thread, &EmuThread::onInputDevicesEnumerated, this,
&ControllerSettingsWindow::onInputDevicesEnumerated);
connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &ControllerSettingsWindow::onInputDeviceConnected);
connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this,
&ControllerSettingsWindow::onInputDeviceDisconnected);
connect(g_emu_thread, &EmuThread::onVibrationMotorsEnumerated, this,
&ControllerSettingsWindow::onVibrationMotorsEnumerated);
if (!game_sif)
{
refreshProfileList();
// trigger a device enumeration to populate the device list
g_emu_thread->enumerateInputDevices();
g_emu_thread->enumerateVibrationMotors();
m_ui.editProfileLayout->removeWidget(m_ui.copyGlobalSettings);
delete m_ui.copyGlobalSettings;
m_ui.copyGlobalSettings = nullptr;
connect(m_ui.currentProfile, &QComboBox::currentIndexChanged, this,
&ControllerSettingsWindow::onCurrentProfileChanged);
connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onNewProfileClicked);
connect(m_ui.applyProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onApplyProfileClicked);
connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onDeleteProfileClicked);
connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &ControllerSettingsWindow::onRestoreDefaultsClicked);
connect(g_emu_thread, &EmuThread::onInputDevicesEnumerated, this,
&ControllerSettingsWindow::onInputDevicesEnumerated);
connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &ControllerSettingsWindow::onInputDeviceConnected);
connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this,
&ControllerSettingsWindow::onInputDeviceDisconnected);
connect(g_emu_thread, &EmuThread::onVibrationMotorsEnumerated, this,
&ControllerSettingsWindow::onVibrationMotorsEnumerated);
// trigger a device enumeration to populate the device list
g_emu_thread->enumerateInputDevices();
g_emu_thread->enumerateVibrationMotors();
}
else
{
m_ui.editProfileLayout->removeWidget(m_ui.editProfileLabel);
delete m_ui.editProfileLabel;
m_ui.editProfileLabel = nullptr;
m_ui.editProfileLayout->removeWidget(m_ui.currentProfile);
delete m_ui.currentProfile;
m_ui.currentProfile = nullptr;
m_ui.editProfileLayout->removeWidget(m_ui.newProfile);
delete m_ui.newProfile;
m_ui.newProfile = nullptr;
m_ui.editProfileLayout->removeWidget(m_ui.applyProfile);
delete m_ui.applyProfile;
m_ui.applyProfile = nullptr;
m_ui.editProfileLayout->removeWidget(m_ui.deleteProfile);
delete m_ui.deleteProfile;
m_ui.deleteProfile = nullptr;
connect(m_ui.copyGlobalSettings, &QPushButton::clicked, this,
&ControllerSettingsWindow::onCopyGlobalSettingsClicked);
connect(m_ui.restoreDefaults, &QPushButton::clicked, this,
&ControllerSettingsWindow::onRestoreDefaultsForGameClicked);
}
createWidgets();
}
ControllerSettingsWindow::~ControllerSettingsWindow() = default;
void ControllerSettingsWindow::editControllerSettingsForGame(QWidget* parent, SettingsInterface* sif)
{
ControllerSettingsWindow* dlg = new ControllerSettingsWindow(sif, parent);
dlg->setWindowFlag(Qt::Window);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setWindowModality(Qt::WindowModality::WindowModal);
dlg->setWindowTitle(parent->windowTitle());
dlg->setWindowIcon(parent->windowIcon());
dlg->show();
}
int ControllerSettingsWindow::getHotkeyCategoryIndex() const
{
const std::array<bool, 2> mtap_enabled = getEnabledMultitaps();
@ -103,20 +149,26 @@ void ControllerSettingsWindow::onCategoryCurrentRowChanged(int row)
void ControllerSettingsWindow::onCurrentProfileChanged(int index)
{
switchProfile((index == 0) ? 0 : m_ui.currentProfile->itemText(index));
std::string profile_name;
if (index > 0)
profile_name = m_ui.currentProfile->itemText(index).toStdString();
switchProfile(profile_name);
}
void ControllerSettingsWindow::onNewProfileClicked()
{
const QString profile_name(
QInputDialog::getText(this, tr("Create Input Profile"), tr("Enter the name for the new input profile:")));
if (profile_name.isEmpty())
const std::string profile_name =
QInputDialog::getText(this, tr("Create Input Profile"), tr("Enter the name for the new input profile:"))
.toStdString();
if (profile_name.empty())
return;
std::string profile_path(System::GetInputProfilePath(profile_name.toStdString()));
std::string profile_path = System::GetInputProfilePath(profile_name);
if (FileSystem::FileExists(profile_path.c_str()))
{
QMessageBox::critical(this, tr("Error"), tr("A profile with the name '%1' already exists.").arg(profile_name));
QMessageBox::critical(this, tr("Error"),
tr("A profile with the name '%1' already exists.").arg(QString::fromStdString(profile_name)));
return;
}
@ -131,7 +183,7 @@ void ControllerSettingsWindow::onNewProfileClicked()
if (res == QMessageBox::Yes)
{
// copy from global or the current profile
if (!m_profile_interface)
if (!m_editing_settings_interface)
{
const int hkres = QMessageBox::question(
this, tr("Create Input Profile"),
@ -153,9 +205,9 @@ void ControllerSettingsWindow::onNewProfileClicked()
{
// from profile
const bool copy_hotkey_bindings =
m_profile_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false);
m_editing_settings_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false);
temp_si.SetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", copy_hotkey_bindings);
InputManager::CopyConfiguration(&temp_si, *m_profile_interface, true, true, copy_hotkey_bindings);
InputManager::CopyConfiguration(&temp_si, *m_editing_settings_interface, true, true, copy_hotkey_bindings);
}
}
@ -184,9 +236,9 @@ void ControllerSettingsWindow::onApplyProfileClicked()
{
const bool copy_hotkey_bindings =
m_profile_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false);
m_editing_settings_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false);
auto lock = Host::GetSettingsLock();
InputManager::CopyConfiguration(Host::Internal::GetBaseSettingsLayer(), *m_profile_interface, true, true,
InputManager::CopyConfiguration(Host::Internal::GetBaseSettingsLayer(), *m_editing_settings_interface, true, true,
copy_hotkey_bindings);
QtHost::QueueSettingsSave();
}
@ -236,6 +288,36 @@ void ControllerSettingsWindow::onRestoreDefaultsClicked()
switchProfile({});
}
void ControllerSettingsWindow::onCopyGlobalSettingsClicked()
{
DebugAssert(isEditingGameSettings());
{
const auto lock = Host::GetSettingsLock();
InputManager::CopyConfiguration(m_editing_settings_interface, *Host::Internal::GetBaseSettingsLayer(), true, true,
false);
}
m_editing_settings_interface->Save();
g_emu_thread->reloadGameSettings();
createWidgets();
QMessageBox::information(QtUtils::GetRootWidget(this), tr("DuckStation Controller Settings"),
tr("Per-game controller configuration reset to global settings."));
}
void ControllerSettingsWindow::onRestoreDefaultsForGameClicked()
{
DebugAssert(isEditingGameSettings());
Settings::SetDefaultControllerConfig(*m_editing_settings_interface);
m_editing_settings_interface->Save();
g_emu_thread->reloadGameSettings();
createWidgets();
QMessageBox::information(QtUtils::GetRootWidget(this), tr("DuckStation Controller Settings"),
tr("Per-game controller configuration reset to default settings."));
}
void ControllerSettingsWindow::onInputDevicesEnumerated(const std::vector<std::pair<std::string, std::string>>& devices)
{
m_device_list = devices;
@ -280,16 +362,16 @@ void ControllerSettingsWindow::onVibrationMotorsEnumerated(const QList<InputBind
bool ControllerSettingsWindow::getBoolValue(const char* section, const char* key, bool default_value) const
{
if (m_profile_interface)
return m_profile_interface->GetBoolValue(section, key, default_value);
if (m_editing_settings_interface)
return m_editing_settings_interface->GetBoolValue(section, key, default_value);
else
return Host::GetBaseBoolSettingValue(section, key, default_value);
}
s32 ControllerSettingsWindow::getIntValue(const char* section, const char* key, s32 default_value) const
{
if (m_profile_interface)
return m_profile_interface->GetIntValue(section, key, default_value);
if (m_editing_settings_interface)
return m_editing_settings_interface->GetIntValue(section, key, default_value);
else
return Host::GetBaseIntSettingValue(section, key, default_value);
}
@ -298,8 +380,8 @@ std::string ControllerSettingsWindow::getStringValue(const char* section, const
const char* default_value) const
{
std::string value;
if (m_profile_interface)
value = m_profile_interface->GetStringValue(section, key, default_value);
if (m_editing_settings_interface)
value = m_editing_settings_interface->GetStringValue(section, key, default_value);
else
value = Host::GetBaseStringSettingValue(section, key, default_value);
return value;
@ -307,9 +389,9 @@ std::string ControllerSettingsWindow::getStringValue(const char* section, const
void ControllerSettingsWindow::setBoolValue(const char* section, const char* key, bool value)
{
if (m_profile_interface)
if (m_editing_settings_interface)
{
m_profile_interface->SetBoolValue(section, key, value);
m_editing_settings_interface->SetBoolValue(section, key, value);
saveAndReloadGameSettings();
}
else
@ -322,9 +404,9 @@ void ControllerSettingsWindow::setBoolValue(const char* section, const char* key
void ControllerSettingsWindow::setIntValue(const char* section, const char* key, s32 value)
{
if (m_profile_interface)
if (m_editing_settings_interface)
{
m_profile_interface->SetIntValue(section, key, value);
m_editing_settings_interface->SetIntValue(section, key, value);
saveAndReloadGameSettings();
}
else
@ -337,9 +419,9 @@ void ControllerSettingsWindow::setIntValue(const char* section, const char* key,
void ControllerSettingsWindow::setStringValue(const char* section, const char* key, const char* value)
{
if (m_profile_interface)
if (m_editing_settings_interface)
{
m_profile_interface->SetStringValue(section, key, value);
m_editing_settings_interface->SetStringValue(section, key, value);
saveAndReloadGameSettings();
}
else
@ -352,17 +434,17 @@ void ControllerSettingsWindow::setStringValue(const char* section, const char* k
void ControllerSettingsWindow::saveAndReloadGameSettings()
{
DebugAssert(m_profile_interface);
QtHost::SaveGameSettings(m_profile_interface.get(), false);
DebugAssert(m_editing_settings_interface);
QtHost::SaveGameSettings(m_editing_settings_interface, false);
g_emu_thread->reloadGameSettings(false);
}
void ControllerSettingsWindow::clearSettingValue(const char* section, const char* key)
{
if (m_profile_interface)
if (m_editing_settings_interface)
{
m_profile_interface->DeleteValue(section, key);
m_profile_interface->Save();
m_editing_settings_interface->DeleteValue(section, key);
m_editing_settings_interface->Save();
g_emu_thread->reloadGameSettings();
}
else
@ -434,7 +516,8 @@ void ControllerSettingsWindow::createWidgets()
}
// only add hotkeys if we're editing global settings
if (!m_profile_interface || m_profile_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false))
if (!m_editing_settings_interface ||
m_editing_settings_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false))
{
QListWidgetItem* item = new QListWidgetItem();
item->setText(tr("Hotkeys"));
@ -444,9 +527,18 @@ void ControllerSettingsWindow::createWidgets()
m_ui.settingsContainer->addWidget(m_hotkey_settings);
}
m_ui.applyProfile->setEnabled(isEditingProfile());
m_ui.deleteProfile->setEnabled(isEditingProfile());
m_ui.restoreDefaults->setEnabled(isEditingGlobalSettings());
if (!isEditingGameSettings())
{
m_ui.applyProfile->setEnabled(isEditingProfile());
m_ui.deleteProfile->setEnabled(isEditingProfile());
m_ui.restoreDefaults->setEnabled(isEditingGlobalSettings());
}
}
void ControllerSettingsWindow::closeEvent(QCloseEvent* event)
{
QWidget::closeEvent(event);
emit windowClosed();
}
void ControllerSettingsWindow::updateListDescription(u32 global_slot, ControllerBindingWidget* widget)
@ -501,30 +593,36 @@ void ControllerSettingsWindow::refreshProfileList()
}
}
void ControllerSettingsWindow::switchProfile(const QString& name)
void ControllerSettingsWindow::switchProfile(const std::string_view name)
{
QSignalBlocker sb(m_ui.currentProfile);
if (!name.isEmpty())
if (!name.empty())
{
std::string path(System::GetInputProfilePath(name.toStdString()));
const QString name_qstr = QtUtils::StringViewToQString(name);
std::string path = System::GetInputProfilePath(name);
if (!FileSystem::FileExists(path.c_str()))
{
QMessageBox::critical(this, tr("Error"), tr("The input profile named '%1' cannot be found.").arg(name));
QMessageBox::critical(this, tr("Error"), tr("The input profile named '%1' cannot be found.").arg(name_qstr));
return;
}
std::unique_ptr<INISettingsInterface> sif(std::make_unique<INISettingsInterface>(std::move(path)));
std::unique_ptr<INISettingsInterface> sif = std::make_unique<INISettingsInterface>(std::move(path));
sif->Load();
m_profile_interface = std::move(sif);
m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->findText(name));
m_profile_settings_interface = std::move(sif);
m_editing_settings_interface = m_profile_settings_interface.get();
m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->findText(name_qstr));
m_profile_name = name_qstr;
}
else
{
m_profile_interface.reset();
m_profile_settings_interface.reset();
m_editing_settings_interface = nullptr;
m_ui.currentProfile->setCurrentIndex(0);
m_profile_name = QString();
}
m_profile_name = name;
createWidgets();
}