/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see .
*/
#include "PrecompiledHeader.h"
#include
#include
#include
#include "ControllerBindingWidgets.h"
#include "ControllerSettingsDialog.h"
#include "EmuThread.h"
#include "QtUtils.h"
#include "SettingWidgetBinder.h"
#include "SettingsDialog.h"
#include "common/StringUtil.h"
#include "pcsx2/HostSettings.h"
#include "pcsx2/PAD/Host/PAD.h"
#include "SettingWidgetBinder.h"
ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port)
: QWidget(parent)
, m_dialog(dialog)
, m_config_section(StringUtil::StdStringFromFormat("Pad%u", port + 1u))
, m_port_number(port)
{
m_ui.setupUi(this);
populateControllerTypes();
onTypeChanged();
SettingWidgetBinder::BindWidgetToStringSetting(nullptr, m_ui.controllerType, m_config_section, "Type", "None");
connect(m_ui.controllerType, QOverload::of(&QComboBox::currentIndexChanged), this, &ControllerBindingWidget::onTypeChanged);
connect(m_ui.automaticBinding, &QPushButton::clicked, this, &ControllerBindingWidget::doAutomaticBinding);
connect(m_ui.clearBindings, &QPushButton::clicked, this, &ControllerBindingWidget::doClearBindings);
}
ControllerBindingWidget::~ControllerBindingWidget() = default;
void ControllerBindingWidget::populateControllerTypes()
{
m_ui.controllerType->addItem(tr("None (Not Connected)"), QStringLiteral("None"));
for (const std::string& type : PAD::GetControllerTypeNames())
m_ui.controllerType->addItem(QString::fromStdString(type), QString::fromStdString(type));
}
void ControllerBindingWidget::onTypeChanged()
{
if (m_current_widget)
{
m_ui.verticalLayout->removeWidget(m_current_widget);
delete m_current_widget;
m_current_widget = nullptr;
}
m_controller_type = Host::GetBaseStringSettingValue(m_config_section.c_str(), "Type");
const int index = m_ui.controllerType->findData(QString::fromStdString(m_controller_type));
if (index >= 0 && index != m_ui.controllerType->currentIndex())
{
QSignalBlocker sb(m_ui.controllerType);
m_ui.controllerType->setCurrentIndex(index);
}
if (m_controller_type == "DualShock2")
m_current_widget = ControllerBindingWidget_DualShock2::createInstance(this);
else
m_current_widget = new ControllerBindingWidget_Base(this);
m_ui.verticalLayout->addWidget(m_current_widget, 1);
}
void ControllerBindingWidget::doAutomaticBinding()
{
QMenu menu(this);
bool added = false;
for (const QPair& 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::doClearBindings()
{
if (QMessageBox::question(QtUtils::GetRootWidget(this), tr("Clear Bindings"),
tr("Are you sure you want to clear all bindings for this controller? This action cannot be undone.")) != QMessageBox::Yes)
{
return;
}
{
auto lock = Host::GetSettingsLock();
PAD::ClearPortBindings(*Host::Internal::GetBaseSettingsLayer(), m_port_number);
}
saveAndRefresh();
}
void ControllerBindingWidget::doDeviceAutomaticBinding(const QString& device)
{
std::vector> mapping = InputManager::GetGenericBindingMapping(device.toStdString());
if (mapping.empty())
{
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Automatic Binding"),
tr("No generic bindings were generated for device '%1'").arg(device));
return;
}
bool result;
{
auto lock = Host::GetSettingsLock();
result = PAD::MapController(*Host::Internal::GetBaseSettingsLayer(), m_port_number, mapping);
}
// force a refresh after mapping
if (result)
saveAndRefresh();
}
void ControllerBindingWidget::saveAndRefresh()
{
onTypeChanged();
QtHost::QueueSettingsSave();
g_emu_thread->applySettings();
}
//////////////////////////////////////////////////////////////////////////
ControllerBindingWidget_Base::ControllerBindingWidget_Base(ControllerBindingWidget* parent)
: QWidget(parent)
{
}
ControllerBindingWidget_Base::~ControllerBindingWidget_Base()
{
}
void ControllerBindingWidget_Base::initBindingWidgets()
{
const std::string& type = getControllerType();
const std::string& config_section = getConfigSection();
std::vector bindings(PAD::GetControllerBinds(type));
for (std::string& binding : bindings)
{
InputBindingWidget* widget = findChild(QString::fromStdString(binding));
if (!widget)
{
Console.Error("(ControllerBindingWidget_Base) No widget found for '%s' (%.*s)",
binding.c_str(), static_cast(type.size()), type.data());
continue;
}
widget->setKey(config_section, std::move(binding));
}
const PAD::VibrationCapabilities vibe_caps = PAD::GetControllerVibrationCapabilities(type);
switch (vibe_caps)
{
case PAD::VibrationCapabilities::LargeSmallMotors:
{
InputVibrationBindingWidget* widget = findChild(QStringLiteral("LargeMotor"));
if (widget)
widget->setKey(getDialog(), config_section, "LargeMotor");
widget = findChild(QStringLiteral("SmallMotor"));
if (widget)
widget->setKey(getDialog(), config_section, "SmallMotor");
}
break;
case PAD::VibrationCapabilities::SingleMotor:
{
InputVibrationBindingWidget* widget = findChild(QStringLiteral("Motor"));
if (widget)
widget->setKey(getDialog(), config_section, "Motor");
}
break;
case PAD::VibrationCapabilities::NoVibration:
default:
break;
}
if (QSlider* widget = findChild(QStringLiteral("AxisScale")); widget)
{
// position 1.0f at the halfway point
const float range = static_cast(widget->maximum()) * 0.5f;
QLabel* label = findChild(QStringLiteral("AxisScaleLabel"));
if (label)
{
connect(widget, &QSlider::valueChanged, this, [range, label](int value) {
label->setText(tr("%1x").arg(static_cast(value) / range, 0, 'f', 2));
});
}
SettingWidgetBinder::BindWidgetToNormalizedSetting(nullptr, widget, config_section, "AxisScale", range, 1.0f);
}
if (QDoubleSpinBox* widget = findChild(QStringLiteral("SmallMotorScale")); widget)
SettingWidgetBinder::BindWidgetToFloatSetting(nullptr, widget, config_section, "SmallMotorScale", 1.0f);
if (QDoubleSpinBox* widget = findChild(QStringLiteral("LargeMotorScale")); widget)
SettingWidgetBinder::BindWidgetToFloatSetting(nullptr, widget, config_section, "LargeMotorScale", 1.0f);
}
ControllerBindingWidget_DualShock2::ControllerBindingWidget_DualShock2(ControllerBindingWidget* parent)
: ControllerBindingWidget_Base(parent)
{
m_ui.setupUi(this);
initBindingWidgets();
}
ControllerBindingWidget_DualShock2::~ControllerBindingWidget_DualShock2()
{
}
ControllerBindingWidget_Base* ControllerBindingWidget_DualShock2::createInstance(ControllerBindingWidget* parent)
{
return new ControllerBindingWidget_DualShock2(parent);
}
//////////////////////////////////////////////////////////////////////////