diff --git a/src/citra_qt/dumping/option_set_dialog.cpp b/src/citra_qt/dumping/option_set_dialog.cpp new file mode 100644 index 000000000..8dab0505e --- /dev/null +++ b/src/citra_qt/dumping/option_set_dialog.cpp @@ -0,0 +1,299 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "citra_qt/dumping/option_set_dialog.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "ui_option_set_dialog.h" + +extern "C" { +#include +} + +static const std::unordered_map TypeNameMap{{ + {AV_OPT_TYPE_BOOL, QT_TR_NOOP("boolean")}, + {AV_OPT_TYPE_FLAGS, QT_TR_NOOP("flags")}, + {AV_OPT_TYPE_DURATION, QT_TR_NOOP("duration")}, + {AV_OPT_TYPE_INT, QT_TR_NOOP("int")}, + {AV_OPT_TYPE_UINT64, QT_TR_NOOP("uint64")}, + {AV_OPT_TYPE_INT64, QT_TR_NOOP("int64")}, + {AV_OPT_TYPE_DOUBLE, QT_TR_NOOP("double")}, + {AV_OPT_TYPE_FLOAT, QT_TR_NOOP("float")}, + {AV_OPT_TYPE_RATIONAL, QT_TR_NOOP("rational")}, + {AV_OPT_TYPE_PIXEL_FMT, QT_TR_NOOP("pixel format")}, + {AV_OPT_TYPE_SAMPLE_FMT, QT_TR_NOOP("sample format")}, + {AV_OPT_TYPE_COLOR, QT_TR_NOOP("color")}, + {AV_OPT_TYPE_IMAGE_SIZE, QT_TR_NOOP("image size")}, + {AV_OPT_TYPE_STRING, QT_TR_NOOP("string")}, + {AV_OPT_TYPE_DICT, QT_TR_NOOP("dictionary")}, + {AV_OPT_TYPE_VIDEO_RATE, QT_TR_NOOP("video rate")}, + {AV_OPT_TYPE_CHANNEL_LAYOUT, QT_TR_NOOP("channel layout")}, +}}; + +static const std::unordered_map TypeDescriptionMap{{ + {AV_OPT_TYPE_DURATION, QT_TR_NOOP("[<hours (integer)>:][<minutes (integer):]<seconds " + "(decimal)> e.g. 03:00.5 (3min 500ms)")}, + {AV_OPT_TYPE_RATIONAL, QT_TR_NOOP("<num>/<den>")}, + {AV_OPT_TYPE_COLOR, QT_TR_NOOP("0xRRGGBBAA")}, + {AV_OPT_TYPE_IMAGE_SIZE, QT_TR_NOOP("<width>x<height>, or preset values like 'vga'.")}, + {AV_OPT_TYPE_DICT, + QT_TR_NOOP("Comma-splitted list of <key>=<value>. Do not put spaces.")}, + {AV_OPT_TYPE_VIDEO_RATE, QT_TR_NOOP("<num>/<den>, or preset values like 'pal'.")}, + {AV_OPT_TYPE_CHANNEL_LAYOUT, QT_TR_NOOP("Hexadecimal channel layout mask starting with '0x'.")}, +}}; + +/// Get the preset values of an option. returns {display value, real value} +std::vector> GetPresetValues(const VideoDumper::OptionInfo& option) { + switch (option.type) { + case AV_OPT_TYPE_BOOL: { + return {{QObject::tr("auto"), QStringLiteral("auto")}, + {QObject::tr("true"), QStringLiteral("true")}, + {QObject::tr("false"), QStringLiteral("false")}}; + } + case AV_OPT_TYPE_PIXEL_FMT: { + std::vector> out{{QObject::tr("none"), QStringLiteral("none")}}; + // List all pixel formats + const AVPixFmtDescriptor* current = nullptr; + while ((current = av_pix_fmt_desc_next(current))) { + out.emplace_back(QString::fromUtf8(current->name), QString::fromUtf8(current->name)); + } + return out; + } + case AV_OPT_TYPE_SAMPLE_FMT: { + std::vector> out{{QObject::tr("none"), QStringLiteral("none")}}; + // List all sample formats + int current = 0; + while (true) { + const char* name = av_get_sample_fmt_name(static_cast(current)); + if (name == nullptr) + break; + out.emplace_back(QString::fromUtf8(name), QString::fromUtf8(name)); + } + return out; + } + case AV_OPT_TYPE_INT: + case AV_OPT_TYPE_INT64: + case AV_OPT_TYPE_UINT64: { + std::vector> out; + // Add in all named constants + for (const auto& constant : option.named_constants) { + out.emplace_back(QObject::tr("%1 (0x%2)") + .arg(QString::fromStdString(constant.name)) + .arg(constant.value, 0, 16), + QString::fromStdString(constant.name)); + } + return out; + } + default: + return {}; + } +} + +void OptionSetDialog::InitializeUI(const std::string& initial_value) { + const QString type_name = + TypeNameMap.count(option.type) ? tr(TypeNameMap.at(option.type)) : tr("unknown"); + ui->nameLabel->setText(tr("%1 <%2> %3") + .arg(QString::fromStdString(option.name), type_name, + QString::fromStdString(option.description))); + if (TypeDescriptionMap.count(option.type)) { + ui->formatLabel->setVisible(true); + ui->formatLabel->setText(tr(TypeDescriptionMap.at(option.type))); + } + + if (option.type == AV_OPT_TYPE_INT || option.type == AV_OPT_TYPE_INT64 || + option.type == AV_OPT_TYPE_UINT64 || option.type == AV_OPT_TYPE_FLOAT || + option.type == AV_OPT_TYPE_DOUBLE || option.type == AV_OPT_TYPE_DURATION || + option.type == AV_OPT_TYPE_RATIONAL) { // scalar types + + ui->formatLabel->setVisible(true); + if (!ui->formatLabel->text().isEmpty()) { + ui->formatLabel->text().append(QStringLiteral("\n")); + } + ui->formatLabel->setText( + ui->formatLabel->text().append(tr("Range: %1 - %2").arg(option.min).arg(option.max))); + } + + // Decide and initialize layout + if (option.type == AV_OPT_TYPE_BOOL || option.type == AV_OPT_TYPE_PIXEL_FMT || + option.type == AV_OPT_TYPE_SAMPLE_FMT || + ((option.type == AV_OPT_TYPE_INT || option.type == AV_OPT_TYPE_INT64 || + option.type == AV_OPT_TYPE_UINT64) && + !option.named_constants.empty())) { // Use the combobox layout + + layout_type = 1; + ui->comboBox->setVisible(true); + ui->comboBoxHelpLabel->setVisible(true); + + QString real_initial_value = QString::fromStdString(initial_value); + if (option.type == AV_OPT_TYPE_INT || option.type == AV_OPT_TYPE_INT64 || + option.type == AV_OPT_TYPE_UINT64) { + + // Get the name of the initial value + try { + s64 initial_value_integer = std::stoll(initial_value, nullptr, 0); + for (const auto& constant : option.named_constants) { + if (constant.value == initial_value_integer) { + real_initial_value = QString::fromStdString(constant.name); + break; + } + } + } catch (...) { + // Not convertible to integer, ignore + } + } + + bool found = false; + for (const auto& [display, value] : GetPresetValues(option)) { + ui->comboBox->addItem(display, value); + if (value == real_initial_value) { + found = true; + ui->comboBox->setCurrentIndex(ui->comboBox->count() - 1); + } + } + ui->comboBox->addItem(tr("custom")); + + if (!found) { + ui->comboBox->setCurrentIndex(ui->comboBox->count() - 1); + ui->lineEdit->setText(QString::fromStdString(initial_value)); + } + + UpdateUIDisplay(); + + connect(ui->comboBox, &QComboBox::currentTextChanged, this, + &OptionSetDialog::UpdateUIDisplay); + } else if (option.type == AV_OPT_TYPE_FLAGS && + !option.named_constants.empty()) { // Use the check boxes layout + + layout_type = 2; + + for (const auto& constant : option.named_constants) { + auto* checkBox = new QCheckBox(tr("%1 (0x%2) %3") + .arg(QString::fromStdString(constant.name)) + .arg(constant.value, 0, 16) + .arg(QString::fromStdString(constant.description))); + checkBox->setProperty("value", static_cast(constant.value)); + checkBox->setProperty("name", QString::fromStdString(constant.name)); + ui->checkBoxLayout->addWidget(checkBox); + } + SetCheckBoxDefaults(initial_value); + } else { // Use the line edit layout + layout_type = 0; + ui->lineEdit->setVisible(true); + ui->lineEdit->setText(QString::fromStdString(initial_value)); + } + + adjustSize(); +} + +void OptionSetDialog::SetCheckBoxDefaults(const std::string& initial_value) { + if (initial_value.size() >= 2 && + (initial_value.substr(0, 2) == "0x" || initial_value.substr(0, 2) == "0X")) { + // This is a hex mask + try { + u64 value = std::stoull(initial_value, nullptr, 16); + for (int i = 0; i < ui->checkBoxLayout->count(); ++i) { + auto* checkBox = qobject_cast(ui->checkBoxLayout->itemAt(i)->widget()); + if (checkBox) { + checkBox->setChecked(value & checkBox->property("value").toULongLong()); + } + } + } catch (...) { + LOG_ERROR(Frontend, "Could not convert {} to number", initial_value); + } + } else { + // This is a combination of constants, splitted with + or | + std::vector tmp; + Common::SplitString(initial_value, '+', tmp); + + std::vector out; + std::vector tmp2; + for (const auto& str : tmp) { + Common::SplitString(str, '|', tmp2); + out.insert(out.end(), tmp2.begin(), tmp2.end()); + } + for (int i = 0; i < ui->checkBoxLayout->count(); ++i) { + auto* checkBox = qobject_cast(ui->checkBoxLayout->itemAt(i)->widget()); + if (checkBox) { + checkBox->setChecked( + std::find(out.begin(), out.end(), + checkBox->property("name").toString().toStdString()) != out.end()); + } + } + } +} + +void OptionSetDialog::UpdateUIDisplay() { + if (layout_type != 1) + return; + + if (ui->comboBox->currentIndex() == ui->comboBox->count() - 1) { // custom + ui->comboBoxHelpLabel->setVisible(false); + ui->lineEdit->setVisible(true); + adjustSize(); + return; + } + + ui->lineEdit->setVisible(false); + for (const auto& constant : option.named_constants) { + if (constant.name == ui->comboBox->currentData().toString().toStdString()) { + ui->comboBoxHelpLabel->setVisible(true); + ui->comboBoxHelpLabel->setText(QString::fromStdString(constant.description)); + return; + } + } +} + +std::pair OptionSetDialog::GetCurrentValue() { + if (!is_set) { + return {}; + } + + switch (layout_type) { + case 0: // line edit layout + return {true, ui->lineEdit->text().toStdString()}; + case 1: // combo box layout + if (ui->comboBox->currentIndex() == ui->comboBox->count() - 1) { + return {true, ui->lineEdit->text().toStdString()}; // custom + } + return {true, ui->comboBox->currentData().toString().toStdString()}; + case 2: { // check boxes layout + std::string out; + for (int i = 0; i < ui->checkBoxLayout->count(); ++i) { + auto* checkBox = qobject_cast(ui->checkBoxLayout->itemAt(i)->widget()); + if (checkBox && checkBox->isChecked()) { + if (!out.empty()) { + out.append("+"); + } + out.append(checkBox->property("name").toString().toStdString()); + } + } + if (out.empty()) { + out = "0x0"; + } + return {true, out}; + } + default: + return {}; + } +} + +OptionSetDialog::OptionSetDialog(QWidget* parent, VideoDumper::OptionInfo option_, + const std::string& initial_value) + : QDialog(parent), ui(std::make_unique()), option(std::move(option_)) { + + ui->setupUi(this); + InitializeUI(initial_value); + + connect(ui->unsetButton, &QPushButton::clicked, [this] { + is_set = false; + accept(); + }); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &OptionSetDialog::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &OptionSetDialog::reject); +} + +OptionSetDialog::~OptionSetDialog() = default; diff --git a/src/citra_qt/dumping/option_set_dialog.h b/src/citra_qt/dumping/option_set_dialog.h new file mode 100644 index 000000000..2c5d378d0 --- /dev/null +++ b/src/citra_qt/dumping/option_set_dialog.h @@ -0,0 +1,33 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "core/dumping/ffmpeg_backend.h" + +namespace Ui { +class OptionSetDialog; +} + +class OptionSetDialog : public QDialog { + Q_OBJECT + +public: + explicit OptionSetDialog(QWidget* parent, VideoDumper::OptionInfo option, + const std::string& initial_value); + ~OptionSetDialog() override; + + // {is_set, value} + std::pair GetCurrentValue(); + +private: + void InitializeUI(const std::string& initial_value); + void SetCheckBoxDefaults(const std::string& initial_value); + void UpdateUIDisplay(); + + std::unique_ptr ui; + VideoDumper::OptionInfo option; + bool is_set = true; + int layout_type = -1; // 0 - line edit, 1 - combo box, 2 - flags (check boxes) +}; diff --git a/src/citra_qt/dumping/option_set_dialog.ui b/src/citra_qt/dumping/option_set_dialog.ui new file mode 100644 index 000000000..dcf4bb572 --- /dev/null +++ b/src/citra_qt/dumping/option_set_dialog.ui @@ -0,0 +1,89 @@ + + + OptionSetDialog + + + + 0 + 0 + 600 + 150 + + + + Options + + + + + + + + + false + + + + + + + + + false + + + + + + + false + + + + + + + + + false + + + + + + + + + + Qt::Vertical + + + + + + + + + Unset + + + + + + + Qt::Horizontal + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + +