winamp/src/duckstation-qt/controllerbindingwidgets.cpp

948 lines
31 KiB
C++

// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "controllerbindingwidgets.h"
#include "controllersettingswindow.h"
#include "controllersettingwidgetbinder.h"
#include "qthost.h"
#include "qtutils.h"
#include "settingswindow.h"
#include "settingwidgetbinder.h"
#include "ui_controllerbindingwidget_analog_controller.h"
#include "ui_controllerbindingwidget_analog_joystick.h"
#include "ui_controllerbindingwidget_digital_controller.h"
#include "ui_controllerbindingwidget_guncon.h"
#include "ui_controllerbindingwidget_justifier.h"
#include "ui_controllerbindingwidget_mouse.h"
#include "ui_controllerbindingwidget_negcon.h"
#include "ui_controllerbindingwidget_negconrumble.h"
#include "core/controller.h"
#include "core/host.h"
#include "util/input_manager.h"
#include "common/log.h"
#include "common/string_util.h"
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QDoubleSpinBox>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QScrollArea>
#include <QtWidgets/QSpinBox>
#include <algorithm>
Log_SetChannel(ControllerBindingWidget);
ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSettingsWindow* dialog, u32 port)
: QWidget(parent), m_dialog(dialog), m_config_section(Controller::GetSettingsSection(port)), m_port_number(port)
{
m_ui.setupUi(this);
populateControllerTypes();
populateWidgets();
connect(m_ui.controllerType, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ControllerBindingWidget::onTypeChanged);
connect(m_ui.bindings, &QPushButton::clicked, this, &ControllerBindingWidget::onBindingsClicked);
connect(m_ui.settings, &QPushButton::clicked, this, &ControllerBindingWidget::onSettingsClicked);
connect(m_ui.macros, &QPushButton::clicked, this, &ControllerBindingWidget::onMacrosClicked);
connect(m_ui.automaticBinding, &QPushButton::clicked, this, &ControllerBindingWidget::onAutomaticBindingClicked);
connect(m_ui.clearBindings, &QPushButton::clicked, this, &ControllerBindingWidget::onClearBindingsClicked);
}
ControllerBindingWidget::~ControllerBindingWidget() = default;
void ControllerBindingWidget::populateControllerTypes()
{
for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++)
{
const ControllerType ctype = static_cast<ControllerType>(i);
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(ctype);
if (!cinfo)
continue;
m_ui.controllerType->addItem(QString::fromUtf8(cinfo->GetDisplayName()), QVariant(static_cast<int>(i)));
}
m_controller_info = Controller::GetControllerInfo(
m_dialog->getStringValue(m_config_section.c_str(), "Type", Controller::GetDefaultPadType(m_port_number)));
if (!m_controller_info)
{
m_controller_info = Controller::GetControllerInfo(m_port_number == 0 ? Settings::DEFAULT_CONTROLLER_1_TYPE :
Settings::DEFAULT_CONTROLLER_2_TYPE);
}
const int index = m_ui.controllerType->findData(QVariant(static_cast<int>(m_controller_info->type)));
if (index >= 0 && index != m_ui.controllerType->currentIndex())
{
QSignalBlocker sb(m_ui.controllerType);
m_ui.controllerType->setCurrentIndex(index);
}
}
void ControllerBindingWidget::populateWidgets()
{
const bool is_initializing = (m_ui.stackedWidget->count() == 0);
if (m_bindings_widget)
{
m_ui.stackedWidget->removeWidget(m_bindings_widget);
delete m_bindings_widget;
m_bindings_widget = nullptr;
}
if (m_settings_widget)
{
m_ui.stackedWidget->removeWidget(m_settings_widget);
delete m_settings_widget;
m_settings_widget = nullptr;
}
if (m_macros_widget)
{
m_ui.stackedWidget->removeWidget(m_macros_widget);
delete m_macros_widget;
m_macros_widget = nullptr;
}
const bool has_settings = !m_controller_info->settings.empty();
const bool has_macros = !m_controller_info->bindings.empty();
m_ui.settings->setEnabled(has_settings);
m_ui.macros->setEnabled(has_macros);
m_bindings_widget = new QWidget(this);
switch (m_controller_info->type)
{
case ControllerType::AnalogController:
{
Ui::ControllerBindingWidget_AnalogController ui;
ui.setupUi(m_bindings_widget);
bindBindingWidgets(m_bindings_widget);
m_icon = QIcon::fromTheme(QStringLiteral("controller-line"));
}
break;
case ControllerType::AnalogJoystick:
{
Ui::ControllerBindingWidget_AnalogJoystick ui;
ui.setupUi(m_bindings_widget);
bindBindingWidgets(m_bindings_widget);
m_icon = QIcon::fromTheme(QStringLiteral("joystick-line"));
}
break;
case ControllerType::DigitalController:
{
Ui::ControllerBindingWidget_DigitalController ui;
ui.setupUi(m_bindings_widget);
bindBindingWidgets(m_bindings_widget);
m_icon = QIcon::fromTheme(QStringLiteral("controller-digital-line"));
}
break;
case ControllerType::GunCon:
{
Ui::ControllerBindingWidget_GunCon ui;
ui.setupUi(m_bindings_widget);
bindBindingWidgets(m_bindings_widget);
m_icon = QIcon::fromTheme(QStringLiteral("guncon-line"));
}
break;
case ControllerType::NeGcon:
{
Ui::ControllerBindingWidget_NeGcon ui;
ui.setupUi(m_bindings_widget);
bindBindingWidgets(m_bindings_widget);
m_icon = QIcon::fromTheme(QStringLiteral("negcon-line"));
}
break;
case ControllerType::NeGconRumble:
{
Ui::ControllerBindingWidget_NeGconRumble ui;
ui.setupUi(m_bindings_widget);
bindBindingWidgets(m_bindings_widget);
m_icon = QIcon::fromTheme(QStringLiteral("negcon-line"));
}
break;
case ControllerType::PlayStationMouse:
{
Ui::ControllerBindingWidget_Mouse ui;
ui.setupUi(m_bindings_widget);
bindBindingWidgets(m_bindings_widget);
m_icon = QIcon::fromTheme(QStringLiteral("mouse-line"));
}
break;
case ControllerType::Justifier:
{
Ui::ControllerBindingWidget_Justifier ui;
ui.setupUi(m_bindings_widget);
bindBindingWidgets(m_bindings_widget);
m_icon = QIcon::fromTheme(QStringLiteral("guncon-line"));
}
break;
case ControllerType::None:
{
m_icon = QIcon::fromTheme(QStringLiteral("controller-strike-line"));
}
break;
default:
{
createBindingWidgets(m_bindings_widget);
m_icon = QIcon::fromTheme(QStringLiteral("controller-line"));
}
break;
}
m_ui.stackedWidget->addWidget(m_bindings_widget);
m_ui.stackedWidget->setCurrentWidget(m_bindings_widget);
if (has_settings)
{
m_settings_widget = new ControllerCustomSettingsWidget(this);
m_ui.stackedWidget->addWidget(m_settings_widget);
}
if (has_macros)
{
m_macros_widget = new ControllerMacroWidget(this);
m_ui.stackedWidget->addWidget(m_macros_widget);
}
updateHeaderToolButtons();
// no need to do this on first init, only changes
if (!is_initializing)
m_dialog->updateListDescription(m_port_number, this);
}
void ControllerBindingWidget::updateHeaderToolButtons()
{
const QWidget* current_widget = m_ui.stackedWidget->currentWidget();
const QSignalBlocker bindings_sb(m_ui.bindings);
const QSignalBlocker settings_sb(m_ui.settings);
const QSignalBlocker macros_sb(m_ui.macros);
const bool is_bindings = (current_widget == m_bindings_widget);
m_ui.bindings->setChecked(is_bindings);
m_ui.automaticBinding->setEnabled(is_bindings);
m_ui.clearBindings->setEnabled(is_bindings);
m_ui.macros->setChecked(current_widget == m_macros_widget);
m_ui.settings->setChecked((current_widget == m_settings_widget));
}
void ControllerBindingWidget::onTypeChanged()
{
bool ok;
const int index = m_ui.controllerType->currentData().toInt(&ok);
if (!ok || index < 0 || index >= static_cast<int>(ControllerType::Count))
return;
m_controller_info = Controller::GetControllerInfo(static_cast<ControllerType>(index));
DebugAssert(m_controller_info);
SettingsInterface* sif = m_dialog->getProfileSettingsInterface();
if (sif)
{
sif->SetStringValue(m_config_section.c_str(), "Type", m_controller_info->name);
QtHost::SaveGameSettings(sif, false);
g_emu_thread->reloadGameSettings();
}
else
{
Host::SetBaseStringSettingValue(m_config_section.c_str(), "Type", m_controller_info->name);
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
}
populateWidgets();
}
void ControllerBindingWidget::onAutomaticBindingClicked()
{
QMenu menu(this);
bool added = false;
for (const QPair<QString, QString>& dev : m_dialog->getDeviceList())
{
// we set it as data, because the device list could get invalidated while the menu is up
QAction* action = menu.addAction(QStringLiteral("%1 (%2)").arg(dev.first).arg(dev.second));
action->setData(dev.first);
connect(action, &QAction::triggered, this,
[this, action]() { doDeviceAutomaticBinding(action->data().toString()); });
added = true;
}
if (!added)
{
QAction* action = menu.addAction(tr("No devices available"));
action->setEnabled(false);
}
menu.exec(QCursor::pos());
}
void ControllerBindingWidget::onClearBindingsClicked()
{
if (QMessageBox::question(
QtUtils::GetRootWidget(this), tr("Clear Mapping"),
tr("Are you sure you want to clear all mappings for this controller? This action cannot be undone.")) !=
QMessageBox::Yes)
{
return;
}
if (m_dialog->isEditingGlobalSettings())
{
auto lock = Host::GetSettingsLock();
InputManager::ClearPortBindings(*Host::Internal::GetBaseSettingsLayer(), m_port_number);
}
else
{
InputManager::ClearPortBindings(*m_dialog->getProfileSettingsInterface(), m_port_number);
}
saveAndRefresh();
}
void ControllerBindingWidget::onBindingsClicked()
{
m_ui.stackedWidget->setCurrentWidget(m_bindings_widget);
updateHeaderToolButtons();
}
void ControllerBindingWidget::onSettingsClicked()
{
if (!m_settings_widget)
return;
m_ui.stackedWidget->setCurrentWidget(m_settings_widget);
updateHeaderToolButtons();
}
void ControllerBindingWidget::onMacrosClicked()
{
if (!m_macros_widget)
return;
m_ui.stackedWidget->setCurrentWidget(m_macros_widget);
updateHeaderToolButtons();
}
void ControllerBindingWidget::doDeviceAutomaticBinding(const QString& device)
{
std::vector<std::pair<GenericInputBinding, std::string>> mapping =
InputManager::GetGenericBindingMapping(device.toStdString());
if (mapping.empty())
{
QMessageBox::critical(
QtUtils::GetRootWidget(this), tr("Automatic Mapping"),
tr("No generic bindings were generated for device '%1'. The controller/source may not support automatic mapping.")
.arg(device));
return;
}
bool result;
if (m_dialog->isEditingGlobalSettings())
{
auto lock = Host::GetSettingsLock();
result = InputManager::MapController(*Host::Internal::GetBaseSettingsLayer(), m_port_number, mapping);
}
else
{
result = InputManager::MapController(*m_dialog->getProfileSettingsInterface(), m_port_number, mapping);
QtHost::SaveGameSettings(m_dialog->getProfileSettingsInterface(), false);
g_emu_thread->reloadInputBindings();
}
// force a refresh after mapping
if (result)
saveAndRefresh();
}
void ControllerBindingWidget::saveAndRefresh()
{
onTypeChanged();
QtHost::QueueSettingsSave();
g_emu_thread->applySettings();
}
void ControllerBindingWidget::createBindingWidgets(QWidget* parent)
{
SettingsInterface* sif = getDialog()->getProfileSettingsInterface();
DebugAssert(m_controller_info);
QGroupBox* axis_gbox = nullptr;
QGridLayout* axis_layout = nullptr;
QGroupBox* button_gbox = nullptr;
QGridLayout* button_layout = nullptr;
QScrollArea* scrollarea = new QScrollArea(parent);
QWidget* scrollarea_widget = new QWidget(scrollarea);
scrollarea->setWidget(scrollarea_widget);
scrollarea->setWidgetResizable(true);
scrollarea->setFrameShape(QFrame::StyledPanel);
scrollarea->setFrameShadow(QFrame::Sunken);
// We do axes and buttons separately, so we can figure out how many columns to use.
constexpr int NUM_AXIS_COLUMNS = 2;
int column = 0;
int row = 0;
for (const Controller::ControllerBindingInfo& bi : m_controller_info->bindings)
{
if (bi.type == InputBindingInfo::Type::Axis || bi.type == InputBindingInfo::Type::HalfAxis ||
bi.type == InputBindingInfo::Type::Pointer)
{
if (!axis_gbox)
{
axis_gbox = new QGroupBox(tr("Axes"), scrollarea_widget);
axis_layout = new QGridLayout(axis_gbox);
}
QGroupBox* gbox = new QGroupBox(qApp->translate("USB", bi.display_name), axis_gbox);
QVBoxLayout* temp = new QVBoxLayout(gbox);
InputBindingWidget* widget = new InputBindingWidget(gbox, sif, bi.type, getConfigSection(), bi.name);
temp->addWidget(widget);
axis_layout->addWidget(gbox, row, column);
if ((++column) == NUM_AXIS_COLUMNS)
{
column = 0;
row++;
}
}
}
if (axis_gbox)
axis_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding), ++row, 0);
const int num_button_columns = axis_layout ? 2 : 4;
row = 0;
column = 0;
for (const Controller::ControllerBindingInfo& bi : m_controller_info->bindings)
{
if (bi.type == InputBindingInfo::Type::Button)
{
if (!button_gbox)
{
button_gbox = new QGroupBox(tr("Buttons"), scrollarea_widget);
button_layout = new QGridLayout(button_gbox);
}
QGroupBox* gbox = new QGroupBox(qApp->translate("USB", bi.display_name), button_gbox);
QVBoxLayout* temp = new QVBoxLayout(gbox);
InputBindingWidget* widget = new InputBindingWidget(gbox, sif, bi.type, getConfigSection(), bi.name);
temp->addWidget(widget);
button_layout->addWidget(gbox, row, column);
if ((++column) == num_button_columns)
{
column = 0;
row++;
}
}
}
if (button_gbox)
button_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding), ++row, 0);
if (!axis_gbox && !button_gbox)
{
delete scrollarea_widget;
delete scrollarea;
return;
}
QHBoxLayout* layout = new QHBoxLayout(scrollarea_widget);
if (axis_gbox)
layout->addWidget(axis_gbox);
if (button_gbox)
layout->addWidget(button_gbox);
layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));
QHBoxLayout* main_layout = new QHBoxLayout(parent);
main_layout->addWidget(scrollarea);
}
void ControllerBindingWidget::bindBindingWidgets(QWidget* parent)
{
SettingsInterface* sif = getDialog()->getProfileSettingsInterface();
DebugAssert(m_controller_info);
const std::string& config_section = getConfigSection();
for (const Controller::ControllerBindingInfo& bi : m_controller_info->bindings)
{
if (bi.type == InputBindingInfo::Type::Axis || bi.type == InputBindingInfo::Type::HalfAxis ||
bi.type == InputBindingInfo::Type::Button || bi.type == InputBindingInfo::Type::Pointer)
{
InputBindingWidget* widget = parent->findChild<InputBindingWidget*>(QString::fromUtf8(bi.name));
if (!widget)
{
ERROR_LOG("No widget found for '{}' ({})", bi.name, m_controller_info->name);
continue;
}
widget->initialize(sif, bi.type, config_section, bi.name);
}
}
switch (m_controller_info->vibration_caps)
{
case Controller::VibrationCapabilities::LargeSmallMotors:
{
InputVibrationBindingWidget* widget =
parent->findChild<InputVibrationBindingWidget*>(QStringLiteral("LargeMotor"));
if (widget)
widget->setKey(getDialog(), config_section, "LargeMotor");
widget = parent->findChild<InputVibrationBindingWidget*>(QStringLiteral("SmallMotor"));
if (widget)
widget->setKey(getDialog(), config_section, "SmallMotor");
}
break;
case Controller::VibrationCapabilities::SingleMotor:
{
InputVibrationBindingWidget* widget = parent->findChild<InputVibrationBindingWidget*>(QStringLiteral("Motor"));
if (widget)
widget->setKey(getDialog(), config_section, "Motor");
}
break;
case Controller::VibrationCapabilities::NoVibration:
default:
break;
}
}
//////////////////////////////////////////////////////////////////////////
ControllerMacroWidget::ControllerMacroWidget(ControllerBindingWidget* parent) : QWidget(parent)
{
m_ui.setupUi(this);
setWindowTitle(tr("Controller Port %1 Macros").arg(parent->getPortNumber() + 1u));
createWidgets(parent);
}
ControllerMacroWidget::~ControllerMacroWidget() = default;
void ControllerMacroWidget::updateListItem(u32 index)
{
m_ui.portList->item(static_cast<int>(index))
->setText(tr("Macro %1\n%2").arg(index + 1).arg(m_macros[index]->getSummary()));
}
void ControllerMacroWidget::createWidgets(ControllerBindingWidget* parent)
{
for (u32 i = 0; i < NUM_MACROS; i++)
{
m_macros[i] = new ControllerMacroEditWidget(this, parent, i);
m_ui.container->addWidget(m_macros[i]);
QListWidgetItem* item = new QListWidgetItem();
item->setIcon(QIcon::fromTheme(QStringLiteral("flashlight-line")));
m_ui.portList->addItem(item);
updateListItem(i);
}
m_ui.portList->setCurrentRow(0);
m_ui.container->setCurrentIndex(0);
connect(m_ui.portList, &QListWidget::currentRowChanged, m_ui.container, &QStackedWidget::setCurrentIndex);
}
//////////////////////////////////////////////////////////////////////////
ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* parent, ControllerBindingWidget* bwidget,
u32 index)
: QWidget(parent), m_parent(parent), m_bwidget(bwidget), m_index(index)
{
m_ui.setupUi(this);
ControllerSettingsWindow* dialog = m_bwidget->getDialog();
const std::string& section = m_bwidget->getConfigSection();
const Controller::ControllerInfo* cinfo = m_bwidget->getControllerInfo();
DebugAssert(cinfo);
// load binds (single string joined by &)
const std::string binds_string(
dialog->getStringValue(section.c_str(), TinyString::from_format("Macro{}Binds", index + 1u), ""));
const std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true));
for (const std::string_view& button : buttons_split)
{
for (const Controller::ControllerBindingInfo& bi : cinfo->bindings)
{
if (button == bi.name)
{
m_binds.push_back(&bi);
break;
}
}
}
// populate list view
for (const Controller::ControllerBindingInfo& bi : cinfo->bindings)
{
if (bi.type == InputBindingInfo::Type::Motor)
continue;
QListWidgetItem* item = new QListWidgetItem();
item->setText(qApp->translate(cinfo->name, bi.display_name));
item->setCheckState((std::find(m_binds.begin(), m_binds.end(), &bi) != m_binds.end()) ? Qt::Checked :
Qt::Unchecked);
m_ui.bindList->addItem(item);
}
m_frequency = dialog->getIntValue(section.c_str(), TinyString::from_format("Macro{}Frequency", index + 1u), 0);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(dialog->getProfileSettingsInterface(), m_ui.triggerToggle,
section.c_str(), fmt::format("Macro{}Toggle", index + 1u),
false);
updateFrequencyText();
m_ui.trigger->initialize(dialog->getProfileSettingsInterface(), InputBindingInfo::Type::Macro, section,
fmt::format("Macro{}", index + 1u));
connect(m_ui.increaseFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(1); });
connect(m_ui.decreateFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(-1); });
connect(m_ui.setFrequency, &QAbstractButton::clicked, this, &ControllerMacroEditWidget::onSetFrequencyClicked);
connect(m_ui.bindList, &QListWidget::itemChanged, this, &ControllerMacroEditWidget::updateBinds);
}
ControllerMacroEditWidget::~ControllerMacroEditWidget() = default;
QString ControllerMacroEditWidget::getSummary() const
{
SmallString str;
for (const Controller::ControllerBindingInfo* bi : m_binds)
{
if (!str.empty())
str.append('/');
str.append(bi->name);
}
return str.empty() ? tr("Not Configured") : QString::fromUtf8(str.c_str(), static_cast<int>(str.length()));
}
void ControllerMacroEditWidget::onSetFrequencyClicked()
{
bool okay;
int new_freq = QInputDialog::getInt(this, tr("Set Frequency"), tr("Frequency: "), static_cast<int>(m_frequency), 0,
std::numeric_limits<int>::max(), 1, &okay);
if (!okay)
return;
m_frequency = static_cast<u32>(new_freq);
updateFrequency();
}
void ControllerMacroEditWidget::modFrequency(s32 delta)
{
if (delta < 0 && m_frequency == 0)
return;
m_frequency = static_cast<u32>(static_cast<s32>(m_frequency) + delta);
updateFrequency();
}
void ControllerMacroEditWidget::updateFrequency()
{
m_bwidget->getDialog()->setIntValue(m_bwidget->getConfigSection().c_str(),
fmt::format("Macro{}Frequency", m_index + 1u).c_str(),
static_cast<s32>(m_frequency));
updateFrequencyText();
}
void ControllerMacroEditWidget::updateFrequencyText()
{
if (m_frequency == 0)
m_ui.frequencyText->setText(tr("Macro will not repeat."));
else
m_ui.frequencyText->setText(tr("Macro will toggle buttons every %1 frames.").arg(m_frequency));
}
void ControllerMacroEditWidget::updateBinds()
{
ControllerSettingsWindow* dialog = m_bwidget->getDialog();
const Controller::ControllerInfo* cinfo = m_bwidget->getControllerInfo();
DebugAssert(cinfo);
std::vector<const Controller::ControllerBindingInfo*> new_binds;
u32 bind_index = 0;
for (const Controller::ControllerBindingInfo& bi : cinfo->bindings)
{
if (bi.type == InputBindingInfo::Type::Motor)
continue;
const QListWidgetItem* item = m_ui.bindList->item(static_cast<int>(bind_index));
bind_index++;
if (!item)
{
// shouldn't happen
continue;
}
if (item->checkState() == Qt::Checked)
new_binds.push_back(&bi);
}
if (m_binds == new_binds)
return;
m_binds = std::move(new_binds);
std::string binds_string;
for (const Controller::ControllerBindingInfo* bi : m_binds)
{
if (!binds_string.empty())
binds_string.append(" & ");
binds_string.append(bi->name);
}
const std::string& section = m_bwidget->getConfigSection();
const std::string key(fmt::format("Macro{}Binds", m_index + 1u));
if (binds_string.empty())
dialog->clearSettingValue(section.c_str(), key.c_str());
else
dialog->setStringValue(section.c_str(), key.c_str(), binds_string.c_str());
m_parent->updateListItem(m_index);
}
//////////////////////////////////////////////////////////////////////////
ControllerCustomSettingsWidget::ControllerCustomSettingsWidget(ControllerBindingWidget* parent)
: QWidget(parent), m_parent(parent)
{
const Controller::ControllerInfo* cinfo = parent->getControllerInfo();
DebugAssert(cinfo);
if (cinfo->settings.empty())
return;
QScrollArea* sarea = new QScrollArea(this);
QWidget* swidget = new QWidget(sarea);
sarea->setWidget(swidget);
sarea->setWidgetResizable(true);
sarea->setFrameShape(QFrame::StyledPanel);
sarea->setFrameShadow(QFrame::Sunken);
QGridLayout* swidget_layout = new QGridLayout(swidget);
createSettingWidgets(parent, swidget, swidget_layout, cinfo);
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(sarea);
}
ControllerCustomSettingsWidget::~ControllerCustomSettingsWidget()
{
}
void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidget* parent, QWidget* parent_widget,
QGridLayout* layout, const Controller::ControllerInfo* cinfo)
{
const std::string& section = parent->getConfigSection();
SettingsInterface* sif = parent->getDialog()->getProfileSettingsInterface();
int current_row = 0;
for (const SettingInfo& si : cinfo->settings)
{
std::string key_name = si.name;
switch (si.type)
{
case SettingInfo::Type::Boolean:
{
QCheckBox* cb = new QCheckBox(qApp->translate(cinfo->name, si.display_name), this);
cb->setObjectName(QString::fromUtf8(si.name));
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, cb, section, std::move(key_name),
si.BooleanDefaultValue());
layout->addWidget(cb, current_row, 0, 1, 4);
current_row++;
}
break;
case SettingInfo::Type::Integer:
{
QSpinBox* sb = new QSpinBox(this);
sb->setObjectName(QString::fromUtf8(si.name));
sb->setMinimum(si.IntegerMinValue());
sb->setMaximum(si.IntegerMaxValue());
sb->setSingleStep(si.IntegerStepValue());
SettingWidgetBinder::BindWidgetToIntSetting(sif, sb, section, std::move(key_name), si.IntegerDefaultValue());
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.display_name), this), current_row, 0);
layout->addWidget(sb, current_row, 1, 1, 3);
current_row++;
}
break;
case SettingInfo::Type::IntegerList:
{
QComboBox* cb = new QComboBox(this);
cb->setObjectName(QString::fromUtf8(si.name));
for (u32 j = 0; si.options[j] != nullptr; j++)
cb->addItem(qApp->translate(cinfo->name, si.options[j]));
SettingWidgetBinder::BindWidgetToIntSetting(sif, cb, section, std::move(key_name), si.IntegerDefaultValue(),
si.IntegerMinValue());
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.display_name), this), current_row, 0);
layout->addWidget(cb, current_row, 1, 1, 3);
current_row++;
}
break;
case SettingInfo::Type::Float:
{
QDoubleSpinBox* sb = new QDoubleSpinBox(this);
sb->setObjectName(QString::fromUtf8(si.name));
if (si.multiplier != 0.0f && si.multiplier != 1.0f)
{
const float multiplier = si.multiplier;
sb->setMinimum(si.FloatMinValue() * multiplier);
sb->setMaximum(si.FloatMaxValue() * multiplier);
sb->setSingleStep(si.FloatStepValue() * multiplier);
if (std::abs(si.multiplier - 100.0f) < 0.01f)
{
sb->setDecimals(0);
sb->setSuffix(QStringLiteral("%"));
}
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, sb, section, std::move(key_name), si.multiplier,
si.FloatDefaultValue());
}
else
{
sb->setMinimum(si.FloatMinValue());
sb->setMaximum(si.FloatMaxValue());
sb->setSingleStep(si.FloatStepValue());
SettingWidgetBinder::BindWidgetToFloatSetting(sif, sb, section, std::move(key_name), si.FloatDefaultValue());
}
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.display_name), this), current_row, 0);
layout->addWidget(sb, current_row, 1, 1, 3);
current_row++;
}
break;
case SettingInfo::Type::String:
{
QLineEdit* le = new QLineEdit(this);
le->setObjectName(QString::fromUtf8(si.name));
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, section, std::move(key_name), si.StringDefaultValue());
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.display_name), this), current_row, 0);
layout->addWidget(le, current_row, 1, 1, 3);
current_row++;
}
break;
case SettingInfo::Type::Path:
{
QLineEdit* le = new QLineEdit(this);
le->setObjectName(QString::fromUtf8(si.name));
QPushButton* browse_button = new QPushButton(tr("Browse..."), this);
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, section, std::move(key_name), si.StringDefaultValue());
connect(browse_button, &QPushButton::clicked, [this, le]() {
QString path = QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Select File")));
if (!path.isEmpty())
le->setText(path);
});
QHBoxLayout* hbox = new QHBoxLayout();
hbox->addWidget(le, 1);
hbox->addWidget(browse_button);
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.display_name), this), current_row, 0);
layout->addLayout(hbox, current_row, 1, 1, 3);
current_row++;
}
break;
}
QLabel* label = new QLabel(si.description ? qApp->translate(cinfo->name, si.description) : QString(), this);
label->setWordWrap(true);
layout->addWidget(label, current_row++, 0, 1, 4);
layout->addItem(new QSpacerItem(1, 10, QSizePolicy::Minimum, QSizePolicy::Fixed), current_row++, 0, 1, 4);
}
QHBoxLayout* bottom_hlayout = new QHBoxLayout();
QPushButton* restore_defaults = new QPushButton(tr("Restore Default Settings"), this);
restore_defaults->setIcon(QIcon::fromTheme(QStringLiteral("restart-line")));
connect(restore_defaults, &QPushButton::clicked, this, &ControllerCustomSettingsWidget::restoreDefaults);
bottom_hlayout->addStretch(1);
bottom_hlayout->addWidget(restore_defaults);
layout->addLayout(bottom_hlayout, current_row++, 0, 1, 4);
layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding), current_row++, 0, 1, 4);
}
void ControllerCustomSettingsWidget::restoreDefaults()
{
const Controller::ControllerInfo* cinfo = m_parent->getControllerInfo();
DebugAssert(cinfo);
if (cinfo->settings.empty())
return;
for (const SettingInfo& si : cinfo->settings)
{
const QString key(QString::fromStdString(si.name));
switch (si.type)
{
case SettingInfo::Type::Boolean:
{
QCheckBox* widget = findChild<QCheckBox*>(QString::fromStdString(si.name));
if (widget)
widget->setChecked(si.BooleanDefaultValue());
}
break;
case SettingInfo::Type::Integer:
{
QSpinBox* widget = findChild<QSpinBox*>(QString::fromStdString(si.name));
if (widget)
widget->setValue(si.IntegerDefaultValue());
}
break;
case SettingInfo::Type::IntegerList:
{
QComboBox* widget = findChild<QComboBox*>(QString::fromStdString(si.name));
if (widget)
widget->setCurrentIndex(si.IntegerDefaultValue() - si.IntegerMinValue());
}
break;
case SettingInfo::Type::Float:
{
QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QString::fromStdString(si.name));
if (widget)
{
if (si.multiplier != 0.0f && si.multiplier != 1.0f)
widget->setValue(si.FloatDefaultValue() * si.multiplier);
else
widget->setValue(si.FloatDefaultValue());
}
}
break;
case SettingInfo::Type::String:
{
QLineEdit* widget = findChild<QLineEdit*>(QString::fromStdString(si.name));
if (widget)
widget->setText(QString::fromUtf8(si.StringDefaultValue()));
}
break;
case SettingInfo::Type::Path:
{
QLineEdit* widget = findChild<QLineEdit*>(QString::fromStdString(si.name));
if (widget)
widget->setText(QString::fromUtf8(si.StringDefaultValue()));
}
break;
}
}
}