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:
zhupengfei 2020-02-01 12:34:20 +08:00
parent 8c4bcf9f59
commit 94bc09d3ae
No known key found for this signature in database
GPG key ID: DD129E108BD09378
3 changed files with 421 additions and 0 deletions

View 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("[&lt;hours (integer)>:][&lt;minutes (integer):]&lt;seconds "
"(decimal)> e.g. 03:00.5 (3min 500ms)")},
{AV_OPT_TYPE_RATIONAL, QT_TR_NOOP("&lt;num>/&lt;den>")},
{AV_OPT_TYPE_COLOR, QT_TR_NOOP("0xRRGGBBAA")},
{AV_OPT_TYPE_IMAGE_SIZE, QT_TR_NOOP("&lt;width>x&lt;height>, or preset values like 'vga'.")},
{AV_OPT_TYPE_DICT,
QT_TR_NOOP("Comma-splitted list of &lt;key>=&lt;value>. Do not put spaces.")},
{AV_OPT_TYPE_VIDEO_RATE, QT_TR_NOOP("&lt;num>/&lt;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 &lt;%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;

View 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)
};

View 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>