bsnes/qt/tools/cheateditor.cpp

391 lines
11 KiB
C++
Executable File

#include "cheateditor.moc"
CheatEditorWindow *cheatEditorWindow;
CheatImportWindow *cheatImportWindow;
CheatEditorWindow::CheatEditorWindow() {
lock = false;
document = 0;
layout = new QVBoxLayout;
layout->setMargin(Style::WindowMargin);
layout->setSpacing(Style::WidgetSpacing);
setLayout(layout);
list = new QTreeWidget;
list->setColumnCount(3);
list->setHeaderLabels(QStringList() << "Slot" << "Code" << "Description");
list->setColumnWidth(1, list->fontMetrics().width(" 89AB-CDEF+... "));
list->setAllColumnsShowFocus(true);
list->sortByColumn(0, Qt::AscendingOrder);
list->setRootIsDecorated(false);
list->setSelectionMode(QAbstractItemView::ExtendedSelection);
list->resizeColumnToContents(0);
layout->addWidget(list);
gridLayout = new QGridLayout;
layout->addLayout(gridLayout);
codeLabel = new QLabel("Code(s):");
codeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
gridLayout->addWidget(codeLabel, 0, 0);
codeEdit = new QLineEdit;
gridLayout->addWidget(codeEdit, 0, 1);
descLabel = new QLabel("Description:");
descLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
gridLayout->addWidget(descLabel, 1, 0);
descEdit = new QLineEdit;
gridLayout->addWidget(descEdit, 1, 1);
controlLayout = new QHBoxLayout;
layout->addLayout(controlLayout);
cheatEnableBox = new QCheckBox("Enable Cheat Engine");
cheatEnableBox->setToolTip("Unchecking this disables all cheat codes");
cheatEnableBox->setChecked(SNES::cheat.enabled());
controlLayout->addWidget(cheatEnableBox);
spacer = new QWidget;
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
controlLayout->addWidget(spacer);
findButton = new QPushButton("Find Cheat Codes ...");
controlLayout->addWidget(findButton);
clearButton = new QPushButton("Clear Selected");
controlLayout->addWidget(clearButton);
cheatImportWindow = new CheatImportWindow;
synchronize();
connect(list, SIGNAL(itemSelectionChanged()), this, SLOT(listChanged()));
connect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(bind()));
connect(codeEdit, SIGNAL(textEdited(const QString&)), this, SLOT(codeEdited()));
connect(descEdit, SIGNAL(textEdited(const QString&)), this, SLOT(descEdited()));
connect(cheatEnableBox, SIGNAL(stateChanged(int)), this, SLOT(toggleCheatEnable()));
connect(findButton, SIGNAL(released()), this, SLOT(findCheatCodes()));
connect(clearButton, SIGNAL(released()), this, SLOT(clearSelected()));
}
void CheatEditorWindow::synchronize() {
auto items = list->selectedItems();
if(items.count() == 1) {
descEdit->setEnabled(true);
codeEdit->setEnabled(true);
} else {
descEdit->setText("");
codeEdit->setText("");
descEdit->setEnabled(false);
codeEdit->setEnabled(false);
}
clearButton->setEnabled(items.count() > 0);
findButton->setEnabled(SNES::cartridge.loaded());
if(SNES::cartridge.loaded() == false && cheatImportWindow->isVisible()) cheatImportWindow->close();
}
void CheatEditorWindow::load(const char *filename) {
lock = true;
list->clear();
list->setSortingEnabled(false);
SNES::cheat.reset();
string data;
lstring line;
if(data.readfile(filename)) {
data.replace("\r", "");
line.split("\n", data);
}
for(unsigned i = 0; i < 128; i++) {
lstring part;
if(line.size() > i) part.qsplit(",", line[i]);
for(unsigned n = 0; n <= 2; n++) {
part[n].trim(" ");
part[n].trim("\"");
}
part[2].replace("\\q", "\"");
auto item = new QTreeWidgetItem(list);
item->setData(0, Qt::UserRole, QVariant(i));
item->setText(0, strunsigned<3, ' '>(i + 1));
item->setCheckState(0, part[0] == "enabled" ? Qt::Checked : Qt::Unchecked);
item->setText(1, part[1]);
item->setText(2, part[2]);
}
list->resizeColumnToContents(0);
list->setSortingEnabled(true);
list->header()->setSortIndicatorShown(false);
lock = false;
bind();
update();
}
void CheatEditorWindow::save(const char *filename) {
bool empty = true;
string data[128];
auto items = list->findItems("", Qt::MatchContains);
foreach(item, items) {
unsigned index = item->data(0, Qt::UserRole).toUInt();
string code = item->text(1).toUtf8().constData();
string desc = item->text(2).toUtf8().constData();
desc.replace("\"", "\\q");
if((code != "") || (desc != "")) empty = false;
data[index] << "\"" << (item->checkState(0) == Qt::Checked ? "enabled" : "disabled") << "\",";
data[index] << "\"" << code << "\",";
data[index] << "\"" << desc << "\"\r\n";
}
if(empty == true) {
unlink(filename);
} else {
file fp;
if(fp.open(filename, file::mode_write)) {
//determine how many rows from the bottom up are empty, and exclude them from the file
//eg if only the first three slots are used, don't save the last 125 empty slots
unsigned last = 127;
do {
if(data[last] != "\"disabled\",\"\",\"\"\r\n") break;
} while(--last);
for(unsigned i = 0; i <= last; i++) fp.print(data[i]);
fp.close();
}
}
list->clear();
SNES::cheat.reset();
SNES::cheat.synchronize();
}
void CheatEditorWindow::update() {
auto items = list->findItems("", Qt::MatchContains);
foreach(item, items) {
string code = item->text(1).toUtf8().constData();
string desc = item->text(2).toUtf8().constData();
if((code != "") || (desc != "")) {
item->setForeground(0, QBrush(QColor(0, 0, 0)));
} else {
//highlight empty slots in gray
item->setForeground(0, QBrush(QColor(128, 128, 128)));
}
unsigned index = item->data(0, Qt::UserRole).toUInt();
if(SNES::cheat[index].addr.size() > 0) {
item->setForeground(1, QBrush(QColor(0, 0, 0)));
} else {
//highlight invalid codes in red
//(this will also highlight empty codes, but as there is no text, it's not an issue)
item->setForeground(1, QBrush(QColor(255, 0, 0)));
}
}
}
void CheatEditorWindow::bind() {
if(lock) return;
auto items = list->findItems("", Qt::MatchContains);
foreach(item, items) {
unsigned index = item->data(0, Qt::UserRole).toUInt();
SNES::cheat[index] = item->text(1).toUtf8().constData();
SNES::cheat[index].enabled = item->checkState(0) == Qt::Checked;
}
SNES::cheat.synchronize();
}
void CheatEditorWindow::listChanged() {
if(lock) return;
auto items = list->selectedItems();
if(items.count() > 0) {
auto item = items[0];
codeEdit->setText(item->text(1));
descEdit->setText(item->text(2));
}
synchronize();
}
void CheatEditorWindow::codeEdited() {
if(lock) return;
auto items = list->selectedItems();
if(items.count() == 1) {
auto item = items[0];
item->setText(1, codeEdit->text());
}
bind();
update();
}
void CheatEditorWindow::descEdited() {
if(lock) return;
auto items = list->selectedItems();
if(items.count() == 1) {
auto item = items[0];
item->setText(2, descEdit->text());
}
update();
}
void CheatEditorWindow::toggleCheatEnable() {
SNES::cheat.enable(cheatEnableBox->isChecked());
}
void CheatEditorWindow::findCheatCodes() {
string data;
QFile file(":/cheats.xml");
if(file.open(QIODevice::ReadOnly | QIODevice::Text)) {
data = file.readAll().constData();
file.close();
}
if(auto position = strpos(data, SNES::cartridge.sha256())) {
const char *block = (const char*)data + position() - 19;
if(position = strpos(block, "</cartridge>")) {
xml_element document = xml_parse(substr(block, 0, position() + 12));
if(document.element.size() == 0) return;
cheatImportWindow->refresh(document.element[0]);
cheatImportWindow->show();
return;
}
}
audio.clear();
QMessageBox::information(toolsWindow, "Cheat Code Importer",
"Sorry, no cheat codes were found for the currently loaded cartridge."
);
}
void CheatEditorWindow::clearSelected() {
if(lock) return;
auto items = list->selectedItems();
foreach(item, items) {
item->setCheckState(0, Qt::Unchecked);
item->setText(1, "");
item->setText(2, "");
}
codeEdit->setText("");
descEdit->setText("");
bind();
update();
}
//=================
//CheatImportWindow
//=================
void CheatImportWindow::refresh(xml_element &root) {
list->clear();
foreach(node, root.element) {
if(node.name == "name") {
title->setText(string() << "<b>Name:</b> " << node.parse());
} else if(node.name == "cheat") {
string description = "<undefined>";
string code = "";
foreach(leaf, node.element) {
if(leaf.name == "description") {
description = leaf.parse();
} else if(leaf.name == "code") {
if(code != "") code << "+";
code << leaf.content;
}
}
auto item = new QTreeWidgetItem(list);
item->setCheckState(0, Qt::Unchecked);
item->setText(0, string() << description);
QString qcode = code;
item->setData(0, Qt::UserRole, QVariant(qcode));
}
}
}
CheatImportWindow::CheatImportWindow() {
resize(640, 360);
setObjectName("cheat-import-window");
setWindowTitle("Cheat Code Importer");
setGeometryString(&config().geometry.cheatImportWindow);
application.windowList.append(this);
layout = new QVBoxLayout;
layout->setMargin(Style::WindowMargin);
layout->setSpacing(Style::WidgetSpacing);
setLayout(layout);
title = new QLabel("Test");
layout->addWidget(title);
list = new QTreeWidget;
list->setColumnCount(1);
list->setAllColumnsShowFocus(true);
list->setRootIsDecorated(false);
list->setHeaderHidden(true);
layout->addWidget(list);
controlLayout = new QHBoxLayout;
layout->addLayout(controlLayout);
selectAllButton = new QPushButton("Select All");
controlLayout->addWidget(selectAllButton);
clearAllButton = new QPushButton("Clear All");
controlLayout->addWidget(clearAllButton);
spacer = new QWidget;
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
controlLayout->addWidget(spacer);
okButton = new QPushButton("Ok");
controlLayout->addWidget(okButton);
cancelButton = new QPushButton("Cancel");
controlLayout->addWidget(cancelButton);
connect(selectAllButton, SIGNAL(released()), this, SLOT(selectAllCodes()));
connect(clearAllButton, SIGNAL(released()), this, SLOT(clearAllCodes()));
connect(okButton, SIGNAL(released()), this, SLOT(addSelectedCodes()));
connect(cancelButton, SIGNAL(released()), this, SLOT(close()));
}
void CheatImportWindow::selectAllCodes() {
auto items = list->findItems("", Qt::MatchContains);
foreach(item, items) item->setCheckState(0, Qt::Checked);
}
void CheatImportWindow::clearAllCodes() {
auto items = list->findItems("", Qt::MatchContains);
foreach(item, items) item->setCheckState(0, Qt::Unchecked);
}
void CheatImportWindow::addSelectedCodes() {
auto items = list->findItems("", Qt::MatchContains);
foreach(item, items) {
if(item->checkState(0) == Qt::Checked) {
auto codeslots = cheatEditorWindow->list->findItems("", Qt::MatchContains);
foreach(codeslot, codeslots) {
if(codeslot->text(1) == "" && codeslot->text(2) == "") {
codeslot->setCheckState(0, Qt::Unchecked);
codeslot->setText(1, item->data(0, Qt::UserRole).toString());
codeslot->setText(2, item->text(0));
break;
}
}
}
}
cheatEditorWindow->bind();
cheatEditorWindow->update();
close();
}