#include #include #include #include #include #include #include #include #include #include #include #include #include namespace scwx { namespace qt { namespace settings { static const std::string logPrefix_ = "scwx::qt::settings::settings_interface"; template class SettingsInterface::Impl { public: explicit Impl(SettingsInterface* self) : self_ {self} { context_->moveToThread(QCoreApplication::instance()->thread()); } ~Impl() {} template void SetWidgetText(U* widget, const T& currentValue); void UpdateEditWidget(); void UpdateResetButton(); void UpdateUnitLabel(); SettingsInterface* self_; SettingsVariable* variable_ {nullptr}; bool stagedValid_ {true}; std::unique_ptr context_ {std::make_unique()}; QWidget* editWidget_ {nullptr}; QAbstractButton* resetButton_ {nullptr}; QLabel* unitLabel_ {nullptr}; std::function mapFromValue_ {nullptr}; std::function mapToValue_ {nullptr}; double unitScale_ {1}; std::optional unitAbbreviation_ {}; bool unitEnabled_ {false}; bool trimmingEnabled_ {false}; }; template SettingsInterface::SettingsInterface() : SettingsInterfaceBase(), p(std::make_unique(this)) { } template SettingsInterface::~SettingsInterface() = default; template SettingsInterface::SettingsInterface(SettingsInterface&& o) noexcept : p {std::move(o.p)} { p->self_ = this; } template SettingsInterface& SettingsInterface::operator=(SettingsInterface&& o) noexcept { p = std::move(o.p); p->self_ = this; return *this; } template void SettingsInterface::SetSettingsVariable(SettingsVariable& variable) { p->variable_ = &variable; } template SettingsVariable* SettingsInterface::GetSettingsVariable() const { return p->variable_; } template bool SettingsInterface::IsDefault() { bool isDefault = false; const std::optional staged = p->variable_->GetStaged(); const T defaultValue = p->variable_->GetDefault(); const T value = p->variable_->GetValue(); if (staged.has_value()) { isDefault = (p->stagedValid_ && *staged == defaultValue); } else { isDefault = (value == defaultValue); } return isDefault; } template bool SettingsInterface::Commit() { return p->variable_->Commit(); } template void SettingsInterface::Reset() { p->variable_->Reset(); p->UpdateEditWidget(); p->UpdateResetButton(); } template void SettingsInterface::StageDefault() { p->variable_->StageDefault(); p->UpdateEditWidget(); p->UpdateResetButton(); } template void SettingsInterface::StageValue(const T& value) { p->variable_->StageValue(value); p->UpdateEditWidget(); p->UpdateResetButton(); } template void SettingsInterface::SetEditWidget(QWidget* widget) { if (p->editWidget_ != nullptr) { QObject::disconnect(p->editWidget_, nullptr, p->context_.get(), nullptr); } p->editWidget_ = widget; if (widget == nullptr) { return; } if (ui::HotkeyEdit* hotkeyEdit = dynamic_cast(widget)) { if constexpr (std::is_same_v) { QObject::connect(hotkeyEdit, &ui::HotkeyEdit::KeySequenceChanged, p->context_.get(), [this](const QKeySequence& sequence) { std::string value { sequence.toString().toStdString()}; // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); // TODO: Display invalid status }); } } else if (QLineEdit* lineEdit = dynamic_cast(widget)) { if constexpr (std::is_same_v) { // If the line is edited (not programatically changed), stage the new // value QObject::connect(lineEdit, &QLineEdit::textEdited, p->context_.get(), [this](const QString& text) { QString trimmedText = p->trimmingEnabled_ ? text.trimmed() : text; // Map to value if required std::string value {trimmedText.toStdString()}; if (p->mapToValue_ != nullptr) { value = p->mapToValue_(value); } // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); // TODO: Display invalid status }); } else if constexpr (std::is_same_v) { // If the line is edited (not programatically changed), stage the new // value QObject::connect(lineEdit, &QLineEdit::textEdited, p->context_.get(), [this](const QString& text) { // Convert to a double bool ok; double value = text.toDouble(&ok); if (ok) { // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); } else { p->stagedValid_ = false; p->UpdateResetButton(); } }); } else if constexpr (std::is_same_v>) { // If the line is edited (not programatically changed), stage the new // value QObject::connect( lineEdit, &QLineEdit::textEdited, p->context_.get(), [this](const QString& text) { // Map to value if required T value {}; if (p->mapToValue_ != nullptr) { // User-defined map to value value = p->mapToValue_(text.toStdString()); } else { // Tokenize string to parse each element const std::string str {text.toStdString()}; boost::tokenizer tokens(str); for (auto it = tokens.begin(); it != tokens.end(); ++it) { try { // Good value value.push_back( static_cast(std::stoll(*it))); } catch (const std::exception&) { // Error value value.push_back( std::numeric_limits::min()); } } } // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); // TODO: Display invalid status }); } } else if (QCheckBox* checkBox = dynamic_cast(widget)) { if constexpr (std::is_same_v) { QObject::connect(checkBox, &QCheckBox::toggled, p->context_.get(), [this](bool checked) { // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(checked); p->UpdateResetButton(); }); } } else if (QComboBox* comboBox = dynamic_cast(widget)) { if constexpr (std::is_same_v) { QObject::connect(comboBox, &QComboBox::currentTextChanged, p->context_.get(), [this](const QString& text) { // Map to value if required std::string value {text.toStdString()}; if (p->mapToValue_ != nullptr) { value = p->mapToValue_(value); } // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); }); } } else if (QSpinBox* spinBox = dynamic_cast(widget)) { if constexpr (std::is_integral_v) { const std::optional minimum = p->variable_->GetMinimum(); const std::optional maximum = p->variable_->GetMaximum(); if (minimum.has_value()) { spinBox->setMinimum(static_cast(*minimum)); } if (maximum.has_value()) { spinBox->setMaximum(static_cast(*maximum)); } // If the spin box is edited, stage a changed value QObject::connect( spinBox, &QSpinBox::valueChanged, p->context_.get(), [this](int i) { const T value = p->variable_->GetValue(); const std::optional staged = p->variable_->GetStaged(); // If there is a value staged, and the new value is the same as // the current value, reset the staged value if (staged.has_value() && static_cast(i) == value) { p->variable_->Reset(); p->stagedValid_ = true; p->UpdateResetButton(); } // If there is no staged value, or if the new value is different // than what is staged, attempt to stage the value else if (!staged.has_value() || static_cast(i) != *staged) { p->stagedValid_ = p->variable_->StageValue(static_cast(i)); p->UpdateResetButton(); } // Otherwise, don't process an unchanged value }); } } else if (QDoubleSpinBox* doubleSpinBox = dynamic_cast(widget)) { if constexpr (std::is_floating_point_v) { const std::optional minimum = p->variable_->GetMinimum(); const std::optional maximum = p->variable_->GetMaximum(); if (minimum.has_value()) { doubleSpinBox->setMinimum(static_cast(*minimum)); } if (maximum.has_value()) { doubleSpinBox->setMaximum(static_cast(*maximum)); } // If the spin box is edited, stage a changed value QObject::connect( doubleSpinBox, &QDoubleSpinBox::valueChanged, p->context_.get(), [this](double d) { if (p->unitEnabled_) { d = d / p->unitScale_; } const T value = p->variable_->GetValue(); const std::optional staged = p->variable_->GetStaged(); // If there is a value staged, and the new value is the same as // the current value, reset the staged value if (staged.has_value() && static_cast(d) == value) { p->variable_->Reset(); p->stagedValid_ = true; p->UpdateResetButton(); } // If there is no staged value, or if the new value is different // than what is staged, attempt to stage the value else if (!staged.has_value() || static_cast(d) != *staged) { p->stagedValid_ = p->variable_->StageValue(static_cast(d)); p->UpdateResetButton(); } // Otherwise, don't process an unchanged value }); } } p->UpdateEditWidget(); } template void SettingsInterface::SetResetButton(QAbstractButton* button) { if (p->resetButton_ != nullptr) { QObject::disconnect(p->resetButton_, nullptr, p->context_.get(), nullptr); } p->resetButton_ = button; if (p->resetButton_ != nullptr) { QObject::connect(p->resetButton_, &QAbstractButton::clicked, p->context_.get(), [this]() { T defaultValue = p->variable_->GetDefault(); if (p->variable_->GetValue() == defaultValue) { // If the current value is default, reset the // staged value p->variable_->Reset(); p->stagedValid_ = true; p->UpdateEditWidget(); p->UpdateResetButton(); } else { // Stage the default value p->stagedValid_ = p->variable_->StageValue(defaultValue); p->UpdateEditWidget(); p->UpdateResetButton(); } }); p->UpdateResetButton(); } } template void SettingsInterface::SetUnitLabel(QLabel* label) { p->unitLabel_ = label; } template void SettingsInterface::SetMapFromValueFunction( std::function function) { p->mapFromValue_ = function; } template void SettingsInterface::SetMapToValueFunction( std::function function) { p->mapToValue_ = function; } template void SettingsInterface::SetUnit(const double& scale, const std::string& abbreviation) { p->unitScale_ = scale; p->unitAbbreviation_ = abbreviation; p->unitEnabled_ = true; p->UpdateEditWidget(); p->UpdateUnitLabel(); } template void SettingsInterface::EnableTrimming(bool trimmingEnabled) { p->trimmingEnabled_ = trimmingEnabled; } template template void SettingsInterface::Impl::SetWidgetText(U* widget, const T& currentValue) { if constexpr (std::is_integral_v) { widget->setText(QString::number(currentValue)); } else if constexpr (std::is_floating_point_v) { widget->setText(QString::number(currentValue)); } else if constexpr (std::is_same_v) { if (mapFromValue_ != nullptr) { widget->setText(QString::fromStdString(mapFromValue_(currentValue))); } else { widget->setText(QString::fromStdString(currentValue)); } } else if constexpr (std::is_same_v>) { if (mapFromValue_ != nullptr) { widget->setText(QString::fromStdString(mapFromValue_(currentValue))); } else { widget->setText(QString::fromStdString( fmt::format("{}", fmt::join(currentValue, ", ")))); } } } template void SettingsInterface::Impl::UpdateEditWidget() { // Use the staged value if present, otherwise the current value const std::optional staged = variable_->GetStaged(); const T value = variable_->GetValue(); const T& currentValue = staged.has_value() ? *staged : value; if (ui::HotkeyEdit* hotkeyEdit = dynamic_cast(editWidget_)) { if constexpr (std::is_same_v) { QKeySequence keySequence = QKeySequence::fromString(QString::fromStdString(currentValue)); hotkeyEdit->set_key_sequence(keySequence); } } else if (QLineEdit* lineEdit = dynamic_cast(editWidget_)) { SetWidgetText(lineEdit, currentValue); } else if (QLabel* label = dynamic_cast(editWidget_)) { SetWidgetText(label, currentValue); } else if (QCheckBox* checkBox = dynamic_cast(editWidget_)) { if constexpr (std::is_same_v) { checkBox->setChecked(currentValue); } } else if (QComboBox* comboBox = dynamic_cast(editWidget_)) { if constexpr (std::is_same_v) { if (mapFromValue_ != nullptr) { comboBox->setCurrentText( QString::fromStdString(mapFromValue_(currentValue))); } else { comboBox->setCurrentText(QString::fromStdString(currentValue)); } } } else if (QSpinBox* spinBox = dynamic_cast(editWidget_)) { if constexpr (std::is_integral_v) { spinBox->setValue(static_cast(currentValue)); } } else if (QDoubleSpinBox* doubleSpinBox = dynamic_cast(editWidget_)) { if constexpr (std::is_floating_point_v) { double doubleValue = static_cast(currentValue); if (unitEnabled_) { doubleValue = doubleValue * unitScale_; } doubleSpinBox->setValue(doubleValue); } } } template void SettingsInterface::Impl::UpdateUnitLabel() { if (unitLabel_ == nullptr || !unitEnabled_) { return; } unitLabel_->setText(QString::fromStdString(unitAbbreviation_.value_or(""))); } template void SettingsInterface::Impl::UpdateResetButton() { if (resetButton_ != nullptr) { resetButton_->setVisible(!self_->IsDefault()); } } template class SettingsInterface; template class SettingsInterface; template class SettingsInterface; template class SettingsInterface; // Containers are not to be used directly template class SettingsInterface>; } // namespace settings } // namespace qt } // namespace scwx