Qt: Add new cheat manager

This commit is contained in:
Connor McLaughlin
2020-10-20 01:14:49 +10:00
parent 9f0f24a5e5
commit b694577c38
21 changed files with 2356 additions and 19 deletions

View File

@ -18,6 +18,12 @@ set(SRCS
biossettingswidget.cpp
biossettingswidget.h
biossettingswidget.ui
cheatmanagerdialog.cpp
cheatmanagerdialog.h
cheatmanagerdialog.ui
cheatcodeeditordialog.cpp
cheatcodeeditordialog.h
cheatcodeeditordialog.ui
consolesettingswidget.cpp
consolesettingswidget.h
consolesettingswidget.ui

View File

@ -0,0 +1,88 @@
#include "cheatcodeeditordialog.h"
#include <QtWidgets/QMessageBox>
CheatCodeEditorDialog::CheatCodeEditorDialog(CheatList* list, CheatCode* code, QWidget* parent)
: m_code(code), QDialog(parent)
{
m_ui.setupUi(this);
setupAdditionalUi(list);
fillUi();
connectUi();
}
CheatCodeEditorDialog::~CheatCodeEditorDialog() = default;
void CheatCodeEditorDialog::saveClicked()
{
std::string new_description = m_ui.description->text().toStdString();
if (new_description.empty())
{
QMessageBox::critical(this, tr("Error"), tr("Description cannot be empty."));
return;
}
if (!m_code->SetInstructionsFromString(m_ui.instructions->toPlainText().toStdString()))
{
QMessageBox::critical(this, tr("Error"), tr("Instructions are invalid."));
return;
}
m_code->description = std::move(new_description);
m_code->type = static_cast<CheatCode::Type>(m_ui.type->currentIndex());
m_code->activation = static_cast<CheatCode::Activation>(m_ui.activation->currentIndex());
m_code->group = m_ui.group->currentText().toStdString();
done(1);
}
void CheatCodeEditorDialog::cancelClicked()
{
done(0);
}
void CheatCodeEditorDialog::setupAdditionalUi(CheatList* list)
{
for (u32 i = 0; i < static_cast<u32>(CheatCode::Type::Count); i++)
{
m_ui.type->addItem(qApp->translate("Cheats", CheatCode::GetTypeDisplayName(static_cast<CheatCode::Type>(i))));
}
for (u32 i = 0; i < static_cast<u32>(CheatCode::Activation::Count); i++)
{
m_ui.activation->addItem(
qApp->translate("Cheats", CheatCode::GetActivationDisplayName(static_cast<CheatCode::Activation>(i))));
}
const auto groups = list->GetCodeGroups();
if (!groups.empty())
{
for (const std::string& group_name : groups)
m_ui.group->addItem(QString::fromStdString(group_name));
}
else
{
m_ui.group->addItem(QStringLiteral("Ungrouped"));
}
}
void CheatCodeEditorDialog::fillUi()
{
m_ui.description->setText(QString::fromStdString(m_code->description));
int index = m_ui.group->findText(QString::fromStdString(m_code->group));
if (index >= 0)
m_ui.group->setCurrentIndex(index);
else
m_ui.group->setCurrentIndex(0);
m_ui.type->setCurrentIndex(static_cast<int>(m_code->type));
m_ui.activation->setCurrentIndex(static_cast<int>(m_code->activation));
m_ui.instructions->setPlainText(QString::fromStdString(m_code->GetInstructionsAsString()));
}
void CheatCodeEditorDialog::connectUi()
{
connect(m_ui.save, &QPushButton::clicked, this, &CheatCodeEditorDialog::saveClicked);
connect(m_ui.cancel, &QPushButton::clicked, this, &CheatCodeEditorDialog::cancelClicked);
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "core/cheats.h"
#include "ui_cheatcodeeditordialog.h"
class CheatCodeEditorDialog : public QDialog
{
Q_OBJECT
public:
CheatCodeEditorDialog(CheatList* list, CheatCode* code, QWidget* parent);
~CheatCodeEditorDialog();
private Q_SLOTS:
void saveClicked();
void cancelClicked();
private:
void setupAdditionalUi(CheatList* list);
void fillUi();
void connectUi();
CheatCode* m_code;
Ui::CheatCodeEditorDialog m_ui;
};

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CheatCodeEditorDialog</class>
<widget class="QDialog" name="CheatCodeEditorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>491</width>
<height>284</height>
</rect>
</property>
<property name="windowTitle">
<string>Cheat Code Editor</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Description:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="description"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Group:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="group"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="type"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Activation:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="activation"/>
</item>
<item row="4" column="0" colspan="2">
<widget class="QPlainTextEdit" name="instructions"/>
</item>
<item row="5" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="save">
<property name="text">
<string>Save</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,689 @@
#include "cheatmanagerdialog.h"
#include "cheatcodeeditordialog.h"
#include "common/assert.h"
#include "common/string_util.h"
#include "core/system.h"
#include "qthostinterface.h"
#include "qtutils.h"
#include <QtCore/QFileInfo>
#include <QtGui/QColor>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QTreeWidgetItemIterator>
#include <array>
static QString formatHexValue(u32 value)
{
return QStringLiteral("0x%1").arg(static_cast<uint>(value), 8, 16, QChar('0'));
}
static QString formatValue(u32 value, bool is_signed)
{
if (is_signed)
return QStringLiteral("%1").arg(static_cast<int>(value));
else
return QStringLiteral("%1").arg(static_cast<uint>(value));
}
CheatManagerDialog::CheatManagerDialog(QWidget* parent) : QDialog(parent)
{
m_ui.setupUi(this);
setupAdditionalUi();
connectUi();
updateCheatList();
}
CheatManagerDialog::~CheatManagerDialog() = default;
void CheatManagerDialog::setupAdditionalUi()
{
m_ui.scanStartAddress->setText(formatHexValue(m_scanner.GetStartAddress()));
m_ui.scanEndAddress->setText(formatHexValue(m_scanner.GetEndAddress()));
}
void CheatManagerDialog::connectUi()
{
connect(m_ui.tabWidget, &QTabWidget::currentChanged, [this](int index) {
resizeColumns();
setUpdateTimerEnabled(index == 1);
});
connect(m_ui.cheatList, &QTreeWidget::currentItemChanged, this, &CheatManagerDialog::cheatListCurrentItemChanged);
connect(m_ui.cheatList, &QTreeWidget::itemActivated, this, &CheatManagerDialog::cheatListItemActivated);
connect(m_ui.cheatList, &QTreeWidget::itemChanged, this, &CheatManagerDialog::cheatListItemChanged);
connect(m_ui.cheatListNewCategory, &QPushButton::clicked, this, &CheatManagerDialog::newCategoryClicked);
connect(m_ui.cheatListAdd, &QPushButton::clicked, this, &CheatManagerDialog::addCodeClicked);
connect(m_ui.cheatListEdit, &QPushButton::clicked, this, &CheatManagerDialog::editCodeClicked);
connect(m_ui.cheatListRemove, &QPushButton::clicked, this, &CheatManagerDialog::deleteCodeClicked);
connect(m_ui.cheatListActivate, &QPushButton::clicked, this, &CheatManagerDialog::activateCodeClicked);
connect(m_ui.cheatListImport, &QPushButton::clicked, this, &CheatManagerDialog::importClicked);
connect(m_ui.cheatListExport, &QPushButton::clicked, this, &CheatManagerDialog::exportClicked);
connect(m_ui.scanValue, &QLineEdit::textChanged, this, &CheatManagerDialog::updateScanValue);
connect(m_ui.scanValueBase, QOverload<int>::of(&QComboBox::currentIndexChanged),
[this](int index) { updateScanValue(); });
connect(m_ui.scanSize, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
m_scanner.SetSize(static_cast<MemoryAccessSize>(index));
m_scanner.ResetSearch();
updateResults();
});
connect(m_ui.scanValueSigned, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
m_scanner.SetValueSigned(index == 0);
m_scanner.ResetSearch();
updateResults();
});
connect(m_ui.scanOperator, QOverload<int>::of(&QComboBox::currentIndexChanged),
[this](int index) { m_scanner.SetOperator(static_cast<MemoryScan::Operator>(index)); });
connect(m_ui.scanNewSearch, &QPushButton::clicked, [this]() {
m_scanner.Search();
updateResults();
});
connect(m_ui.scanSearchAgain, &QPushButton::clicked, [this]() {
m_scanner.SearchAgain();
updateResults();
});
connect(m_ui.scanResetSearch, &QPushButton::clicked, [this]() {
m_scanner.ResetSearch();
updateResults();
});
connect(m_ui.scanAddWatch, &QPushButton::clicked, this, &CheatManagerDialog::addToWatchClicked);
connect(m_ui.scanRemoveWatch, &QPushButton::clicked, this, &CheatManagerDialog::removeWatchClicked);
connect(m_ui.scanTable, &QTableWidget::currentItemChanged, this, &CheatManagerDialog::scanCurrentItemChanged);
connect(m_ui.watchTable, &QTableWidget::currentItemChanged, this, &CheatManagerDialog::watchCurrentItemChanged);
connect(m_ui.scanTable, &QTableWidget::itemChanged, this, &CheatManagerDialog::scanItemChanged);
connect(m_ui.watchTable, &QTableWidget::itemChanged, this, &CheatManagerDialog::watchItemChanged);
}
void CheatManagerDialog::showEvent(QShowEvent* event)
{
QDialog::showEvent(event);
resizeColumns();
}
void CheatManagerDialog::resizeEvent(QResizeEvent* event)
{
QDialog::resizeEvent(event);
resizeColumns();
}
void CheatManagerDialog::resizeColumns()
{
QtUtils::ResizeColumnsForTableView(m_ui.scanTable, {-1, 100, 100});
QtUtils::ResizeColumnsForTableView(m_ui.watchTable, {50, -1, 100, 150, 100});
QtUtils::ResizeColumnsForTreeView(m_ui.cheatList, {-1, 100, 150, 100});
}
void CheatManagerDialog::setUpdateTimerEnabled(bool enabled)
{
if ((!m_update_timer && !enabled) && m_update_timer->isActive() == enabled)
return;
if (!m_update_timer)
{
m_update_timer = new QTimer(this);
connect(m_update_timer, &QTimer::timeout, this, &CheatManagerDialog::updateScanUi);
}
if (enabled)
m_update_timer->start(100);
else
m_update_timer->stop();
}
int CheatManagerDialog::getSelectedResultIndex() const
{
QList<QTableWidgetSelectionRange> sel = m_ui.scanTable->selectedRanges();
if (sel.isEmpty())
return -1;
return sel.front().topRow();
}
int CheatManagerDialog::getSelectedWatchIndex() const
{
QList<QTableWidgetSelectionRange> sel = m_ui.watchTable->selectedRanges();
if (sel.isEmpty())
return -1;
return sel.front().topRow();
}
QTreeWidgetItem* CheatManagerDialog::getItemForCheatIndex(u32 index) const
{
QTreeWidgetItemIterator iter(m_ui.cheatList);
while (*iter)
{
QTreeWidgetItem* item = *iter;
const QVariant item_data(item->data(0, Qt::UserRole));
if (item_data.isValid() && item_data.toUInt() == index)
return item;
++iter;
}
return nullptr;
}
static u32 getCheatIndexFromItem(QTreeWidgetItem* item)
{
return item->data(0, Qt::UserRole).toUInt();
}
int CheatManagerDialog::getSelectedCheatIndex() const
{
QList<QTreeWidgetItem*> sel = m_ui.cheatList->selectedItems();
if (sel.isEmpty())
return -1;
return static_cast<int>(getCheatIndexFromItem(sel.first()));
}
CheatList* CheatManagerDialog::getCheatList() const
{
Assert(System::IsValid());
CheatList* list = System::GetCheatList();
if (!list)
QtHostInterface::GetInstance()->LoadCheatListFromGameTitle();
if (!list)
{
QtHostInterface::GetInstance()->executeOnEmulationThread(
[]() { System::SetCheatList(std::make_unique<CheatList>()); }, true);
list = System::GetCheatList();
}
return list;
}
void CheatManagerDialog::updateCheatList()
{
QSignalBlocker sb(m_ui.cheatList);
CheatList* list = getCheatList();
while (m_ui.cheatList->topLevelItemCount() > 0)
delete m_ui.cheatList->takeTopLevelItem(0);
const std::vector<std::string> groups = list->GetCodeGroups();
for (const std::string& group_name : groups)
{
QTreeWidgetItem* group = new QTreeWidgetItem();
group->setFlags(group->flags() | Qt::ItemIsUserCheckable);
group->setText(0, QString::fromStdString(group_name));
m_ui.cheatList->addTopLevelItem(group);
const u32 count = list->GetCodeCount();
bool all_enabled = true;
for (u32 i = 0; i < count; i++)
{
const CheatCode& code = list->GetCode(i);
if (code.group != group_name)
continue;
QTreeWidgetItem* item = new QTreeWidgetItem(group);
item->setData(0, Qt::UserRole, QVariant(static_cast<uint>(i)));
if (code.IsManuallyActivated())
{
item->setFlags(item->flags() & ~(Qt::ItemIsUserCheckable));
}
else
{
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(0, code.enabled ? Qt::Checked : Qt::Unchecked);
}
item->setText(0, QString::fromStdString(code.description));
item->setText(1, qApp->translate("Cheats", CheatCode::GetTypeDisplayName(code.type)));
item->setText(2, qApp->translate("Cheats", CheatCode::GetActivationDisplayName(code.activation)));
item->setText(3, QString::number(static_cast<uint>(code.instructions.size())));
all_enabled &= code.enabled;
}
group->setCheckState(0, all_enabled ? Qt::Checked : Qt::Unchecked);
group->setExpanded(true);
}
m_ui.cheatListEdit->setEnabled(false);
m_ui.cheatListRemove->setEnabled(false);
m_ui.cheatListActivate->setText(tr("Activate"));
m_ui.cheatListActivate->setEnabled(false);
m_ui.cheatListExport->setEnabled(list->GetCodeCount() > 0);
}
void CheatManagerDialog::saveCheatList()
{
QtHostInterface::GetInstance()->executeOnEmulationThread([]() { QtHostInterface::GetInstance()->SaveCheatList(); });
}
void CheatManagerDialog::cheatListCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
const bool has_current = (current != nullptr);
m_ui.cheatListEdit->setEnabled(has_current);
m_ui.cheatListRemove->setEnabled(has_current);
m_ui.cheatListActivate->setEnabled(has_current);
if (!current)
{
m_ui.cheatListActivate->setText(tr("Activate"));
}
else
{
const bool manual_activation = getCheatList()->GetCode(getCheatIndexFromItem(current)).IsManuallyActivated();
m_ui.cheatListActivate->setText(manual_activation ? tr("Activate") : tr("Toggle"));
}
}
void CheatManagerDialog::cheatListItemActivated(QTreeWidgetItem* item)
{
if (!item)
return;
const u32 index = getCheatIndexFromItem(item);
activateCheat(index);
}
void CheatManagerDialog::cheatListItemChanged(QTreeWidgetItem* item, int column)
{
if (!item || column != 0)
return;
const u32 index = getCheatIndexFromItem(item);
CheatList* list = getCheatList();
if (index >= list->GetCodeCount())
return;
CheatCode& cc = list->GetCode(index);
if (cc.IsManuallyActivated())
return;
const bool new_enabled = (item->checkState(0) == Qt::Checked);
if (cc.enabled == new_enabled)
return;
QtHostInterface::GetInstance()->executeOnEmulationThread([index, new_enabled]() {
System::GetCheatList()->SetCodeEnabled(index, new_enabled);
QtHostInterface::GetInstance()->SaveCheatList();
});
}
void CheatManagerDialog::activateCheat(u32 index)
{
CheatList* list = getCheatList();
if (index >= list->GetCodeCount())
return;
CheatCode& cc = list->GetCode(index);
if (cc.IsManuallyActivated())
{
QtHostInterface::GetInstance()->applyCheat(index);
return;
}
const bool new_enabled = !cc.enabled;
QTreeWidgetItem* item = getItemForCheatIndex(index);
if (item)
{
QSignalBlocker sb(m_ui.cheatList);
item->setCheckState(0, new_enabled ? Qt::Checked : Qt::Unchecked);
}
QtHostInterface::GetInstance()->executeOnEmulationThread([index, new_enabled]() {
System::GetCheatList()->SetCodeEnabled(index, new_enabled);
QtHostInterface::GetInstance()->SaveCheatList();
});
}
void CheatManagerDialog::newCategoryClicked()
{
//
}
void CheatManagerDialog::addCodeClicked()
{
CheatList* list = getCheatList();
CheatCode new_code;
CheatCodeEditorDialog editor(list, &new_code, this);
if (editor.exec() > 0)
{
QtHostInterface::GetInstance()->executeOnEmulationThread(
[this, &new_code]() {
System::GetCheatList()->AddCode(std::move(new_code));
QtHostInterface::GetInstance()->SaveCheatList();
},
true);
updateCheatList();
}
}
void CheatManagerDialog::editCodeClicked()
{
int index = getSelectedCheatIndex();
if (index < 0)
return;
CheatList* list = getCheatList();
if (static_cast<u32>(index) >= list->GetCodeCount())
return;
CheatCode new_code = list->GetCode(static_cast<u32>(index));
CheatCodeEditorDialog editor(list, &new_code, this);
if (editor.exec() > 0)
{
QtHostInterface::GetInstance()->executeOnEmulationThread(
[index, &new_code]() {
System::GetCheatList()->SetCode(static_cast<u32>(index), std::move(new_code));
QtHostInterface::GetInstance()->SaveCheatList();
},
true);
updateCheatList();
}
}
void CheatManagerDialog::deleteCodeClicked()
{
int index = getSelectedCheatIndex();
if (index < 0)
return;
CheatList* list = getCheatList();
if (static_cast<u32>(index) >= list->GetCodeCount())
return;
if (QMessageBox::question(this, tr("Delete Code"),
tr("Are you sure you wish to delete the selected code? This action is not reversible."),
QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
{
return;
}
QtHostInterface::GetInstance()->executeOnEmulationThread(
[index]() {
System::GetCheatList()->RemoveCode(static_cast<u32>(index));
QtHostInterface::GetInstance()->SaveCheatList();
},
true);
updateCheatList();
}
void CheatManagerDialog::activateCodeClicked()
{
int index = getSelectedCheatIndex();
if (index < 0)
return;
activateCheat(static_cast<u32>(index));
}
void CheatManagerDialog::importClicked()
{
const QString filter(tr("PCSXR/Libretro Cheat Files (*.cht *.txt);;All Files (*.*)"));
const QString filename(QFileDialog::getOpenFileName(this, tr("Import Cheats"), QString(), filter));
if (filename.isEmpty())
return;
CheatList new_cheats;
if (!new_cheats.LoadFromFile(filename.toUtf8().constData(), CheatList::Format::Autodetect))
{
QMessageBox::critical(this, tr("Error"), tr("Failed to parse cheat file. The log may contain more information."));
return;
}
QtHostInterface::GetInstance()->executeOnEmulationThread(
[&new_cheats]() {
DebugAssert(System::HasCheatList());
CheatList* list = System::GetCheatList();
for (u32 i = 0; i < new_cheats.GetCodeCount(); i++)
list->AddCode(new_cheats.GetCode(i));
QtHostInterface::GetInstance()->SaveCheatList();
},
true);
updateCheatList();
}
void CheatManagerDialog::exportClicked()
{
const QString filter(tr("PCSXR Cheat Files (*.cht);;All Files (*.*)"));
const QString filename(QFileDialog::getSaveFileName(this, tr("Export Cheats"), QString(), filter));
if (filename.isEmpty())
return;
if (!getCheatList()->SaveToPCSXRFile(filename.toUtf8().constData()))
QMessageBox::critical(this, tr("Error"), tr("Failed to save cheat file. The log may contain more information."));
}
void CheatManagerDialog::addToWatchClicked()
{
const int index = getSelectedResultIndex();
if (index < 0)
return;
const MemoryScan::Result& res = m_scanner.GetResults()[static_cast<u32>(index)];
m_watch.AddEntry(StringUtil::StdStringFromFormat("0x%08x", res.address), res.address, m_scanner.GetSize(),
m_scanner.GetValueSigned(), false);
updateWatch();
}
void CheatManagerDialog::removeWatchClicked()
{
const int index = getSelectedWatchIndex();
if (index < 0)
return;
m_watch.RemoveEntry(static_cast<u32>(index));
updateWatch();
}
void CheatManagerDialog::scanCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous)
{
m_ui.scanAddWatch->setEnabled((current != nullptr));
}
void CheatManagerDialog::watchCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous)
{
m_ui.scanRemoveWatch->setEnabled((current != nullptr));
}
void CheatManagerDialog::scanItemChanged(QTableWidgetItem* item)
{
const u32 index = static_cast<u32>(item->row());
switch (item->column())
{
case 1:
{
bool value_ok = false;
if (m_scanner.GetValueSigned())
{
int value = item->text().toInt(&value_ok);
if (value_ok)
m_scanner.SetResultValue(index, static_cast<u32>(value));
}
else
{
uint value = item->text().toUInt(&value_ok);
if (value_ok)
m_scanner.SetResultValue(index, static_cast<u32>(value));
}
}
break;
default:
break;
}
}
void CheatManagerDialog::watchItemChanged(QTableWidgetItem* item)
{
const u32 index = static_cast<u32>(item->row());
if (index >= m_watch.GetEntryCount())
return;
switch (item->column())
{
case 0:
{
m_watch.SetEntryFreeze(index, (item->checkState() == Qt::Checked));
}
break;
case 1:
{
m_watch.SetEntryDescription(index, item->text().toStdString());
}
break;
case 4:
{
const MemoryWatchList::Entry& entry = m_watch.GetEntry(index);
bool value_ok = false;
if (entry.is_signed)
{
int value = item->text().toInt(&value_ok);
if (value_ok)
m_watch.SetEntryValue(index, static_cast<u32>(value));
}
else
{
uint value = item->text().toUInt(&value_ok);
if (value_ok)
m_watch.SetEntryValue(index, static_cast<u32>(value));
}
}
break;
default:
break;
}
}
void CheatManagerDialog::updateScanValue()
{
QString value = m_ui.scanValue->text();
if (value.startsWith(QStringLiteral("0x")))
value.remove(0, 2);
bool ok = false;
uint uint_value = value.toUInt(&ok, (m_ui.scanValueBase->currentIndex() > 0) ? 16 : 10);
if (ok)
m_scanner.SetValue(uint_value);
}
void CheatManagerDialog::updateResults()
{
QSignalBlocker sb(m_ui.scanTable);
m_ui.scanTable->setRowCount(0);
const MemoryScan::ResultVector& results = m_scanner.GetResults();
if (!results.empty())
{
int row = 0;
for (const MemoryScan::Result& res : m_scanner.GetResults())
{
m_ui.scanTable->insertRow(row);
QTableWidgetItem* address_item = new QTableWidgetItem(formatHexValue(res.address));
address_item->setFlags(address_item->flags() & ~(Qt::ItemIsEditable));
m_ui.scanTable->setItem(row, 0, address_item);
QTableWidgetItem* value_item = new QTableWidgetItem(formatValue(res.value, m_scanner.GetValueSigned()));
m_ui.scanTable->setItem(row, 1, value_item);
QTableWidgetItem* previous_item = new QTableWidgetItem(formatValue(res.last_value, m_scanner.GetValueSigned()));
previous_item->setFlags(address_item->flags() & ~(Qt::ItemIsEditable));
m_ui.scanTable->setItem(row, 2, previous_item);
row++;
}
}
m_ui.scanResetSearch->setEnabled(!results.empty());
m_ui.scanSearchAgain->setEnabled(!results.empty());
m_ui.scanAddWatch->setEnabled(false);
}
void CheatManagerDialog::updateResultsValues()
{
QSignalBlocker sb(m_ui.scanTable);
int row = 0;
for (const MemoryScan::Result& res : m_scanner.GetResults())
{
if (res.value_changed)
{
QTableWidgetItem* item = m_ui.scanTable->item(row, 1);
item->setText(formatValue(res.value, m_scanner.GetValueSigned()));
item->setForeground(Qt::red);
}
row++;
}
}
void CheatManagerDialog::updateWatch()
{
static constexpr std::array<const char*, 6> size_strings = {
{QT_TR_NOOP("Byte"), QT_TR_NOOP("Halfword"), QT_TR_NOOP("Word"), QT_TR_NOOP("Signed Byte"),
QT_TR_NOOP("Signed Halfword"), QT_TR_NOOP("Signed Word")}};
m_watch.UpdateValues();
QSignalBlocker sb(m_ui.watchTable);
m_ui.watchTable->setRowCount(0);
const MemoryWatchList::EntryVector& entries = m_watch.GetEntries();
if (!entries.empty())
{
int row = 0;
for (const MemoryWatchList::Entry& res : entries)
{
m_ui.watchTable->insertRow(row);
QTableWidgetItem* freeze_item = new QTableWidgetItem();
freeze_item->setFlags(freeze_item->flags() | (Qt::ItemIsEditable | Qt::ItemIsUserCheckable));
freeze_item->setCheckState(res.freeze ? Qt::Checked : Qt::Unchecked);
m_ui.watchTable->setItem(row, 0, freeze_item);
QTableWidgetItem* description_item = new QTableWidgetItem(QString::fromStdString(res.description));
m_ui.watchTable->setItem(row, 1, description_item);
QTableWidgetItem* address_item = new QTableWidgetItem(formatHexValue(res.address));
address_item->setFlags(address_item->flags() & ~(Qt::ItemIsEditable));
m_ui.watchTable->setItem(row, 2, address_item);
QTableWidgetItem* size_item =
new QTableWidgetItem(tr(size_strings[static_cast<u32>(res.size) + (res.is_signed ? 3 : 0)]));
size_item->setFlags(address_item->flags() & ~(Qt::ItemIsEditable));
m_ui.watchTable->setItem(row, 3, size_item);
QTableWidgetItem* value_item = new QTableWidgetItem(formatValue(res.value, res.is_signed));
m_ui.watchTable->setItem(row, 4, value_item);
row++;
}
}
m_ui.scanSaveWatch->setEnabled(!entries.empty());
m_ui.scanRemoveWatch->setEnabled(false);
}
void CheatManagerDialog::updateWatchValues()
{
QSignalBlocker sb(m_ui.watchTable);
int row = 0;
for (const MemoryWatchList::Entry& res : m_watch.GetEntries())
{
if (res.changed)
m_ui.watchTable->item(row, 4)->setText(formatValue(res.value, res.is_signed));
row++;
}
}
void CheatManagerDialog::updateScanUi()
{
m_scanner.UpdateResultsValues();
m_watch.UpdateValues();
updateResultsValues();
updateWatchValues();
}

