// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: GPL-3.0+ #include "RegisterWidget.h" #include "QtUtils.h" #include #include #include #include #include #include #include #include #include #include #define CAT_SHOW_FLOAT (categoryIndex == EECAT_FPR && m_showFPRFloat) || (categoryIndex == EECAT_VU0F && m_showVU0FFloat) using namespace QtUtils; RegisterWidget::RegisterWidget(QWidget* parent) : QWidget(parent) { this->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); ui.setupUi(this); ui.registerTabs->setDrawBase(false); connect(this, &RegisterWidget::customContextMenuRequested, this, &RegisterWidget::customMenuRequested); connect(ui.registerTabs, &QTabBar::currentChanged, this, &RegisterWidget::tabCurrentChanged); }; RegisterWidget::~RegisterWidget() { } void RegisterWidget::SetCpu(DebugInterface* cpu) { m_cpu = cpu; for (int i = 0; i < m_cpu->getRegisterCategoryCount(); i++) { ui.registerTabs->addTab(m_cpu->getRegisterCategoryName(i)); } connect(ui.registerTabs, &QTabBar::currentChanged, [this]() { this->repaint(); }); } void RegisterWidget::tabCurrentChanged(int cur) { m_rowStart = 0; } void RegisterWidget::paintEvent(QPaintEvent* event) { if (!m_cpu) return; QPainter painter(this); painter.setPen(this->palette().text().color()); m_renderStart = QPoint(0, ui.registerTabs->pos().y() + ui.registerTabs->size().height()); const QSize renderSize = QSize(this->size().width(), this->size().height() - ui.registerTabs->size().height()); m_rowHeight = painter.fontMetrics().height() + 2; m_rowEnd = m_rowStart + (renderSize.height() / m_rowHeight) - 1; // Maybe move this to a onsize event bool alternate = m_rowStart % 2; const int categoryIndex = ui.registerTabs->currentIndex(); // Used for 128 bit and VU0f registers const int titleStartX = m_renderStart.x() + (painter.fontMetrics().averageCharWidth() * 6); m_fieldWidth = ((renderSize.width() - (painter.fontMetrics().averageCharWidth() * 6)) / 4); m_fieldStartX[0] = titleStartX; m_fieldStartX[1] = titleStartX + m_fieldWidth; m_fieldStartX[2] = titleStartX + (m_fieldWidth * 2); m_fieldStartX[3] = titleStartX + (m_fieldWidth * 3); if (categoryIndex == EECAT_VU0F) { painter.fillRect(m_renderStart.x(), m_renderStart.y(), renderSize.width(), m_rowHeight, this->palette().highlight()); painter.drawText(m_fieldStartX[0], m_renderStart.y(), m_fieldWidth, m_rowHeight, Qt::AlignLeft, "W"); painter.drawText(m_fieldStartX[1], m_renderStart.y(), m_fieldWidth, m_rowHeight, Qt::AlignLeft, "Z"); painter.drawText(m_fieldStartX[2], m_renderStart.y(), m_fieldWidth, m_rowHeight, Qt::AlignLeft, "Y"); painter.drawText(m_fieldStartX[3], m_renderStart.y(), m_fieldWidth, m_rowHeight, Qt::AlignLeft, "X"); m_renderStart += QPoint(0, m_rowHeight); // Make room for VU0f titles } // Find the longest register name and calculate where to place our values // off of that. // Can probably constexpr the loop out as register names are known during runtime int safeValueStartX = 0; for (int i = 0; i < m_cpu->getRegisterCount(categoryIndex); i++) { const int registerNameWidth = strlen(m_cpu->getRegisterName(categoryIndex, i)); if (safeValueStartX < registerNameWidth) { safeValueStartX = registerNameWidth; } } // Add a space between the value and name safeValueStartX += 2; // Convert to width in pixels safeValueStartX *= painter.fontMetrics().averageCharWidth(); // Make it relative to where we start rendering safeValueStartX += m_renderStart.x(); for (s32 i = 0; i < m_cpu->getRegisterCount(categoryIndex) - m_rowStart; i++) { const s32 registerIndex = i + m_rowStart; const int yStart = (i * m_rowHeight) + m_renderStart.y(); painter.fillRect(m_renderStart.x(), yStart, renderSize.width(), m_rowHeight, alternate ? this->palette().base() : this->palette().alternateBase()); alternate = !alternate; // Draw register name painter.setPen(this->palette().text().color()); painter.drawText(m_renderStart.x() + painter.fontMetrics().averageCharWidth(), yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, m_cpu->getRegisterName(categoryIndex, registerIndex)); if (m_cpu->getRegisterSize(categoryIndex) == 128) { const u128 curRegister = m_cpu->getRegister(categoryIndex, registerIndex); int regIndex = 3; for (int j = 0; j < 4; j++) { if (m_selectedRow == registerIndex && m_selected128Field == j) painter.setPen(this->palette().highlight().color()); else painter.setPen(this->palette().text().color()); if (categoryIndex == EECAT_VU0F && m_showVU0FFloat) painter.drawText(m_fieldStartX[j], yStart, m_fieldWidth, m_rowHeight, Qt::AlignLeft, painter.fontMetrics().elidedText(QString::number(std::bit_cast(m_cpu->getRegister(categoryIndex, registerIndex)._u32[regIndex])), Qt::ElideRight, m_fieldWidth - painter.fontMetrics().averageCharWidth())); else painter.drawText(m_fieldStartX[j], yStart, m_fieldWidth, m_rowHeight, Qt::AlignLeft, FilledQStringFromValue(curRegister._u32[regIndex], 16)); regIndex--; } painter.setPen(this->palette().text().color()); } else { if (m_selectedRow == registerIndex) painter.setPen(this->palette().highlight().color()); else painter.setPen(this->palette().text().color()); if (categoryIndex == EECAT_FPR && m_showFPRFloat) painter.drawText(safeValueStartX, yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, QString("%1").arg(QString::number(std::bit_cast(m_cpu->getRegister(categoryIndex, registerIndex)._u32[0]))).toUpper()); else if (m_cpu->getRegisterSize(categoryIndex) == 64) painter.drawText(safeValueStartX, yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, FilledQStringFromValue(m_cpu->getRegister(categoryIndex, registerIndex).lo, 16)); else painter.drawText(safeValueStartX, yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, FilledQStringFromValue(m_cpu->getRegister(categoryIndex, registerIndex)._u32[0], 16)); } } painter.end(); } void RegisterWidget::mousePressEvent(QMouseEvent* event) { const int categoryIndex = ui.registerTabs->currentIndex(); m_selectedRow = static_cast(((event->position().y() - m_renderStart.y()) / m_rowHeight)) + m_rowStart; // For 128 bit types, support selecting segments if (m_cpu->getRegisterSize(categoryIndex) == 128) { constexpr auto inRange = [](u32 low, u32 high, u32 val) { return (low <= val && val <= high); }; for (int i = 0; i < 4; i++) { if (inRange(m_fieldStartX[i], m_fieldStartX[i] + m_fieldWidth, event->position().x())) { m_selected128Field = i; } } } this->repaint(); } void RegisterWidget::wheelEvent(QWheelEvent* event) { if (event->angleDelta().y() < 0 && m_rowEnd < m_cpu->getRegisterCount(ui.registerTabs->currentIndex())) { m_rowStart += 1; } else if (event->angleDelta().y() > 0 && m_rowStart > 0) { m_rowStart -= 1; } this->repaint(); } void RegisterWidget::mouseDoubleClickEvent(QMouseEvent* event) { if (!m_cpu->isAlive()) return; if (m_selectedRow > m_rowEnd) // Unsigned underflow; selectedRow will be > m_rowEnd (technically negative) return; const int categoryIndex = ui.registerTabs->currentIndex(); if (m_cpu->getRegisterSize(categoryIndex) == 128) contextChangeSegment(); else contextChangeValue(); } void RegisterWidget::customMenuRequested(QPoint pos) { if (!m_cpu->isAlive()) return; if (m_selectedRow > m_rowEnd) // Unsigned underflow; selectedRow will be > m_rowEnd (technically negative) return; // Unlike the disassembly widget, we need to create a new context menu every time // we show it. Because some register groups are special if (!m_contextMenu) m_contextMenu = new QMenu(this); else m_contextMenu->clear(); const int categoryIndex = ui.registerTabs->currentIndex(); QAction* action = 0; if (categoryIndex == EECAT_FPR) { m_contextMenu->addAction(action = new QAction(m_showFPRFloat ? tr("View as hex") : tr("View as float"))); connect(action, &QAction::triggered, this, [this]() { m_showFPRFloat = !m_showFPRFloat; }); m_contextMenu->addSeparator(); } if (categoryIndex == EECAT_VU0F) { m_contextMenu->addAction(action = new QAction(m_showVU0FFloat ? tr("View as hex") : tr("View as float"))); connect(action, &QAction::triggered, this, [this]() { m_showVU0FFloat = !m_showVU0FFloat; }); m_contextMenu->addSeparator(); } if (m_cpu->getRegisterSize(categoryIndex) == 128) { m_contextMenu->addAction(action = new QAction(tr("Copy Top Half"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyTop); m_contextMenu->addAction(action = new QAction(tr("Copy Bottom Half"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyBottom); m_contextMenu->addAction(action = new QAction(tr("Copy Segment"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextCopySegment); } else { m_contextMenu->addAction(action = new QAction(tr("Copy Value"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyValue); } m_contextMenu->addSeparator(); if (m_cpu->getRegisterSize(categoryIndex) == 128) { m_contextMenu->addAction(action = new QAction(tr("Change Top Half"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeTop); m_contextMenu->addAction(action = new QAction(tr("Change Bottom Half"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeBottom); m_contextMenu->addAction(action = new QAction(tr("Change Segment"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeSegment); } else { m_contextMenu->addAction(action = new QAction(tr("Change Value"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeValue); } m_contextMenu->addSeparator(); m_contextMenu->addAction(action = new QAction(tr("Go to in Disassembly"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextGotoDisasm); m_contextMenu->addAction(action = new QAction(tr("Go to in Memory View"), this)); connect(action, &QAction::triggered, this, &RegisterWidget::contextGotoMemory); m_contextMenu->popup(this->mapToGlobal(pos)); } void RegisterWidget::contextCopyValue() { const int categoryIndex = ui.registerTabs->currentIndex(); const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow); if (CAT_SHOW_FLOAT) QApplication::clipboard()->setText(QString("%1").arg(QString::number(std::bit_cast(val._u32[0])).toUpper(), 16)); else QApplication::clipboard()->setText(QString("%1").arg(QString::number(val._u64[0], 16).toUpper(), 16)); } void RegisterWidget::contextCopyTop() { const int categoryIndex = ui.registerTabs->currentIndex(); const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow); QApplication::clipboard()->setText(FilledQStringFromValue(val.hi, 16)); } void RegisterWidget::contextCopyBottom() { const int categoryIndex = ui.registerTabs->currentIndex(); const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow); QApplication::clipboard()->setText(FilledQStringFromValue(val.lo, 16)); } void RegisterWidget::contextCopySegment() { const int categoryIndex = ui.registerTabs->currentIndex(); const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow); if (CAT_SHOW_FLOAT) QApplication::clipboard()->setText(FilledQStringFromValue(std::bit_cast(val._u32[3 - m_selected128Field]), 10)); else QApplication::clipboard()->setText(FilledQStringFromValue(val._u32[3 - m_selected128Field], 16)); } bool RegisterWidget::contextFetchNewValue(u64& out, u64 currentValue, bool segment) { const int categoryIndex = ui.registerTabs->currentIndex(); const bool floatingPoint = CAT_SHOW_FLOAT && segment; const int regSize = m_cpu->getRegisterSize(categoryIndex); bool ok = false; QString existingValue("%1"); if (!floatingPoint) existingValue = existingValue.arg(currentValue, regSize == 64 ? 16 : 8, 16, QChar('0')); else existingValue = existingValue.arg(std::bit_cast((u32)currentValue)); //: Changing the value in a CPU register (e.g. "Change t0") QString input = QInputDialog::getText(this, tr("Change %1").arg(m_cpu->getRegisterName(categoryIndex, m_selectedRow)), "", QLineEdit::Normal, existingValue, &ok); if (!ok) return false; if (!floatingPoint) // Get input as hexadecimal { out = input.toULongLong(&ok, 16); if (!ok) { QMessageBox::warning(this, tr("Invalid register value"), tr("Invalid hexadecimal register value.")); return false; } } else { out = std::bit_cast(input.toFloat(&ok)); if (!ok) { QMessageBox::warning(this, tr("Invalid register value"), tr("Invalid floating-point register value.")); return false; } } return true; } void RegisterWidget::contextChangeValue() { const int categoryIndex = ui.registerTabs->currentIndex(); u64 newVal; if (contextFetchNewValue(newVal, m_cpu->getRegister(categoryIndex, m_selectedRow).lo)) { m_cpu->setRegister(categoryIndex, m_selectedRow, u128::From64(newVal)); VMUpdate(); } } void RegisterWidget::contextChangeTop() { u64 newVal; u128 oldVal = m_cpu->getRegister(ui.registerTabs->currentIndex(), m_selectedRow); if (contextFetchNewValue(newVal, oldVal.hi)) { oldVal.hi = newVal; m_cpu->setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); VMUpdate(); } } void RegisterWidget::contextChangeBottom() { u64 newVal; u128 oldVal = m_cpu->getRegister(ui.registerTabs->currentIndex(), m_selectedRow); if (contextFetchNewValue(newVal, oldVal.lo)) { oldVal.lo = newVal; m_cpu->setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); VMUpdate(); } } void RegisterWidget::contextChangeSegment() { u64 newVal; u128 oldVal = m_cpu->getRegister(ui.registerTabs->currentIndex(), m_selectedRow); if (contextFetchNewValue(newVal, oldVal._u32[3 - m_selected128Field], true)) { oldVal._u32[3 - m_selected128Field] = (u32)newVal; m_cpu->setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal); VMUpdate(); } } void RegisterWidget::contextGotoDisasm() { const int categoryIndex = ui.registerTabs->currentIndex(); u128 regVal = m_cpu->getRegister(categoryIndex, m_selectedRow); u32 addr = 0; if (m_cpu->getRegisterSize(categoryIndex) == 128) addr = regVal._u32[3 - m_selected128Field]; else addr = regVal._u32[0]; if (m_cpu->isValidAddress(addr)) gotoInDisasm(addr); else QMessageBox::warning(this, tr("Invalid target address"), ("This register holds an invalid address.")); } void RegisterWidget::contextGotoMemory() { const int categoryIndex = ui.registerTabs->currentIndex(); u128 regVal = m_cpu->getRegister(categoryIndex, m_selectedRow); u32 addr = 0; if (m_cpu->getRegisterSize(categoryIndex) == 128) addr = regVal._u32[3 - m_selected128Field]; else addr = regVal._u32[0]; gotoInMemory(addr); }