Qt/GameConfigWidget: Complete overhaul

This commit is contained in:
spycrab 2018-07-13 12:56:58 +02:00
parent 6962d5bc52
commit ff5556ce2f
8 changed files with 556 additions and 63 deletions

View File

@ -30,6 +30,8 @@ add_executable(dolphin-emu
Config/CheatWarningWidget.cpp
Config/ControllersWindow.cpp
Config/FilesystemWidget.cpp
Config/GameConfigEdit.cpp
Config/GameConfigHighlighter.cpp
Config/GameConfigWidget.cpp
Config/GeckoCodeWidget.cpp
Config/Graphics/AdvancedWidget.cpp

View File

@ -0,0 +1,311 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt/Config/GameConfigEdit.h"
#include <QAbstractItemView>
#include <QCompleter>
#include <QDesktopServices>
#include <QFile>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QScrollBar>
#include <QStringListModel>
#include <QTextCursor>
#include <QTextEdit>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWhatsThis>
#include "DolphinQt/Config/GameConfigHighlighter.h"
GameConfigEdit::GameConfigEdit(QWidget* parent, const QString& path, bool read_only)
: m_path(path), m_read_only(read_only)
{
CreateWidgets();
LoadFile();
new GameConfigHighlighter(m_edit->document());
AddDescription(QStringLiteral("Core"),
tr("Section that contains most CPU and Hardware related settings."));
AddDescription(QStringLiteral("CPUThread"), tr("Controls whether or not Dual Core should be "
"enabled. Can improve performance but can also "
"cause issues. Defaults to <b>True</b>"));
AddDescription(QStringLiteral("FastDiscSpeed"),
tr("Shortens loading times but may break some games. Can have negative effects on "
"performance. Defaults to <b>False</b>"));
AddDescription(QStringLiteral("MMU"), tr("Controls whether or not the Memory Management Unit "
"should be emulated fully. Few games require it."));
AddDescription(
QStringLiteral("DSPHLE"),
tr("Controls whether to use high or low-level DSP emulation. Defaults to <b>True</b>"));
AddDescription(
QStringLiteral("JITFollowBranch"),
tr("Tries to translate branches ahead of time, improving performance in most cases. Defaults "
"to <b>True</b>"));
AddDescription(QStringLiteral("Gecko"), tr("Section that contains all Gecko cheat codes."));
AddDescription(QStringLiteral("ActionReplay"),
tr("Section that contains all Action Replay cheat codes."));
AddDescription(QStringLiteral("Video_Settings"),
tr("Section that contains all graphics related settings."));
m_completer = new QCompleter(m_edit);
auto* completion_model = new QStringListModel;
completion_model->setStringList(m_completions);
m_completer->setModel(completion_model);
m_completer->setModelSorting(QCompleter::UnsortedModel);
m_completer->setCompletionMode(QCompleter::PopupCompletion);
m_completer->setWidget(m_edit);
AddMenubarOptions();
ConnectWidgets();
}
void GameConfigEdit::CreateWidgets()
{
m_menu = new QMenu;
m_edit = new QTextEdit;
m_edit->setReadOnly(m_read_only);
m_edit->setAcceptRichText(false);
auto* layout = new QVBoxLayout;
auto* menu_button = new QToolButton;
menu_button->setText(tr("Presets") + QStringLiteral(" "));
menu_button->setMenu(m_menu);
connect(menu_button, &QToolButton::pressed, [menu_button] { menu_button->showMenu(); });
layout->addWidget(menu_button);
layout->addWidget(m_edit);
setLayout(layout);
}
void GameConfigEdit::AddDescription(const QString& keyword, const QString& description)
{
m_keyword_map[keyword] = description;
m_completions << keyword;
}
void GameConfigEdit::LoadFile()
{
QFile file(m_path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
m_edit->setPlainText(QString::fromStdString(file.readAll().toStdString()));
}
void GameConfigEdit::SaveFile()
{
if (!isVisible() || m_read_only)
return;
QFile file(m_path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
return;
const QByteArray contents = m_edit->toPlainText().toUtf8();
if (!file.write(contents))
QMessageBox::warning(this, tr("Warning"), tr("Failed to write config file!"));
}
void GameConfigEdit::ConnectWidgets()
{
connect(m_edit, &QTextEdit::textChanged, this, &GameConfigEdit::SaveFile);
connect(m_edit, &QTextEdit::selectionChanged, this, &GameConfigEdit::OnSelectionChanged);
connect(m_completer, static_cast<void (QCompleter::*)(const QString&)>(&QCompleter::activated),
this, &GameConfigEdit::OnAutoComplete);
}
void GameConfigEdit::OnSelectionChanged()
{
const QString& keyword = m_edit->textCursor().selectedText();
if (m_keyword_map.count(keyword))
QWhatsThis::showText(QCursor::pos(), m_keyword_map[keyword], this);
}
void GameConfigEdit::AddBoolOption(QMenu* menu, const QString& name, const QString& section,
const QString& key)
{
auto* option = menu->addMenu(name);
option->addAction(tr("On"), this,
[this, section, key] { SetOption(section, key, QStringLiteral("True")); });
option->addAction(tr("Off"), this,
[this, section, key] { SetOption(section, key, QStringLiteral("False")); });
}
void GameConfigEdit::SetOption(const QString& section, const QString& key, const QString& value)
{
auto section_cursor =
m_edit->document()->find(QRegExp(QStringLiteral("^\\[%1\\]").arg(section)), 0);
// Check if the section this belongs in can be found
if (section_cursor.isNull())
{
m_edit->append(QStringLiteral("[%1]\n\n%2 = %3\n").arg(section).arg(key).arg(value));
}
else
{
auto value_cursor =
m_edit->document()->find(QRegExp(QStringLiteral("^%1 = .*").arg(key)), section_cursor);
const QString new_line = QStringLiteral("%1 = %2").arg(key).arg(value);
// Check if the value that has to be set already exists
if (value_cursor.isNull())
{
section_cursor.clearSelection();
section_cursor.insertText(QStringLiteral("\n") + new_line);
}
else
{
value_cursor.insertText(new_line);
}
}
}
QString GameConfigEdit::GetTextUnderCursor()
{
QTextCursor tc = m_edit->textCursor();
tc.select(QTextCursor::WordUnderCursor);
return tc.selectedText();
}
void GameConfigEdit::AddMenubarOptions()
{
auto* editor = m_menu->addMenu(tr("Editor"));
editor->addAction(tr("Refresh"), this, &GameConfigEdit::LoadFile);
editor->addAction(tr("Open in External Editor"), this, &GameConfigEdit::OpenExternalEditor);
if (!m_read_only)
{
m_menu->addSeparator();
auto* core_menubar = m_menu->addMenu(tr("Core"));
AddBoolOption(core_menubar, tr("Dual Core"), QStringLiteral("Core"),
QStringLiteral("CPUThread"));
AddBoolOption(core_menubar, tr("MMU"), QStringLiteral("Core"), QStringLiteral("MMU"));
auto* video_menubar = m_menu->addMenu(tr("Video"));
AddBoolOption(video_menubar, tr("Store EFB Copies to Texture Only"),
QStringLiteral("Video_Settings"), QStringLiteral("EFBToTextureEnable"));
AddBoolOption(video_menubar, tr("Store XFB Copies to Texture Only"),
QStringLiteral("Video_Settings"), QStringLiteral("XFBToTextureEnable"));
{
auto* texture_cache = video_menubar->addMenu(tr("Texture Cache"));
texture_cache->addAction(tr("Safe"), this, [this] {
SetOption(QStringLiteral("Video_Settings"), QStringLiteral("SafeTextureCacheColorSamples"),
QStringLiteral("0"));
});
texture_cache->addAction(tr("Medium"), this, [this] {
SetOption(QStringLiteral("Video_Settings"), QStringLiteral("SafeTextureCacheColorSamples"),
QStringLiteral("512"));
});
texture_cache->addAction(tr("Fast"), this, [this] {
SetOption(QStringLiteral("Video_Settings"), QStringLiteral("SafeTextureCacheColorSamples"),
QStringLiteral("128"));
});
}
}
}
void GameConfigEdit::OnAutoComplete(const QString& completion)
{
QTextCursor cursor = m_edit->textCursor();
int extra = completion.length() - m_completer->completionPrefix().length();
cursor.movePosition(QTextCursor::Left);
cursor.movePosition(QTextCursor::EndOfWord);
cursor.insertText(completion.right(extra));
m_edit->setTextCursor(cursor);
}
void GameConfigEdit::OpenExternalEditor()
{
QFile file(m_path);
if (!file.exists())
{
if (m_read_only)
return;
file.open(QIODevice::WriteOnly);
file.close();
}
QDesktopServices::openUrl(QUrl::fromLocalFile(m_path));
}
void GameConfigEdit::keyPressEvent(QKeyEvent* e)
{
if (m_completer->popup()->isVisible())
{
// The following keys are forwarded by the completer to the widget
switch (e->key())
{
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Tab:
case Qt::Key_Backtab:
e->ignore();
return; // let the completer do default behavior
default:
break;
}
}
QWidget::keyPressEvent(e);
const static QString end_of_word = QStringLiteral("~!@#$%^&*()_+{}|:\"<>?,./;'\\-=");
QString completion_prefix = GetTextUnderCursor();
if (e->text().isEmpty() || completion_prefix.length() < 2 ||
end_of_word.contains(e->text().right(1)))
{
m_completer->popup()->hide();
return;
}
if (completion_prefix != m_completer->completionPrefix())
{
m_completer->setCompletionPrefix(completion_prefix);
m_completer->popup()->setCurrentIndex(m_completer->completionModel()->index(0, 0));
}
QRect cr = m_edit->cursorRect();
cr.setWidth(m_completer->popup()->sizeHintForColumn(0) +
m_completer->popup()->verticalScrollBar()->sizeHint().width());
m_completer->complete(cr); // popup it up!
}
void GameConfigEdit::focusInEvent(QFocusEvent* e)
{
m_completer->setWidget(m_edit);
QWidget::focusInEvent(e);
}

View File

@ -0,0 +1,57 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QMap>
#include <QString>
#include <QStringList>
#include <QWidget>
class QCompleter;
class QMenu;
class QTextEdit;
class GameConfigEdit : public QWidget
{
public:
explicit GameConfigEdit(QWidget* parent, const QString& path, bool read_only);
protected:
void keyPressEvent(QKeyEvent* e) override;
void focusInEvent(QFocusEvent* e) override;
private:
void CreateWidgets();
void ConnectWidgets();
void AddMenubarOptions();
void LoadFile();
void SaveFile();
void OnSelectionChanged();
void OnAutoComplete(const QString& completion);
void OpenExternalEditor();
void SetReadOnly(bool read_only);
QString GetTextUnderCursor();
void AddBoolOption(QMenu* menu, const QString& name, const QString& section, const QString& key);
void SetOption(const QString& section, const QString& key, const QString& value);
void AddDescription(const QString& keyword, const QString& description);
QCompleter* m_completer;
QStringList m_completions;
QMenu* m_menu;
QTextEdit* m_edit;
const QString m_path;
bool m_read_only;
QMap<QString, QString> m_keyword_map;
};

View File

@ -0,0 +1,63 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt/Config/GameConfigHighlighter.h"
struct HighlightingRule
{
QRegularExpression pattern;
QTextCharFormat format;
};
GameConfigHighlighter::~GameConfigHighlighter() = default;
GameConfigHighlighter::GameConfigHighlighter(QTextDocument* parent) : QSyntaxHighlighter(parent)
{
QTextCharFormat equal_format;
equal_format.setForeground(Qt::red);
QTextCharFormat section_format;
section_format.setFontWeight(QFont::Bold);
QTextCharFormat comment_format;
comment_format.setForeground(Qt::darkGreen);
comment_format.setFontItalic(true);
QTextCharFormat const_format;
const_format.setFontWeight(QFont::Bold);
const_format.setForeground(Qt::blue);
QTextCharFormat num_format;
num_format.setForeground(Qt::darkBlue);
m_rules.emplace_back(HighlightingRule{QRegularExpression(QStringLiteral("=")), equal_format});
m_rules.emplace_back(
HighlightingRule{QRegularExpression(QStringLiteral("^\\[.*?\\]")), section_format});
m_rules.emplace_back(
HighlightingRule{QRegularExpression(QStringLiteral("\\bTrue\\b")), const_format});
m_rules.emplace_back(
HighlightingRule{QRegularExpression(QStringLiteral("\\bFalse\\b")), const_format});
m_rules.emplace_back(
HighlightingRule{QRegularExpression(QStringLiteral("\\b[0-9a-fA-F]+\\b")), num_format});
m_rules.emplace_back(
HighlightingRule{QRegularExpression(QStringLiteral("^#.*")), comment_format});
m_rules.emplace_back(
HighlightingRule{QRegularExpression(QStringLiteral("^\\$.*")), comment_format});
m_rules.emplace_back(
HighlightingRule{QRegularExpression(QStringLiteral("^\\*.*")), comment_format});
}
void GameConfigHighlighter::highlightBlock(const QString& text)
{
for (const auto& rule : m_rules)
{
auto it = rule.pattern.globalMatch(text);
while (it.hasNext())
{
auto match = it.next();
setFormat(match.capturedStart(), match.capturedLength(), rule.format);
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include <QRegularExpression>
#include <QSyntaxHighlighter>
#include <QTextCharFormat>
struct HighlightingRule;
class GameConfigHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
public:
explicit GameConfigHighlighter(QTextDocument* parent = nullptr);
~GameConfigHighlighter();
protected:
void highlightBlock(const QString& text) override;
private:
std::vector<HighlightingRule> m_rules;
};

View File

@ -6,16 +6,12 @@
#include <QCheckBox>
#include <QComboBox>
#include <QDesktopServices>
#include <QFile>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSlider>
#include <QSpinBox>
#include <QUrl>
#include <QTabWidget>
#include <QVBoxLayout>
#include "Common/CommonPaths.h"
@ -24,6 +20,7 @@
#include "Core/ConfigLoaders/GameConfigLoader.h"
#include "Core/ConfigManager.h"
#include "DolphinQt/Config/GameConfigEdit.h"
#include "DolphinQt/Config/Graphics/GraphicsSlider.h"
#include "UICommon/GameFile.h"
@ -38,22 +35,51 @@ constexpr const char* DETERMINISM_AUTO_STRING = "auto";
constexpr const char* DETERMINISM_NONE_STRING = "none";
constexpr const char* DETERMINISM_FAKE_COMPLETION_STRING = "fake-completion";
static void PopulateTab(QTabWidget* tab, const std::string& path, std::string game_id,
bool read_only)
{
while (!game_id.empty())
{
const std::string ini_path = path + game_id + ".ini";
if (File::Exists(ini_path))
{
auto* edit =
new GameConfigEdit(nullptr, QString::fromStdString(path + game_id + ".ini"), read_only);
tab->addTab(edit, QString::fromStdString(game_id));
}
game_id = game_id.substr(0, game_id.size() - 1);
}
}
GameConfigWidget::GameConfigWidget(const UICommon::GameFile& game) : m_game(game)
{
m_game_id = m_game.GetGameID();
m_gameini_local_path =
QString::fromStdString(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
CreateWidgets();
LoadSettings();
ConnectWidgets();
PopulateTab(m_default_tab, File::GetSysDirectory() + "GameSettings/", m_game_id, true);
PopulateTab(m_local_tab, File::GetUserPath(D_GAMESETTINGS_IDX), m_game_id, false);
// Always give the user the opportunity to create a new INI
if (m_local_tab->count() == 0)
{
auto* edit = new GameConfigEdit(
nullptr, QString::fromStdString(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"),
false);
m_local_tab->addTab(edit, QString::fromStdString(m_game_id));
}
}
void GameConfigWidget::CreateWidgets()
{
// General
m_refresh_config = new QPushButton(tr("Refresh"));
m_edit_user_config = new QPushButton(tr("Edit User Config"));
m_view_default_config = new QPushButton(tr("View Default Config"));
// Core
auto* core_box = new QGroupBox(tr("Core"));
@ -128,23 +154,51 @@ void GameConfigWidget::CreateWidgets()
settings_layout->addWidget(core_box);
settings_layout->addWidget(stereoscopy_box);
auto* layout = new QGridLayout;
auto* general_layout = new QGridLayout;
layout->addWidget(settings_box, 0, 0, 1, -1);
general_layout->addWidget(settings_box, 0, 0, 1, -1);
auto* button_layout = new QHBoxLayout;
button_layout->setMargin(0);
layout->addLayout(button_layout, 1, 0, 1, -1);
button_layout->addWidget(m_refresh_config);
button_layout->addWidget(m_edit_user_config);
button_layout->addWidget(m_view_default_config);
general_layout->addWidget(m_refresh_config, 1, 0, 1, -1);
for (QCheckBox* item : {m_enable_dual_core, m_enable_mmu, m_enable_fprf, m_sync_gpu,
m_enable_fast_disc, m_use_dsp_hle, m_use_monoscopic_shadows})
item->setTristate(true);
auto* general_widget = new QWidget;
general_widget->setLayout(general_layout);
// Advanced
auto* advanced_layout = new QVBoxLayout;
auto* default_group = new QGroupBox(tr("Default Config (Read Only)"));
auto* default_layout = new QVBoxLayout;
m_default_tab = new QTabWidget;
default_group->setLayout(default_layout);
default_layout->addWidget(m_default_tab);
auto* local_group = new QGroupBox(tr("User Config"));
auto* local_layout = new QVBoxLayout;
m_local_tab = new QTabWidget;
local_group->setLayout(local_layout);
local_layout->addWidget(m_local_tab);
advanced_layout->addWidget(default_group);
advanced_layout->addWidget(local_group);
auto* advanced_widget = new QWidget;
advanced_widget->setLayout(advanced_layout);
auto* layout = new QVBoxLayout;
auto* tab_widget = new QTabWidget;
tab_widget->addTab(general_widget, tr("General"));
tab_widget->addTab(advanced_widget, tr("Editor"));
layout->addWidget(tab_widget);
setLayout(layout);
}
@ -152,8 +206,6 @@ void GameConfigWidget::ConnectWidgets()
{
// Buttons
connect(m_refresh_config, &QPushButton::pressed, this, &GameConfigWidget::LoadSettings);
connect(m_edit_user_config, &QPushButton::pressed, this, &GameConfigWidget::EditUserConfig);
connect(m_view_default_config, &QPushButton::pressed, this, &GameConfigWidget::ViewDefaultConfig);
for (QCheckBox* box : {m_enable_dual_core, m_enable_mmu, m_enable_fprf, m_sync_gpu,
m_enable_fast_disc, m_use_dsp_hle, m_use_monoscopic_shadows})
@ -347,29 +399,3 @@ void GameConfigWidget::SaveSettings()
if (success && File::GetSize(m_gameini_local_path.toStdString()) == 0)
File::Delete(m_gameini_local_path.toStdString());
}
void GameConfigWidget::EditUserConfig()
{
QFile file(m_gameini_local_path);
if (!file.exists())
{
file.open(QIODevice::WriteOnly);
file.close();
}
QDesktopServices::openUrl(QUrl::fromLocalFile(m_gameini_local_path));
}
void GameConfigWidget::ViewDefaultConfig()
{
for (const std::string& filename :
ConfigLoaders::GetGameIniFilenames(m_game_id, m_game.GetRevision()))
{
QString path =
QString::fromStdString(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename);
if (QFile(path).exists())
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}
}

View File

@ -18,12 +18,10 @@ class GameFile;
class QCheckBox;
class QComboBox;
class QGroupBox;
class QLineEdit;
class QPushButton;
class QSlider;
class QSpinBox;
class QVBoxLayout;
class QTabWidget;
class GameConfigWidget : public QWidget
{
@ -34,38 +32,40 @@ public:
private:
void CreateWidgets();
void ConnectWidgets();
void LoadSettings();
void SaveSettings();
void EditUserConfig();
void ViewDefaultConfig();
void LoadCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key);
void SaveCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key);
void LoadCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key);
QPushButton* m_refresh_config;
QPushButton* m_edit_user_config;
QPushButton* m_view_default_config;
QString m_gameini_sys_path;
QString m_gameini_local_path;
QTabWidget* m_default_tab;
QTabWidget* m_local_tab;
// Core
QCheckBox* m_enable_dual_core;
QCheckBox* m_enable_mmu;
QCheckBox* m_enable_fprf;
QCheckBox* m_sync_gpu;
QCheckBox* m_enable_fast_disc;
QCheckBox* m_use_dsp_hle;
QComboBox* m_deterministic_dual_core;
// Stereoscopy
QSlider* m_depth_slider;
QSpinBox* m_convergence_spin;
QCheckBox* m_use_monoscopic_shadows;
QString m_gameini_local_path;
QPushButton* m_refresh_config;
IniFile m_gameini_local;
IniFile m_gameini_default;
QComboBox* m_deterministic_dual_core;
QSlider* m_depth_slider;
QSpinBox* m_convergence_spin;
const UICommon::GameFile& m_game;
std::string m_game_id;
IniFile m_gameini_local;
IniFile m_gameini_default;
};

View File

@ -63,6 +63,8 @@
<QtMoc Include="Config\CheatWarningWidget.h" />
<QtMoc Include="Config\ControllersWindow.h" />
<QtMoc Include="Config\FilesystemWidget.h" />
<QtMoc Include="Config\GameConfigEdit.h" />
<QtMoc Include="Config\GameConfigHighlighter.h" />
<QtMoc Include="Config\GameConfigWidget.h" />
<QtMoc Include="Config\GeckoCodeWidget.h" />
<QtMoc Include="Config\Mapping\GCKeyboardEmu.h" />
@ -194,6 +196,8 @@
<ClCompile Include="$(QtMocOutPrefix)GCPadEmu.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GCPadWiiUConfigDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GCTASInputWindow.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GameConfigEdit.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GameConfigHighlighter.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GameConfigWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GameCubePane.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GameList.cpp" />
@ -275,6 +279,8 @@
<ClCompile Include="Config\CheatWarningWidget.cpp" />
<ClCompile Include="Config\ControllersWindow.cpp" />
<ClCompile Include="Config\FilesystemWidget.cpp" />
<ClCompile Include="Config\GameConfigEdit.cpp" />
<ClCompile Include="Config\GameConfigHighlighter.cpp" />
<ClCompile Include="Config\GameConfigWidget.cpp" />
<ClCompile Include="Config\GeckoCodeWidget.cpp" />
<ClCompile Include="Config\Graphics\AdvancedWidget.cpp" />