View File

@ -0,0 +1,71 @@
#pragma once
#include "core/cheats.h"
#include "ui_cheatmanagerdialog.h"
#include <QtCore/QTimer>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialog>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTableWidget>
#include <optional>
class CheatManagerDialog : public QDialog
{
Q_OBJECT
public:
CheatManagerDialog(QWidget* parent);
~CheatManagerDialog();
protected:
void showEvent(QShowEvent* event);
void resizeEvent(QResizeEvent* event);
private Q_SLOTS:
void resizeColumns();
CheatList* getCheatList() const;
void updateCheatList();
void saveCheatList();
void cheatListCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
void cheatListItemActivated(QTreeWidgetItem* item);
void cheatListItemChanged(QTreeWidgetItem* item, int column);
void activateCheat(u32 index);
void newCategoryClicked();
void addCodeClicked();
void editCodeClicked();
void deleteCodeClicked();
void activateCodeClicked();
void importClicked();
void exportClicked();
void addToWatchClicked();
void removeWatchClicked();
void scanCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous);
void watchCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous);
void scanItemChanged(QTableWidgetItem* item);
void watchItemChanged(QTableWidgetItem* item);
void updateScanValue();
void updateScanUi();
private:
void setupAdditionalUi();
void connectUi();
void setUpdateTimerEnabled(bool enabled);
void updateResults();
void updateResultsValues();
void updateWatch();
void updateWatchValues();
QTreeWidgetItem* getItemForCheatIndex(u32 index) const;
int getSelectedCheatIndex() const;
int getSelectedResultIndex() const;
int getSelectedWatchIndex() const;
Ui::CheatManagerDialog m_ui;
MemoryScan m_scanner;
MemoryWatchList m_watch;
QTimer* m_update_timer = nullptr;
};

View File

@ -0,0 +1,550 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CheatManagerDialog</class>
<widget class="QDialog" name="CheatManagerDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>864</width>
<height>599</height>
</rect>
</property>
<property name="windowTitle">
<string>Cheat Manager</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Cheat List</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="cheatListNewCategory">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;New Category...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListAdd">
<property name="text">
<string>&amp;Add Code...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListEdit">
<property name="text">
<string>&amp;Edit Code...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListRemove">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Delete Code</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListActivate">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Activate</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListImport">
<property name="text">
<string>Import...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatListExport">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Export...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QTreeWidget" name="cheatList">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>Activation</string>
</property>
</column>
<column>
<property name="text">
<string>Instructions</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Memory Scanner</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="layoutWidget2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QTableWidget" name="scanTable">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Address</string>
</property>
</column>
<column>
<property name="text">
<string>Value</string>
</property>
</column>
<column>
<property name="text">
<string>Previous Value</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Search Parameters</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Value:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="scanValue"/>
</item>
<item>
<widget class="QComboBox" name="scanValueSigned">
<item>
<property name="text">
<string>Signed</string>
</property>
</item>
<item>
<property name="text">
<string>Unsigned</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="scanValueBase">
<item>
<property name="text">
<string>Decimal</string>
</property>
</item>
<item>
<property name="text">
<string>Hex</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Data Size:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="scanSize">
<property name="currentIndex">
<number>2</number>
</property>
<item>
<property name="text">
<string>Byte (1 byte)</string>
</property>
</item>
<item>
<property name="text">
<string>Halfword (2 bytes)</string>
</property>
</item>
<item>
<property name="text">
<string>Word (4 bytes)</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Operator:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="scanOperator">
<item>
<property name="text">
<string>Equal to...</string>
</property>
</item>
<item>
<property name="text">
<string>Not Equal to...</string>
</property>
</item>
<item>
<property name="text">
<string>Greater Than...</string>
</property>
</item>
<item>
<property name="text">
<string>Greater or Equal...</string>
</property>
</item>
<item>
<property name="text">
<string>Less Than...</string>
</property>
</item>
<item>
<property name="text">
<string>Less or Equal...</string>
</property>
</item>
<item>
<property name="text">
<string>Increased By...</string>
</property>
</item>
<item>
<property name="text">
<string>Decreased By...</string>
</property>
</item>
<item>
<property name="text">
<string>Changed By...</string>
</property>
</item>
<item>
<property name="text">
<string>Equal to Previous</string>
</property>
</item>
<item>
<property name="text">
<string>Not Equal to Previous</string>
</property>
</item>
<item>
<property name="text">
<string>Greater Than Previous</string>
</property>
</item>
<item>
<property name="text">
<string>Greater or Equal to Previous</string>
</property>
</item>
<item>
<property name="text">
<string>Less Than Previous</string>
</property>
</item>
<item>
<property name="text">
<string>Less or Equal to Previous</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Start Address:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="scanStartAddress"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>End Address:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="scanEndAddress"/>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="scanNewSearch">
<property name="text">
<string>New Search</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="scanSearchAgain">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Search Again</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="scanResetSearch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Clear Results</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTableWidget" name="watchTable">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Freeze</string>
</property>
</column>
<column>
<property name="text">
<string>Description</string>
</property>
</column>
<column>
<property name="text">
<string>Address</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>Value</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="scanAddWatch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Add To Watch</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="scanAddManualAddress">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Add Manual Address</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="scanRemoveWatch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove Watch</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="scanLoadWatch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Load Watch</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="scanSaveWatch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Save Watch</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -56,6 +56,8 @@
<ClCompile Include="audiosettingswidget.cpp" />
<ClCompile Include="autoupdaterdialog.cpp" />
<ClCompile Include="biossettingswidget.cpp" />
<ClCompile Include="cheatmanagerdialog.cpp" />
<ClCompile Include="cheatcodeeditordialog.cpp" />
<ClCompile Include="consolesettingswidget.cpp" />
<ClCompile Include="enhancementsettingswidget.cpp" />
<ClCompile Include="gamelistmodel.cpp" />
@ -86,6 +88,8 @@
<QtMoc Include="aboutdialog.h" />
<QtMoc Include="audiosettingswidget.h" />
<QtMoc Include="biossettingswidget.h" />
<QtMoc Include="cheatmanagerdialog.h" />
<QtMoc Include="cheatcodeeditordialog.h" />
<QtMoc Include="controllersettingswidget.h" />
<QtMoc Include="enhancementsettingswidget.h" />
<QtMoc Include="memorycardsettingswidget.h" />
@ -181,6 +185,12 @@
<QtUi Include="memorycardeditordialog.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="cheatmanagerdialog.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="cheatcodeeditordialog.ui">
<FileType>Document</FileType>
</QtUi>
</ItemGroup>
<ItemGroup>
<QtResource Include="resources\resources.qrc">
@ -193,6 +203,8 @@
<ClCompile Include="$(IntDir)moc_autoupdaterdialog.cpp" />
<ClCompile Include="$(IntDir)moc_advancedsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_biossettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_cheatmanagerdialog.cpp" />
<ClCompile Include="$(IntDir)moc_cheatcodeeditordialog.cpp" />
<ClCompile Include="$(IntDir)moc_consolesettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_controllersettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_enhancementsettingswidget.cpp" />

