settingwidgetbinder.h (53886B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #pragma once 5 6 #include "qthost.h" 7 #include "qtutils.h" 8 #include "settingswindow.h" 9 10 #include "core/host.h" 11 #include "core/settings.h" 12 13 #include "common/assert.h" 14 #include "common/file_system.h" 15 #include "common/path.h" 16 17 #include <QtCore/QtCore> 18 #include <QtGui/QAction> 19 #include <QtWidgets/QAbstractButton> 20 #include <QtWidgets/QCheckBox> 21 #include <QtWidgets/QComboBox> 22 #include <QtWidgets/QDoubleSpinBox> 23 #include <QtWidgets/QFileDialog> 24 #include <QtWidgets/QLabel> 25 #include <QtWidgets/QLineEdit> 26 #include <QtWidgets/QMenu> 27 #include <QtWidgets/QMessageBox> 28 #include <QtWidgets/QSlider> 29 #include <QtWidgets/QSpinBox> 30 #include <optional> 31 #include <type_traits> 32 33 namespace SettingWidgetBinder { 34 static constexpr const char* NULLABLE_PROPERTY = "SettingWidgetBinder_isNullable"; 35 static constexpr const char* IS_NULL_PROPERTY = "SettingWidgetBinder_isNull"; 36 static constexpr const char* GLOBAL_VALUE_PROPERTY = "SettingWidgetBinder_globalValue"; 37 38 template<typename T> 39 struct SettingAccessor 40 { 41 static bool getBoolValue(const T* widget); 42 static void setBoolValue(T* widget, bool value); 43 static void makeNullableBool(T* widget, bool globalValue); 44 static std::optional<bool> getNullableBoolValue(const T* widget); 45 static void setNullableBoolValue(T* widget, std::optional<bool> value); 46 47 static int getIntValue(const T* widget); 48 static void setIntValue(T* widget, bool nullable, int value); 49 static void makeNullableInt(T* widget, int globalValue); 50 static std::optional<int> getNullableIntValue(const T* widget); 51 static void setNullableIntValue(T* widget, std::optional<int> value); 52 53 static int getFloatValue(const T* widget); 54 static void setFloatValue(T* widget, int value); 55 static void makeNullableFloat(T* widget, float globalValue); 56 static std::optional<float> getNullableFloatValue(const T* widget); 57 static void setNullableFloatValue(T* widget, std::optional<float> value); 58 59 static QString getStringValue(const T* widget); 60 static void setStringValue(T* widget, const QString& value); 61 static void makeNullableString(T* widget, const QString& globalValue); 62 static std::optional<QString> getNullableStringValue(const T* widget); 63 static void setNullableFloatValue(T* widget, std::optional<QString> value); 64 65 template<typename F> 66 static void connectValueChanged(T* widget, F func); 67 }; 68 69 template<> 70 struct SettingAccessor<QLineEdit> 71 { 72 static bool getBoolValue(const QLineEdit* widget) { return widget->text().toInt() != 0; } 73 static void setBoolValue(QLineEdit* widget, bool value) 74 { 75 widget->setText(value ? QStringLiteral("1") : QStringLiteral("0")); 76 } 77 static void makeNullableBool(QLineEdit* widget, bool globalValue) { widget->setEnabled(false); } 78 static std::optional<bool> getNullableBoolValue(const QLineEdit* widget) { return getBoolValue(widget); } 79 static void setNullableBoolValue(QLineEdit* widget, std::optional<bool> value) 80 { 81 setBoolValue(widget, value.value_or(false)); 82 } 83 84 static int getIntValue(const QLineEdit* widget) { return widget->text().toInt(); } 85 static void setIntValue(QLineEdit* widget, int value) { widget->setText(QString::number(value)); } 86 static void makeNullableInt(QLineEdit* widget, int globalValue) { widget->setEnabled(false); } 87 static std::optional<int> getNullableIntValue(const QLineEdit* widget) { return getIntValue(widget); } 88 static void setNullableIntValue(QLineEdit* widget, std::optional<int> value) 89 { 90 setIntValue(widget, value.value_or(0)); 91 } 92 93 static float getFloatValue(const QLineEdit* widget) { return widget->text().toFloat(); } 94 static void setFloatValue(QLineEdit* widget, float value) { widget->setText(QString::number(value)); } 95 static void makeNullableFloat(QLineEdit* widget, float globalValue) { widget->setEnabled(false); } 96 static std::optional<float> getNullableFloatValue(const QLineEdit* widget) { return getFloatValue(widget); } 97 static void setNullableFloatValue(QLineEdit* widget, std::optional<float> value) 98 { 99 setFloatValue(widget, value.value_or(0.0f)); 100 } 101 102 static QString getStringValue(const QLineEdit* widget) { return widget->text(); } 103 static void setStringValue(QLineEdit* widget, const QString& value) { widget->setText(value); } 104 static void makeNullableString(QLineEdit* widget, const QString& globalValue) { widget->setEnabled(false); } 105 static std::optional<QString> getNullableStringValue(const QLineEdit* widget) { return getStringValue(widget); } 106 static void setNullableStringValue(QLineEdit* widget, std::optional<QString> value) 107 { 108 setStringValue(widget, value.value_or(QString())); 109 } 110 111 template<typename F> 112 static void connectValueChanged(QLineEdit* widget, F func) 113 { 114 widget->connect(widget, &QLineEdit::textChanged, func); 115 } 116 }; 117 118 template<> 119 struct SettingAccessor<QComboBox> 120 { 121 static bool isNullValue(const QComboBox* widget) { return (widget->currentIndex() == 0); } 122 123 static bool getBoolValue(const QComboBox* widget) { return widget->currentIndex() > 0; } 124 static void setBoolValue(QComboBox* widget, bool value) { widget->setCurrentIndex(value ? 1 : 0); } 125 static void makeNullableBool(QComboBox* widget, bool globalValue) 126 { 127 widget->insertItem(0, globalValue ? qApp->translate("SettingsDialog", "Use Global Setting [Enabled]") : 128 qApp->translate("SettingsDialog", "Use Global Setting [Disabled]")); 129 } 130 131 static int getIntValue(const QComboBox* widget) { return widget->currentIndex(); } 132 static void setIntValue(QComboBox* widget, int value) { widget->setCurrentIndex(value); } 133 static void makeNullableInt(QComboBox* widget, int globalValue) 134 { 135 widget->insertItem( 136 0, qApp->translate("SettingsDialog", "Use Global Setting [%1]") 137 .arg((globalValue >= 0 && globalValue < widget->count()) ? widget->itemText(globalValue) : QString())); 138 } 139 static std::optional<int> getNullableIntValue(const QComboBox* widget) 140 { 141 return isNullValue(widget) ? std::nullopt : std::optional<int>(widget->currentIndex() - 1); 142 } 143 static void setNullableIntValue(QComboBox* widget, std::optional<int> value) 144 { 145 widget->setCurrentIndex(value.has_value() ? (value.value() + 1) : 0); 146 } 147 148 static float getFloatValue(const QComboBox* widget) { return static_cast<float>(widget->currentIndex()); } 149 static void setFloatValue(QComboBox* widget, float value) { widget->setCurrentIndex(static_cast<int>(value)); } 150 static void makeNullableFloat(QComboBox* widget, float globalValue) 151 { 152 widget->insertItem(0, qApp->translate("SettingsDialog", "Use Global Setting [%1]") 153 .arg((globalValue >= 0.0f && static_cast<int>(globalValue) < widget->count()) ? 154 widget->itemText(static_cast<int>(globalValue)) : 155 QString())); 156 } 157 static std::optional<float> getNullableFloatValue(const QComboBox* widget) 158 { 159 return isNullValue(widget) ? std::nullopt : std::optional<float>(static_cast<float>(widget->currentIndex() + 1)); 160 } 161 static void setNullableFloatValue(QComboBox* widget, std::optional<float> value) 162 { 163 widget->setCurrentIndex(value.has_value() ? static_cast<int>(value.value() + 1.0f) : 0); 164 } 165 166 static QString getStringValue(const QComboBox* widget) 167 { 168 const QVariant currentData(widget->currentData()); 169 if (currentData.metaType().id() == QMetaType::QString) 170 return currentData.toString(); 171 172 return widget->currentText(); 173 } 174 static void setStringValue(QComboBox* widget, const QString& value) 175 { 176 const int index = widget->findData(value); 177 if (index >= 0) 178 { 179 widget->setCurrentIndex(index); 180 return; 181 } 182 183 widget->setCurrentText(value); 184 } 185 static void makeNullableString(QComboBox* widget, const QString& globalValue) 186 { 187 makeNullableInt(widget, widget->findData(globalValue)); 188 } 189 static std::optional<QString> getNullableStringValue(const QComboBox* widget) 190 { 191 return isNullValue(widget) ? std::nullopt : std::optional<QString>(getStringValue(widget)); 192 } 193 static void setNullableStringValue(QComboBox* widget, std::optional<QString> value) 194 { 195 value.has_value() ? setStringValue(widget, value.value()) : widget->setCurrentIndex(0); 196 } 197 198 template<typename F> 199 static void connectValueChanged(QComboBox* widget, F func) 200 { 201 widget->connect(widget, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), func); 202 } 203 }; 204 205 template<> 206 struct SettingAccessor<QCheckBox> 207 { 208 static bool getBoolValue(const QCheckBox* widget) { return widget->isChecked(); } 209 static void setBoolValue(QCheckBox* widget, bool value) { widget->setChecked(value); } 210 static void makeNullableBool(QCheckBox* widget, bool globalValue) { widget->setTristate(true); } 211 static std::optional<bool> getNullableBoolValue(const QCheckBox* widget) 212 { 213 return (widget->checkState() == Qt::PartiallyChecked) ? std::nullopt : std::optional<bool>(widget->isChecked()); 214 } 215 static void setNullableBoolValue(QCheckBox* widget, std::optional<bool> value) 216 { 217 widget->setCheckState(value.has_value() ? (value.value() ? Qt::Checked : Qt::Unchecked) : Qt::PartiallyChecked); 218 } 219 220 static int getIntValue(const QCheckBox* widget) { return widget->isChecked() ? 1 : 0; } 221 static void setIntValue(QCheckBox* widget, int value) { widget->setChecked(value != 0); } 222 static void makeNullableInt(QCheckBox* widget, int globalValue) { widget->setTristate(true); } 223 static std::optional<int> getNullableIntValue(const QCheckBox* widget) 224 { 225 return (widget->checkState() == Qt::PartiallyChecked) ? std::nullopt : 226 std::optional<int>(widget->isChecked() ? 1 : 0); 227 } 228 static void setNullableIntValue(QCheckBox* widget, std::optional<int> value) 229 { 230 widget->setCheckState(value.has_value() ? ((value.value() != 0) ? Qt::Checked : Qt::Unchecked) : 231 Qt::PartiallyChecked); 232 } 233 234 static float getFloatValue(const QCheckBox* widget) { return widget->isChecked() ? 1.0f : 0.0f; } 235 static void setFloatValue(QCheckBox* widget, float value) { widget->setChecked(value != 0.0f); } 236 static void makeNullableFloat(QCheckBox* widget, float globalValue) { widget->setTristate(true); } 237 static std::optional<float> getNullableFloatValue(const QCheckBox* widget) 238 { 239 return (widget->checkState() == Qt::PartiallyChecked) ? std::nullopt : 240 std::optional<float>(widget->isChecked() ? 1.0f : 0.0f); 241 } 242 static void setNullableFloatValue(QCheckBox* widget, std::optional<float> value) 243 { 244 widget->setCheckState(value.has_value() ? ((value.value() != 0.0f) ? Qt::Checked : Qt::Unchecked) : 245 Qt::PartiallyChecked); 246 } 247 248 static QString getStringValue(const QCheckBox* widget) 249 { 250 return widget->isChecked() ? QStringLiteral("1") : QStringLiteral("0"); 251 } 252 static void setStringValue(QCheckBox* widget, const QString& value) { widget->setChecked(value.toInt() != 0); } 253 static void makeNullableString(QCheckBox* widget, const QString& globalValue) { widget->setTristate(true); } 254 static std::optional<QString> getNullableStringValue(const QCheckBox* widget) 255 { 256 return (widget->checkState() == Qt::PartiallyChecked) ? 257 std::nullopt : 258 std::optional<QString>(widget->isChecked() ? QStringLiteral("1") : QStringLiteral("0")); 259 } 260 static void setNullableStringValue(QCheckBox* widget, std::optional<QString> value) 261 { 262 widget->setCheckState(value.has_value() ? ((value->toInt() != 0) ? Qt::Checked : Qt::Unchecked) : 263 Qt::PartiallyChecked); 264 } 265 266 template<typename F> 267 static void connectValueChanged(QCheckBox* widget, F func) 268 { 269 widget->connect(widget, &QCheckBox::checkStateChanged, func); 270 } 271 }; 272 273 template<> 274 struct SettingAccessor<QSlider> 275 { 276 static bool isNullable(const QSlider* widget) { return widget->property(NULLABLE_PROPERTY).toBool(); } 277 278 static bool getBoolValue(const QSlider* widget) { return widget->value() > 0; } 279 static void setBoolValue(QSlider* widget, bool value) { widget->setValue(value ? 1 : 0); } 280 static void makeNullableBool(QSlider* widget, bool globalSetting) 281 { 282 widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); 283 widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalSetting)); 284 } 285 static std::optional<bool> getNullableBoolValue(const QSlider* widget) 286 { 287 if (widget->property(IS_NULL_PROPERTY).toBool()) 288 return std::nullopt; 289 290 return getBoolValue(widget); 291 } 292 static void setNullableBoolValue(QSlider* widget, std::optional<bool> value) 293 { 294 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 295 setBoolValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toBool()); 296 } 297 298 static int getIntValue(const QSlider* widget) { return widget->value(); } 299 static void setIntValue(QSlider* widget, int value) { widget->setValue(value); } 300 static void makeNullableInt(QSlider* widget, int globalValue) 301 { 302 widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); 303 widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue)); 304 } 305 static std::optional<int> getNullableIntValue(const QSlider* widget) 306 { 307 if (widget->property(IS_NULL_PROPERTY).toBool()) 308 return std::nullopt; 309 310 return getIntValue(widget); 311 } 312 static void setNullableIntValue(QSlider* widget, std::optional<int> value) 313 { 314 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 315 setIntValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toInt()); 316 } 317 318 static float getFloatValue(const QSlider* widget) { return static_cast<float>(widget->value()); } 319 static void setFloatValue(QSlider* widget, float value) { widget->setValue(static_cast<int>(value)); } 320 static void makeNullableFloat(QSlider* widget, float globalValue) { widget->setEnabled(false); } 321 static std::optional<float> getNullableFloatValue(const QSlider* widget) 322 { 323 if (widget->property(IS_NULL_PROPERTY).toBool()) 324 return std::nullopt; 325 326 return getFloatValue(widget); 327 } 328 static void setNullableFloatValue(QSlider* widget, std::optional<float> value) 329 { 330 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 331 setFloatValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toFloat()); 332 } 333 334 static QString getStringValue(const QSlider* widget) { return QString::number(widget->value()); } 335 static void setStringValue(QSlider* widget, const QString& value) { widget->setValue(value.toInt()); } 336 static void makeNullableString(QSlider* widget, const QString& globalValue) { widget->setEnabled(false); } 337 static std::optional<QString> getNullableStringValue(const QSlider* widget) 338 { 339 if (widget->property(IS_NULL_PROPERTY).toBool()) 340 return std::nullopt; 341 342 return getStringValue(widget); 343 } 344 static void setNullableStringValue(QSlider* widget, std::optional<QString> value) 345 { 346 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 347 setStringValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toString()); 348 } 349 350 template<typename F> 351 static void connectValueChanged(QSlider* widget, F func) 352 { 353 if (!isNullable(widget)) 354 { 355 widget->connect(widget, &QSlider::valueChanged, func); 356 } 357 else 358 { 359 widget->setContextMenuPolicy(Qt::CustomContextMenu); 360 widget->connect(widget, &QSlider::customContextMenuRequested, widget, [widget, func](const QPoint& pt) { 361 QMenu menu(widget); 362 widget->connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, widget, 363 [widget, func = std::move(func)]() { 364 const bool old = widget->blockSignals(true); 365 setNullableIntValue(widget, std::nullopt); 366 widget->blockSignals(old); 367 func(); 368 }); 369 menu.exec(widget->mapToGlobal(pt)); 370 }); 371 widget->connect(widget, &QSlider::valueChanged, widget, [widget, func = std::move(func)]() { 372 if (widget->property(IS_NULL_PROPERTY).toBool()) 373 widget->setProperty(IS_NULL_PROPERTY, QVariant(false)); 374 func(); 375 }); 376 } 377 } 378 }; 379 380 template<> 381 struct SettingAccessor<QSpinBox> 382 { 383 static bool isNullable(const QSpinBox* widget) { return widget->property(NULLABLE_PROPERTY).toBool(); } 384 385 static void updateFont(QSpinBox* widget, bool isNull) 386 { 387 // We should be able to use QFont here.. but it doesn't update on change. 388 widget->setStyleSheet(isNull ? QStringLiteral("font-style: italic;") : QString()); 389 widget->setPrefix(isNull ? qApp->translate("SettingWidgetBinder", "Default: ") : QString()); 390 } 391 392 static bool getBoolValue(const QSpinBox* widget) { return widget->value() > 0; } 393 static void setBoolValue(QSpinBox* widget, bool value) { widget->setValue(value ? 1 : 0); } 394 static void makeNullableBool(QSpinBox* widget, bool globalSetting) 395 { 396 widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); 397 widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalSetting)); 398 } 399 static std::optional<bool> getNullableBoolValue(const QSpinBox* widget) 400 { 401 if (widget->property(IS_NULL_PROPERTY).toBool()) 402 return std::nullopt; 403 404 return getBoolValue(widget); 405 } 406 static void setNullableBoolValue(QSpinBox* widget, std::optional<bool> value) 407 { 408 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 409 setBoolValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toBool()); 410 updateFont(widget, !value.has_value()); 411 } 412 413 static int getIntValue(const QSpinBox* widget) { return widget->value(); } 414 static void setIntValue(QSpinBox* widget, int value) { widget->setValue(value); } 415 static void makeNullableInt(QSpinBox* widget, int globalValue) 416 { 417 widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); 418 widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue)); 419 } 420 static std::optional<int> getNullableIntValue(const QSpinBox* widget) 421 { 422 if (widget->property(IS_NULL_PROPERTY).toBool()) 423 return std::nullopt; 424 425 return getIntValue(widget); 426 } 427 static void setNullableIntValue(QSpinBox* widget, std::optional<int> value) 428 { 429 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 430 setIntValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toInt()); 431 updateFont(widget, !value.has_value()); 432 } 433 434 static float getFloatValue(const QSpinBox* widget) { return static_cast<float>(widget->value()); } 435 static void setFloatValue(QSpinBox* widget, float value) { widget->setValue(static_cast<int>(value)); } 436 static void makeNullableFloat(QSpinBox* widget, float globalValue) 437 { 438 widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); 439 widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue)); 440 } 441 static std::optional<float> getNullableFloatValue(const QSpinBox* widget) 442 { 443 if (widget->property(IS_NULL_PROPERTY).toBool()) 444 return std::nullopt; 445 446 return getFloatValue(widget); 447 } 448 static void setNullableFloatValue(QSpinBox* widget, std::optional<float> value) 449 { 450 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 451 setFloatValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toFloat()); 452 updateFont(widget, !value.has_value()); 453 } 454 455 static QString getStringValue(const QSpinBox* widget) { return QString::number(widget->value()); } 456 static void setStringValue(QSpinBox* widget, const QString& value) { widget->setValue(value.toInt()); } 457 static void makeNullableString(QSpinBox* widget, const QString& globalValue) 458 { 459 widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); 460 widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue)); 461 } 462 static std::optional<QString> getNullableStringValue(const QSpinBox* widget) 463 { 464 if (widget->property(IS_NULL_PROPERTY).toBool()) 465 return std::nullopt; 466 467 return getStringValue(widget); 468 } 469 static void setNullableStringValue(QSpinBox* widget, std::optional<QString> value) 470 { 471 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 472 setStringValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toString()); 473 updateFont(widget, !value.has_value()); 474 } 475 476 template<typename F> 477 static void connectValueChanged(QSpinBox* widget, F func) 478 { 479 if (!isNullable(widget)) 480 { 481 widget->connect(widget, QOverload<int>::of(&QSpinBox::valueChanged), func); 482 } 483 else 484 { 485 widget->setContextMenuPolicy(Qt::CustomContextMenu); 486 widget->connect(widget, &QSpinBox::customContextMenuRequested, widget, [widget, func](const QPoint& pt) { 487 QMenu menu(widget); 488 widget->connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, widget, 489 [widget, func = std::move(func)]() { 490 const bool old = widget->blockSignals(true); 491 setNullableIntValue(widget, std::nullopt); 492 widget->blockSignals(old); 493 updateFont(widget, true); 494 func(); 495 }); 496 menu.exec(widget->mapToGlobal(pt)); 497 }); 498 widget->connect(widget, &QSpinBox::valueChanged, widget, [widget, func = std::move(func)]() { 499 if (widget->property(IS_NULL_PROPERTY).toBool()) 500 { 501 widget->setProperty(IS_NULL_PROPERTY, QVariant(false)); 502 updateFont(widget, false); 503 } 504 func(); 505 }); 506 } 507 } 508 }; 509 510 template<> 511 struct SettingAccessor<QDoubleSpinBox> 512 { 513 static bool isNullable(const QDoubleSpinBox* widget) { return widget->property(NULLABLE_PROPERTY).toBool(); } 514 515 static void updateFont(QDoubleSpinBox* widget, bool isNull) 516 { 517 // We should be able to use QFont here.. but it doesn't update on change. 518 widget->setStyleSheet(isNull ? QStringLiteral("font-style: italic;") : QString()); 519 widget->setPrefix(isNull ? qApp->translate("SettingWidgetBinder", "Default: ") : QString()); 520 } 521 522 static bool getBoolValue(const QDoubleSpinBox* widget) { return widget->value() > 0.0; } 523 static void setBoolValue(QDoubleSpinBox* widget, bool value) { widget->setValue(value ? 1.0 : 0.0); } 524 static void makeNullableBool(QDoubleSpinBox* widget, bool globalSetting) 525 { 526 widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); 527 widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalSetting)); 528 } 529 static std::optional<bool> getNullableBoolValue(const QDoubleSpinBox* widget) 530 { 531 if (widget->property(IS_NULL_PROPERTY).toBool()) 532 return std::nullopt; 533 534 return getBoolValue(widget); 535 } 536 static void setNullableBoolValue(QDoubleSpinBox* widget, std::optional<bool> value) 537 { 538 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 539 setBoolValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toBool()); 540 updateFont(widget, !value.has_value()); 541 } 542 543 static int getIntValue(const QDoubleSpinBox* widget) { return static_cast<int>(widget->value()); } 544 static void setIntValue(QDoubleSpinBox* widget, int value) { widget->setValue(static_cast<double>(value)); } 545 static void makeNullableInt(QDoubleSpinBox* widget, int globalValue) 546 { 547 widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); 548 widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue)); 549 } 550 static std::optional<int> getNullableIntValue(const QDoubleSpinBox* widget) 551 { 552 if (widget->property(IS_NULL_PROPERTY).toBool()) 553 return std::nullopt; 554 555 return getIntValue(widget); 556 } 557 static void setNullableIntValue(QDoubleSpinBox* widget, std::optional<int> value) 558 { 559 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 560 setIntValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toInt()); 561 updateFont(widget, !value.has_value()); 562 } 563 564 static float getFloatValue(const QDoubleSpinBox* widget) { return static_cast<float>(widget->value()); } 565 static void setFloatValue(QDoubleSpinBox* widget, float value) { widget->setValue(static_cast<double>(value)); } 566 static void makeNullableFloat(QDoubleSpinBox* widget, float globalValue) 567 { 568 widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); 569 widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue)); 570 } 571 static std::optional<float> getNullableFloatValue(const QDoubleSpinBox* widget) 572 { 573 if (widget->property(IS_NULL_PROPERTY).toBool()) 574 return std::nullopt; 575 576 return getFloatValue(widget); 577 } 578 static void setNullableFloatValue(QDoubleSpinBox* widget, std::optional<float> value) 579 { 580 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 581 setFloatValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toFloat()); 582 updateFont(widget, !value.has_value()); 583 } 584 585 static QString getStringValue(const QDoubleSpinBox* widget) { return QString::number(widget->value()); } 586 static void setStringValue(QDoubleSpinBox* widget, const QString& value) { widget->setValue(value.toDouble()); } 587 static void makeNullableString(QDoubleSpinBox* widget, const QString& globalValue) 588 { 589 widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); 590 widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue)); 591 } 592 static std::optional<QString> getNullableStringValue(const QDoubleSpinBox* widget) 593 { 594 if (widget->property(IS_NULL_PROPERTY).toBool()) 595 return std::nullopt; 596 597 return getStringValue(widget); 598 } 599 static void setNullableStringValue(QDoubleSpinBox* widget, std::optional<QString> value) 600 { 601 widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); 602 setStringValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toString()); 603 updateFont(widget, !value.has_value()); 604 } 605 606 template<typename F> 607 static void connectValueChanged(QDoubleSpinBox* widget, F func) 608 { 609 if (!isNullable(widget)) 610 { 611 widget->connect(widget, QOverload<double>::of(&QDoubleSpinBox::valueChanged), func); 612 } 613 else 614 { 615 widget->setContextMenuPolicy(Qt::CustomContextMenu); 616 widget->connect(widget, &QDoubleSpinBox::customContextMenuRequested, widget, [widget, func](const QPoint& pt) { 617 QMenu menu(widget); 618 widget->connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, widget, 619 [widget, func = std::move(func)]() { 620 const bool old = widget->blockSignals(true); 621 setNullableFloatValue(widget, std::nullopt); 622 widget->blockSignals(old); 623 updateFont(widget, true); 624 func(); 625 }); 626 menu.exec(widget->mapToGlobal(pt)); 627 }); 628 widget->connect(widget, QOverload<double>::of(&QDoubleSpinBox::valueChanged), widget, 629 [widget, func = std::move(func)]() { 630 if (widget->property(IS_NULL_PROPERTY).toBool()) 631 { 632 widget->setProperty(IS_NULL_PROPERTY, QVariant(false)); 633 updateFont(widget, false); 634 } 635 func(); 636 }); 637 } 638 } 639 }; 640 641 template<> 642 struct SettingAccessor<QAction> 643 { 644 static bool getBoolValue(const QAction* widget) { return widget->isChecked(); } 645 static void setBoolValue(QAction* widget, bool value) { widget->setChecked(value); } 646 static void makeNullableBool(QAction* widget, bool globalSetting) { widget->setEnabled(false); } 647 static std::optional<bool> getNullableBoolValue(const QAction* widget) { return getBoolValue(widget); } 648 static void setNullableBoolValue(QAction* widget, std::optional<bool> value) 649 { 650 setBoolValue(widget, value.value_or(false)); 651 } 652 653 static int getIntValue(const QAction* widget) { return widget->isChecked() ? 1 : 0; } 654 static void setIntValue(QAction* widget, int value) { widget->setChecked(value != 0); } 655 static void makeNullableInt(QAction* widget, int globalValue) { widget->setEnabled(false); } 656 static std::optional<int> getNullableIntValue(const QAction* widget) { return getIntValue(widget); } 657 static void setNullableIntValue(QAction* widget, std::optional<int> value) { setIntValue(widget, value.value_or(0)); } 658 659 static float getFloatValue(const QAction* widget) { return widget->isChecked() ? 1.0f : 0.0f; } 660 static void setFloatValue(QAction* widget, float value) { widget->setChecked(value != 0.0f); } 661 static void makeNullableFloat(QAction* widget, float globalValue) { widget->setEnabled(false); } 662 static std::optional<float> getNullableFloatValue(const QAction* widget) { return getFloatValue(widget); } 663 static void setNullableFloatValue(QAction* widget, std::optional<float> value) 664 { 665 setFloatValue(widget, value.value_or(0.0f)); 666 } 667 668 static QString getStringValue(const QAction* widget) 669 { 670 return widget->isChecked() ? QStringLiteral("1") : QStringLiteral("0"); 671 } 672 static void setStringValue(QAction* widget, const QString& value) { widget->setChecked(value.toInt() != 0); } 673 static void makeNullableString(QAction* widget, const QString& globalValue) { widget->setEnabled(false); } 674 static std::optional<QString> getNullableStringValue(const QAction* widget) { return getStringValue(widget); } 675 static void setNullableStringValue(QAction* widget, std::optional<QString> value) 676 { 677 setStringValue(widget, value.value_or(QString())); 678 } 679 680 template<typename F> 681 static void connectValueChanged(QAction* widget, F func) 682 { 683 widget->connect(widget, &QAction::toggled, func); 684 } 685 }; 686 687 /// Binds a widget's value to a setting, updating it when the value changes. 688 689 template<typename WidgetType> 690 static void BindWidgetToBoolSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, 691 bool default_value) 692 { 693 using Accessor = SettingAccessor<WidgetType>; 694 695 const bool value = Host::GetBaseBoolSettingValue(section.c_str(), key.c_str(), default_value); 696 697 if (sif) 698 { 699 Accessor::makeNullableBool(widget, value); 700 701 bool sif_value; 702 if (sif->GetBoolValue(section.c_str(), key.c_str(), &sif_value)) 703 Accessor::setNullableBoolValue(widget, sif_value); 704 else 705 Accessor::setNullableBoolValue(widget, std::nullopt); 706 707 Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() { 708 if (std::optional<bool> new_value = Accessor::getNullableBoolValue(widget); new_value.has_value()) 709 sif->SetBoolValue(section.c_str(), key.c_str(), new_value.value()); 710 else 711 sif->DeleteValue(section.c_str(), key.c_str()); 712 713 QtHost::SaveGameSettings(sif, true); 714 g_emu_thread->reloadGameSettings(); 715 }); 716 } 717 else 718 { 719 Accessor::setBoolValue(widget, value); 720 721 Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() { 722 const bool new_value = Accessor::getBoolValue(widget); 723 Host::SetBaseBoolSettingValue(section.c_str(), key.c_str(), new_value); 724 Host::CommitBaseSettingChanges(); 725 g_emu_thread->applySettings(); 726 }); 727 } 728 } 729 730 template<typename WidgetType> 731 static void BindWidgetToIntSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, 732 int default_value, int option_offset = 0) 733 { 734 using Accessor = SettingAccessor<WidgetType>; 735 736 const s32 value = 737 Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), static_cast<s32>(default_value)) - option_offset; 738 739 if (sif) 740 { 741 Accessor::makeNullableInt(widget, value); 742 743 int sif_value; 744 if (sif->GetIntValue(section.c_str(), key.c_str(), &sif_value)) 745 Accessor::setNullableIntValue(widget, sif_value - option_offset); 746 else 747 Accessor::setNullableIntValue(widget, std::nullopt); 748 749 Accessor::connectValueChanged( 750 widget, [sif, widget, section = std::move(section), key = std::move(key), option_offset]() { 751 if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value()) 752 sif->SetIntValue(section.c_str(), key.c_str(), new_value.value() + option_offset); 753 else 754 sif->DeleteValue(section.c_str(), key.c_str()); 755 756 QtHost::SaveGameSettings(sif, true); 757 g_emu_thread->reloadGameSettings(); 758 }); 759 } 760 else 761 { 762 Accessor::setIntValue(widget, static_cast<int>(value)); 763 764 Accessor::connectValueChanged( 765 widget, [widget, section = std::move(section), key = std::move(key), option_offset]() { 766 const int new_value = Accessor::getIntValue(widget); 767 Host::SetBaseIntSettingValue(section.c_str(), key.c_str(), new_value + option_offset); 768 Host::CommitBaseSettingChanges(); 769 g_emu_thread->applySettings(); 770 }); 771 } 772 } 773 774 template<typename WidgetType> 775 static inline void BindWidgetAndLabelToIntSetting(SettingsInterface* sif, WidgetType* widget, QLabel* label, 776 const QString& label_suffix, std::string section, std::string key, 777 int default_value, int option_offset = 0) 778 { 779 using Accessor = SettingAccessor<WidgetType>; 780 781 const s32 global_value = 782 Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), static_cast<s32>(default_value)) - option_offset; 783 784 if (sif) 785 { 786 QFont orig_font(label->font()); 787 QFont bold_font(orig_font); 788 bold_font.setBold(true); 789 790 Accessor::makeNullableInt(widget, global_value); 791 792 int sif_value; 793 if (sif->GetIntValue(section.c_str(), key.c_str(), &sif_value)) 794 { 795 Accessor::setNullableIntValue(widget, sif_value - option_offset); 796 if (label) 797 { 798 label->setText(QStringLiteral("%1%2").arg(sif_value).arg(label_suffix)); 799 label->setFont(bold_font); 800 } 801 } 802 else 803 { 804 Accessor::setNullableIntValue(widget, std::nullopt); 805 if (label) 806 label->setText(QStringLiteral("%1%2").arg(global_value).arg(label_suffix)); 807 } 808 809 Accessor::connectValueChanged(widget, [sif, widget, label, label_suffix, section = std::move(section), 810 key = std::move(key), option_offset, global_value, 811 bold_font = std::move(bold_font), orig_font = std::move(orig_font)]() { 812 if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value()) 813 { 814 sif->SetIntValue(section.c_str(), key.c_str(), new_value.value() + option_offset); 815 if (label) 816 { 817 label->setFont(bold_font); 818 label->setText(QStringLiteral("%1%2").arg(new_value.value()).arg(label_suffix)); 819 } 820 } 821 else 822 { 823 sif->DeleteValue(section.c_str(), key.c_str()); 824 if (label) 825 { 826 label->setFont(orig_font); 827 label->setText(QStringLiteral("%1%2").arg(global_value).arg(label_suffix)); 828 } 829 } 830 831 QtHost::SaveGameSettings(sif, true); 832 g_emu_thread->reloadGameSettings(); 833 }); 834 } 835 else 836 { 837 Accessor::setIntValue(widget, static_cast<int>(global_value)); 838 839 if (label) 840 label->setText(QStringLiteral("%1%2").arg(global_value).arg(label_suffix)); 841 842 Accessor::connectValueChanged( 843 widget, [widget, label, label_suffix, section = std::move(section), key = std::move(key), option_offset]() { 844 const int new_value = Accessor::getIntValue(widget); 845 Host::SetBaseIntSettingValue(section.c_str(), key.c_str(), new_value + option_offset); 846 Host::CommitBaseSettingChanges(); 847 g_emu_thread->applySettings(); 848 849 if (label) 850 label->setText(QStringLiteral("%1%2").arg(new_value).arg(label_suffix)); 851 }); 852 } 853 } 854 855 template<typename WidgetType> 856 static void BindWidgetToFloatSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, 857 float default_value) 858 { 859 using Accessor = SettingAccessor<WidgetType>; 860 861 const float value = Host::GetBaseFloatSettingValue(section.c_str(), key.c_str(), default_value); 862 863 if (sif) 864 { 865 Accessor::makeNullableFloat(widget, value); 866 867 float sif_value; 868 if (sif->GetFloatValue(section.c_str(), key.c_str(), &sif_value)) 869 Accessor::setNullableFloatValue(widget, sif_value); 870 else 871 Accessor::setNullableFloatValue(widget, std::nullopt); 872 873 Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() { 874 if (std::optional<float> new_value = Accessor::getNullableFloatValue(widget); new_value.has_value()) 875 sif->SetFloatValue(section.c_str(), key.c_str(), new_value.value()); 876 else 877 sif->DeleteValue(section.c_str(), key.c_str()); 878 879 QtHost::SaveGameSettings(sif, true); 880 g_emu_thread->reloadGameSettings(); 881 }); 882 } 883 else 884 { 885 Accessor::setFloatValue(widget, value); 886 887 Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() { 888 const float new_value = Accessor::getFloatValue(widget); 889 Host::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value); 890 Host::CommitBaseSettingChanges(); 891 g_emu_thread->applySettings(); 892 }); 893 } 894 } 895 896 template<typename WidgetType> 897 static void BindWidgetToNormalizedSetting(SettingsInterface* sif, WidgetType* widget, std::string section, 898 std::string key, float range, float default_value) 899 { 900 using Accessor = SettingAccessor<WidgetType>; 901 902 const float value = Host::GetBaseFloatSettingValue(section.c_str(), key.c_str(), default_value); 903 904 if (sif) 905 { 906 Accessor::makeNullableInt(widget, static_cast<int>(value * range)); 907 908 float sif_value; 909 if (sif->GetFloatValue(section.c_str(), key.c_str(), &sif_value)) 910 Accessor::setNullableIntValue(widget, static_cast<int>(sif_value * range)); 911 else 912 Accessor::setNullableIntValue(widget, std::nullopt); 913 914 Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), range]() { 915 if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value()) 916 sif->SetFloatValue(section.c_str(), key.c_str(), static_cast<float>(new_value.value()) / range); 917 else 918 sif->DeleteValue(section.c_str(), key.c_str()); 919 920 QtHost::SaveGameSettings(sif, true); 921 g_emu_thread->reloadGameSettings(); 922 }); 923 } 924 else 925 { 926 Accessor::setIntValue(widget, static_cast<int>(value * range)); 927 928 Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), range]() { 929 const float new_value = (static_cast<float>(Accessor::getIntValue(widget)) / range); 930 Host::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value); 931 Host::CommitBaseSettingChanges(); 932 g_emu_thread->applySettings(); 933 }); 934 } 935 } 936 937 template<typename WidgetType> 938 static void BindWidgetToStringSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, 939 std::string default_value = std::string()) 940 { 941 using Accessor = SettingAccessor<WidgetType>; 942 943 const QString value( 944 QString::fromStdString(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str()))); 945 946 if (sif) 947 { 948 Accessor::makeNullableString(widget, value); 949 950 std::string sif_value; 951 if (sif->GetStringValue(section.c_str(), key.c_str(), &sif_value)) 952 Accessor::setNullableStringValue(widget, QString::fromStdString(sif_value)); 953 else 954 Accessor::setNullableStringValue(widget, std::nullopt); 955 956 Accessor::connectValueChanged(widget, [widget, sif, section = std::move(section), key = std::move(key)]() { 957 if (std::optional<QString> new_value = Accessor::getNullableStringValue(widget); new_value.has_value()) 958 sif->SetStringValue(section.c_str(), key.c_str(), new_value->toUtf8().constData()); 959 else 960 sif->DeleteValue(section.c_str(), key.c_str()); 961 962 QtHost::SaveGameSettings(sif, true); 963 g_emu_thread->reloadGameSettings(); 964 }); 965 } 966 else 967 { 968 Accessor::setStringValue(widget, value); 969 970 Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() { 971 const QString new_value = Accessor::getStringValue(widget); 972 if (!new_value.isEmpty()) 973 Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), new_value.toUtf8().constData()); 974 else 975 Host::DeleteBaseSettingValue(section.c_str(), key.c_str()); 976 977 Host::CommitBaseSettingChanges(); 978 g_emu_thread->applySettings(); 979 }); 980 } 981 } 982 983 template<typename WidgetType, typename DataType> 984 static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, 985 std::optional<DataType> (*from_string_function)(const char* str), 986 const char* (*to_string_function)(DataType value), DataType default_value) 987 { 988 using Accessor = SettingAccessor<WidgetType>; 989 using UnderlyingType = std::underlying_type_t<DataType>; 990 991 const std::string value( 992 Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), to_string_function(default_value))); 993 const std::optional<DataType> typed_value = from_string_function(value.c_str()); 994 995 if (sif) 996 { 997 Accessor::makeNullableInt( 998 widget, typed_value.has_value() ? static_cast<int>(static_cast<UnderlyingType>(typed_value.value())) : 0); 999 1000 std::string sif_value; 1001 if (sif->GetStringValue(section.c_str(), key.c_str(), &sif_value)) 1002 { 1003 const std::optional<DataType> old_setting_value = from_string_function(sif_value.c_str()); 1004 if (old_setting_value.has_value()) 1005 Accessor::setNullableIntValue(widget, static_cast<int>(static_cast<UnderlyingType>(old_setting_value.value()))); 1006 else 1007 Accessor::setNullableIntValue(widget, std::nullopt); 1008 } 1009 else 1010 { 1011 Accessor::setNullableIntValue(widget, std::nullopt); 1012 } 1013 1014 Accessor::connectValueChanged( 1015 widget, [sif, widget, section = std::move(section), key = std::move(key), to_string_function]() { 1016 if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value()) 1017 { 1018 const char* string_value = 1019 to_string_function(static_cast<DataType>(static_cast<UnderlyingType>(new_value.value()))); 1020 sif->SetStringValue(section.c_str(), key.c_str(), string_value); 1021 } 1022 else 1023 { 1024 sif->DeleteValue(section.c_str(), key.c_str()); 1025 } 1026 1027 QtHost::SaveGameSettings(sif, true); 1028 g_emu_thread->reloadGameSettings(); 1029 }); 1030 } 1031 else 1032 { 1033 if (typed_value.has_value()) 1034 Accessor::setIntValue(widget, static_cast<int>(static_cast<UnderlyingType>(typed_value.value()))); 1035 else 1036 Accessor::setIntValue(widget, static_cast<int>(static_cast<UnderlyingType>(default_value))); 1037 1038 Accessor::connectValueChanged( 1039 widget, [widget, section = std::move(section), key = std::move(key), to_string_function]() { 1040 const DataType value = static_cast<DataType>(static_cast<UnderlyingType>(Accessor::getIntValue(widget))); 1041 const char* string_value = to_string_function(value); 1042 Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), string_value); 1043 Host::CommitBaseSettingChanges(); 1044 g_emu_thread->applySettings(); 1045 }); 1046 } 1047 } 1048 1049 template<typename WidgetType, typename DataType> 1050 static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, 1051 const char** enum_names, DataType default_value) 1052 { 1053 using Accessor = SettingAccessor<WidgetType>; 1054 using UnderlyingType = std::underlying_type_t<DataType>; 1055 1056 const std::string value(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), 1057 enum_names[static_cast<UnderlyingType>(default_value)])); 1058 1059 UnderlyingType enum_index = static_cast<UnderlyingType>(default_value); 1060 for (UnderlyingType i = 0; enum_names[i] != nullptr; i++) 1061 { 1062 if (value == enum_names[i]) 1063 { 1064 enum_index = i; 1065 break; 1066 } 1067 } 1068 1069 if (sif) 1070 { 1071 Accessor::makeNullableInt(widget, static_cast<int>(enum_index)); 1072 1073 std::string sif_value; 1074 std::optional<int> sif_int_value; 1075 if (sif->GetStringValue(section.c_str(), key.c_str(), &sif_value)) 1076 { 1077 for (UnderlyingType i = 0; enum_names[i] != nullptr; i++) 1078 { 1079 if (sif_value == enum_names[i]) 1080 { 1081 sif_int_value = static_cast<int>(i); 1082 break; 1083 } 1084 } 1085 } 1086 Accessor::setNullableIntValue(widget, sif_int_value); 1087 1088 Accessor::connectValueChanged( 1089 widget, [sif, widget, section = std::move(section), key = std::move(key), enum_names]() { 1090 if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value()) 1091 sif->SetStringValue(section.c_str(), key.c_str(), enum_names[new_value.value()]); 1092 else 1093 sif->DeleteValue(section.c_str(), key.c_str()); 1094 1095 QtHost::SaveGameSettings(sif, true); 1096 g_emu_thread->reloadGameSettings(); 1097 }); 1098 } 1099 else 1100 { 1101 Accessor::setIntValue(widget, static_cast<int>(enum_index)); 1102 1103 Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), enum_names]() { 1104 const UnderlyingType value = static_cast<UnderlyingType>(Accessor::getIntValue(widget)); 1105 Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), enum_names[value]); 1106 Host::CommitBaseSettingChanges(); 1107 g_emu_thread->applySettings(); 1108 }); 1109 } 1110 } 1111 1112 template<typename WidgetType> 1113 static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, 1114 const char** enum_names, const char** enum_values, const char* default_value, 1115 const char* translation_ctx = nullptr) 1116 { 1117 using Accessor = SettingAccessor<WidgetType>; 1118 1119 const std::string value = Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value); 1120 1121 for (int i = 0; enum_names[i] != nullptr; i++) 1122 { 1123 widget->addItem(translation_ctx ? qApp->translate(translation_ctx, enum_names[i]) : 1124 QString::fromUtf8(enum_names[i])); 1125 } 1126 1127 int enum_index = -1; 1128 for (int i = 0; enum_values[i] != nullptr; i++) 1129 { 1130 if (value == enum_values[i]) 1131 { 1132 enum_index = i; 1133 break; 1134 } 1135 } 1136 1137 if (sif) 1138 { 1139 Accessor::makeNullableInt(widget, enum_index); 1140 1141 std::string sif_value; 1142 std::optional<int> sif_int_value; 1143 if (sif->GetStringValue(section.c_str(), key.c_str(), &sif_value)) 1144 { 1145 for (int i = 0; enum_values[i] != nullptr; i++) 1146 { 1147 if (sif_value == enum_values[i]) 1148 { 1149 sif_int_value = i; 1150 break; 1151 } 1152 } 1153 } 1154 Accessor::setNullableIntValue(widget, sif_int_value); 1155 1156 Accessor::connectValueChanged( 1157 widget, [sif, widget, section = std::move(section), key = std::move(key), enum_values]() { 1158 if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value()) 1159 sif->SetStringValue(section.c_str(), key.c_str(), enum_values[new_value.value()]); 1160 else 1161 sif->DeleteValue(section.c_str(), key.c_str()); 1162 1163 QtHost::SaveGameSettings(sif, true); 1164 g_emu_thread->reloadGameSettings(); 1165 }); 1166 } 1167 else 1168 { 1169 if (enum_index >= 0) 1170 Accessor::setIntValue(widget, enum_index); 1171 1172 Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), enum_values]() { 1173 const int value = Accessor::getIntValue(widget); 1174 Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), enum_values[value]); 1175 Host::CommitBaseSettingChanges(); 1176 g_emu_thread->applySettings(); 1177 }); 1178 } 1179 } 1180 1181 static inline void BindWidgetToFolderSetting(SettingsInterface* sif, QLineEdit* widget, QAbstractButton* browse_button, 1182 QString browse_title, QAbstractButton* open_button, 1183 QAbstractButton* reset_button, std::string section, std::string key, 1184 std::string default_value, bool use_relative = true) 1185 { 1186 using Accessor = SettingAccessor<QLineEdit>; 1187 1188 std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str())); 1189 if (current_path.empty()) 1190 current_path = default_value; 1191 else if (use_relative && !Path::IsAbsolute(current_path)) 1192 current_path = Path::Canonicalize(Path::Combine(EmuFolders::DataRoot, current_path)); 1193 const QString value(QString::fromStdString(current_path)); 1194 Accessor::setStringValue(widget, value); 1195 // if we're doing per-game settings, disable the widget, we only allow folder changes in the base config 1196 if (sif) 1197 { 1198 widget->setEnabled(false); 1199 if (browse_button) 1200 browse_button->setEnabled(false); 1201 if (reset_button) 1202 reset_button->setEnabled(false); 1203 return; 1204 } 1205 1206 auto value_changed = [widget, section = std::move(section), key = std::move(key), default_value, use_relative]() { 1207 const std::string new_value(widget->text().toStdString()); 1208 if (!new_value.empty()) 1209 { 1210 if (FileSystem::DirectoryExists(new_value.c_str()) || 1211 QMessageBox::question( 1212 QtUtils::GetRootWidget(widget), qApp->translate("SettingWidgetBinder", "Confirm Folder"), 1213 qApp 1214 ->translate( 1215 "SettingWidgetBinder", 1216 "The chosen directory does not currently exist:\n\n%1\n\nDo you want to create this directory?") 1217 .arg(QString::fromStdString(new_value)), 1218 QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) 1219 { 1220 if (use_relative) 1221 { 1222 const std::string relative_path(Path::MakeRelative(new_value, EmuFolders::DataRoot)); 1223 Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), relative_path.c_str()); 1224 } 1225 else 1226 { 1227 Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), new_value.c_str()); 1228 } 1229 1230 Host::CommitBaseSettingChanges(); 1231 g_emu_thread->updateEmuFolders(); 1232 return; 1233 } 1234 } 1235 else 1236 { 1237 QMessageBox::critical(QtUtils::GetRootWidget(widget), qApp->translate("SettingWidgetBinder", "Error"), 1238 qApp->translate("SettingWidgetBinder", "Folder path cannot be empty.")); 1239 } 1240 1241 // reset to old value 1242 std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str())); 1243 if (current_path.empty()) 1244 current_path = default_value; 1245 else if (use_relative && !Path::IsAbsolute(current_path)) 1246 current_path = Path::Canonicalize(Path::Combine(EmuFolders::DataRoot, current_path)); 1247 1248 widget->setText(QString::fromStdString(current_path)); 1249 }; 1250 1251 if (browse_button) 1252 { 1253 QObject::connect(browse_button, &QAbstractButton::clicked, browse_button, 1254 [widget, browse_title = std::move(browse_title), value_changed]() { 1255 const QString path = QDir::toNativeSeparators( 1256 QFileDialog::getExistingDirectory(QtUtils::GetRootWidget(widget), browse_title)); 1257 if (path.isEmpty()) 1258 return; 1259 1260 widget->setText(path); 1261 value_changed(); 1262 }); 1263 } 1264 if (open_button) 1265 { 1266 QObject::connect(open_button, &QAbstractButton::clicked, open_button, [widget]() { 1267 QString path(Accessor::getStringValue(widget)); 1268 if (!path.isEmpty()) 1269 QtUtils::OpenURL(QtUtils::GetRootWidget(widget), QUrl::fromLocalFile(path)); 1270 }); 1271 } 1272 if (reset_button) 1273 { 1274 QObject::connect(reset_button, &QAbstractButton::clicked, reset_button, 1275 [widget, default_value = std::move(default_value), value_changed]() { 1276 widget->setText(QString::fromStdString(default_value)); 1277 value_changed(); 1278 }); 1279 } 1280 1281 widget->connect(widget, &QLineEdit::editingFinished, widget, std::move(value_changed)); 1282 } 1283 1284 template<typename WidgetType> 1285 static inline void SetAvailability(WidgetType* widget, bool available) 1286 { 1287 if (available) 1288 return; 1289 1290 widget->disconnect(); 1291 1292 if constexpr (std::is_same_v<WidgetType, QComboBox>) 1293 { 1294 widget->clear(); 1295 widget->addItem(qApp->translate("SettingWidgetBinder", "Incompatible with this game.")); 1296 } 1297 else if constexpr (std::is_same_v<WidgetType, QLineEdit>) 1298 { 1299 widget->setText(qApp->translate("SettingWidgetBinder", "Incompatible with this game.")); 1300 } 1301 else if constexpr (std::is_same_v<WidgetType, QCheckBox>) 1302 { 1303 widget->setText(widget->text() + qApp->translate("SettingWidgetBinder", " [incompatible]")); 1304 widget->setCheckState(Qt::Unchecked); 1305 } 1306 else if constexpr (std::is_same_v<WidgetType, QSlider>) 1307 { 1308 widget->setTickPosition(0); 1309 } 1310 else if constexpr (std::is_same_v<WidgetType, QSpinBox> || std::is_same_v<WidgetType, QDoubleSpinBox>) 1311 { 1312 widget->setValue(0); 1313 } 1314 1315 widget->setEnabled(false); 1316 } 1317 1318 } // namespace SettingWidgetBinder