diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index a0ba252b3..d594915e3 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -13,6 +13,7 @@ set(SRCS debugger/graphics_framebuffer.cpp debugger/ramview.cpp debugger/registers.cpp + debugger/register_view.cpp util/spinbox.cpp bootmanager.cpp hotkeys.cpp @@ -33,6 +34,7 @@ set(HEADERS debugger/graphics_framebuffer.h debugger/ramview.h debugger/registers.h + debugger/register_view.h util/spinbox.h bootmanager.h hotkeys.h diff --git a/src/citra_qt/debugger/register_view.cpp b/src/citra_qt/debugger/register_view.cpp new file mode 100644 index 000000000..c1e3fe48c --- /dev/null +++ b/src/citra_qt/debugger/register_view.cpp @@ -0,0 +1,212 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "register_view.h" + +#include +#include +#include +#include "core/core.h" +#include "core/arm/arm_interface.h" + +enum ExtraRegisters { REGISTER_M, REGISTER_IT, REGISTER_GE, REGISTER_DNM, REGISTER_CPSR, REGISTER_EXTRACOUNT }; + +RegisterView::RegisterView(QWidget* parent) : QFrame(parent) +{ + font = QFont("Lucida Console", 10); + + QFontMetrics metrics = QFontMetrics(font); + charWidth = metrics.width('0'); + charHeight = metrics.height(); + charDescent = metrics.descent(); + + borderGap = 3; + valueX = borderGap + (maxNameLength() + 1)*charWidth; + + lastPc = 0; + selectedRegister = 0; + changedRegs = new ChangedReg[getNumRegisters()]; + memset(changedRegs, 0, sizeof(ChangedReg)*getNumRegisters()); + + QShortcut* upShortcut = new QShortcut(QKeySequence(Qt::Key_Up), this); + upShortcut->setContext(Qt::WidgetShortcut); + connect(upShortcut, SIGNAL(activated()), this, SLOT(previousRegister())); + + QShortcut* downShortcut = new QShortcut(QKeySequence(Qt::Key_Down), this); + downShortcut->setContext(Qt::WidgetShortcut); + connect(downShortcut, SIGNAL(activated()), this, SLOT(nextRegister())); + + setFocusPolicy(Qt::FocusPolicy::ClickFocus); +} + +int RegisterView::maxNameLength() const +{ + int maxLength = 0; + for (int i = 0; i < getNumRegisters(); i++) + { + maxLength = qMax(maxLength, getRegisterName(i).length()); + } + + return maxLength; +} + +QSize RegisterView::sizeHint() const +{ + int maxLength = maxNameLength(); + + int numChars = maxLength + 1 + 8; + return QSize(2 * borderGap + numChars*charWidth, 2 * borderGap + getNumRegisters()*charHeight); +} + +int RegisterView::getNumRegisters() const +{ + return 16 + REGISTER_EXTRACOUNT; +} + +QString RegisterView::getRegisterName(int index) const +{ + if (index < 16) + return QString("r%1").arg(index); + + switch (index - 16) + { + case REGISTER_CPSR: + return "cpsr"; + case REGISTER_M: + return "m"; + case REGISTER_IT: + return "it"; + case REGISTER_GE: + return "ge"; + case REGISTER_DNM: + return "dnm"; + } +} + +unsigned int RegisterView::getRegisterValue(int index) const +{ + ARM_Interface* app_core = Core::g_app_core; + if (app_core == nullptr) + return 0; + + if (index < 16) + return app_core->GetReg(index); + + switch (index - 16) + { + case REGISTER_CPSR: + return app_core->GetCPSR(); + case REGISTER_M: + return app_core->GetCPSR() & 0x1F; + case REGISTER_IT: + return (app_core->GetCPSR() >> 10) & 0x3F; + case REGISTER_GE: + return (app_core->GetCPSR() >> 16) & 0xF; + case REGISTER_DNM: + return (app_core->GetCPSR() >> 20) & 0xF; + } +} + +void RegisterView::refreshChangedRegs() +{ + ARM_Interface* app_core = Core::g_app_core; + if (app_core == nullptr || app_core->GetPC() == lastPc) + return; + + for (int i = 0; i < getNumRegisters(); i++) + { + unsigned int value = getRegisterValue(i); + changedRegs[i].changed = (value != changedRegs[i].oldValue); + changedRegs[i].oldValue = value; + } + + lastPc = app_core->GetPC(); +} + +void RegisterView::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + + refreshChangedRegs(); + + // clear background + p.setBrush(QBrush(QColor(Qt::white))); + p.setPen(QPen(QColor(Qt::white))); + p.drawRect(0, 0, width(), height()); + + p.setFont(font); + for (int i = 0; i < getNumRegisters(); i++) + { + int rowY = borderGap + i*charHeight; + int textOffset = charHeight - charDescent; + + // highlight selected register + if (i == selectedRegister) + { + QColor col(0xE8, 0xEF, 0xFF); + p.setBrush(col); + p.setPen(col); + p.drawRect(0, rowY - 1, width(), charHeight - 1); + } + + p.setPen(QColor(0, 0, 0x60)); + p.drawText(borderGap, rowY + textOffset, getRegisterName(i)); + + if (changedRegs[i].changed) + p.setPen(QColor(Qt::red)); + else + p.setPen(QColor(Qt::black)); + + QString value; + value.sprintf("%08X", getRegisterValue(i)); + + p.drawText(valueX, rowY + textOffset, value); + } + + // draw frame last, otherwise it gets overwritten + QFrame::paintEvent(event); +} + +int RegisterView::yToRow(int y) const +{ + if (y < borderGap || y + borderGap >= height()) + return -1; + + return (y - borderGap) / charHeight; +} + +void RegisterView::mousePressEvent(QMouseEvent* event) +{ + int newRow = yToRow(event->y()); + if (newRow != -1 && selectedRegister != newRow) + { + selectedRegister = newRow; + update(); + } +} + +void RegisterView::mouseMoveEvent(QMouseEvent* event) +{ + if (event->buttons() & (Qt::MouseButton::LeftButton | Qt::MouseButton::RightButton)) + { + int newRow = yToRow(event->y()); + if (newRow != -1 && selectedRegister != newRow) + { + selectedRegister = newRow; + update(); + } + } +} + +void RegisterView::previousRegister() +{ + selectedRegister = qMax(0, selectedRegister - 1); + update(); +} + +void RegisterView::nextRegister() +{ + selectedRegister = qMin(getNumRegisters() - 1, selectedRegister + 1); + update(); +} \ No newline at end of file diff --git a/src/citra_qt/debugger/register_view.h b/src/citra_qt/debugger/register_view.h new file mode 100644 index 000000000..99d57bdf7 --- /dev/null +++ b/src/citra_qt/debugger/register_view.h @@ -0,0 +1,47 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +#pragma once +#include + +class RegisterView: public QFrame +{ + Q_OBJECT + +public: + RegisterView(QWidget* parent = NULL); + +protected: + QSize sizeHint() const; + QSize minimumSizeHint() const { return sizeHint(); } + void paintEvent(QPaintEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); +private slots: + void previousRegister(); + void nextRegister(); +private: + int maxNameLength() const; + int yToRow(int y) const; + int getNumRegisters() const; + QString getRegisterName(int index) const; + unsigned int getRegisterValue(int index) const; + void refreshChangedRegs(); + + struct ChangedReg + { + unsigned int oldValue; + bool changed; + }; + + QFont font; + int charWidth; + int charHeight; + int charDescent; + int borderGap; + int valueX; + + unsigned int lastPc; + ChangedReg* changedRegs; + int selectedRegister; +}; \ No newline at end of file diff --git a/src/citra_qt/debugger/registers.cpp b/src/citra_qt/debugger/registers.cpp index e982dfb3f..592aba9bb 100644 --- a/src/citra_qt/debugger/registers.cpp +++ b/src/citra_qt/debugger/registers.cpp @@ -7,61 +7,70 @@ #include "core/core.h" #include "core/arm/arm_interface.h" +#include + RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) { cpu_regs_ui.setupUi(this); - tree = cpu_regs_ui.treeWidget; - tree->addTopLevelItem(registers = new QTreeWidgetItem(QStringList("Registers"))); - tree->addTopLevelItem(CSPR = new QTreeWidgetItem(QStringList("CSPR"))); + addCpsrFlag("n",31); + addCpsrFlag("z",30); + addCpsrFlag("c",29); + addCpsrFlag("v",28); + addCpsrFlag("q",27); + addCpsrFlag("j",24); + addCpsrFlag("e",9); + addCpsrFlag("a",8); + addCpsrFlag("i",7); + addCpsrFlag("f",6); + addCpsrFlag("t",5); + + cpu_regs_ui.flags->addItem(new QSpacerItem(0,0,QSizePolicy::Minimum,QSizePolicy::Expanding)); +} - registers->setExpanded(true); - CSPR->setExpanded(true); +void RegistersWidget::addCpsrFlag(QString name, int bitPos) +{ + QCheckBox* box = new QCheckBox(name,this); + cpu_regs_ui.flags->addWidget(box); + connect(box,SIGNAL(stateChanged(int)),this,SLOT(OnFlagToggled(int))); - for (int i = 0; i < 16; ++i) + flagLookup[box] = bitPos; +} + +void RegistersWidget::OnFlagToggled(int state) +{ + QCheckBox* sender = dynamic_cast(this->sender()); + int bitPos = flagLookup[sender]; + + // TODO: should probably not be done while the core is running... + ARM_Interface* core = Core::g_app_core; + if (core != nullptr) { - QTreeWidgetItem* child = new QTreeWidgetItem(QStringList(QString("R[%1]").arg(i, 2, 10, QLatin1Char('0')))); - registers->addChild(child); - } + u32 cpsr = core->GetCPSR() & ~(1 << bitPos); - CSPR->addChild(new QTreeWidgetItem(QStringList("M"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("T"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("F"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("I"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("A"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("E"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("IT"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("GE"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("DNM"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("J"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("Q"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("V"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("C"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("Z"))); - CSPR->addChild(new QTreeWidgetItem(QStringList("N"))); + if (state != 0) + cpsr |= (1 << bitPos); + + core->SetCPSR(cpsr); + cpu_regs_ui.registerView->update(); + } } void RegistersWidget::OnCPUStepped() { ARM_Interface* app_core = Core::g_app_core; + + u32 cpsr = app_core->GetCPSR(); + for (auto it = flagLookup.begin(); it != flagLookup.end(); it++) + { + QCheckBox* box = it.key(); + int bitPos = it.value(); - for (int i = 0; i < 16; ++i) - registers->child(i)->setText(1, QString("0x%1").arg(app_core->GetReg(i), 8, 16, QLatin1Char('0'))); + bool check = (cpsr & (1 << bitPos)) != 0; + box->blockSignals(true); + box->setChecked(check); + box->blockSignals(false); + } - CSPR->setText(1, QString("0x%1").arg(app_core->GetCPSR(), 8, 16, QLatin1Char('0'))); - CSPR->child(0)->setText(1, QString("b%1").arg(app_core->GetCPSR() & 0x1F, 5, 2, QLatin1Char('0'))); // M - Mode - CSPR->child(1)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 5) & 0x1)); // T - State - CSPR->child(2)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 6) & 0x1)); // F - FIQ disable - CSPR->child(3)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 7) & 0x1)); // I - IRQ disable - CSPR->child(4)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 8) & 0x1)); // A - Imprecise abort - CSPR->child(5)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 9) & 0x1)); // E - Data endianess - CSPR->child(6)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 10) & 0x3F)); // IT - If-Then state (DNM) - CSPR->child(7)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 16) & 0xF)); // GE - Greater-than-or-Equal - CSPR->child(8)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 20) & 0xF)); // DNM - Do not modify - CSPR->child(9)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 24) & 0x1)); // J - Java state - CSPR->child(10)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 27) & 0x1)); // Q - Sticky overflow - CSPR->child(11)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 28) & 0x1)); // V - Overflow - CSPR->child(12)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 29) & 0x1)); // C - Carry/Borrow/Extend - CSPR->child(13)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 30) & 0x1)); // Z - Zero - CSPR->child(14)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 31) & 0x1)); // N - Negative/Less than + cpu_regs_ui.registerView->update(); } diff --git a/src/citra_qt/debugger/registers.h b/src/citra_qt/debugger/registers.h index ac8429f2b..ce59fc29e 100644 --- a/src/citra_qt/debugger/registers.h +++ b/src/citra_qt/debugger/registers.h @@ -6,9 +6,12 @@ #include #include +#include +#include class QTreeWidget; + class RegistersWidget : public QDockWidget { Q_OBJECT @@ -18,12 +21,10 @@ public: public slots: void OnCPUStepped(); - + void OnFlagToggled(int state); private: + void addCpsrFlag(QString name, int bitPos); + + QMap flagLookup; Ui::ARMRegisters cpu_regs_ui; - - QTreeWidget* tree; - - QTreeWidgetItem* registers; - QTreeWidgetItem* CSPR; }; diff --git a/src/citra_qt/debugger/registers.ui b/src/citra_qt/debugger/registers.ui index c81ae03f9..aced7af13 100644 --- a/src/citra_qt/debugger/registers.ui +++ b/src/citra_qt/debugger/registers.ui @@ -14,27 +14,41 @@ ARM Registers - - - - - true + + + + + QFrame::Panel + + + QFrame::Sunken + + + 1 + + + 3 - - - Register - - - - - Value - - + + + + 2 + + + + + + RegisterView + QFrame +
debugger/register_view.h
+ 1 +
+