View File

@ -11,6 +11,7 @@ int main(int argc, char* argv[])
{
// Register any standard types we need elsewhere
qRegisterMetaType<std::optional<bool>>();
qRegisterMetaType<std::function<void()>>();
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);

View File

@ -1,6 +1,7 @@
#include "mainwindow.h"
#include "aboutdialog.h"
#include "autoupdaterdialog.h"
#include "cheatmanagerdialog.h"
#include "common/assert.h"
#include "core/host_display.h"
#include "core/settings.h"
@ -233,6 +234,12 @@ void MainWindow::onEmulationStopped()
m_emulation_running = false;
updateEmulationActions(false, false);
switchToGameListView();
if (m_cheat_manager_dialog)
{
delete m_cheat_manager_dialog;
m_cheat_manager_dialog = nullptr;
}
}
void MainWindow::onEmulationPaused(bool paused)
@ -316,6 +323,8 @@ void MainWindow::onChangeDiscFromPlaylistMenuAboutToHide()
void MainWindow::onCheatsMenuAboutToShow()
{
m_ui.menuCheats->clear();
connect(m_ui.menuCheats->addAction(tr("Cheat Manager")), &QAction::triggered, this, &MainWindow::onToolsCheatManagerTriggered);
m_ui.menuCheats->addSeparator();
m_host_interface->populateCheatsMenu(m_ui.menuCheats);
}
@ -645,6 +654,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running)
m_ui.actionViewSystemDisplay->setEnabled(starting || running);
m_ui.menuChangeDisc->setDisabled(starting || !running);
m_ui.menuCheats->setDisabled(starting || !running);
m_ui.actionCheatManager->setDisabled(starting || !running);
m_ui.actionSaveState->setDisabled(starting || !running);
m_ui.menuSaveState->setDisabled(starting || !running);
@ -779,6 +789,7 @@ void MainWindow::connectSignals()
connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered);
connect(m_ui.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered);
connect(m_ui.actionMemory_Card_Editor, &QAction::triggered, this, &MainWindow::onToolsMemoryCardEditorTriggered);
connect(m_ui.actionCheatManager, &QAction::triggered, this, &MainWindow::onToolsCheatManagerTriggered);
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
@ -1167,6 +1178,15 @@ void MainWindow::onToolsMemoryCardEditorTriggered()
m_memory_card_editor_dialog->show();
}
void MainWindow::onToolsCheatManagerTriggered()
{
if (!m_cheat_manager_dialog)
m_cheat_manager_dialog = new CheatManagerDialog(this);
m_cheat_manager_dialog->setModal(false);
m_cheat_manager_dialog->show();
}
void MainWindow::onToolsOpenDataDirectoryTriggered()
{
QtUtils::OpenURL(this, QUrl::fromLocalFile(m_host_interface->getUserDirectoryRelativePath(QString())));

View File

@ -15,6 +15,7 @@ class QtHostInterface;
class QtDisplayWidget;
class AutoUpdaterDialog;
class MemoryCardEditorDialog;
class CheatManagerDialog;
class HostDisplay;
struct GameListEntry;
@ -76,6 +77,7 @@ private Q_SLOTS:
void onAboutActionTriggered();
void onCheckForUpdatesActionTriggered();
void onToolsMemoryCardEditorTriggered();
void onToolsCheatManagerTriggered();
void onToolsOpenDataDirectoryTriggered();
void onGameListEntrySelected(const GameListEntry* entry);
@ -127,6 +129,7 @@ private:
SettingsDialog* m_settings_dialog = nullptr;
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
MemoryCardEditorDialog* m_memory_card_editor_dialog = nullptr;
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
bool m_emulation_running = false;
};

