mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-15 18:49:11 +01:00
242bce27a1
Previously you could type whatever gibberish you wanted into the formula bar, press OK, and receive a modal syntax error window. Closing the syntax error window would cause the hotkey config window to close as well, and your gibberish would be applied to the hotkey assignment. This commit requires that a syntax error does not occur before accept() is called.
757 lines
23 KiB
C++
757 lines
23 KiB
C++
// Copyright 2017 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "DolphinQt/Config/Mapping/IOWindow.h"
|
|
|
|
#include <optional>
|
|
#include <thread>
|
|
|
|
#include <QComboBox>
|
|
#include <QDialogButtonBox>
|
|
#include <QHeaderView>
|
|
#include <QItemDelegate>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QPainter>
|
|
#include <QPlainTextEdit>
|
|
#include <QPushButton>
|
|
#include <QSlider>
|
|
#include <QSpinBox>
|
|
#include <QTableWidget>
|
|
#include <QVBoxLayout>
|
|
|
|
#include "Core/Core.h"
|
|
|
|
#include "DolphinQt/Config/Mapping/MappingCommon.h"
|
|
#include "DolphinQt/Config/Mapping/MappingIndicator.h"
|
|
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
|
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
|
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
|
|
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
|
#include "DolphinQt/Settings.h"
|
|
|
|
#include "InputCommon/ControlReference/ControlReference.h"
|
|
#include "InputCommon/ControlReference/ExpressionParser.h"
|
|
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
|
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
|
|
|
constexpr int SLIDER_TICK_COUNT = 100;
|
|
|
|
namespace
|
|
{
|
|
// TODO: Make sure these functions return colors that will be visible in the current theme.
|
|
QTextCharFormat GetSpecialCharFormat()
|
|
{
|
|
QTextCharFormat format;
|
|
format.setFontWeight(QFont::Weight::Bold);
|
|
return format;
|
|
}
|
|
|
|
QTextCharFormat GetLiteralCharFormat()
|
|
{
|
|
QTextCharFormat format;
|
|
format.setForeground(QBrush{Qt::darkMagenta});
|
|
return format;
|
|
}
|
|
|
|
QTextCharFormat GetInvalidCharFormat()
|
|
{
|
|
QTextCharFormat format;
|
|
format.setUnderlineStyle(QTextCharFormat::WaveUnderline);
|
|
format.setUnderlineColor(Qt::darkRed);
|
|
return format;
|
|
}
|
|
|
|
QTextCharFormat GetControlCharFormat()
|
|
{
|
|
QTextCharFormat format;
|
|
format.setForeground(QBrush{Qt::darkGreen});
|
|
return format;
|
|
}
|
|
|
|
QTextCharFormat GetVariableCharFormat()
|
|
{
|
|
QTextCharFormat format;
|
|
format.setForeground(QBrush{Qt::darkYellow});
|
|
return format;
|
|
}
|
|
|
|
QTextCharFormat GetBarewordCharFormat()
|
|
{
|
|
QTextCharFormat format;
|
|
format.setForeground(QBrush{Qt::darkBlue});
|
|
return format;
|
|
}
|
|
|
|
QTextCharFormat GetCommentCharFormat()
|
|
{
|
|
QTextCharFormat format;
|
|
format.setForeground(QBrush{Qt::darkGray});
|
|
return format;
|
|
}
|
|
} // namespace
|
|
|
|
ControlExpressionSyntaxHighlighter::ControlExpressionSyntaxHighlighter(QTextDocument* parent)
|
|
: QSyntaxHighlighter(parent)
|
|
{
|
|
}
|
|
|
|
void QComboBoxWithMouseWheelDisabled::wheelEvent(QWheelEvent* event)
|
|
{
|
|
// Do nothing
|
|
}
|
|
|
|
void ControlExpressionSyntaxHighlighter::highlightBlock(const QString&)
|
|
{
|
|
// TODO: This is going to result in improper highlighting with non-ascii characters:
|
|
ciface::ExpressionParser::Lexer lexer(document()->toPlainText().toStdString());
|
|
|
|
std::vector<ciface::ExpressionParser::Token> tokens;
|
|
const auto tokenize_status = lexer.Tokenize(tokens);
|
|
|
|
using ciface::ExpressionParser::TokenType;
|
|
|
|
const auto set_block_format = [this](int start, int count, const QTextCharFormat& format) {
|
|
if (start + count <= currentBlock().position() ||
|
|
start >= currentBlock().position() + currentBlock().length())
|
|
{
|
|
// This range is not within the current block.
|
|
return;
|
|
}
|
|
|
|
int block_start = start - currentBlock().position();
|
|
|
|
if (block_start < 0)
|
|
{
|
|
count += block_start;
|
|
block_start = 0;
|
|
}
|
|
|
|
setFormat(block_start, count, format);
|
|
};
|
|
|
|
for (auto& token : tokens)
|
|
{
|
|
std::optional<QTextCharFormat> char_format;
|
|
|
|
switch (token.type)
|
|
{
|
|
case TokenType::TOK_INVALID:
|
|
char_format = GetInvalidCharFormat();
|
|
break;
|
|
case TokenType::TOK_LPAREN:
|
|
case TokenType::TOK_RPAREN:
|
|
case TokenType::TOK_COMMA:
|
|
char_format = GetSpecialCharFormat();
|
|
break;
|
|
case TokenType::TOK_LITERAL:
|
|
char_format = GetLiteralCharFormat();
|
|
break;
|
|
case TokenType::TOK_CONTROL:
|
|
char_format = GetControlCharFormat();
|
|
break;
|
|
case TokenType::TOK_BAREWORD:
|
|
char_format = GetBarewordCharFormat();
|
|
break;
|
|
case TokenType::TOK_VARIABLE:
|
|
char_format = GetVariableCharFormat();
|
|
break;
|
|
case TokenType::TOK_COMMENT:
|
|
char_format = GetCommentCharFormat();
|
|
break;
|
|
default:
|
|
if (token.IsBinaryOperator())
|
|
char_format = GetSpecialCharFormat();
|
|
break;
|
|
}
|
|
|
|
if (char_format.has_value())
|
|
set_block_format(int(token.string_position), int(token.string_length), *char_format);
|
|
}
|
|
|
|
// This doesn't need to be run for every "block", but it works.
|
|
if (ciface::ExpressionParser::ParseStatus::Successful == tokenize_status)
|
|
{
|
|
ciface::ExpressionParser::RemoveInertTokens(&tokens);
|
|
const auto parse_status = ciface::ExpressionParser::ParseTokens(tokens);
|
|
|
|
if (ciface::ExpressionParser::ParseStatus::Successful != parse_status.status)
|
|
{
|
|
const auto token = *parse_status.token;
|
|
set_block_format(int(token.string_position), int(token.string_length),
|
|
GetInvalidCharFormat());
|
|
}
|
|
}
|
|
}
|
|
|
|
class InputStateDelegate : public QItemDelegate
|
|
{
|
|
public:
|
|
explicit InputStateDelegate(IOWindow* parent, int column,
|
|
std::function<ControlState(int row)> state_evaluator);
|
|
|
|
void paint(QPainter* painter, const QStyleOptionViewItem& option,
|
|
const QModelIndex& index) const override;
|
|
|
|
private:
|
|
std::function<ControlState(int row)> m_state_evaluator;
|
|
int m_column;
|
|
};
|
|
|
|
class InputStateLineEdit : public QLineEdit
|
|
{
|
|
public:
|
|
explicit InputStateLineEdit(std::function<ControlState()> state_evaluator);
|
|
void SetShouldPaintStateIndicator(bool value);
|
|
void paintEvent(QPaintEvent* event) override;
|
|
|
|
private:
|
|
std::function<ControlState()> m_state_evaluator;
|
|
bool m_should_paint_state_indicator;
|
|
};
|
|
|
|
IOWindow::IOWindow(MappingWidget* parent, ControllerEmu::EmulatedController* controller,
|
|
ControlReference* ref, IOWindow::Type type)
|
|
: QDialog(parent), m_reference(ref), m_original_expression(ref->GetExpression()),
|
|
m_controller(controller), m_type(type)
|
|
{
|
|
CreateMainLayout();
|
|
|
|
connect(parent, &MappingWidget::Update, this, &IOWindow::Update);
|
|
connect(parent->GetParent(), &MappingWindow::ConfigChanged, this, &IOWindow::ConfigChanged);
|
|
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &IOWindow::ConfigChanged);
|
|
|
|
setWindowTitle(type == IOWindow::Type::Input ? tr("Configure Input") : tr("Configure Output"));
|
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
|
|
ConfigChanged();
|
|
|
|
ConnectWidgets();
|
|
}
|
|
|
|
std::shared_ptr<ciface::Core::Device> IOWindow::GetSelectedDevice() const
|
|
{
|
|
return m_selected_device;
|
|
}
|
|
|
|
void IOWindow::CreateMainLayout()
|
|
{
|
|
m_main_layout = new QVBoxLayout();
|
|
|
|
m_devices_combo = new QComboBox();
|
|
m_option_list = new QTableWidget();
|
|
m_select_button = new QPushButton(tr("Select"));
|
|
m_detect_button = new QPushButton(tr("Detect"), this);
|
|
m_test_button = new QPushButton(tr("Test"), this);
|
|
m_button_box = new QDialogButtonBox();
|
|
m_clear_button = new QPushButton(tr("Clear"));
|
|
m_range_slider = new QSlider(Qt::Horizontal);
|
|
m_range_spinbox = new QSpinBox();
|
|
|
|
m_parse_text = new InputStateLineEdit([this] {
|
|
const auto lock = m_controller->GetStateLock();
|
|
return m_reference->GetState<ControlState>();
|
|
});
|
|
m_parse_text->setReadOnly(true);
|
|
|
|
m_expression_text = new QPlainTextEdit();
|
|
m_expression_text->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
|
|
new ControlExpressionSyntaxHighlighter(m_expression_text->document());
|
|
|
|
m_operators_combo = new QComboBoxWithMouseWheelDisabled(this);
|
|
m_operators_combo->addItem(tr("Operators"));
|
|
m_operators_combo->insertSeparator(1);
|
|
if (m_type == Type::Input)
|
|
{
|
|
m_operators_combo->addItem(tr("! Not"));
|
|
m_operators_combo->addItem(tr("* Multiply"));
|
|
m_operators_combo->addItem(tr("/ Divide"));
|
|
m_operators_combo->addItem(tr("% Modulo"));
|
|
m_operators_combo->addItem(tr("+ Add"));
|
|
m_operators_combo->addItem(tr("- Subtract"));
|
|
m_operators_combo->addItem(tr("> Greater-than"));
|
|
m_operators_combo->addItem(tr("< Less-than"));
|
|
m_operators_combo->addItem(tr("& And"));
|
|
m_operators_combo->addItem(tr("^ Xor"));
|
|
}
|
|
m_operators_combo->addItem(tr("| Or"));
|
|
m_operators_combo->addItem(tr("$ User Variable"));
|
|
if (m_type == Type::Input)
|
|
{
|
|
m_operators_combo->addItem(tr(", Comma"));
|
|
}
|
|
|
|
m_functions_combo = new QComboBoxWithMouseWheelDisabled(this);
|
|
m_functions_combo->addItem(tr("Functions"));
|
|
m_functions_combo->insertSeparator(1);
|
|
m_functions_combo->addItem(QStringLiteral("if"));
|
|
m_functions_combo->addItem(QStringLiteral("timer"));
|
|
m_functions_combo->addItem(QStringLiteral("toggle"));
|
|
m_functions_combo->addItem(QStringLiteral("deadzone"));
|
|
m_functions_combo->addItem(QStringLiteral("smooth"));
|
|
m_functions_combo->addItem(QStringLiteral("hold"));
|
|
m_functions_combo->addItem(QStringLiteral("tap"));
|
|
m_functions_combo->addItem(QStringLiteral("relative"));
|
|
m_functions_combo->addItem(QStringLiteral("pulse"));
|
|
m_functions_combo->addItem(QStringLiteral("sin"));
|
|
m_functions_combo->addItem(QStringLiteral("cos"));
|
|
m_functions_combo->addItem(QStringLiteral("tan"));
|
|
m_functions_combo->addItem(QStringLiteral("asin"));
|
|
m_functions_combo->addItem(QStringLiteral("acos"));
|
|
m_functions_combo->addItem(QStringLiteral("atan"));
|
|
m_functions_combo->addItem(QStringLiteral("atan2"));
|
|
m_functions_combo->addItem(QStringLiteral("sqrt"));
|
|
m_functions_combo->addItem(QStringLiteral("pow"));
|
|
m_functions_combo->addItem(QStringLiteral("min"));
|
|
m_functions_combo->addItem(QStringLiteral("max"));
|
|
m_functions_combo->addItem(QStringLiteral("clamp"));
|
|
|
|
m_variables_combo = new QComboBoxWithMouseWheelDisabled(this);
|
|
m_variables_combo->addItem(tr("User Variables"));
|
|
m_variables_combo->setToolTip(
|
|
tr("User defined variables usable in the control expression.\nYou can use them to save or "
|
|
"retrieve values between\ninputs and outputs of the same parent controller."));
|
|
m_variables_combo->insertSeparator(m_variables_combo->count());
|
|
m_variables_combo->addItem(tr("Reset Values"));
|
|
m_variables_combo->insertSeparator(m_variables_combo->count());
|
|
|
|
// Devices
|
|
m_main_layout->addWidget(m_devices_combo);
|
|
|
|
// Range
|
|
auto* range_hbox = new QHBoxLayout();
|
|
range_hbox->addWidget(new QLabel(tr("Range")));
|
|
range_hbox->addWidget(m_range_slider);
|
|
range_hbox->addWidget(m_range_spinbox);
|
|
m_range_slider->setMinimum(-500);
|
|
m_range_slider->setMaximum(500);
|
|
m_range_spinbox->setMinimum(-500);
|
|
m_range_spinbox->setMaximum(500);
|
|
m_main_layout->addLayout(range_hbox);
|
|
|
|
// Options (Buttons, Outputs) and action buttons
|
|
|
|
m_option_list->setTabKeyNavigation(false);
|
|
|
|
if (m_type == Type::Input)
|
|
{
|
|
m_option_list->setColumnCount(2);
|
|
m_option_list->setColumnWidth(1, 64);
|
|
m_option_list->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
|
|
|
|
m_option_list->setItemDelegate(new InputStateDelegate(this, 1, [&](int row) {
|
|
std::lock_guard lock(m_selected_device_mutex);
|
|
// Clamp off negative values but allow greater than one in the text display.
|
|
return std::max(GetSelectedDevice()->Inputs()[row]->GetState(), 0.0);
|
|
}));
|
|
}
|
|
else
|
|
{
|
|
m_option_list->setColumnCount(1);
|
|
}
|
|
|
|
m_option_list->horizontalHeader()->hide();
|
|
m_option_list->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
|
|
m_option_list->verticalHeader()->hide();
|
|
m_option_list->verticalHeader()->setDefaultSectionSize(
|
|
m_option_list->verticalHeader()->minimumSectionSize());
|
|
m_option_list->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
m_option_list->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
m_option_list->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
|
|
auto* hbox = new QHBoxLayout();
|
|
auto* button_vbox = new QVBoxLayout();
|
|
hbox->addWidget(m_option_list, 8);
|
|
hbox->addLayout(button_vbox, 1);
|
|
|
|
button_vbox->addWidget(m_select_button);
|
|
|
|
if (m_type == Type::Input)
|
|
{
|
|
m_test_button->hide();
|
|
button_vbox->addWidget(m_detect_button);
|
|
}
|
|
else
|
|
{
|
|
m_detect_button->hide();
|
|
button_vbox->addWidget(m_test_button);
|
|
}
|
|
|
|
button_vbox->addWidget(m_variables_combo);
|
|
|
|
button_vbox->addWidget(m_operators_combo);
|
|
|
|
if (m_type == Type::Input)
|
|
button_vbox->addWidget(m_functions_combo);
|
|
else
|
|
m_functions_combo->hide();
|
|
|
|
m_main_layout->addLayout(hbox, 2);
|
|
m_main_layout->addWidget(m_expression_text, 1);
|
|
m_main_layout->addWidget(m_parse_text);
|
|
|
|
// Button Box
|
|
m_main_layout->addWidget(m_button_box);
|
|
m_button_box->addButton(m_clear_button, QDialogButtonBox::ActionRole);
|
|
m_button_box->addButton(QDialogButtonBox::Ok);
|
|
|
|
setLayout(m_main_layout);
|
|
}
|
|
|
|
void IOWindow::ConfigChanged()
|
|
{
|
|
const QSignalBlocker blocker(this);
|
|
const auto lock = ControllerEmu::EmulatedController::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);
|
|
m_range_spinbox->setValue(m_reference->range * SLIDER_TICK_COUNT);
|
|
m_range_slider->setValue(m_reference->range * SLIDER_TICK_COUNT);
|
|
|
|
if (m_devq.ToString().empty())
|
|
m_devq = m_controller->GetDefaultDevice();
|
|
|
|
UpdateDeviceList();
|
|
}
|
|
|
|
void IOWindow::Update()
|
|
{
|
|
m_option_list->viewport()->update();
|
|
m_parse_text->update();
|
|
}
|
|
|
|
void IOWindow::ConnectWidgets()
|
|
{
|
|
connect(m_select_button, &QPushButton::clicked, [this] { AppendSelectedOption(); });
|
|
connect(&Settings::Instance(), &Settings::ReleaseDevices, this, &IOWindow::ReleaseDevices);
|
|
connect(&Settings::Instance(), &Settings::DevicesChanged, this, &IOWindow::UpdateDeviceList);
|
|
|
|
connect(m_detect_button, &QPushButton::clicked, this, &IOWindow::OnDetectButtonPressed);
|
|
connect(m_test_button, &QPushButton::clicked, this, &IOWindow::OnTestButtonPressed);
|
|
|
|
connect(m_button_box, &QDialogButtonBox::clicked, this, &IOWindow::OnDialogButtonPressed);
|
|
connect(m_devices_combo, &QComboBox::currentTextChanged, this, &IOWindow::OnDeviceChanged);
|
|
connect(m_range_spinbox, qOverload<int>(&QSpinBox::valueChanged), this,
|
|
&IOWindow::OnRangeChanged);
|
|
|
|
connect(m_expression_text, &QPlainTextEdit::textChanged,
|
|
[this] { UpdateExpression(m_expression_text->toPlainText().toStdString()); });
|
|
|
|
connect(m_variables_combo, qOverload<int>(&QComboBox::activated), [this](int index) {
|
|
if (index == 0)
|
|
return;
|
|
|
|
// Reset button. 1 and 3 are separators.
|
|
if (index == 2)
|
|
{
|
|
const auto lock = ControllerEmu::EmulatedController::GetStateLock();
|
|
m_controller->ResetExpressionVariables();
|
|
}
|
|
else
|
|
{
|
|
m_expression_text->insertPlainText(QLatin1Char('$') + m_variables_combo->currentText());
|
|
}
|
|
|
|
m_variables_combo->setCurrentIndex(0);
|
|
});
|
|
|
|
connect(m_operators_combo, qOverload<int>(&QComboBox::activated), [this](int index) {
|
|
if (index == 0)
|
|
return;
|
|
|
|
m_expression_text->insertPlainText(m_operators_combo->currentText().left(1));
|
|
|
|
m_operators_combo->setCurrentIndex(0);
|
|
});
|
|
|
|
connect(m_functions_combo, qOverload<int>(&QComboBox::activated), [this](int index) {
|
|
if (index == 0)
|
|
return;
|
|
|
|
m_expression_text->insertPlainText(m_functions_combo->currentText() + QStringLiteral("()"));
|
|
|
|
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()
|
|
{
|
|
if (m_option_list->currentRow() < 0)
|
|
return;
|
|
|
|
m_expression_text->insertPlainText(MappingCommon::GetExpressionForControl(
|
|
m_option_list->item(m_option_list->currentRow(), 0)->text(), m_devq,
|
|
m_controller->GetDefaultDevice()));
|
|
}
|
|
|
|
void IOWindow::OnDeviceChanged()
|
|
{
|
|
const std::string device_name =
|
|
m_devices_combo->count() > 0 ? m_devices_combo->currentData().toString().toStdString() : "";
|
|
m_devq.FromString(device_name);
|
|
UpdateOptionList();
|
|
}
|
|
|
|
void IOWindow::OnDialogButtonPressed(QAbstractButton* button)
|
|
{
|
|
if (button == m_clear_button)
|
|
{
|
|
m_expression_text->clear();
|
|
return;
|
|
}
|
|
|
|
const auto lock = ControllerEmu::EmulatedController::GetStateLock();
|
|
|
|
UpdateExpression(m_expression_text->toPlainText().toStdString());
|
|
m_original_expression = m_reference->GetExpression();
|
|
|
|
if (ciface::ExpressionParser::ParseStatus::SyntaxError == m_reference->GetParseStatus())
|
|
{
|
|
ModalMessageBox::warning(this, tr("Error"), tr("The expression contains a syntax error."));
|
|
}
|
|
else
|
|
{
|
|
// must be the OK button
|
|
accept();
|
|
}
|
|
}
|
|
|
|
void IOWindow::OnDetectButtonPressed()
|
|
{
|
|
const auto expression =
|
|
MappingCommon::DetectExpression(m_detect_button, g_controller_interface, {m_devq.ToString()},
|
|
m_devq, MappingCommon::Quote::Off);
|
|
|
|
if (expression.isEmpty())
|
|
return;
|
|
|
|
const auto list = m_option_list->findItems(expression, Qt::MatchFixedString);
|
|
|
|
// Try to select the first. If this fails, the last selected item would still appear as such
|
|
if (!list.empty())
|
|
m_option_list->setCurrentItem(list[0]);
|
|
}
|
|
|
|
void IOWindow::OnTestButtonPressed()
|
|
{
|
|
MappingCommon::TestOutput(m_test_button, static_cast<OutputReference*>(m_reference));
|
|
}
|
|
|
|
void IOWindow::OnRangeChanged(int value)
|
|
{
|
|
m_reference->range = static_cast<double>(value) / SLIDER_TICK_COUNT;
|
|
m_range_spinbox->setValue(m_reference->range * SLIDER_TICK_COUNT);
|
|
m_range_slider->setValue(m_reference->range * SLIDER_TICK_COUNT);
|
|
}
|
|
|
|
void IOWindow::ReleaseDevices()
|
|
{
|
|
std::lock_guard lock(m_selected_device_mutex);
|
|
m_selected_device = nullptr;
|
|
}
|
|
|
|
void IOWindow::UpdateOptionList()
|
|
{
|
|
std::lock_guard lock(m_selected_device_mutex);
|
|
m_selected_device = g_controller_interface.FindDevice(m_devq);
|
|
m_option_list->setRowCount(0);
|
|
|
|
if (m_selected_device == nullptr)
|
|
return;
|
|
|
|
if (m_reference->IsInput())
|
|
{
|
|
int row = 0;
|
|
for (const auto* input : m_selected_device->Inputs())
|
|
{
|
|
m_option_list->insertRow(row);
|
|
m_option_list->setItem(row, 0,
|
|
new QTableWidgetItem(QString::fromStdString(input->GetName())));
|
|
++row;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int row = 0;
|
|
for (const auto* output : m_selected_device->Outputs())
|
|
{
|
|
m_option_list->insertRow(row);
|
|
m_option_list->setItem(row, 0,
|
|
new QTableWidgetItem(QString::fromStdString(output->GetName())));
|
|
++row;
|
|
}
|
|
}
|
|
}
|
|
|
|
void IOWindow::UpdateDeviceList()
|
|
{
|
|
const QSignalBlocker blocker(m_devices_combo);
|
|
|
|
const auto previous_device_name = m_devices_combo->currentData().toString().toStdString();
|
|
|
|
m_devices_combo->clear();
|
|
|
|
// Default to the default device or to the first device if there isn't a default.
|
|
// Try to the keep the previous selected device, mark it as disconnected if it's gone, as it could
|
|
// reconnect soon after if this is a devices refresh and it would be annoying to lose the value.
|
|
const auto default_device_name = m_controller->GetDefaultDevice().ToString();
|
|
int default_device_index = -1;
|
|
int previous_device_index = -1;
|
|
for (const auto& name : g_controller_interface.GetAllDeviceStrings())
|
|
{
|
|
QString qname = QString();
|
|
if (name == default_device_name)
|
|
{
|
|
default_device_index = m_devices_combo->count();
|
|
// Specify "default" even if we only have one device
|
|
qname.append(QLatin1Char{'['} + tr("default") + QStringLiteral("] "));
|
|
}
|
|
if (name == previous_device_name)
|
|
{
|
|
previous_device_index = m_devices_combo->count();
|
|
}
|
|
qname.append(QString::fromStdString(name));
|
|
m_devices_combo->addItem(qname, QString::fromStdString(name));
|
|
}
|
|
|
|
if (previous_device_index >= 0)
|
|
{
|
|
m_devices_combo->setCurrentIndex(previous_device_index);
|
|
}
|
|
else if (!previous_device_name.empty())
|
|
{
|
|
const QString qname = QString::fromStdString(previous_device_name);
|
|
QString adjusted_qname;
|
|
if (previous_device_name == default_device_name)
|
|
{
|
|
adjusted_qname.append(QLatin1Char{'['} + tr("default") + QStringLiteral("] "));
|
|
}
|
|
adjusted_qname.append(QLatin1Char{'['} + tr("disconnected") + QStringLiteral("] "))
|
|
.append(qname);
|
|
m_devices_combo->addItem(adjusted_qname, qname);
|
|
m_devices_combo->setCurrentIndex(m_devices_combo->count() - 1);
|
|
}
|
|
else if (default_device_index >= 0)
|
|
{
|
|
m_devices_combo->setCurrentIndex(default_device_index);
|
|
}
|
|
else if (m_devices_combo->count() > 0)
|
|
{
|
|
m_devices_combo->setCurrentIndex(0);
|
|
}
|
|
// The device object might have changed so we need to always refresh it
|
|
OnDeviceChanged();
|
|
}
|
|
|
|
void IOWindow::UpdateExpression(std::string new_expression, UpdateMode mode)
|
|
{
|
|
const auto lock = m_controller->GetStateLock();
|
|
if (mode != UpdateMode::Force && 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);
|
|
|
|
// This is the only place where we need to update the user variables. Keep the first 4 items.
|
|
while (m_variables_combo->count() > 4)
|
|
{
|
|
m_variables_combo->removeItem(m_variables_combo->count() - 1);
|
|
}
|
|
for (const auto& expression : m_controller->GetExpressionVariables())
|
|
{
|
|
m_variables_combo->addItem(QString::fromStdString(expression.first));
|
|
}
|
|
|
|
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->SetShouldPaintStateIndicator(true);
|
|
m_parse_text->setText(QString());
|
|
}
|
|
}
|
|
|
|
InputStateDelegate::InputStateDelegate(IOWindow* parent, int column,
|
|
std::function<ControlState(int row)> state_evaluator)
|
|
: QItemDelegate(parent), m_state_evaluator(std::move(state_evaluator)), m_column(column)
|
|
{
|
|
}
|
|
|
|
InputStateLineEdit::InputStateLineEdit(std::function<ControlState()> 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);
|
|
|
|
if (index.column() != m_column)
|
|
return;
|
|
|
|
painter->save();
|
|
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());
|
|
}
|