mirror of https://github.com/PCSX2/pcsx2.git
447 lines
15 KiB
C++
447 lines
15 KiB
C++
|
/* PCSX2 - PS2 Emulator for PCs
|
||
|
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||
|
*
|
||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||
|
* PURPOSE. See the GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "PrecompiledHeader.h"
|
||
|
|
||
|
#include "RegisterWidget.h"
|
||
|
|
||
|
#include "common/BitCast.h"
|
||
|
|
||
|
#include "QtUtils.h"
|
||
|
#include <QtGui/QMouseEvent>
|
||
|
#include <QtWidgets/QTabBar>
|
||
|
#include <QtWidgets/QStylePainter>
|
||
|
#include <QtWidgets/QStyleOptionTab>
|
||
|
#include <QtGui/QClipboard>
|
||
|
#include <QtWidgets/QInputDialog>
|
||
|
#include <QtWidgets/QProxyStyle>
|
||
|
#include <QtWidgets/QMessageBox>
|
||
|
|
||
|
#include <algorithm>
|
||
|
|
||
|
#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(bit_cast<float>(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(bit_cast<float>(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 = ((event->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->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::customMenuRequested(QPoint pos)
|
||
|
{
|
||
|
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();
|
||
|
|
||
|
int categoryIndex = ui.registerTabs->currentIndex();
|
||
|
|
||
|
QAction* action = 0;
|
||
|
|
||
|
if (categoryIndex == EECAT_FPR)
|
||
|
{
|
||
|
m_contextMenu->addAction(action = new QAction(m_showFPRFloat ? "View as hex" : "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 ? "View as hex" : "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("Copy Top Half", this));
|
||
|
connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyTop);
|
||
|
m_contextMenu->addAction(action = new QAction("Copy Bottom Half", this));
|
||
|
connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyBottom);
|
||
|
m_contextMenu->addAction(action = new QAction("Copy Segment", this));
|
||
|
connect(action, &QAction::triggered, this, &RegisterWidget::contextCopySegment);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_contextMenu->addAction(action = new QAction("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("Change Top Half", this));
|
||
|
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeTop);
|
||
|
m_contextMenu->addAction(action = new QAction("Change Bottom Half", this));
|
||
|
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeBottom);
|
||
|
m_contextMenu->addAction(action = new QAction("Change Segment", this));
|
||
|
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeSegment);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_contextMenu->addAction(action = new QAction("Change Value", this));
|
||
|
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeValue);
|
||
|
}
|
||
|
|
||
|
m_contextMenu->addSeparator();
|
||
|
|
||
|
m_contextMenu->addAction(action = new QAction("Go to in Disassembly", this));
|
||
|
connect(action, &QAction::triggered, this, &RegisterWidget::contextGotoDisasm);
|
||
|
|
||
|
m_contextMenu->addAction(action = new QAction("Go to in Memory", 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(bit_cast<float>(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(bit_cast<float>(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(bit_cast<float>((u32)currentValue));
|
||
|
|
||
|
QString input = QInputDialog::getText(this, QString("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, "Invalid register value", "Invalid hexadecimal register value.");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
out = bit_cast<u32>(input.toFloat(&ok));
|
||
|
if (!ok)
|
||
|
{
|
||
|
QMessageBox::warning(this, "Invalid register value", "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, "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);
|
||
|
}
|