diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index bbc521f8a..38241cf0f 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -13,6 +13,8 @@ set(SRCS debugger/graphics_framebuffer.cpp debugger/ramview.cpp debugger/registers.cpp + debugger/register_view.cpp + debugger/util.cpp util/spinbox.cpp bootmanager.cpp hotkeys.cpp @@ -33,6 +35,8 @@ set(HEADERS debugger/graphics_framebuffer.h debugger/ramview.h debugger/registers.h + debugger/register_view.h + debugger/util.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..7c29967a9 --- /dev/null +++ b/src/citra_qt/debugger/register_view.cpp @@ -0,0 +1,168 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +#include "core/debugger/debug_interface.h" + +#include "register_view.h" + +RegisterView::RegisterView(QWidget* parent) : QFrame(parent) { + // initialize registers + for (int cat = 0; cat < 2; cat++) { + for (int index = 0; index < g_debug->GetNumRegsInCategory(cat); index++) { + Register reg; + reg.category = cat; + reg.index = index; + reg.oldValue = 0; + reg.changed = false; + + registers.append(reg); + } + } + + initializeSize(); + + last_pc = 0; + selection = 0; + + QShortcut* up_shortcut = new QShortcut(QKeySequence(Qt::Key_Up), this); + up_shortcut->setContext(Qt::WidgetShortcut); + connect(up_shortcut, SIGNAL(activated()), this, SLOT(previousRegister())); + + QShortcut* down_shortcut = new QShortcut(QKeySequence(Qt::Key_Down), this); + down_shortcut->setContext(Qt::WidgetShortcut); + connect(down_shortcut, SIGNAL(activated()), this, SLOT(nextRegister())); + + setFocusPolicy(Qt::FocusPolicy::ClickFocus); +} + +void RegisterView::initializeSize() { + // calculate max register name length + int max_length = 0; + for (int i = 0; i < registers.size(); i++) { + const char* name = g_debug->GetRegName(registers[i].category,registers[i].index); + max_length = qMax(max_length, strlen(name)); + } + + // initialize positions + border_gap = 3; + value_offset = border_gap + (max_length + 1) * font.charWidth(); + + // characters per line = length of register name + space + length of register value + int numChars = max_length + 1 + 8; + + // calculate size of widget. borderGap pixels to all sides, + // and as many rows and columns as needed + QSize size = QSize(2 * border_gap + numChars*font.charWidth(), + 2 * border_gap + registers.size()*font.charHeight()); + + // make sure the widget has exactly that size + setMinimumSize(size); + setMaximumSize(size); +} + +void RegisterView::refreshChangedRegs() { + // the registers need to be refreshed if the PC of the core has + // changed since the last call + if (g_debug->GetPC() == last_pc) + return; + + for (Register& reg: registers) { + unsigned int value = g_debug->GetRegValue(reg.category,reg.index); + reg.changed = (value != reg.oldValue); + reg.oldValue = value; + } + + last_pc = g_debug->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.font()); + + QPoint pos; + QString value; + for (int i = 0; i < registers.size(); i++) { + Register& reg = registers[i]; + int y = border_gap + i * font.charHeight(); + + // highlight background of selected register + if (i == selection) + { + QColor col(0xE8, 0xEF, 0xFF); + p.setBrush(col); + p.setPen(col); + p.drawRect(0, y - 1, width(), font.charHeight() - 1); + } + + // draw register name + pos = font.calculateDrawPos(border_gap, y); + p.setPen(QColor(0, 0, 0x60)); + p.drawText(pos, g_debug->GetRegName(reg.category, reg.index)); + + // draw register value + pos = font.calculateDrawPos(value_offset, y); + value.sprintf("%08X", g_debug->GetRegValue(reg.category, reg.index)); + + // draw changed registers in red, rest in black + if (reg.changed) + p.setPen(QColor(Qt::red)); + else + p.setPen(QColor(Qt::black)); + + p.drawText(pos, value); + } + + // draw frame last, otherwise it gets overwritten + QFrame::paintEvent(event); +} + +int RegisterView::mapPositionToRegister(QPoint pos) const { + int y = pos.y(); + + if (y < border_gap || y + border_gap >= height()) + return -1; + + return (y - border_gap) / font.charHeight(); +} + +void RegisterView::mousePressEvent(QMouseEvent* event) { + int new_selection = mapPositionToRegister(event->pos()); + if (new_selection != -1 && selection != new_selection) { + selection = new_selection; + update(); + } +} + +void RegisterView::mouseMoveEvent(QMouseEvent* event) { + if (event->buttons() & (Qt::MouseButton::LeftButton | Qt::MouseButton::RightButton)) { + int new_selection = mapPositionToRegister(event->pos()); + if (new_selection != -1 && selection != new_selection) + { + selection = new_selection; + update(); + } + } +} + +void RegisterView::moveSelectionUp() { + selection = qMax(0, selection - 1); + update(); +} + +void RegisterView::moveSelectionDown() { + selection = qMin(registers.size() - 1, selection + 1); + update(); +} diff --git a/src/citra_qt/debugger/register_view.h b/src/citra_qt/debugger/register_view.h new file mode 100644 index 000000000..2451f5b18 --- /dev/null +++ b/src/citra_qt/debugger/register_view.h @@ -0,0 +1,52 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +#pragma once + +#include +#include "util.h" + +class RegisterView: public QFrame { + Q_OBJECT + +public: + RegisterView(QWidget* parent = NULL); + +protected: + void paintEvent(QPaintEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + +private slots: + // Select previous register + void moveSelectionUp(); + + // Select next register + void moveSelectionDown(); + +private: + // Reloads register values if necessary + void refreshChangedRegs(); + + // Calculates and sets the widget size and the used positions + void initializeSize(); + + // Returns the number of the register at the given position + int mapPositionToRegister(QPoint pos) const; + + struct Register + { + unsigned int oldValue; + bool changed; + int category; + int index; + }; + + DebuggerFont font; + int border_gap; + int value_offset; + + unsigned int last_pc; + QVector registers; + int selection; +}; diff --git a/src/citra_qt/debugger/registers.cpp b/src/citra_qt/debugger/registers.cpp index ab3666156..4726ac19b 100644 --- a/src/citra_qt/debugger/registers.cpp +++ b/src/citra_qt/debugger/registers.cpp @@ -2,68 +2,53 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "registers.h" +#include -#include "core/core.h" -#include "core/arm/arm_interface.h" +#include "core/debugger/debug_interface.h" + +#include "registers.h" 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"))); - - registers->setExpanded(true); - CSPR->setExpanded(true); - - for (int i = 0; i < 16; ++i) + for (int i = 0; i < g_debug->GetNumRegsInCategory(REGCAT_CPSR_FLAGS); i++) { - QTreeWidgetItem* child = new QTreeWidgetItem(QStringList(QString("R[%1]").arg(i, 2, 10, QLatin1Char('0')))); - registers->addChild(child); + const char* name = g_debug->GetRegName(REGCAT_CPSR_FLAGS,i); + + QCheckBox* box = new QCheckBox(name,this); + cpu_regs_ui.flags->addWidget(box); + connect(box,SIGNAL(stateChanged(int)),this,SLOT(OnFlagToggled(int))); + + flagLookup[box] = i; } - 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"))); + cpu_regs_ui.flags->addItem(new QSpacerItem(0,0,QSizePolicy::Minimum,QSizePolicy::Expanding)); +} + +void RegistersWidget::OnFlagToggled(int state) +{ + QCheckBox* sender = dynamic_cast(this->sender()); + int index = flagLookup[sender]; + + g_debug->SetRegValue(REGCAT_CPSR_FLAGS,index,state == 0 ? 0 : 1); + cpu_regs_ui.registerView->update(); } void RegistersWidget::OnDebugModeEntered() { - ARM_Interface* app_core = Core::g_app_core; + for (auto it = flagLookup.begin(); it != flagLookup.end(); it++) + { + QCheckBox* box = it.key(); + int index = 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 = g_debug->GetRegValue(REGCAT_CPSR_FLAGS,index) != 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(); } void RegistersWidget::OnDebugModeLeft() diff --git a/src/citra_qt/debugger/registers.h b/src/citra_qt/debugger/registers.h index bf8955625..fdeccc666 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 @@ -19,12 +22,9 @@ public: public slots: void OnDebugModeEntered(); void OnDebugModeLeft(); + void OnFlagToggled(int state); private: + 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 +
+
diff --git a/src/citra_qt/debugger/util.cpp b/src/citra_qt/debugger/util.cpp new file mode 100644 index 000000000..1cdcab7c1 --- /dev/null +++ b/src/citra_qt/debugger/util.cpp @@ -0,0 +1,29 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "util.h" + +DebuggerFont::DebuggerFont() { + font_ = QFont("Lucida Console", 10); + font_.setStyleHint(QFont::Courier, QFont::PreferMatch); + updateMetrics(); +} + +void DebuggerFont::setBold(bool bold) { + font_.setBold(bold); + updateMetrics(); +} + +QPoint DebuggerFont::calculateDrawPos(int left, int top) { + return QPoint(left, top + charHeight() - charDescent()); +} + +void DebuggerFont::updateMetrics() { + QFontMetrics metrics = QFontMetrics(font_); + width = metrics.width('0'); + height = metrics.height(); + descent = metrics.descent(); +} diff --git a/src/citra_qt/debugger/util.h b/src/citra_qt/debugger/util.h new file mode 100644 index 000000000..f43b46095 --- /dev/null +++ b/src/citra_qt/debugger/util.h @@ -0,0 +1,33 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +#pragma once + +#include +#include + +// Abstracts font calculations used by some +// debugger widgets +class DebuggerFont { +public: + DebuggerFont(); + + // Returns the position needed to draw a string that should + // have its upper left corner at the given coordinates + QPoint calculateDrawPos(int left, int top); + + QFont& font() { return font_; } + int charWidth() const { return width; }; + int charHeight() const { return height; }; + int charDescent() const { return descent; }; + + void setBold(bool bold); + +private: + void updateMetrics(); + + QFont font_; + int width; + int height; + int descent; +};