mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-11 00:29:11 +01:00
6e14dcf70a
Previously, the constructor of GameConfigEdit wasn't doing anything with the passed in parent pointer. This is dangerous because it can result in memory being leaked in certain scenarios. It can also affect layout decisions made by the parent. Instead, pass it through to the base class. Current usages of the class pass in nullptr as the parent, so this is a safe change to make with regards to the class hierarchy. While we're at it, we can std::move the passed in QString into the class member, allowing calling code to move strings into the constructor, avoiding copies.
319 lines
9.7 KiB
C++
319 lines
9.7 KiB
C++
// 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 <QPushButton>
|
|
#include <QScrollBar>
|
|
#include <QStringListModel>
|
|
#include <QTextCursor>
|
|
#include <QTextEdit>
|
|
#include <QVBoxLayout>
|
|
#include <QWhatsThis>
|
|
|
|
#include "DolphinQt/Config/GameConfigHighlighter.h"
|
|
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
|
|
|
GameConfigEdit::GameConfigEdit(QWidget* parent, QString path, bool read_only)
|
|
: QWidget{parent}, m_path(std::move(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(m_completer);
|
|
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_edit = new QTextEdit;
|
|
m_edit->setReadOnly(m_read_only);
|
|
m_edit->setAcceptRichText(false);
|
|
|
|
auto* layout = new QVBoxLayout;
|
|
|
|
auto* menu_button = new QPushButton;
|
|
|
|
menu_button->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
|
|
menu_button->setText(tr("Presets"));
|
|
|
|
m_menu = new QMenu(menu_button);
|
|
menu_button->setMenu(m_menu);
|
|
|
|
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))
|
|
{
|
|
ModalMessageBox::warning(this, tr("Warning"), tr("Failed to open config file!"));
|
|
return;
|
|
}
|
|
|
|
const QByteArray contents = m_edit->toPlainText().toUtf8();
|
|
|
|
if (file.write(contents) == -1)
|
|
ModalMessageBox::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_Hacks"), QStringLiteral("EFBToTextureEnable"));
|
|
|
|
AddBoolOption(video_menubar, tr("Store XFB Copies to Texture Only"),
|
|
QStringLiteral("Video_Hacks"), 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();
|
|
}
|
|
|
|
if (!QDesktopServices::openUrl(QUrl::fromLocalFile(m_path)))
|
|
{
|
|
ModalMessageBox::warning(this, tr("Error"),
|
|
tr("Failed to open file in external editor.\nMake sure there's an "
|
|
"application assigned to open INI files."));
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|