From cae741584b10fc7451929a1341ef5e25ec01c454 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sun, 8 Nov 2020 01:04:33 +0100 Subject: [PATCH 1/4] InputCommon: Return error, if any, from ControlReference::SetExpression(). --- Source/Core/InputCommon/ControlReference/ControlReference.cpp | 3 ++- Source/Core/InputCommon/ControlReference/ControlReference.h | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp index b1dd5deee3..671f2f3379 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.cpp +++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp @@ -50,12 +50,13 @@ std::string ControlReference::GetExpression() const return m_expression; } -void ControlReference::SetExpression(std::string expr) +std::optional ControlReference::SetExpression(std::string expr) { m_expression = std::move(expr); auto parse_result = ParseExpression(m_expression); m_parse_status = parse_result.status; m_parsed_expression = std::move(parse_result.expr); + return parse_result.description; } ControlReference::ControlReference() : range(1), m_parsed_expression(nullptr) diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.h b/Source/Core/InputCommon/ControlReference/ControlReference.h index de289ba265..fb4b5cc1df 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.h +++ b/Source/Core/InputCommon/ControlReference/ControlReference.h @@ -38,7 +38,9 @@ public: ciface::ExpressionParser::ParseStatus GetParseStatus() const; void UpdateReference(ciface::ExpressionParser::ControlEnvironment& env); std::string GetExpression() const; - void SetExpression(std::string expr); + + // Returns a human-readable error description when the given expression is invalid. + std::optional SetExpression(std::string expr); ControlState range; From ddfb8fa40411b62d281af91c35c734a187877ca1 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sun, 8 Nov 2020 01:10:26 +0100 Subject: [PATCH 2/4] Qt/IOWindow: Apply expressions immediately so we can query the current value of the expression. --- .../DolphinQt/Config/Mapping/IOWindow.cpp | 49 +++++++++++++------ .../Core/DolphinQt/Config/Mapping/IOWindow.h | 11 +++-- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp index 1648175360..2f506ae7aa 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp @@ -93,9 +93,8 @@ QTextCharFormat GetCommentCharFormat() } } // namespace -ControlExpressionSyntaxHighlighter::ControlExpressionSyntaxHighlighter(QTextDocument* parent, - QLineEdit* result) - : QSyntaxHighlighter(parent), m_result_text(result) +ControlExpressionSyntaxHighlighter::ControlExpressionSyntaxHighlighter(QTextDocument* parent) + : QSyntaxHighlighter(parent) { } @@ -168,18 +167,11 @@ void ControlExpressionSyntaxHighlighter::highlightBlock(const QString&) } // This doesn't need to be run for every "block", but it works. - if (ciface::ExpressionParser::ParseStatus::Successful != tokenize_status) - { - m_result_text->setText(tr("Invalid Token.")); - } - else + if (ciface::ExpressionParser::ParseStatus::Successful == tokenize_status) { ciface::ExpressionParser::RemoveInertTokens(&tokens); const auto parse_status = ciface::ExpressionParser::ParseTokens(tokens); - m_result_text->setText( - QString::fromStdString(parse_status.description.value_or(_trans("Success.")))); - if (ciface::ExpressionParser::ParseStatus::Successful != parse_status.status) { const auto token = *parse_status.token; @@ -203,7 +195,8 @@ private: IOWindow::IOWindow(MappingWidget* parent, ControllerEmu::EmulatedController* controller, ControlReference* ref, IOWindow::Type type) - : QDialog(parent), m_reference(ref), m_controller(controller), m_type(type) + : QDialog(parent), m_reference(ref), m_original_expression(ref->GetExpression()), + m_controller(controller), m_type(type) { CreateMainLayout(); @@ -242,7 +235,7 @@ void IOWindow::CreateMainLayout() m_expression_text = new QPlainTextEdit(); m_expression_text->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - new ControlExpressionSyntaxHighlighter(m_expression_text->document(), m_parse_text); + new ControlExpressionSyntaxHighlighter(m_expression_text->document()); m_operators_combo = new QComboBox(); m_operators_combo->addItem(tr("Operators")); @@ -404,6 +397,7 @@ void IOWindow::ConnectWidgets() connect(m_expression_text, &QPlainTextEdit::textChanged, [this] { m_apply_button->setText(m_apply_button->text().remove(QStringLiteral("*"))); m_apply_button->setText(m_apply_button->text() + QStringLiteral("*")); + UpdateExpression(m_expression_text->toPlainText().toStdString()); }); connect(m_operators_combo, qOverload(&QComboBox::activated), [this](int index) { @@ -423,6 +417,9 @@ void IOWindow::ConnectWidgets() m_functions_combo->setCurrentIndex(0); }); + + // revert the expression when the window closes without using the OK button + connect(this, &IOWindow::finished, [this] { UpdateExpression(m_original_expression); }); } void IOWindow::AppendSelectedOption() @@ -448,8 +445,10 @@ void IOWindow::OnDialogButtonPressed(QAbstractButton* button) return; } - m_reference->SetExpression(m_expression_text->toPlainText().toStdString()); - m_controller->UpdateSingleControlReference(g_controller_interface, m_reference); + const auto lock = m_controller->GetStateLock(); + + UpdateExpression(m_expression_text->toPlainText().toStdString()); + m_original_expression = m_reference->GetExpression(); m_apply_button->setText(m_apply_button->text().remove(QStringLiteral("*"))); @@ -532,6 +531,26 @@ void IOWindow::UpdateDeviceList() QString::fromStdString(m_controller->GetDefaultDevice().ToString())); } +void IOWindow::UpdateExpression(std::string new_expression) +{ + const auto lock = m_controller->GetStateLock(); + if (new_expression == m_reference->GetExpression()) + return; + + const auto error = m_reference->SetExpression(std::move(new_expression)); + const auto status = m_reference->GetParseStatus(); + m_controller->UpdateSingleControlReference(g_controller_interface, m_reference); + + if (error) + m_parse_text->setText(QString::fromStdString(*error)); + else if (status == ciface::ExpressionParser::ParseStatus::EmptyExpression) + m_parse_text->setText(QString()); + else if (status != ciface::ExpressionParser::ParseStatus::Successful) + m_parse_text->setText(tr("Invalid Expression.")); + else + m_parse_text->setText(tr("Success.")); +} + InputStateDelegate::InputStateDelegate(IOWindow* parent) : QItemDelegate(parent), m_parent(parent) { } diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h index 9870a4c954..0b98487211 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h @@ -4,6 +4,9 @@ #pragma once +#include +#include + #include #include #include @@ -34,13 +37,10 @@ class ControlExpressionSyntaxHighlighter final : public QSyntaxHighlighter { Q_OBJECT public: - ControlExpressionSyntaxHighlighter(QTextDocument* parent, QLineEdit* result); + explicit ControlExpressionSyntaxHighlighter(QTextDocument* parent); protected: void highlightBlock(const QString& text) final override; - -private: - QLineEdit* const m_result_text; }; class IOWindow final : public QDialog @@ -74,6 +74,8 @@ private: void UpdateOptionList(); void UpdateDeviceList(); + void UpdateExpression(std::string new_expression); + // Main Layout QVBoxLayout* m_main_layout; @@ -108,6 +110,7 @@ private: QPushButton* m_apply_button; ControlReference* m_reference; + std::string m_original_expression; ControllerEmu::EmulatedController* m_controller; ciface::Core::DeviceQualifier m_devq; From 334100509b9315b784a7c7a792b10cc81d48e802 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sun, 22 Nov 2020 03:33:36 +0100 Subject: [PATCH 3/4] Qt/IOWindow: Show the current value of the expression. --- .../DolphinQt/Config/Mapping/IOWindow.cpp | 129 +++++++++++++----- .../Core/DolphinQt/Config/Mapping/IOWindow.h | 12 +- 2 files changed, 104 insertions(+), 37 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp index 2f506ae7aa..6592d1128c 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp @@ -184,13 +184,27 @@ void ControlExpressionSyntaxHighlighter::highlightBlock(const QString&) class InputStateDelegate : public QItemDelegate { public: - explicit InputStateDelegate(IOWindow* parent); + explicit InputStateDelegate(IOWindow* parent, int column, + std::function state_evaluator); void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; private: - IOWindow* m_parent; + std::function m_state_evaluator; + int m_column; +}; + +class InputStateLineEdit : public QLineEdit +{ +public: + explicit InputStateLineEdit(std::function state_evaluator); + void SetShouldPaintStateIndicator(bool value); + void paintEvent(QPaintEvent* event) override; + +private: + std::function m_state_evaluator; + bool m_should_paint_state_indicator; }; IOWindow::IOWindow(MappingWidget* parent, ControllerEmu::EmulatedController* controller, @@ -230,7 +244,10 @@ void IOWindow::CreateMainLayout() m_range_slider = new QSlider(Qt::Horizontal); m_range_spinbox = new QSpinBox(); - m_parse_text = new QLineEdit(); + m_parse_text = new InputStateLineEdit([this] { + const auto lock = m_controller->GetStateLock(); + return m_reference->GetState(); + }); m_parse_text->setReadOnly(true); m_expression_text = new QPlainTextEdit(); @@ -308,7 +325,10 @@ void IOWindow::CreateMainLayout() m_option_list->setColumnWidth(1, 64); m_option_list->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); - m_option_list->setItemDelegate(new InputStateDelegate(this)); + m_option_list->setItemDelegate(new InputStateDelegate(this, 1, [&](int row) { + // Clamp off negative values but allow greater than one in the text display. + return std::max(GetSelectedDevice()->Inputs()[row]->GetState(), 0.0); + })); } else { @@ -365,6 +385,10 @@ void IOWindow::CreateMainLayout() void IOWindow::ConfigChanged() { const QSignalBlocker blocker(this); + const auto lock = m_controller->GetStateLock(); + + // ensure m_parse_text is in the right state + UpdateExpression(m_reference->GetExpression(), UpdateMode::Force); m_expression_text->setPlainText(QString::fromStdString(m_reference->GetExpression())); m_expression_text->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); @@ -380,6 +404,7 @@ void IOWindow::ConfigChanged() void IOWindow::Update() { m_option_list->viewport()->update(); + m_parse_text->update(); } void IOWindow::ConnectWidgets() @@ -531,10 +556,10 @@ void IOWindow::UpdateDeviceList() QString::fromStdString(m_controller->GetDefaultDevice().ToString())); } -void IOWindow::UpdateExpression(std::string new_expression) +void IOWindow::UpdateExpression(std::string new_expression, UpdateMode mode) { const auto lock = m_controller->GetStateLock(); - if (new_expression == m_reference->GetExpression()) + if (mode != UpdateMode::Force && new_expression == m_reference->GetExpression()) return; const auto error = m_reference->SetExpression(std::move(new_expression)); @@ -542,53 +567,87 @@ void IOWindow::UpdateExpression(std::string new_expression) m_controller->UpdateSingleControlReference(g_controller_interface, m_reference); if (error) + { + m_parse_text->SetShouldPaintStateIndicator(false); m_parse_text->setText(QString::fromStdString(*error)); + } else if (status == ciface::ExpressionParser::ParseStatus::EmptyExpression) + { + m_parse_text->SetShouldPaintStateIndicator(false); m_parse_text->setText(QString()); + } else if (status != ciface::ExpressionParser::ParseStatus::Successful) + { + m_parse_text->SetShouldPaintStateIndicator(false); m_parse_text->setText(tr("Invalid Expression.")); + } else - m_parse_text->setText(tr("Success.")); + { + m_parse_text->SetShouldPaintStateIndicator(true); + m_parse_text->setText(QString()); + } } -InputStateDelegate::InputStateDelegate(IOWindow* parent) : QItemDelegate(parent), m_parent(parent) +InputStateDelegate::InputStateDelegate(IOWindow* parent, int column, + std::function state_evaluator) + : QItemDelegate(parent), m_state_evaluator(std::move(state_evaluator)), m_column(column) { } +InputStateLineEdit::InputStateLineEdit(std::function state_evaluator) + : m_state_evaluator(std::move(state_evaluator)) +{ +} + +static void PaintStateIndicator(QPainter& painter, const QRect& region, ControlState state) +{ + const QString state_string = QString::number(state, 'g', 4); + + QRect meter_region = region; + meter_region.setWidth(region.width() * std::clamp(state, 0.0, 1.0)); + + // Create a temporary indicator object to retreive color constants. + MappingIndicator indicator; + + // Normal text. + painter.setPen(indicator.GetTextColor()); + painter.drawText(region, Qt::AlignCenter, state_string); + + // Input state meter. + painter.fillRect(meter_region, indicator.GetAdjustedInputColor()); + + // Text on top of meter. + painter.setPen(indicator.GetAltTextColor()); + painter.setClipping(true); + painter.setClipRect(meter_region); + painter.drawText(region, Qt::AlignCenter, state_string); +} + void InputStateDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QItemDelegate::paint(painter, option, index); - // Don't do anything special for the first column. - if (index.column() == 0) + if (index.column() != m_column) return; - // Clamp off negative values but allow greater than one in the text display. - const auto state = - std::max(m_parent->GetSelectedDevice()->Inputs()[index.row()]->GetState(), 0.0); - const auto state_str = QString::number(state, 'g', 4); - - QRect rect = option.rect; - rect.setWidth(rect.width() * std::clamp(state, 0.0, 1.0)); - - // Create a temporary indicator object to retreive color constants. - MappingIndicator indicator; - painter->save(); - - // Normal text. - painter->setPen(indicator.GetTextColor()); - painter->drawText(option.rect, Qt::AlignCenter, state_str); - - // Input state meter. - painter->fillRect(rect, indicator.GetAdjustedInputColor()); - - // Text on top of meter. - painter->setPen(indicator.GetAltTextColor()); - painter->setClipping(true); - painter->setClipRect(rect); - painter->drawText(option.rect, Qt::AlignCenter, state_str); - + PaintStateIndicator(*painter, option.rect, m_state_evaluator(index.row())); painter->restore(); } + +void InputStateLineEdit::SetShouldPaintStateIndicator(bool value) +{ + m_should_paint_state_indicator = value; +} + +void InputStateLineEdit::paintEvent(QPaintEvent* event) +{ + QLineEdit::paintEvent(event); + + if (!m_should_paint_state_indicator) + return; + + QPainter painter(this); + PaintStateIndicator(painter, this->rect(), m_state_evaluator()); +} diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h index 0b98487211..8500e0e170 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h @@ -33,6 +33,8 @@ namespace ControllerEmu class EmulatedController; } +class InputStateLineEdit; + class ControlExpressionSyntaxHighlighter final : public QSyntaxHighlighter { Q_OBJECT @@ -74,7 +76,13 @@ private: void UpdateOptionList(); void UpdateDeviceList(); - void UpdateExpression(std::string new_expression); + enum class UpdateMode + { + Normal, + Force, + }; + + void UpdateExpression(std::string new_expression, UpdateMode mode = UpdateMode::Normal); // Main Layout QVBoxLayout* m_main_layout; @@ -102,7 +110,7 @@ private: // Textarea QPlainTextEdit* m_expression_text; - QLineEdit* m_parse_text; + InputStateLineEdit* m_parse_text; // Buttonbox QDialogButtonBox* m_button_box; From 11e226a91a1f38061b33deb247ca2e33e748305b Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sun, 22 Nov 2020 04:00:28 +0100 Subject: [PATCH 4/4] Qt/IOWindow: Remove Apply button. --- Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp | 15 ++++----------- Source/Core/DolphinQt/Config/Mapping/IOWindow.h | 1 - 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp index 6592d1128c..887063e8aa 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp @@ -240,7 +240,6 @@ void IOWindow::CreateMainLayout() m_test_button = new QPushButton(tr("Test"), this); m_button_box = new QDialogButtonBox(); m_clear_button = new QPushButton(tr("Clear")); - m_apply_button = new QPushButton(tr("Apply")); m_range_slider = new QSlider(Qt::Horizontal); m_range_spinbox = new QSpinBox(); @@ -376,7 +375,6 @@ void IOWindow::CreateMainLayout() // Button Box m_main_layout->addWidget(m_button_box); m_button_box->addButton(m_clear_button, QDialogButtonBox::ActionRole); - m_button_box->addButton(m_apply_button, QDialogButtonBox::ActionRole); m_button_box->addButton(QDialogButtonBox::Ok); setLayout(m_main_layout); @@ -419,11 +417,8 @@ void IOWindow::ConnectWidgets() connect(m_range_spinbox, qOverload(&QSpinBox::valueChanged), this, &IOWindow::OnRangeChanged); - connect(m_expression_text, &QPlainTextEdit::textChanged, [this] { - m_apply_button->setText(m_apply_button->text().remove(QStringLiteral("*"))); - m_apply_button->setText(m_apply_button->text() + QStringLiteral("*")); - UpdateExpression(m_expression_text->toPlainText().toStdString()); - }); + connect(m_expression_text, &QPlainTextEdit::textChanged, + [this] { UpdateExpression(m_expression_text->toPlainText().toStdString()); }); connect(m_operators_combo, qOverload(&QComboBox::activated), [this](int index) { if (0 == index) @@ -475,15 +470,13 @@ void IOWindow::OnDialogButtonPressed(QAbstractButton* button) UpdateExpression(m_expression_text->toPlainText().toStdString()); m_original_expression = m_reference->GetExpression(); - m_apply_button->setText(m_apply_button->text().remove(QStringLiteral("*"))); - if (ciface::ExpressionParser::ParseStatus::SyntaxError == m_reference->GetParseStatus()) { ModalMessageBox::warning(this, tr("Error"), tr("The expression contains a syntax error.")); } - if (button != m_apply_button) - accept(); + // must be the OK button + accept(); } void IOWindow::OnDetectButtonPressed() diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h index 8500e0e170..28294fc284 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h @@ -115,7 +115,6 @@ private: // Buttonbox QDialogButtonBox* m_button_box; QPushButton* m_clear_button; - QPushButton* m_apply_button; ControlReference* m_reference; std::string m_original_expression;