diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index 453a02502..aa31401ae 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -22,6 +22,15 @@ InputBindingWidget::~InputBindingWidget() Q_ASSERT(!isListeningForInput()); } +void InputBindingWidget::beginRebindAll() +{ + m_is_binding_all = true; + if (isListeningForInput()) + stopListeningForInput(); + + startListeningForInput(TIMEOUT_FOR_ALL_BINDING); +} + bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event) { const QEvent::Type event_type = event->type(); @@ -71,7 +80,7 @@ void InputBindingWidget::onPressed() if (isListeningForInput()) stopListeningForInput(); - startListeningForInput(); + startListeningForInput(TIMEOUT_FOR_SINGLE_BINDING); } void InputBindingWidget::onInputListenTimerTimeout() @@ -86,7 +95,7 @@ void InputBindingWidget::onInputListenTimerTimeout() setText(tr("Push Button/Axis... [%1]").arg(m_input_listen_remaining_seconds)); } -void InputBindingWidget::startListeningForInput() +void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds) { m_input_listen_timer = new QTimer(this); m_input_listen_timer->setSingleShot(false); @@ -94,7 +103,7 @@ void InputBindingWidget::startListeningForInput() m_input_listen_timer->connect(m_input_listen_timer, &QTimer::timeout, this, &InputBindingWidget::onInputListenTimerTimeout); - m_input_listen_remaining_seconds = 5; + m_input_listen_remaining_seconds = timeout_in_seconds; setText(tr("Push Button/Axis... [%1]").arg(m_input_listen_remaining_seconds)); installEventFilter(this); @@ -111,6 +120,10 @@ void InputBindingWidget::stopListeningForInput() releaseMouse(); releaseKeyboard(); removeEventFilter(this); + + if (m_is_binding_all && m_next_widget) + m_next_widget->beginRebindAll(); + m_is_binding_all = false; } InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interface, QString setting_name, @@ -203,9 +216,9 @@ void InputButtonBindingWidget::bindToControllerButton(int controller_index, int stopListeningForInput(); } -void InputButtonBindingWidget::startListeningForInput() +void InputButtonBindingWidget::startListeningForInput(u32 timeout_in_seconds) { - InputBindingWidget::startListeningForInput(); + InputBindingWidget::startListeningForInput(timeout_in_seconds); hookControllerInput(); } @@ -266,9 +279,9 @@ void InputAxisBindingWidget::bindToControllerAxis(int controller_index, int axis stopListeningForInput(); } -void InputAxisBindingWidget::startListeningForInput() +void InputAxisBindingWidget::startListeningForInput(u32 timeout_in_seconds) { - InputBindingWidget::startListeningForInput(); + InputBindingWidget::startListeningForInput(timeout_in_seconds); hookControllerInput(); } diff --git a/src/duckstation-qt/inputbindingwidgets.h b/src/duckstation-qt/inputbindingwidgets.h index a6c7f8015..9492cf7c0 100644 --- a/src/duckstation-qt/inputbindingwidgets.h +++ b/src/duckstation-qt/inputbindingwidgets.h @@ -14,21 +14,32 @@ public: InputBindingWidget(QtHostInterface* host_interface, QString setting_name, QWidget* parent); ~InputBindingWidget(); -protected: - virtual bool eventFilter(QObject* watched, QEvent* event) override; - virtual void mouseReleaseEvent(QMouseEvent* e) override; + ALWAYS_INLINE InputBindingWidget* getNextWidget() const { return m_next_widget; } + ALWAYS_INLINE void setNextWidget(InputBindingWidget* widget) { m_next_widget = widget; } + +public Q_SLOTS: + void beginRebindAll(); + void clearBinding(); protected Q_SLOTS: void onPressed(); void onInputListenTimerTimeout(); protected: - virtual void startListeningForInput(); + enum : u32 + { + TIMEOUT_FOR_SINGLE_BINDING = 5, + TIMEOUT_FOR_ALL_BINDING = 10 + }; + + virtual bool eventFilter(QObject* watched, QEvent* event) override; + virtual void mouseReleaseEvent(QMouseEvent* e) override; + + virtual void startListeningForInput(u32 timeout_in_seconds); virtual void stopListeningForInput(); bool isListeningForInput() const { return m_input_listen_timer != nullptr; } void setNewBinding(); - void clearBinding(); QtHostInterface* m_host_interface; QString m_setting_name; @@ -36,6 +47,9 @@ protected: QString m_new_binding_value; QTimer* m_input_listen_timer = nullptr; u32 m_input_listen_remaining_seconds = 0; + + InputBindingWidget* m_next_widget = nullptr; + bool m_is_binding_all = false; }; class InputButtonBindingWidget : public InputBindingWidget @@ -54,7 +68,7 @@ private Q_SLOTS: void bindToControllerButton(int controller_index, int button_index); protected: - void startListeningForInput() override; + void startListeningForInput(u32 timeout_in_seconds) override; void stopListeningForInput() override; void hookControllerInput(); void unhookControllerInput(); @@ -72,7 +86,7 @@ private Q_SLOTS: void bindToControllerAxis(int controller_index, int axis_index); protected: - void startListeningForInput() override; + void startListeningForInput(u32 timeout_in_seconds) override; void stopListeningForInput() override; void hookControllerInput(); void unhookControllerInput(); diff --git a/src/duckstation-qt/portsettingswidget.cpp b/src/duckstation-qt/portsettingswidget.cpp index 883fb3e14..d66623038 100644 --- a/src/duckstation-qt/portsettingswidget.cpp +++ b/src/duckstation-qt/portsettingswidget.cpp @@ -6,6 +6,7 @@ #include "qtutils.h" #include #include +#include PortSettingsWidget::PortSettingsWidget(QtHostInterface* host_interface, QWidget* parent /* = nullptr */) : QWidget(parent), m_host_interface(host_interface) @@ -43,6 +44,8 @@ void PortSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* ui) ui->layout->addWidget(new QLabel(tr("Memory Card Path:"), ui->widget)); ui->layout->addLayout(memory_card_layout); + ui->layout->addWidget(new QLabel(tr("Controller Type:"), ui->widget)); + ui->controller_type = new QComboBox(ui->widget); for (int i = 0; i < static_cast(ControllerType::Count); i++) { @@ -58,7 +61,7 @@ void PortSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* ui) ui->controller_type->setCurrentIndex(static_cast(ctype)); connect(ui->controller_type, static_cast(&QComboBox::currentIndexChanged), [this, index]() { onControllerTypeChanged(index); }); - ui->layout->addWidget(new QLabel(tr("Controller Type:"), ui->widget)); + ui->layout->addWidget(ui->controller_type); createPortBindingSettingsUi(index, ui, ctype); @@ -77,13 +80,13 @@ void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* layout->setContentsMargins(0, 0, 0, 0); const auto buttons = Controller::GetButtonNames(ctype); + InputBindingWidget* first_button = nullptr; + InputBindingWidget* last_button = nullptr; + int start_row = 0; if (!buttons.empty()) { - QFrame* line = new QFrame(container); - line->setFrameShape(QFrame::HLine); - line->setFrameShadow(QFrame::Sunken); - layout->addWidget(line, start_row++, 0, 1, 4); + layout->addWidget(QtUtils::CreateHorizontalLine(container), start_row++, 0, 1, 4); layout->addWidget(new QLabel(tr("Button Bindings:"), container), start_row++, 0, 1, 4); const int num_rows = (static_cast(buttons.size()) + 1) / 2; @@ -104,6 +107,12 @@ void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* layout->addWidget(label, start_row + current_row, current_column); layout->addWidget(button, start_row + current_row, current_column + 1); + if (!first_button) + first_button = button; + if (last_button) + last_button->setNextWidget(button); + last_button = button; + current_row++; } @@ -137,12 +146,62 @@ void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* layout->addWidget(label, start_row + current_row, current_column); layout->addWidget(button, start_row + current_row, current_column + 1); + if (!first_button) + first_button = button; + if (last_button) + last_button->setNextWidget(button); + last_button = button; + current_row++; } start_row += num_rows; } + layout->addWidget(QtUtils::CreateHorizontalLine(ui->widget), start_row++, 0, 1, 4); + + if (first_button) + { + QHBoxLayout* 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]() { + if (QMessageBox::question(this, tr("Clear Bindings"), + tr("Are you sure you want to clear all bound controls? This cannot be reversed.")) != + QMessageBox::Yes) + { + return; + } + + InputBindingWidget* widget = first_button; + while (widget) + { + widget->clearBinding(); + widget = widget->getNextWidget(); + } + }); + + QPushButton* rebind_all_button = new QPushButton(tr("Rebind All"), ui->widget); + rebind_all_button->connect(rebind_all_button, &QPushButton::pressed, [this, first_button]() { + if (QMessageBox::question(this, tr("Clear Bindings"), tr("Do you want to clear all currently-bound controls?")) == + QMessageBox::Yes) + { + InputBindingWidget* widget = first_button; + while (widget) + { + widget->clearBinding(); + widget = widget->getNextWidget(); + } + } + + first_button->beginRebindAll(); + }); + + hbox->addWidget(clear_all_button); + hbox->addWidget(rebind_all_button); + layout->addLayout(hbox, start_row++, 0, 1, 4, Qt::AlignRight); + } + if (ui->button_binding_container) { QLayoutItem* old_item = ui->layout->replaceWidget(ui->button_binding_container, container); diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp index 60048c2c3..ab81745b8 100644 --- a/src/duckstation-qt/qtutils.cpp +++ b/src/duckstation-qt/qtutils.cpp @@ -13,6 +13,14 @@ namespace QtUtils { +QFrame* CreateHorizontalLine(QWidget* parent) +{ + QFrame* line = new QFrame(parent); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + return line; +} + QWidget* GetRootWidget(QWidget* widget, bool stop_at_window_or_dialog) { QWidget* next_parent = widget->parentWidget(); diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h index 4279ef976..376b1fee5 100644 --- a/src/duckstation-qt/qtutils.h +++ b/src/duckstation-qt/qtutils.h @@ -6,12 +6,16 @@ class ByteStream; +class QFrame; class QKeyEvent; class QTableView; class QWidget; namespace QtUtils { +/// Creates a horizontal line widget. +QFrame* CreateHorizontalLine(QWidget* parent); + /// Returns the greatest parent of a widget, i.e. its dialog/window. QWidget* GetRootWidget(QWidget* widget, bool stop_at_window_or_dialog = true);