citra_qt/dumping: Add option set dialog
This dialog allows changing the value and unsetting one option. There are three possible variants of this dialog: 1. The LineEdit layout. This is used for normal options like string and duration, and just features a textbox for the user to type in whatever they want to set. 2. The ComboBox layout. This is used when there are named constants for an option, or when the option accepts an enum value like sample_format or pixel_format. A description will be displayed for the currently selected named constant. The user can also select 'custom' and type in their own value. 3. The CheckBox-es layout. This is used for flags options. A checkbox will be displayed for each named constant and the user can tick the flags they want to set.
This commit is contained in:
parent
8c4bcf9f59
commit
94bc09d3ae
3 changed files with 421 additions and 0 deletions
299
src/citra_qt/dumping/option_set_dialog.cpp
Normal file
299
src/citra_qt/dumping/option_set_dialog.cpp
Normal file
|
@ -0,0 +1,299 @@
|
||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QStringList>
|
||||||
|
#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 <libavutil/pixdesc.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::unordered_map<AVOptionType, const char*> 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<AVOptionType, const char*> 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<std::pair<QString, QString>> 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<std::pair<QString, QString>> 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<std::pair<QString, QString>> 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<AVSampleFormat>(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<std::pair<QString, QString>> 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<unsigned long long>(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<QCheckBox*>(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<std::string> tmp;
|
||||||
|
Common::SplitString(initial_value, '+', tmp);
|
||||||
|
|
||||||
|
std::vector<std::string> out;
|
||||||
|
std::vector<std::string> 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<QCheckBox*>(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<bool, std::string> 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<QCheckBox*>(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<Ui::OptionSetDialog>()), 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;
|
33
src/citra_qt/dumping/option_set_dialog.h
Normal file
33
src/citra_qt/dumping/option_set_dialog.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <QDialog>
|
||||||
|
#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<bool, std::string> GetCurrentValue();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void InitializeUI(const std::string& initial_value);
|
||||||
|
void SetCheckBoxDefaults(const std::string& initial_value);
|
||||||
|
void UpdateUIDisplay();
|
||||||
|
|
||||||
|
std::unique_ptr<Ui::OptionSetDialog> ui;
|
||||||
|
VideoDumper::OptionInfo option;
|
||||||
|
bool is_set = true;
|
||||||
|
int layout_type = -1; // 0 - line edit, 1 - combo box, 2 - flags (check boxes)
|
||||||
|
};
|
89
src/citra_qt/dumping/option_set_dialog.ui
Normal file
89
src/citra_qt/dumping/option_set_dialog.ui
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>OptionSetDialog</class>
|
||||||
|
<widget class="QDialog" name="OptionSetDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>600</width>
|
||||||
|
<height>150</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Options</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="nameLabel"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="formatLabel">
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="comboBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox">
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="comboBoxHelpLabel">
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lineEdit">
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="checkBoxLayout"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="unsetButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Unset</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</ui>
|
Loading…
Reference in a new issue