View File

@ -205,6 +205,7 @@
<string>&amp;Tools</string>
</property>
<addaction name="actionMemory_Card_Editor"/>
<addaction name="actionCheatManager"/>
<addaction name="separator"/>
<addaction name="actionOpenDataDirectory"/>
</widget>
@ -712,6 +713,11 @@
<string>Memory &amp;Card Editor</string>
</property>
</action>
<action name="actionCheatManager">
<property name="text">
<string>C&amp;heat Manager</string>
</property>
</action>
<action name="actionViewGameGrid">
<property name="text">
<string>Game &amp;Grid</string>

View File

@ -971,7 +971,7 @@ void QtHostInterface::populateCheatsMenu(QMenu* menu)
QAction* action = menu->addAction(tr("&Load Cheats..."));
connect(action, &QAction::triggered, [this]() {
QString filename = QFileDialog::getOpenFileName(m_main_window, tr("Select Cheat File"), QString(),
tr("PCSXR/Libretro Cheat Files (*.cht);;All Files (*.*)"));
tr("PCSXR/Libretro Cheat Files (*.cht *.txt);;All Files (*.*)"));
if (!filename.isEmpty())
loadCheatList(filename);
});
@ -980,7 +980,7 @@ void QtHostInterface::populateCheatsMenu(QMenu* menu)
action->setEnabled(has_cheat_list);
connect(action, &QAction::triggered, [this]() {
QString filename = QFileDialog::getSaveFileName(m_main_window, tr("Select Cheat File"), QString(),
tr("PCSXR/Libretro Cheat Files (*.cht);;All Files (*.*)"));
tr("PCSXR Cheat Files (*.cht);;All Files (*.*)"));
if (!filename.isEmpty())
SaveCheatList(filename.toUtf8().constData());
});
@ -1052,6 +1052,28 @@ void QtHostInterface::reloadPostProcessingShaders()
ReloadPostProcessingShaders();
}
void QtHostInterface::executeOnEmulationThread(std::function<void()> callback, bool wait)
{
if (isOnWorkerThread())
{
callback();
if (wait)
m_worker_thread_sync_execute_done.Signal();
return;
}
QMetaObject::invokeMethod(this, "executeOnEmulationThread", Qt::QueuedConnection,
Q_ARG(std::function<void()>, callback), Q_ARG(bool, wait));
if (wait)
{
// don't deadlock
while (!m_worker_thread_sync_execute_done.TryWait(10))
qApp->processEvents(QEventLoop::ExcludeSocketNotifiers);
m_worker_thread_sync_execute_done.Reset();
}
}
void QtHostInterface::loadState(const QString& filename)
{
if (!isOnWorkerThread())

View File

@ -167,6 +167,7 @@ public Q_SLOTS:
void setCheatEnabled(quint32 index, bool enabled);
void applyCheat(quint32 index);
void reloadPostProcessingShaders();
void executeOnEmulationThread(std::function<void()> callback, bool wait = false);
private Q_SLOTS:
void doStopThread();
@ -254,6 +255,7 @@ private:
QThread* m_original_thread = nullptr;
Thread* m_worker_thread = nullptr;
QEventLoop* m_worker_thread_event_loop = nullptr;
Common::Event m_worker_thread_sync_execute_done;
std::atomic_bool m_shutdown_flag{false};

View File

@ -12,6 +12,7 @@
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QStyle>
#include <QtWidgets/QTableView>
#include <QtWidgets/QTreeView>
#include <algorithm>
#include <array>
#include <map>
@ -44,10 +45,17 @@ QWidget* GetRootWidget(QWidget* widget, bool stop_at_window_or_dialog)
return widget;
}
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths)
template<typename T>
ALWAYS_INLINE_RELEASE static void ResizeColumnsForView(T* view, const std::initializer_list<int>& widths)
{
const int min_column_width = view->horizontalHeader()->minimumSectionSize();
const int max_column_width = view->horizontalHeader()->maximumSectionSize();
QHeaderView* header;
if constexpr (std::is_same_v<T, QTableView>)
header = view->horizontalHeader();
else
header = view->header();
const int min_column_width = header->minimumSectionSize();
const int max_column_width = header->maximumSectionSize();
const int total_width =
std::accumulate(widths.begin(), widths.end(), 0, [&min_column_width, &max_column_width](int a, int b) {
return a + ((b < 0) ? 0 : std::clamp(b, min_column_width, max_column_width));
@ -74,6 +82,16 @@ void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int
}
}
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths)
{
ResizeColumnsForView(view, widths);
}
void ResizeColumnsForTreeView(QTreeView* view, const std::initializer_list<int>& widths)
{
ResizeColumnsForView(view, widths);
}
static const std::map<int, QString> s_qt_key_names = {
{Qt::Key_Escape, QStringLiteral("Escape")},
{Qt::Key_Tab, QStringLiteral("Tab")},

View File

@ -2,10 +2,12 @@
#include <QtCore/QByteArray>
#include <QtCore/QMetaType>
#include <QtCore/QString>
#include <functional>
#include <initializer_list>
#include <optional>
Q_DECLARE_METATYPE(std::optional<bool>);
Q_DECLARE_METATYPE(std::function<void()>);
class ByteStream;
@ -13,6 +15,7 @@ class QComboBox;
class QFrame;
class QKeyEvent;
class QTableView;
class QTreeView;
class QWidget;
class QUrl;
@ -27,6 +30,7 @@ QWidget* GetRootWidget(QWidget* widget, bool stop_at_window_or_dialog = true);
/// Resizes columns of the table view to at the specified widths. A negative width will stretch the column to use the
/// remaining space.
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths);
void ResizeColumnsForTreeView(QTreeView* view, const std::initializer_list<int>& widths);
/// Returns a string identifier for a Qt key ID.
QString GetKeyIdentifier(int key);