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

@ -247,7 +247,7 @@ void ControllerBindingWidget::onTypeChanged()
m_controller_info = Controller::GetControllerInfo(static_cast<ControllerType>(index));
DebugAssert(m_controller_info);
SettingsInterface* sif = m_dialog->getProfileSettingsInterface();
SettingsInterface* sif = m_dialog->getEditingSettingsInterface();
if (sif)
{
sif->SetStringValue(m_config_section.c_str(), "Type", m_controller_info->name);
@ -307,7 +307,7 @@ void ControllerBindingWidget::onClearBindingsClicked()
}
else
{
InputManager::ClearPortBindings(*m_dialog->getProfileSettingsInterface(), m_port_number);
InputManager::ClearPortBindings(*m_dialog->getEditingSettingsInterface(), m_port_number);
}
saveAndRefresh();
@ -358,8 +358,8 @@ void ControllerBindingWidget::doDeviceAutomaticBinding(const QString& device)
}
else
{
result = InputManager::MapController(*m_dialog->getProfileSettingsInterface(), m_port_number, mapping);
QtHost::SaveGameSettings(m_dialog->getProfileSettingsInterface(), false);
result = InputManager::MapController(*m_dialog->getEditingSettingsInterface(), m_port_number, mapping);
QtHost::SaveGameSettings(m_dialog->getEditingSettingsInterface(), false);
g_emu_thread->reloadInputBindings();
}
@ -377,7 +377,7 @@ void ControllerBindingWidget::saveAndRefresh()
void ControllerBindingWidget::createBindingWidgets(QWidget* parent)
{
SettingsInterface* sif = getDialog()->getProfileSettingsInterface();
SettingsInterface* sif = getDialog()->getEditingSettingsInterface();
DebugAssert(m_controller_info);
QGroupBox* axis_gbox = nullptr;
@ -471,7 +471,7 @@ void ControllerBindingWidget::createBindingWidgets(QWidget* parent)
void ControllerBindingWidget::bindBindingWidgets(QWidget* parent)
{
SettingsInterface* sif = getDialog()->getProfileSettingsInterface();
SettingsInterface* sif = getDialog()->getEditingSettingsInterface();
DebugAssert(m_controller_info);
const std::string& config_section = getConfigSection();
@ -601,12 +601,12 @@ ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* pare
}
m_frequency = dialog->getIntValue(section.c_str(), TinyString::from_format("Macro{}Frequency", index + 1u), 0);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(dialog->getProfileSettingsInterface(), m_ui.triggerToggle,
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(dialog->getEditingSettingsInterface(), m_ui.triggerToggle,
section.c_str(), fmt::format("Macro{}Toggle", index + 1u),
false);
updateFrequencyText();
m_ui.trigger->initialize(dialog->getProfileSettingsInterface(), InputBindingInfo::Type::Macro, section,
m_ui.trigger->initialize(dialog->getEditingSettingsInterface(), InputBindingInfo::Type::Macro, section,
fmt::format("Macro{}", index + 1u));
connect(m_ui.increaseFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(1); });
@ -747,7 +747,7 @@ void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidge
QGridLayout* layout, const Controller::ControllerInfo* cinfo)
{
const std::string& section = parent->getConfigSection();
SettingsInterface* sif = parent->getDialog()->getProfileSettingsInterface();
SettingsInterface* sif = parent->getDialog()->getEditingSettingsInterface();
int current_row = 0;
for (const SettingInfo& si : cinfo->settings)

View File

@ -14,7 +14,7 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
{
m_ui.setupUi(this);
SettingsInterface* sif = dialog->getProfileSettingsInterface();
SettingsInterface* sif = dialog->getEditingSettingsInterface();
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLSource, "InputSources", "SDL", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLEnhancedMode, "InputSources",
@ -28,10 +28,10 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLMFIDriver, "InputSources", "SDLMFIDriver", true);
#else
m_ui.sdlGridLayout->removeWidget(m_ui.enableSDLIOKitDriver);
m_ui.enableSDLIOKitDriver->deleteLater();
delete m_ui.enableSDLIOKitDriver;
m_ui.enableSDLIOKitDriver = nullptr;
m_ui.sdlGridLayout->removeWidget(m_ui.enableSDLMFIDriver);
m_ui.enableSDLMFIDriver->deleteLater();
delete m_ui.enableSDLMFIDriver;
m_ui.enableSDLMFIDriver = nullptr;
#endif
@ -41,10 +41,10 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableRawInput, "InputSources", "RawInput", false);
#else
m_ui.mainLayout->removeWidget(m_ui.xinputGroup);
m_ui.xinputGroup->deleteLater();
delete m_ui.xinputGroup;
m_ui.xinputGroup = nullptr;
m_ui.mainLayout->removeWidget(m_ui.dinputGroup);
m_ui.dinputGroup->deleteLater();
delete m_ui.dinputGroup;
m_ui.dinputGroup = nullptr;
#endif
@ -71,10 +71,13 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
{
// remove profile options from the UI.
m_ui.mainLayout->removeWidget(m_ui.profileSettings);
m_ui.profileSettings->deleteLater();
delete m_ui.profileSettings;
m_ui.profileSettings = nullptr;
}
if (dialog->isEditingGameSettings())
m_ui.deviceListGroup->setEnabled(false);
connect(m_ui.multitapMode, &QComboBox::currentIndexChanged, this, [this]() { emit bindingSetupChanged(); });
connect(m_ui.pointerXScale, &QSlider::valueChanged, this,
@ -134,9 +137,10 @@ ControllerLEDSettingsDialog::ControllerLEDSettingsDialog(QWidget* parent, Contro
linkButton(m_ui.SDL2LED, 2);
linkButton(m_ui.SDL3LED, 3);
SettingsInterface* sif = dialog->getProfileSettingsInterface();
SettingsInterface* sif = dialog->getEditingSettingsInterface();
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLPS5PlayerLED, "InputSources", "SDLPS5PlayerLED", false);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableSDLPS5PlayerLED, "InputSources",
"SDLPS5PlayerLED", false);
connect(m_ui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &QDialog::accept);
}

