Skip to content

Commit

Permalink
Qt/GameConfigWidget: Complete overhaul
Browse files Browse the repository at this point in the history
  • Loading branch information
spycrab committed Jan 1, 2019
1 parent 0a8e04e commit d686922
Show file tree
Hide file tree
Showing 8 changed files with 548 additions and 61 deletions.
2 changes: 2 additions & 0 deletions Source/Core/DolphinQt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,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
Expand Down
304 changes: 304 additions & 0 deletions Source/Core/DolphinQt/Config/GameConfigEdit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
// 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 <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_menubar = new QMenuBar(this);
m_menubar->setNativeMenuBar(false);

m_edit = new QTextEdit;
m_edit->setReadOnly(m_read_only);
m_edit->setAcceptRichText(false);

auto* layout = new QVBoxLayout;
layout->setSpacing(0);
layout->addWidget(m_menubar);
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_menubar->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_menubar->addSeparator();
auto* core_m_menubar = m_menubar->addMenu(tr("Core"));

AddBoolOption(core_m_menubar, tr("Dual Core"), QStringLiteral("Core"),
QStringLiteral("CPUThread"));
AddBoolOption(core_m_menubar, tr("MMU"), QStringLiteral("Core"), QStringLiteral("MMU"));

auto* video_m_menubar = m_menubar->addMenu(tr("Video"));

AddBoolOption(video_m_menubar, tr("Store EFB Copies to Texture Only"),
QStringLiteral("Video_Settings"), QStringLiteral("EFBToTextureEnable"));

AddBoolOption(video_m_menubar, tr("Store XFB Copies to Texture Only"),
QStringLiteral("Video_Settings"), QStringLiteral("XFBToTextureEnable"));

{
auto* texture_cache = video_m_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);
}
58 changes: 58 additions & 0 deletions Source/Core/DolphinQt/Config/GameConfigEdit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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 QMenuBar;
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;
QMenuBar* m_menubar;
QTextEdit* m_edit;

const QString m_path;

bool m_read_only;

QMap<QString, QString> m_keyword_map;
};
Loading

0 comments on commit d686922

Please sign in to comment.