420 lines
11 KiB
C++
420 lines
11 KiB
C++
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
#include "ConfigController.h"
|
|
|
|
#include "ActionMapper.h"
|
|
#include "CoreController.h"
|
|
|
|
#include <QDir>
|
|
#include <QMenu>
|
|
|
|
#include <mgba/feature/commandline.h>
|
|
#ifdef M_CORE_GB
|
|
#include <mgba/internal/gb/overrides.h>
|
|
#endif
|
|
|
|
static const mOption s_frontendOptions[] = {
|
|
{ "ecard", true, '\0' },
|
|
{ "mb", true, '\0' },
|
|
#ifdef ENABLE_SCRIPTING
|
|
{ "script", true, '\0' },
|
|
#endif
|
|
{ 0 }
|
|
};
|
|
|
|
using namespace QGBA;
|
|
|
|
ConfigOption::ConfigOption(const QString& name, QObject* parent)
|
|
: QObject(parent)
|
|
, m_name(name)
|
|
{
|
|
}
|
|
|
|
void ConfigOption::connect(std::function<void(const QVariant&)> slot, QObject* parent) {
|
|
m_slots[parent] = slot;
|
|
QObject::connect(parent, &QObject::destroyed, this, [this, parent]() {
|
|
m_slots.remove(parent);
|
|
});
|
|
}
|
|
|
|
std::shared_ptr<Action> ConfigOption::addValue(const QString& text, const QVariant& value, ActionMapper* actions, const QString& menu) {
|
|
std::shared_ptr<Action> action;
|
|
auto function = [this, value]() {
|
|
emit valueChanged(value);
|
|
};
|
|
QString name = QString("%1.%2").arg(m_name).arg(value.toString());
|
|
if (actions) {
|
|
action = actions->addAction(text, name, function, menu);
|
|
} else {
|
|
action = std::make_shared<Action>(function, name, text, this);
|
|
}
|
|
action->setExclusive();
|
|
std::weak_ptr<Action> weakAction(action);
|
|
QObject::connect(action.get(), &QObject::destroyed, this, [this, weakAction, value]() {
|
|
if (weakAction.expired()) {
|
|
return;
|
|
}
|
|
std::shared_ptr<Action> action(weakAction.lock());
|
|
m_actions.removeAll(std::make_pair(action, value));
|
|
});
|
|
m_actions.append(std::make_pair(action, value));
|
|
return action;
|
|
}
|
|
|
|
std::shared_ptr<Action> ConfigOption::addValue(const QString& text, const char* value, ActionMapper* actions, const QString& menu) {
|
|
return addValue(text, QString(value), actions, menu);
|
|
}
|
|
|
|
std::shared_ptr<Action> ConfigOption::addBoolean(const QString& text, ActionMapper* actions, const QString& menu) {
|
|
std::shared_ptr<Action> action;
|
|
auto function = [this](bool value) {
|
|
emit valueChanged(value);
|
|
};
|
|
if (actions) {
|
|
action = actions->addBooleanAction(text, m_name, function, menu);
|
|
} else {
|
|
action = std::make_shared<Action>(function, m_name, text, this);
|
|
}
|
|
|
|
std::weak_ptr<Action> weakAction(action);
|
|
QObject::connect(action.get(), &QObject::destroyed, this, [this, weakAction]() {
|
|
if (weakAction.expired()) {
|
|
return;
|
|
}
|
|
std::shared_ptr<Action> action(weakAction.lock());
|
|
m_actions.removeAll(std::make_pair(action, QVariant(1)));
|
|
});
|
|
m_actions.append(std::make_pair(action, QVariant(1)));
|
|
|
|
return action;
|
|
}
|
|
|
|
void ConfigOption::setValue(bool value) {
|
|
setValue(QVariant(value));
|
|
}
|
|
|
|
void ConfigOption::setValue(int value) {
|
|
setValue(QVariant(value));
|
|
}
|
|
|
|
void ConfigOption::setValue(unsigned value) {
|
|
setValue(QVariant(value));
|
|
}
|
|
|
|
void ConfigOption::setValue(const char* value) {
|
|
setValue(QVariant(QString(value)));
|
|
}
|
|
|
|
void ConfigOption::setValue(const QVariant& value) {
|
|
for (std::pair<std::shared_ptr<Action>, QVariant>& action : m_actions) {
|
|
action.first->setActive(value == action.second);
|
|
}
|
|
for (std::function<void(const QVariant&)>& slot : m_slots.values()) {
|
|
slot(value);
|
|
}
|
|
}
|
|
|
|
QString ConfigController::s_configDir;
|
|
|
|
ConfigController::ConfigController(QObject* parent)
|
|
: QObject(parent)
|
|
{
|
|
QString fileName = configDir();
|
|
fileName.append(QDir::separator());
|
|
fileName.append("qt.ini");
|
|
m_settings = std::make_unique<QSettings>(fileName, QSettings::IniFormat);
|
|
|
|
mCoreConfigInit(&m_config, PORT);
|
|
|
|
m_opts.audioSync = CoreController::AUDIO_SYNC;
|
|
m_opts.videoSync = CoreController::VIDEO_SYNC;
|
|
m_opts.fpsTarget = 60;
|
|
m_opts.audioBuffers = 1536;
|
|
m_opts.sampleRate = 44100;
|
|
m_opts.volume = 0x100;
|
|
m_opts.logLevel = mLOG_WARN | mLOG_ERROR | mLOG_FATAL;
|
|
m_opts.rewindEnable = false;
|
|
m_opts.rewindBufferCapacity = 300;
|
|
m_opts.rewindBufferInterval = 1;
|
|
m_opts.useBios = true;
|
|
m_opts.suspendScreensaver = true;
|
|
m_opts.lockAspectRatio = true;
|
|
m_opts.interframeBlending = false;
|
|
mCoreConfigLoad(&m_config);
|
|
mCoreConfigLoadDefaults(&m_config, &m_opts);
|
|
#ifdef M_CORE_GB
|
|
mCoreConfigSetDefaultIntValue(&m_config, "sgb.borders", 1);
|
|
mCoreConfigSetDefaultIntValue(&m_config, "gb.colors", GB_COLORS_CGB);
|
|
#endif
|
|
mCoreConfigMap(&m_config, &m_opts);
|
|
|
|
mSubParserGraphicsInit(&m_subparsers[0], &m_graphicsOpts);
|
|
|
|
m_subparsers[1].usage = "Frontend options:\n"
|
|
" --ecard FILE Scan an e-Reader card in the first loaded game\n"
|
|
" Can be passed multiple times for multiple cards\n"
|
|
" --mb FILE Boot a multiboot image with FILE inserted into the ROM slot"
|
|
#ifdef ENABLE_SCRIPTING
|
|
"\n --script FILE Script file to load on start"
|
|
#endif
|
|
;
|
|
|
|
m_subparsers[1].parse = nullptr;
|
|
m_subparsers[1].parseLong = [](struct mSubParser* parser, const char* option, const char* arg) {
|
|
ConfigController* self = static_cast<ConfigController*>(parser->opts);
|
|
QString optionName(QString::fromUtf8(option));
|
|
if (optionName == QLatin1String("ecard")) {
|
|
QStringList ecards;
|
|
if (self->m_argvOptions.contains(optionName)) {
|
|
ecards = self->m_argvOptions[optionName].toStringList();
|
|
}
|
|
ecards.append(QString::fromUtf8(arg));
|
|
self->m_argvOptions[optionName] = ecards;
|
|
return true;
|
|
}
|
|
if (optionName == QLatin1String("mb")) {
|
|
self->m_argvOptions[optionName] = QString::fromUtf8(arg);
|
|
return true;
|
|
}
|
|
#ifdef ENABLE_SCRIPTING
|
|
if (optionName == QLatin1String("script")) {
|
|
QStringList scripts;
|
|
if (self->m_argvOptions.contains(optionName)) {
|
|
scripts = self->m_argvOptions[optionName].toStringList();
|
|
}
|
|
scripts.append(QString::fromUtf8(arg));
|
|
self->m_argvOptions[optionName] = scripts;
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
};
|
|
m_subparsers[1].apply = nullptr;
|
|
m_subparsers[1].extraOptions = nullptr;
|
|
m_subparsers[1].longOptions = s_frontendOptions;
|
|
m_subparsers[1].opts = this;
|
|
}
|
|
|
|
ConfigController::~ConfigController() {
|
|
mCoreConfigDeinit(&m_config);
|
|
mCoreConfigFreeOpts(&m_opts);
|
|
|
|
if (m_parsed) {
|
|
mArgumentsDeinit(&m_args);
|
|
}
|
|
}
|
|
|
|
bool ConfigController::parseArguments(int argc, char* argv[]) {
|
|
if (m_parsed) {
|
|
return false;
|
|
}
|
|
|
|
if (mArgumentsParse(&m_args, argc, argv, m_subparsers.data(), m_subparsers.size())) {
|
|
mCoreConfigFreeOpts(&m_opts);
|
|
mArgumentsApply(&m_args, m_subparsers.data(), m_subparsers.size(), &m_config);
|
|
mCoreConfigMap(&m_config, &m_opts);
|
|
m_parsed = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ConfigOption* ConfigController::addOption(const char* key) {
|
|
QString optionName(key);
|
|
|
|
if (m_optionSet.contains(optionName)) {
|
|
return m_optionSet[optionName];
|
|
}
|
|
ConfigOption* newOption = new ConfigOption(optionName, this);
|
|
m_optionSet[optionName] = newOption;
|
|
connect(newOption, &ConfigOption::valueChanged, this, [this, key](const QVariant& value) {
|
|
setOption(key, value);
|
|
});
|
|
return newOption;
|
|
}
|
|
|
|
void ConfigController::updateOption(const char* key) {
|
|
if (!key) {
|
|
return;
|
|
}
|
|
|
|
QString optionName(key);
|
|
|
|
if (!m_optionSet.contains(optionName)) {
|
|
return;
|
|
}
|
|
m_optionSet[optionName]->setValue(mCoreConfigGetValue(&m_config, key));
|
|
}
|
|
|
|
QString ConfigController::getOption(const char* key, const QVariant& defaultVal) const {
|
|
const char* val = mCoreConfigGetValue(&m_config, key);
|
|
if (val) {
|
|
return QString(val);
|
|
}
|
|
return defaultVal.toString();
|
|
}
|
|
|
|
QString ConfigController::getOption(const QString& key, const QVariant& defaultVal) const {
|
|
return getOption(key.toUtf8().constData(), defaultVal);
|
|
}
|
|
|
|
QVariant ConfigController::getQtOption(const QString& key, const QString& group) const {
|
|
if (!group.isNull()) {
|
|
m_settings->beginGroup(group);
|
|
}
|
|
QVariant value = m_settings->value(key);
|
|
if (!group.isNull()) {
|
|
m_settings->endGroup();
|
|
}
|
|
return value;
|
|
}
|
|
|
|
QVariant ConfigController::getArgvOption(const QString& key) const {
|
|
return m_argvOptions.value(key);
|
|
}
|
|
|
|
QVariant ConfigController::takeArgvOption(const QString& key) {
|
|
return m_argvOptions.take(key);
|
|
}
|
|
|
|
void ConfigController::saveOverride(const Override& override) {
|
|
override.save(overrides());
|
|
write();
|
|
}
|
|
|
|
void ConfigController::setOption(const char* key, bool value) {
|
|
mCoreConfigSetIntValue(&m_config, key, value);
|
|
QString optionName(key);
|
|
if (m_optionSet.contains(optionName)) {
|
|
m_optionSet[optionName]->setValue(value);
|
|
}
|
|
}
|
|
|
|
void ConfigController::setOption(const char* key, int value) {
|
|
mCoreConfigSetIntValue(&m_config, key, value);
|
|
QString optionName(key);
|
|
if (m_optionSet.contains(optionName)) {
|
|
m_optionSet[optionName]->setValue(value);
|
|
}
|
|
}
|
|
|
|
void ConfigController::setOption(const char* key, unsigned value) {
|
|
mCoreConfigSetUIntValue(&m_config, key, value);
|
|
QString optionName(key);
|
|
if (m_optionSet.contains(optionName)) {
|
|
m_optionSet[optionName]->setValue(value);
|
|
}
|
|
}
|
|
|
|
void ConfigController::setOption(const char* key, const char* value) {
|
|
mCoreConfigSetValue(&m_config, key, value);
|
|
QString optionName(key);
|
|
if (m_optionSet.contains(optionName)) {
|
|
m_optionSet[optionName]->setValue(value);
|
|
}
|
|
}
|
|
|
|
void ConfigController::setOption(const char* key, const QVariant& value) {
|
|
if (value.type() == QVariant::Bool) {
|
|
setOption(key, value.toBool());
|
|
return;
|
|
}
|
|
QString stringValue(value.toString());
|
|
setOption(key, stringValue.toUtf8().constData());
|
|
}
|
|
|
|
void ConfigController::setQtOption(const QString& key, const QVariant& value, const QString& group) {
|
|
if (!group.isNull()) {
|
|
m_settings->beginGroup(group);
|
|
}
|
|
m_settings->setValue(key, value);
|
|
if (!group.isNull()) {
|
|
m_settings->endGroup();
|
|
}
|
|
}
|
|
|
|
QStringList ConfigController::getMRU(ConfigController::MRU mruType) const {
|
|
QStringList mru;
|
|
m_settings->beginGroup(mruName(mruType));
|
|
for (int i = 0; i < MRU_LIST_SIZE; ++i) {
|
|
QString item = m_settings->value(QString::number(i)).toString();
|
|
if (item.isNull()) {
|
|
continue;
|
|
}
|
|
mru.append(item);
|
|
}
|
|
m_settings->endGroup();
|
|
return mru;
|
|
}
|
|
|
|
void ConfigController::setMRU(const QStringList& mru, ConfigController::MRU mruType) {
|
|
int i = 0;
|
|
m_settings->beginGroup(mruName(mruType));
|
|
for (const QString& item : mru) {
|
|
m_settings->setValue(QString::number(i), item);
|
|
++i;
|
|
if (i >= MRU_LIST_SIZE) {
|
|
break;
|
|
}
|
|
}
|
|
for (; i < MRU_LIST_SIZE; ++i) {
|
|
m_settings->remove(QString::number(i));
|
|
}
|
|
m_settings->endGroup();
|
|
}
|
|
|
|
constexpr const char* ConfigController::mruName(ConfigController::MRU mru) {
|
|
switch (mru) {
|
|
case MRU::ROM:
|
|
return "mru";
|
|
case MRU::Script:
|
|
return "recentScripts";
|
|
}
|
|
Q_UNREACHABLE();
|
|
}
|
|
|
|
void ConfigController::write() {
|
|
mCoreConfigSave(&m_config);
|
|
m_settings->sync();
|
|
|
|
mCoreConfigFreeOpts(&m_opts);
|
|
mCoreConfigMap(&m_config, &m_opts);
|
|
}
|
|
|
|
void ConfigController::makePortable() {
|
|
mCoreConfigMakePortable(&m_config, nullptr);
|
|
|
|
QString fileName(configDir());
|
|
fileName.append(QDir::separator());
|
|
fileName.append("qt.ini");
|
|
auto settings2 = std::make_unique<QSettings>(fileName, QSettings::IniFormat);
|
|
for (const auto& key : m_settings->allKeys()) {
|
|
settings2->setValue(key, m_settings->value(key));
|
|
}
|
|
m_settings = std::move(settings2);
|
|
}
|
|
|
|
void ConfigController::usage(const char* arg0) const {
|
|
::usage(arg0, nullptr, nullptr, m_subparsers.data(), m_subparsers.size());
|
|
}
|
|
|
|
bool ConfigController::isPortable() {
|
|
return mCoreConfigIsPortable();
|
|
}
|
|
|
|
const QString& ConfigController::configDir() {
|
|
if (s_configDir.isNull()) {
|
|
char path[PATH_MAX];
|
|
mCoreConfigDirectory(path, sizeof(path));
|
|
s_configDir = QString::fromUtf8(path);
|
|
}
|
|
return s_configDir;
|
|
}
|
|
|
|
const QString& ConfigController::cacheDir() {
|
|
return configDir();
|
|
}
|