diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index 54d64e66a..eeacaaa12 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -77,6 +77,12 @@ void InputBindingWidget::clearBinding() setText(m_current_binding_value); } +void InputBindingWidget::reloadBinding() +{ + m_current_binding_value = m_host_interface->getSettingValue(m_setting_name).toString(); + setText(m_current_binding_value); +} + void InputBindingWidget::onPressed() { if (isListeningForInput()) diff --git a/src/duckstation-qt/inputbindingwidgets.h b/src/duckstation-qt/inputbindingwidgets.h index c9c0668f4..e4058d098 100644 --- a/src/duckstation-qt/inputbindingwidgets.h +++ b/src/duckstation-qt/inputbindingwidgets.h @@ -20,6 +20,7 @@ public: public Q_SLOTS: void beginRebindAll(); void clearBinding(); + void reloadBinding(); protected Q_SLOTS: void onPressed(); diff --git a/src/duckstation-qt/portsettingswidget.cpp b/src/duckstation-qt/portsettingswidget.cpp index e3871845b..31403bd58 100644 --- a/src/duckstation-qt/portsettingswidget.cpp +++ b/src/duckstation-qt/portsettingswidget.cpp @@ -7,16 +7,21 @@ #include "settingwidgetbinder.h" #include #include +#include #include #include +#include #include static constexpr char MEMORY_CARD_IMAGE_FILTER[] = "All Memory Card Types (*.mcd *.mcr *.mc)"; +static constexpr char INPUT_PROFILE_FILTER[] = "Input Profiles (*.ini)"; PortSettingsWidget::PortSettingsWidget(QtHostInterface* host_interface, QWidget* parent /* = nullptr */) : QWidget(parent), m_host_interface(host_interface) { createUi(); + + connect(host_interface, &QtHostInterface::inputProfileLoaded, this, &PortSettingsWidget::onProfileLoaded); } PortSettingsWidget::~PortSettingsWidget() = default; @@ -35,6 +40,38 @@ void PortSettingsWidget::createUi() setLayout(layout); } +void PortSettingsWidget::onProfileLoaded() +{ + for (int i = 0; i < static_cast(m_port_ui.size()); i++) + { + ControllerType ctype = Settings::ParseControllerTypeName( + m_host_interface->getSettingValue(QStringLiteral("Controller%1/Type").arg(i + 1)) + .toString() + .toStdString() + .c_str()) + .value_or(ControllerType::None); + + { + QSignalBlocker blocker(m_port_ui[i].controller_type); + m_port_ui[i].controller_type->setCurrentIndex(static_cast(ctype)); + } + createPortBindingSettingsUi(i, &m_port_ui[i], ctype); + } +} + +void PortSettingsWidget::reloadBindingButtons() +{ + for (PortSettingsUI& ui : m_port_ui) + { + InputBindingWidget* widget = ui.first_button; + while (widget) + { + widget->reloadBinding(); + widget = widget->getNextWidget(); + } + } +} + void PortSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* ui) { ui->widget = new QWidget(m_tab_widget); @@ -171,7 +208,6 @@ void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* start_row += num_rows; } - const u32 num_motors = Controller::GetVibrationMotorCount(ctype); if (num_motors > 0) { @@ -195,12 +231,23 @@ void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* layout->addWidget(QtUtils::CreateHorizontalLine(ui->widget), start_row++, 0, 1, 4); + QHBoxLayout* left_hbox = new QHBoxLayout(); + QPushButton* load_profile_button = new QPushButton(tr("Load Profile"), ui->widget); + connect(load_profile_button, &QPushButton::clicked, this, &PortSettingsWidget::onLoadProfileClicked); + left_hbox->addWidget(load_profile_button); + + QPushButton* save_profile_button = new QPushButton(tr("Save Profile"), ui->widget); + connect(save_profile_button, &QPushButton::clicked, this, &PortSettingsWidget::onSaveProfileClicked); + left_hbox->addWidget(save_profile_button); + + layout->addLayout(left_hbox, start_row, 0, 1, 2, Qt::AlignLeft); + if (first_button) { - QHBoxLayout* hbox = new QHBoxLayout(); + QHBoxLayout* right_hbox = new QHBoxLayout(); QPushButton* clear_all_button = new QPushButton(tr("Clear All"), ui->widget); - clear_all_button->connect(clear_all_button, &QPushButton::pressed, [this, first_button]() { + clear_all_button->connect(clear_all_button, &QPushButton::clicked, [this, first_button]() { if (QMessageBox::question(this, tr("Clear Bindings"), tr("Are you sure you want to clear all bound controls? This cannot be reversed.")) != QMessageBox::Yes) @@ -217,7 +264,7 @@ void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* }); QPushButton* rebind_all_button = new QPushButton(tr("Rebind All"), ui->widget); - rebind_all_button->connect(rebind_all_button, &QPushButton::pressed, [this, first_button]() { + rebind_all_button->connect(rebind_all_button, &QPushButton::clicked, [this, first_button]() { if (QMessageBox::question(this, tr("Clear Bindings"), tr("Do you want to clear all currently-bound controls?")) == QMessageBox::Yes) { @@ -232,9 +279,9 @@ void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* first_button->beginRebindAll(); }); - hbox->addWidget(clear_all_button); - hbox->addWidget(rebind_all_button); - layout->addLayout(hbox, start_row, 2, 1, 2, Qt::AlignRight); + right_hbox->addWidget(clear_all_button); + right_hbox->addWidget(rebind_all_button); + layout->addLayout(right_hbox, start_row, 2, 1, 2, Qt::AlignRight); } if (ui->button_binding_container) @@ -243,13 +290,14 @@ void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* Q_ASSERT(old_item != nullptr); delete old_item; - delete ui->button_binding_container; + ui->button_binding_container->deleteLater(); } else { ui->layout->addWidget(container); } ui->button_binding_container = container; + ui->first_button = first_button; } void PortSettingsWidget::onControllerTypeChanged(int index) @@ -282,3 +330,55 @@ void PortSettingsWidget::onEjectMemoryCardClicked(int index) m_host_interface->removeSettingValue(QStringLiteral("MemoryCards/Card%1Path").arg(index + 1)); m_host_interface->applySettings(); } + +void PortSettingsWidget::onLoadProfileClicked() +{ + const auto profile_names = m_host_interface->getInputProfileList(); + + QMenu menu; + for (const auto& [name, path] : profile_names) + { + QAction* action = menu.addAction(QString::fromStdString(name)); + QString path_qstr = QString::fromStdString(path); + connect(action, &QAction::triggered, [this, path_qstr]() { m_host_interface->applyInputProfile(path_qstr); }); + } + + if (!profile_names.empty()) + menu.addSeparator(); + + QAction* browse = menu.addAction(tr("Browse...")); + connect(browse, &QAction::triggered, [this]() { + QString path = + QFileDialog::getOpenFileName(this, tr("Select path to input profile ini"), QString(), tr(INPUT_PROFILE_FILTER)); + if (!path.isEmpty()) + m_host_interface->applyInputProfile(path); + }); + + menu.exec(QCursor::pos()); +} + +void PortSettingsWidget::onSaveProfileClicked() +{ + const auto profile_names = m_host_interface->getInputProfileList(); + + QMenu menu; + for (const auto& [name, path] : profile_names) + { + QAction* action = menu.addAction(QString::fromStdString(name)); + QString path_qstr = QString::fromStdString(path); + connect(action, &QAction::triggered, [this, path_qstr]() { m_host_interface->saveInputProfile(path_qstr); }); + } + + if (!profile_names.empty()) + menu.addSeparator(); + + QAction* browse = menu.addAction(tr("Browse...")); + connect(browse, &QAction::triggered, [this]() { + QString path = + QFileDialog::getSaveFileName(this, tr("Select path to input profile ini"), QString(), tr(INPUT_PROFILE_FILTER)); + if (!path.isEmpty()) + m_host_interface->saveInputProfile(path); + }); + + menu.exec(QCursor::pos()); +} diff --git a/src/duckstation-qt/portsettingswidget.h b/src/duckstation-qt/portsettingswidget.h index f540c5908..073c3fda9 100644 --- a/src/duckstation-qt/portsettingswidget.h +++ b/src/duckstation-qt/portsettingswidget.h @@ -13,7 +13,7 @@ class QTimer; class QtHostInterface; -class InputButtonBindingWidget; +class InputBindingWidget; class PortSettingsWidget : public QWidget { @@ -23,6 +23,9 @@ public: PortSettingsWidget(QtHostInterface* host_interface, QWidget* parent = nullptr); ~PortSettingsWidget(); +private Q_SLOTS: + void onProfileLoaded(); + private: QtHostInterface* m_host_interface; @@ -35,14 +38,18 @@ private: QComboBox* controller_type; QLineEdit* memory_card_path; QWidget* button_binding_container; + InputBindingWidget* first_button; }; void createUi(); + void reloadBindingButtons(); void createPortSettingsUi(int index, PortSettingsUI* ui); void createPortBindingSettingsUi(int index, PortSettingsUI* ui, ControllerType ctype); void onControllerTypeChanged(int index); void onBrowseMemoryCardPathClicked(int index); void onEjectMemoryCardClicked(int index); + void onLoadProfileClicked(); + void onSaveProfileClicked(); std::array m_port_ui = {}; }; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 8281388a7..900772d22 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -460,6 +460,27 @@ void QtHostInterface::updateInputMap() CommonHostInterface::UpdateInputMap(si); } +void QtHostInterface::applyInputProfile(const QString& profile_path) +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "applyInputProfile", Qt::QueuedConnection, Q_ARG(const QString&, profile_path)); + return; + } + + std::lock_guard lock(m_qsettings_mutex); + QtSettingsInterface si(m_qsettings.get()); + ApplyInputProfile(profile_path.toUtf8().data(), si); + emit inputProfileLoaded(); +} + +void QtHostInterface::saveInputProfile(const QString& profile_name) +{ + std::lock_guard lock(m_qsettings_mutex); + QtSettingsInterface si(m_qsettings.get()); + SaveInputProfile(profile_name.toUtf8().data(), si); +} + void QtHostInterface::powerOffSystem() { if (!isOnWorkerThread()) diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index bc80f073d..ba226e755 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -72,6 +72,12 @@ public: /// Fills menu with save state info and handlers. void populateGameListContextMenu(const char* game_code, QWidget* parent_window, QMenu* menu); + ALWAYS_INLINE std::vector> getInputProfileList() const + { + return GetInputProfileList(); + } + void saveInputProfile(const QString& profile_path); + Q_SIGNALS: void errorReported(const QString& message); void messageReported(const QString& message); @@ -90,11 +96,13 @@ Q_SIGNALS: float worst_frame_time); void runningGameChanged(const QString& filename, const QString& game_code, const QString& game_title); void exitRequested(); + void inputProfileLoaded(); public Q_SLOTS: void setDefaultSettings(); void applySettings(); void updateInputMap(); + void applyInputProfile(const QString& profile_path); void handleKeyEvent(int key, bool pressed); void bootSystem(const SystemBootParameters& params); void resumeSystemFromState(const QString& filename, bool boot_on_failure);