View File

@ -50,7 +50,7 @@
</widget>
</item>
<item row="0" column="1" rowspan="7">
<widget class="QGroupBox" name="groupBox_3">
<widget class="QGroupBox" name="deviceListGroup">
<property name="title">
<string>Detected Devices</string>
</property>
@ -162,8 +162,7 @@
<string>Controller LED Settings</string>
</property>
<property name="icon">
<iconset theme="lightbulb-line">
<normaloff>.</normaloff>.</iconset>
<iconset theme="lightbulb-line"/>
</property>
</widget>
</item>
@ -284,7 +283,7 @@
<number>30</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
@ -339,7 +338,7 @@
<number>30</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
@ -388,7 +387,7 @@
<item row="6" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>

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();
}

View File

@ -20,6 +20,8 @@
#include <utility>
#include <vector>
class Error;
class ControllerGlobalSettingsWidget;
class ControllerBindingWidget;
class HotkeySettingsWidget;
@ -44,22 +46,33 @@ public:
MAX_PORTS = 8
};
ControllerSettingsWindow();
ControllerSettingsWindow(SettingsInterface* game_sif = nullptr, QWidget* parent = nullptr);
~ControllerSettingsWindow();
static void editControllerSettingsForGame(QWidget* parent, SettingsInterface* sif);
ALWAYS_INLINE HotkeySettingsWidget* getHotkeySettingsWidget() const { return m_hotkey_settings; }
ALWAYS_INLINE const std::vector<std::pair<std::string, std::string>>& getDeviceList() const { return m_device_list; }
ALWAYS_INLINE const QStringList& getVibrationMotors() const { return m_vibration_motors; }
ALWAYS_INLINE bool isEditingGlobalSettings() const { return m_profile_name.isEmpty(); }
ALWAYS_INLINE bool isEditingGlobalSettings() const
{
return (m_profile_name.isEmpty() && !m_editing_settings_interface);
}
ALWAYS_INLINE bool isEditingGameSettings() const
{
return (m_profile_name.isEmpty() && m_editing_settings_interface);
}
ALWAYS_INLINE bool isEditingProfile() const { return !m_profile_name.isEmpty(); }
ALWAYS_INLINE SettingsInterface* getProfileSettingsInterface() { return m_profile_interface.get(); }
ALWAYS_INLINE SettingsInterface* getEditingSettingsInterface() { return m_editing_settings_interface; }
Category getCurrentCategory() const;
void updateListDescription(u32 global_slot, ControllerBindingWidget* widget);
void switchProfile(const std::string_view name);
// Helper functions for updating setting values globally or in the profile.
bool getBoolValue(const char* section, const char* key, bool default_value) const;
s32 getIntValue(const char* section, const char* key, s32 default_value) const;
@ -71,6 +84,7 @@ public:
void saveAndReloadGameSettings();
Q_SIGNALS:
void windowClosed();
void inputProfileSwitched();
public Q_SLOTS:
@ -83,6 +97,8 @@ private Q_SLOTS:
void onApplyProfileClicked();
void onDeleteProfileClicked();
void onRestoreDefaultsClicked();
void onCopyGlobalSettingsClicked();
void onRestoreDefaultsForGameClicked();
void onInputDevicesEnumerated(const std::vector<std::pair<std::string, std::string>>& devices);
void onInputDeviceConnected(const std::string& identifier, const std::string& device_name);
@ -91,15 +107,19 @@ private Q_SLOTS:
void createWidgets();
protected:
void closeEvent(QCloseEvent* event) override;
private:
int getHotkeyCategoryIndex() const;
void refreshProfileList();
void switchProfile(const QString& name);
std::array<bool, 2> getEnabledMultitaps() const;
Ui::ControllerSettingsWindow m_ui;
SettingsInterface* m_editing_settings_interface = nullptr;
ControllerGlobalSettingsWidget* m_global_settings = nullptr;
std::array<ControllerBindingWidget*, MAX_PORTS> m_port_bindings{};
HotkeySettingsWidget* m_hotkey_settings = nullptr;
@ -108,5 +128,5 @@ private:
QStringList m_vibration_motors;
QString m_profile_name;
std::unique_ptr<SettingsInterface> m_profile_interface;
std::unique_ptr<SettingsInterface> m_profile_settings_interface;
};

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>1279</width>
<width>1284</width>
<height>672</height>
</rect>
</property>
@ -20,7 +20,7 @@
<string>DuckStation Controller Settings</string>
</property>
<property name="windowIcon">
<iconset resource="resources/resources.qrc">
<iconset resource="resources/duckstation-qt.qrc">
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout">
@ -65,9 +65,9 @@
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="editProfileLayout">
<item>
<widget class="QLabel" name="label">
<widget class="QLabel" name="editProfileLabel">
<property name="text">
<string>Editing Profile:</string>
</property>
@ -106,6 +106,16 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="copyGlobalSettings">
<property name="text">
<string>Copy Global Settings</string>
</property>
<property name="icon">
<iconset theme="folder-open-line"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreDefaults">
<property name="text">
@ -130,7 +140,7 @@
</layout>
</widget>
<resources>
<include location="resources/resources.qrc"/>
<include location="resources/duckstation-qt.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -11,6 +11,7 @@
#include "core/game_database.h"
#include "core/game_list.h"
#include "common/error.h"
#include "common/string_util.h"
#include "fmt/format.h"
@ -54,6 +55,7 @@ GameSummaryWidget::GameSummaryWidget(const std::string& path, const std::string&
connect(m_ui.compatibilityComments, &QToolButton::clicked, this, &GameSummaryWidget::onCompatibilityCommentsClicked);
connect(m_ui.inputProfile, &QComboBox::currentIndexChanged, this, &GameSummaryWidget::onInputProfileChanged);
connect(m_ui.editInputProfile, &QAbstractButton::clicked, this, &GameSummaryWidget::onEditInputProfileClicked);
connect(m_ui.computeHashes, &QAbstractButton::clicked, this, &GameSummaryWidget::onComputeHashClicked);
connect(m_ui.title, &QLineEdit::editingFinished, this, [this]() {
@ -159,15 +161,27 @@ void GameSummaryWidget::populateUi(const std::string& path, const std::string& s
m_ui.compatibilityComments->setVisible(!m_compatibility_comments.isEmpty());
m_ui.inputProfile->addItem(QIcon::fromTheme(QStringLiteral("controller-digital-line")), tr("Use Global Settings"));
m_ui.inputProfile->addItem(QIcon::fromTheme(QStringLiteral("global-line")), tr("Use Global Settings"));
m_ui.inputProfile->addItem(QIcon::fromTheme(QStringLiteral("controller-digital-line")),
tr("Game Specific Configuration"));
for (const std::string& name : InputManager::GetInputProfileNames())
m_ui.inputProfile->addItem(QString::fromStdString(name));
std::optional<std::string> profile(m_dialog->getStringValue("ControllerPorts", "InputProfileName", std::nullopt));
if (profile.has_value())
m_ui.inputProfile->setCurrentIndex(m_ui.inputProfile->findText(QString::fromStdString(profile.value())));
if (m_dialog->getBoolValue("ControllerPorts", "UseGameSettingsForController", std::nullopt).value_or(false))
{
m_ui.inputProfile->setCurrentIndex(1);
}
else if (const std::optional<std::string> profile_name =
m_dialog->getStringValue("ControllerPorts", "InputProfileName", std::nullopt);
profile_name.has_value() && !profile_name->empty())
{
m_ui.inputProfile->setCurrentIndex(m_ui.inputProfile->findText(QString::fromStdString(profile_name.value())));
}
else
{
m_ui.inputProfile->setCurrentIndex(0);
}
m_ui.editInputProfile->setEnabled(m_ui.inputProfile->currentIndex() >= 1);
populateCustomAttributes();
populateTracksInfo();
@ -221,6 +235,21 @@ void GameSummaryWidget::setCustomRegion(int region)
g_main_window->refreshGameListModel();
}
void GameSummaryWidget::setRevisionText(const QString& text)
{
if (text.isEmpty())
return;
if (m_ui.verifySpacer)
{
m_ui.verifyLayout->removeItem(m_ui.verifySpacer);
delete m_ui.verifySpacer;
m_ui.verifySpacer = nullptr;
}
m_ui.revision->setText(text);
m_ui.revision->setVisible(true);
}
static QString MSFTotString(const CDImage::Position& position)
{
return QStringLiteral("%1:%2:%3 (LBA %4)")
@ -242,6 +271,11 @@ void GameSummaryWidget::populateTracksInfo()
if (!image)
return;
setRevisionText(tr("%1 tracks covering %2 MB (%3 MB on disk)")
.arg(image->GetTrackCount())
.arg(((image->GetLBACount() * CDImage::RAW_SECTOR_SIZE) + 1048575) / 1048576)
.arg((image->GetSizeOnDisk() + 1048575) / 1048576));
const u32 num_tracks = image->GetTrackCount();
for (u32 track = 1; track <= num_tracks; track++)
{
@ -288,10 +322,61 @@ void GameSummaryWidget::onCompatibilityCommentsClicked()
void GameSummaryWidget::onInputProfileChanged(int index)
{
SettingsInterface* sif = m_dialog->getSettingsInterface();
if (index == 0)
m_dialog->setStringSettingValue("ControllerPorts", "InputProfileName", std::nullopt);
{
// Use global settings.
sif->DeleteValue("ControllerPorts", "InputProfileName");
sif->DeleteValue("ControllerPorts", "UseGameSettingsForController");
}
else if (index == 1)
{
// Per-game configuration.
sif->DeleteValue("ControllerPorts", "InputProfileName");
sif->SetBoolValue("ControllerPorts", "UseGameSettingsForController", true);
if (!sif->GetBoolValue("ControllerPorts", "GameSettingsInitialized", false))
{
sif->SetBoolValue("ControllerPorts", "GameSettingsInitialized", true);
{
const auto lock = Host::GetSettingsLock();
SettingsInterface* base_sif = Host::Internal::GetBaseSettingsLayer();
InputManager::CopyConfiguration(sif, *base_sif, true, true, false);
QWidget* dlg_parent = QtUtils::GetRootWidget(this);
QMessageBox::information(dlg_parent, dlg_parent->windowTitle(),
tr("Per-game controller configuration initialized with global settings."));
}
}
}
else
m_dialog->setStringSettingValue("ControllerPorts", "InputProfileName", m_ui.inputProfile->itemText(index).toUtf8());
{
// Input profile.
sif->SetStringValue("ControllerPorts", "InputProfileName", m_ui.inputProfile->itemText(index).toUtf8());
sif->DeleteValue("ControllerPorts", "UseGameSettingsForController");
}
m_dialog->saveAndReloadGameSettings();
m_ui.editInputProfile->setEnabled(index > 0);
}
void GameSummaryWidget::onEditInputProfileClicked()
{
if (m_dialog->getBoolValue("ControllerPorts", "UseGameSettingsForController", std::nullopt).value_or(false))
{
// Edit game configuration.
ControllerSettingsWindow::editControllerSettingsForGame(QtUtils::GetRootWidget(this),
m_dialog->getSettingsInterface());
}
else if (const std::optional<std::string> profile_name =
m_dialog->getStringValue("ControllerPorts", "InputProfileName", std::nullopt);
profile_name.has_value() && !profile_name->empty())
{
// Edit input profile.
g_main_window->openInputProfileEditor(profile_name.value());
}
}
void GameSummaryWidget::onComputeHashClicked()
@ -417,17 +502,7 @@ void GameSummaryWidget::onComputeHashClicked()
text = mismatch_str;
}
if (!text.isEmpty())
{
if (m_ui.verifySpacer)
{
m_ui.verifyLayout->removeItem(m_ui.verifySpacer);
delete m_ui.verifySpacer;
m_ui.verifySpacer = nullptr;
}
m_ui.revision->setText(text);
m_ui.revision->setVisible(true);
}
setRevisionText(text);
}
for (u8 track = 0; track < image->GetTrackCount(); track++)

View File

@ -27,6 +27,7 @@ public:
private Q_SLOTS:
void onCompatibilityCommentsClicked();
void onInputProfileChanged(int index);
void onEditInputProfileClicked();
void onComputeHashClicked();
private:
@ -36,6 +37,7 @@ private:
void updateWindowTitle();
void setCustomTitle(const std::string& text);
void setCustomRegion(int region);
void setRevisionText(const QString& text);
void populateTracksInfo();

View File

@ -60,17 +60,17 @@
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QComboBox" name="region">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<item>
<widget class="QComboBox" name="region">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreRegion">
<property name="enabled">
<bool>false</bool>
@ -79,7 +79,7 @@
<string>Restore</string>
</property>
</widget>
</item>
</item>
</layout>
</item>
<item row="0" column="0">
@ -106,7 +106,7 @@
<item row="18" column="0" colspan="2">
<widget class="QTableWidget" name="tracks">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
@ -216,15 +216,12 @@
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QComboBox" name="inputProfile"/>
</item>
<item row="16" column="1">
<layout class="QHBoxLayout" name="verifyLayout" stretch="0,1,0">
<item>
<spacer name="verifySpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -306,8 +303,21 @@
<string>Comments</string>
</property>
<property name="icon">
<iconset theme="information-line">
<normaloff>.</normaloff>.</iconset>
<iconset theme="information-line"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="13" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
<item>
<widget class="QComboBox" name="inputProfile"/>
</item>
<item>
<widget class="QPushButton" name="editInputProfile">
<property name="text">
<string>Edit...</string>
</property>
</widget>
</item>

View File

@ -73,7 +73,7 @@ void HotkeySettingsWidget::createButtons()
QLabel* label = new QLabel(qApp->translate("Hotkeys", hotkey->display_name), m_container);
layout->addWidget(label, target_row, 0);
InputBindingWidget* bind = new InputBindingWidget(m_container, m_dialog->getProfileSettingsInterface(),
InputBindingWidget* bind = new InputBindingWidget(m_container, m_dialog->getEditingSettingsInterface(),
InputBindingInfo::Type::Button, "Hotkeys", hotkey->name);
bind->setMinimumWidth(300);
layout->addWidget(bind, target_row, 1);

View File

@ -2394,6 +2394,13 @@ void MainWindow::doControllerSettings(
dlg->setCategory(category);
}
void MainWindow::openInputProfileEditor(const std::string_view name)
{
ControllerSettingsWindow* dlg = getControllerSettingsWindow();
QtUtils::ShowOrRaiseWindow(dlg);
dlg->switchProfile(name);
}
void MainWindow::updateDebugMenuCPUExecutionMode()
{
std::optional<CPUExecutionMode> current_mode =

View File

@ -99,6 +99,9 @@ public:
/// Accessors for child windows.
CheatManagerWindow* getCheatManagerWindow() const { return m_cheat_manager_window; }
/// Opens the editor for a specific input profile.
void openInputProfileEditor(const std::string_view name);
public Q_SLOTS:
/// Updates debug menu visibility (hides if disabled).
void updateDebugMenuVisibility();

View File

@ -296,7 +296,7 @@ void SettingsWindow::onCopyGlobalSettingsClicked()
{
auto lock = Host::GetSettingsLock();
Settings temp;
temp.Load(*Host::Internal::GetBaseSettingsLayer());
temp.Load(*Host::Internal::GetBaseSettingsLayer(), *Host::Internal::GetBaseSettingsLayer());
temp.Save(*m_sif.get(), true);
}
saveAndReloadGameSettings();

View File

@ -93,6 +93,7 @@ public:
bool containsSettingValue(const char* section, const char* key) const;
void removeSettingValue(const char* section, const char* key);
void saveAndReloadGameSettings();
void reloadGameSettingsFromIni();
bool hasGameTrait(GameDatabase::Trait trait);