mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-14 13:27:45 -07:00
Merge c35dc0cab7
into 2c92e5b5b3
This commit is contained in:
commit
02e061d84b
@ -8,12 +8,14 @@
|
|||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
#include <QColorDialog>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
#include <QLineEdit>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
#include <QSignalBlocker>
|
#include <QStyledItemDelegate>
|
||||||
#include <QTableWidget>
|
#include <QTableWidget>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
@ -42,6 +44,7 @@ constexpr int MISC_COLUMNS = 2;
|
|||||||
constexpr auto USER_ROLE_IS_ROW_BREAKPOINT_CELL = Qt::UserRole;
|
constexpr auto USER_ROLE_IS_ROW_BREAKPOINT_CELL = Qt::UserRole;
|
||||||
constexpr auto USER_ROLE_CELL_ADDRESS = Qt::UserRole + 1;
|
constexpr auto USER_ROLE_CELL_ADDRESS = Qt::UserRole + 1;
|
||||||
constexpr auto USER_ROLE_VALUE_TYPE = Qt::UserRole + 2;
|
constexpr auto USER_ROLE_VALUE_TYPE = Qt::UserRole + 2;
|
||||||
|
constexpr auto USER_ROLE_VALID_ADDRESS = Qt::UserRole + 3;
|
||||||
|
|
||||||
// Numbers for the scrollbar. These affect how much big the draggable part of the scrollbar is, how
|
// Numbers for the scrollbar. These affect how much big the draggable part of the scrollbar is, how
|
||||||
// smooth it scrolls, and how much memory it traverses while dragging.
|
// smooth it scrolls, and how much memory it traverses while dragging.
|
||||||
@ -52,6 +55,14 @@ constexpr int SCROLLBAR_CENTER = SCROLLBAR_MAXIMUM / 2;
|
|||||||
|
|
||||||
const QString INVALID_MEMORY = QStringLiteral("-");
|
const QString INVALID_MEMORY = QStringLiteral("-");
|
||||||
|
|
||||||
|
void TableEditDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
|
||||||
|
const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
// Triggers on placing data into a cell. Editor has the text to be input, index has the location.
|
||||||
|
const QString input = qobject_cast<QLineEdit*>(editor)->text();
|
||||||
|
emit editFinished(index.row(), index.column(), input);
|
||||||
|
}
|
||||||
|
|
||||||
class MemoryViewTable final : public QTableWidget
|
class MemoryViewTable final : public QTableWidget
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -66,6 +77,11 @@ public:
|
|||||||
setSelectionMode(NoSelection);
|
setSelectionMode(NoSelection);
|
||||||
setTextElideMode(Qt::TextElideMode::ElideNone);
|
setTextElideMode(Qt::TextElideMode::ElideNone);
|
||||||
|
|
||||||
|
// Route user's direct cell inputs to an editFinished signal. Much better than an itemChanged
|
||||||
|
// signal and QSignalBlock juggling.
|
||||||
|
TableEditDelegate* table_edit_delegate = new TableEditDelegate(this);
|
||||||
|
setItemDelegate(table_edit_delegate);
|
||||||
|
|
||||||
// Prevent colors from changing based on focus.
|
// Prevent colors from changing based on focus.
|
||||||
QPalette palette(m_view->palette());
|
QPalette palette(m_view->palette());
|
||||||
palette.setBrush(QPalette::Inactive, QPalette::Highlight, palette.brush(QPalette::Highlight));
|
palette.setBrush(QPalette::Inactive, QPalette::Highlight, palette.brush(QPalette::Highlight));
|
||||||
@ -78,13 +94,17 @@ public:
|
|||||||
|
|
||||||
connect(this, &MemoryViewTable::customContextMenuRequested, m_view,
|
connect(this, &MemoryViewTable::customContextMenuRequested, m_view,
|
||||||
&MemoryViewWidget::OnContextMenu);
|
&MemoryViewWidget::OnContextMenu);
|
||||||
connect(this, &MemoryViewTable::itemChanged, this, &MemoryViewTable::OnItemChanged);
|
connect(table_edit_delegate, &TableEditDelegate::editFinished, this,
|
||||||
|
&MemoryViewTable::OnDirectTableEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void resizeEvent(QResizeEvent* event) override
|
void resizeEvent(QResizeEvent* event) override
|
||||||
{
|
{
|
||||||
QTableWidget::resizeEvent(event);
|
QTableWidget::resizeEvent(event);
|
||||||
m_view->CreateTable();
|
// Remakes table if vertically resized
|
||||||
|
const int rows = std::round((height() / static_cast<float>(rowHeight(0))) - 0.25);
|
||||||
|
if (rows != rowCount())
|
||||||
|
m_view->UpdateDisbatcher(MemoryViewWidget::UpdateType::Full);
|
||||||
}
|
}
|
||||||
|
|
||||||
void keyPressEvent(QKeyEvent* event) override
|
void keyPressEvent(QKeyEvent* event) override
|
||||||
@ -93,24 +113,21 @@ public:
|
|||||||
{
|
{
|
||||||
case Qt::Key_Up:
|
case Qt::Key_Up:
|
||||||
m_view->m_address -= m_view->m_bytes_per_row;
|
m_view->m_address -= m_view->m_bytes_per_row;
|
||||||
m_view->Update();
|
break;
|
||||||
return;
|
|
||||||
case Qt::Key_Down:
|
case Qt::Key_Down:
|
||||||
m_view->m_address += m_view->m_bytes_per_row;
|
m_view->m_address += m_view->m_bytes_per_row;
|
||||||
m_view->Update();
|
break;
|
||||||
return;
|
|
||||||
case Qt::Key_PageUp:
|
case Qt::Key_PageUp:
|
||||||
m_view->m_address -= this->rowCount() * m_view->m_bytes_per_row;
|
m_view->m_address -= this->rowCount() * m_view->m_bytes_per_row;
|
||||||
m_view->Update();
|
break;
|
||||||
return;
|
|
||||||
case Qt::Key_PageDown:
|
case Qt::Key_PageDown:
|
||||||
m_view->m_address += this->rowCount() * m_view->m_bytes_per_row;
|
m_view->m_address += this->rowCount() * m_view->m_bytes_per_row;
|
||||||
m_view->Update();
|
break;
|
||||||
return;
|
|
||||||
default:
|
default:
|
||||||
QWidget::keyPressEvent(event);
|
QWidget::keyPressEvent(event);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
m_view->UpdateDisbatcher(MemoryViewWidget::UpdateType::Addresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
void wheelEvent(QWheelEvent* event) override
|
void wheelEvent(QWheelEvent* event) override
|
||||||
@ -122,7 +139,7 @@ public:
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
m_view->m_address += delta * m_view->m_bytes_per_row;
|
m_view->m_address += delta * m_view->m_bytes_per_row;
|
||||||
m_view->Update();
|
m_view->UpdateDisbatcher(MemoryViewWidget::UpdateType::Addresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mousePressEvent(QMouseEvent* event) override
|
void mousePressEvent(QMouseEvent* event) override
|
||||||
@ -138,7 +155,6 @@ public:
|
|||||||
{
|
{
|
||||||
const u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt();
|
const u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt();
|
||||||
m_view->ToggleBreakpoint(address, true);
|
m_view->ToggleBreakpoint(address, true);
|
||||||
m_view->Update();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -146,9 +162,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnItemChanged(QTableWidgetItem* item)
|
void OnDirectTableEdit(const int row, const int column, const QString& text)
|
||||||
{
|
{
|
||||||
QString text = item->text();
|
QTableWidgetItem* item = this->item(row, column);
|
||||||
MemoryViewWidget::Type type =
|
MemoryViewWidget::Type type =
|
||||||
static_cast<MemoryViewWidget::Type>(item->data(USER_ROLE_VALUE_TYPE).toInt());
|
static_cast<MemoryViewWidget::Type>(item->data(USER_ROLE_VALUE_TYPE).toInt());
|
||||||
std::vector<u8> bytes = m_view->ConvertTextToBytes(type, text);
|
std::vector<u8> bytes = m_view->ConvertTextToBytes(type, text);
|
||||||
@ -156,17 +172,18 @@ public:
|
|||||||
u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt();
|
u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt();
|
||||||
u32 end_address = address + static_cast<u32>(bytes.size()) - 1;
|
u32 end_address = address + static_cast<u32>(bytes.size()) - 1;
|
||||||
AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_view->GetAddressSpace());
|
AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_view->GetAddressSpace());
|
||||||
|
|
||||||
const Core::CPUThreadGuard guard(m_view->m_system);
|
|
||||||
|
|
||||||
if (!bytes.empty() && accessors->IsValidAddress(guard, address) &&
|
|
||||||
accessors->IsValidAddress(guard, end_address))
|
|
||||||
{
|
{
|
||||||
for (const u8 c : bytes)
|
const Core::CPUThreadGuard guard(m_view->m_system);
|
||||||
accessors->WriteU8(guard, address++, c);
|
|
||||||
|
if (!bytes.empty() && accessors->IsValidAddress(guard, address) &&
|
||||||
|
accessors->IsValidAddress(guard, end_address))
|
||||||
|
{
|
||||||
|
for (const u8 c : bytes)
|
||||||
|
accessors->WriteU8(guard, address++, c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_view->Update();
|
m_view->UpdateDisbatcher(MemoryViewWidget::UpdateType::Values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -198,13 +215,25 @@ MemoryViewWidget::MemoryViewWidget(Core::System& system, QWidget* parent)
|
|||||||
this->setLayout(layout);
|
this->setLayout(layout);
|
||||||
|
|
||||||
connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &MemoryViewWidget::UpdateFont);
|
connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &MemoryViewWidget::UpdateFont);
|
||||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
|
||||||
qOverload<>(&MemoryViewWidget::UpdateColumns));
|
|
||||||
connect(Host::GetInstance(), &Host::PPCBreakpointsChanged, this,
|
connect(Host::GetInstance(), &Host::PPCBreakpointsChanged, this,
|
||||||
qOverload<>(&MemoryViewWidget::Update));
|
&MemoryViewWidget::UpdateBreakpointTags);
|
||||||
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this,
|
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] {
|
||||||
qOverload<>(&MemoryViewWidget::UpdateColumns));
|
// UpdateDisasmDialog currently catches pauses, no need to signal it twice.
|
||||||
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &MemoryViewWidget::Update);
|
if (Core::GetState(m_system) != Core::State::Paused)
|
||||||
|
UpdateDisbatcher(UpdateType::Values);
|
||||||
|
});
|
||||||
|
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, [this] {
|
||||||
|
// Disasm spam will break updates while running. Only need it for things like steps when paused
|
||||||
|
// and breaks which trigger a pause.
|
||||||
|
if (Core::GetState(m_system) != Core::State::Running)
|
||||||
|
UpdateDisbatcher(UpdateType::Values);
|
||||||
|
});
|
||||||
|
|
||||||
|
// CPU Thread to Main Thread.
|
||||||
|
connect(this, &MemoryViewWidget::AutoUpdate, this,
|
||||||
|
[this] { UpdateDisbatcher(UpdateType::Auto); });
|
||||||
|
connect(&Settings::Instance(), &Settings::ThemeChanged, this,
|
||||||
|
[this] { UpdateDisbatcher(UpdateType::Full); });
|
||||||
|
|
||||||
// Also calls create table.
|
// Also calls create table.
|
||||||
UpdateFont(Settings::Instance().GetDebugFont());
|
UpdateFont(Settings::Instance().GetDebugFont());
|
||||||
@ -220,7 +249,7 @@ void MemoryViewWidget::UpdateFont(const QFont& font)
|
|||||||
m_font_width = fm.horizontalAdvance(QLatin1Char('0'));
|
m_font_width = fm.horizontalAdvance(QLatin1Char('0'));
|
||||||
m_table->setFont(font);
|
m_table->setFont(font);
|
||||||
|
|
||||||
CreateTable();
|
UpdateDisbatcher(UpdateType::Full);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int GetTypeSize(MemoryViewWidget::Type type)
|
constexpr int GetTypeSize(MemoryViewWidget::Type type)
|
||||||
@ -282,6 +311,42 @@ constexpr int GetCharacterCount(MemoryViewWidget::Type type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MemoryViewWidget::UpdateDisbatcher(UpdateType type)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_updating, std::defer_lock);
|
||||||
|
|
||||||
|
// A full update may change parameters like row count, so make sure it goes through.
|
||||||
|
if (type == UpdateType::Full)
|
||||||
|
lock.lock();
|
||||||
|
else if (!isVisible() || !lock.try_lock())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if table needs to be created.
|
||||||
|
if (m_table->item(2, 1) == nullptr)
|
||||||
|
type = UpdateType::Full;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case UpdateType::Full:
|
||||||
|
CreateTable();
|
||||||
|
[[fallthrough]];
|
||||||
|
case UpdateType::Addresses:
|
||||||
|
Update();
|
||||||
|
[[fallthrough]];
|
||||||
|
case UpdateType::Values:
|
||||||
|
if (Core::GetState(m_system) == Core::State::Paused)
|
||||||
|
GetValues();
|
||||||
|
UpdateColumns();
|
||||||
|
break;
|
||||||
|
case UpdateType::Auto:
|
||||||
|
// Values were captured on CPU thread while doing a callback.
|
||||||
|
if (m_values.size() != 0)
|
||||||
|
UpdateColumns();
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MemoryViewWidget::CreateTable()
|
void MemoryViewWidget::CreateTable()
|
||||||
{
|
{
|
||||||
m_table->clearContents();
|
m_table->clearContents();
|
||||||
@ -292,8 +357,6 @@ void MemoryViewWidget::CreateTable()
|
|||||||
m_table->verticalHeader()->setMinimumSectionSize(m_font_vspace);
|
m_table->verticalHeader()->setMinimumSectionSize(m_font_vspace);
|
||||||
m_table->horizontalHeader()->setMinimumSectionSize(m_font_width * 2);
|
m_table->horizontalHeader()->setMinimumSectionSize(m_font_width * 2);
|
||||||
|
|
||||||
const QSignalBlocker blocker(m_table);
|
|
||||||
|
|
||||||
// Set column and row parameters.
|
// Set column and row parameters.
|
||||||
// Span is the number of unique memory values covered in one row.
|
// Span is the number of unique memory values covered in one row.
|
||||||
const int data_span = m_bytes_per_row / GetTypeSize(m_type);
|
const int data_span = m_bytes_per_row / GetTypeSize(m_type);
|
||||||
@ -388,17 +451,10 @@ void MemoryViewWidget::CreateTable()
|
|||||||
const int width = m_font_width * GetCharacterCount(m_type);
|
const int width = m_font_width * GetCharacterCount(m_type);
|
||||||
for (int i = start_fill; i < total_columns; i++)
|
for (int i = start_fill; i < total_columns; i++)
|
||||||
m_table->setColumnWidth(i, width);
|
m_table->setColumnWidth(i, width);
|
||||||
|
|
||||||
Update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryViewWidget::Update()
|
void MemoryViewWidget::Update()
|
||||||
{
|
{
|
||||||
// Check if table is created
|
|
||||||
if (m_table->item(1, 1) == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const QSignalBlocker blocker(m_table);
|
|
||||||
m_table->clearSelection();
|
m_table->clearSelection();
|
||||||
|
|
||||||
// Update addresses
|
// Update addresses
|
||||||
@ -406,6 +462,9 @@ void MemoryViewWidget::Update()
|
|||||||
u32 row_address = address - (m_table->rowCount() / 2) * m_bytes_per_row;
|
u32 row_address = address - (m_table->rowCount() / 2) * m_bytes_per_row;
|
||||||
const int data_span = m_bytes_per_row / GetTypeSize(m_type);
|
const int data_span = m_bytes_per_row / GetTypeSize(m_type);
|
||||||
|
|
||||||
|
m_address_range.first = row_address;
|
||||||
|
m_address_range.second = row_address + m_table->rowCount() * m_bytes_per_row - 1;
|
||||||
|
|
||||||
for (int i = 0; i < m_table->rowCount(); i++, row_address += m_bytes_per_row)
|
for (int i = 0; i < m_table->rowCount(); i++, row_address += m_bytes_per_row)
|
||||||
{
|
{
|
||||||
auto* bp_item = m_table->item(i, 0);
|
auto* bp_item = m_table->item(i, 0);
|
||||||
@ -426,69 +485,139 @@ void MemoryViewWidget::Update()
|
|||||||
item_address = row_address + c * GetTypeSize(m_type);
|
item_address = row_address + c * GetTypeSize(m_type);
|
||||||
|
|
||||||
item->setData(USER_ROLE_CELL_ADDRESS, item_address);
|
item->setData(USER_ROLE_CELL_ADDRESS, item_address);
|
||||||
|
|
||||||
|
// Reset highlighting.
|
||||||
|
item->setBackground(Qt::transparent);
|
||||||
|
item->setData(USER_ROLE_VALID_ADDRESS, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateColumns();
|
|
||||||
UpdateBreakpointTags();
|
UpdateBreakpointTags();
|
||||||
|
|
||||||
m_table->viewport()->update();
|
|
||||||
m_table->update();
|
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryViewWidget::UpdateColumns()
|
void MemoryViewWidget::UpdateColumns()
|
||||||
{
|
{
|
||||||
if (!isVisible())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Check if table is created
|
|
||||||
if (m_table->item(1, 1) == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Core::GetState(m_system) == Core::State::Paused)
|
|
||||||
{
|
|
||||||
const Core::CPUThreadGuard guard(m_system);
|
|
||||||
UpdateColumns(&guard);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the core is running, blank out the view of memory instead of reading anything.
|
|
||||||
UpdateColumns(nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryViewWidget::UpdateColumns(const Core::CPUThreadGuard* guard)
|
|
||||||
{
|
|
||||||
// Check if table is created
|
|
||||||
if (m_table->item(1, 1) == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const QSignalBlocker blocker(m_table);
|
|
||||||
|
|
||||||
for (int i = 0; i < m_table->rowCount(); i++)
|
for (int i = 0; i < m_table->rowCount(); i++)
|
||||||
{
|
{
|
||||||
for (int c = 0; c < m_data_columns; c++)
|
for (int c = 0; c < m_data_columns; c++)
|
||||||
{
|
{
|
||||||
auto* cell_item = m_table->item(i, c + MISC_COLUMNS);
|
auto* cell_item = m_table->item(i, c + MISC_COLUMNS);
|
||||||
|
if (!cell_item)
|
||||||
|
return;
|
||||||
|
|
||||||
const u32 cell_address = cell_item->data(USER_ROLE_CELL_ADDRESS).toUInt();
|
const u32 cell_address = cell_item->data(USER_ROLE_CELL_ADDRESS).toUInt();
|
||||||
const Type type = static_cast<Type>(cell_item->data(USER_ROLE_VALUE_TYPE).toInt());
|
const Type type = static_cast<Type>(cell_item->data(USER_ROLE_VALUE_TYPE).toInt());
|
||||||
|
std::optional<QString> new_text;
|
||||||
|
|
||||||
cell_item->setText(guard ? ValueToString(*guard, cell_address, type) : INVALID_MEMORY);
|
// Dual view auto sets the type of the left-side based on m_type. Only time type and
|
||||||
|
// m_type differ.
|
||||||
|
if (type != m_type)
|
||||||
|
{
|
||||||
|
new_text = m_values_dual_view.empty() || !m_values_dual_view.contains(cell_address) ?
|
||||||
|
std::nullopt :
|
||||||
|
m_values_dual_view.at(cell_address);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new_text = m_values.empty() || !m_values.contains(cell_address) ? std::nullopt :
|
||||||
|
m_values.at(cell_address);
|
||||||
|
}
|
||||||
|
|
||||||
// Set search address to selected / colored
|
// Set search address to selected / colored
|
||||||
if (cell_address == m_address_highlight)
|
if (cell_address == m_address_highlight)
|
||||||
cell_item->setSelected(true);
|
cell_item->setSelected(true);
|
||||||
|
|
||||||
|
// Color recently changed items.
|
||||||
|
QColor bcolor = cell_item->background().color();
|
||||||
|
const bool valid = cell_item->data(USER_ROLE_VALID_ADDRESS).toBool();
|
||||||
|
|
||||||
|
// It gets a bit complicated, because invalid addresses becoming valid should not be
|
||||||
|
// colored.
|
||||||
|
if (!new_text.has_value())
|
||||||
|
{
|
||||||
|
cell_item->setBackground(Qt::transparent);
|
||||||
|
cell_item->setData(USER_ROLE_VALID_ADDRESS, false);
|
||||||
|
cell_item->setText(INVALID_MEMORY);
|
||||||
|
}
|
||||||
|
else if (!valid)
|
||||||
|
{
|
||||||
|
// Wasn't valid on last update, is valid now.
|
||||||
|
cell_item->setData(USER_ROLE_VALID_ADDRESS, true);
|
||||||
|
cell_item->setText(new_text.value());
|
||||||
|
}
|
||||||
|
else if (bcolor.rgb() != m_highlight_color.rgb() && bcolor != Qt::transparent)
|
||||||
|
{
|
||||||
|
// Filter out colors that shouldn't change, such as breakpoints.
|
||||||
|
cell_item->setText(new_text.value());
|
||||||
|
}
|
||||||
|
else if (cell_item->text() != new_text.value())
|
||||||
|
{
|
||||||
|
// Cell changed, apply highlighting.
|
||||||
|
cell_item->setBackground(m_highlight_color);
|
||||||
|
cell_item->setText(new_text.value());
|
||||||
|
}
|
||||||
|
else if (bcolor.alpha() > 0)
|
||||||
|
{
|
||||||
|
// Fade out highlighting each frame.
|
||||||
|
bcolor.setAlpha(bcolor.alpha() - 1);
|
||||||
|
cell_item->setBackground(bcolor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always runs on CPU thread from a callback.
|
||||||
|
void MemoryViewWidget::UpdateOnFrameEnd()
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_updating, std::try_to_lock);
|
||||||
|
if (lock)
|
||||||
|
{
|
||||||
|
GetValues();
|
||||||
|
// Should not directly trigger widget updates on a cpu thread. Signal main thread to do it.
|
||||||
|
emit AutoUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryViewWidget::GetValues()
|
||||||
|
{
|
||||||
|
m_values.clear();
|
||||||
|
m_values_dual_view.clear();
|
||||||
|
|
||||||
|
// Check for dual view type
|
||||||
|
Type type = Type::Null;
|
||||||
|
|
||||||
|
if (m_dual_view)
|
||||||
|
{
|
||||||
|
if (GetTypeSize(m_type) == 1)
|
||||||
|
type = Type::Hex8;
|
||||||
|
else if (GetTypeSize(m_type) == 2)
|
||||||
|
type = Type::Hex16;
|
||||||
|
else if (GetTypeSize(m_type) == 8)
|
||||||
|
type = Type::Hex64;
|
||||||
|
else
|
||||||
|
type = Type::Hex32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab memory values as QStrings
|
||||||
|
Core::CPUThreadGuard guard(m_system);
|
||||||
|
|
||||||
|
for (u32 address = m_address_range.first; address <= m_address_range.second;
|
||||||
|
address += GetTypeSize(m_type))
|
||||||
|
{
|
||||||
|
m_values.insert(std::pair(address, ValueToString(guard, address, m_type)));
|
||||||
|
|
||||||
|
if (m_dual_view)
|
||||||
|
m_values_dual_view.insert(std::pair(address, ValueToString(guard, address, type)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// May only be called if we have taken on the role of the CPU thread
|
// May only be called if we have taken on the role of the CPU thread
|
||||||
QString MemoryViewWidget::ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type)
|
std::optional<QString> MemoryViewWidget::ValueToString(const Core::CPUThreadGuard& guard,
|
||||||
|
u32 address, Type type)
|
||||||
{
|
{
|
||||||
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
|
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
|
||||||
if (!accessors->IsValidAddress(guard, address))
|
if (!accessors->IsValidAddress(guard, address))
|
||||||
return INVALID_MEMORY;
|
return std::nullopt;
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
@ -550,7 +679,7 @@ QString MemoryViewWidget::ValueToString(const Core::CPUThreadGuard& guard, u32 a
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return INVALID_MEMORY;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,7 +735,7 @@ void MemoryViewWidget::SetAddressSpace(AddressSpace::Type address_space)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_address_space = address_space;
|
m_address_space = address_space;
|
||||||
Update();
|
UpdateDisbatcher(UpdateType::Addresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddressSpace::Type MemoryViewWidget::GetAddressSpace() const
|
AddressSpace::Type MemoryViewWidget::GetAddressSpace() const
|
||||||
@ -777,7 +906,55 @@ void MemoryViewWidget::SetDisplay(Type type, int bytes_per_row, int alignment, b
|
|||||||
else
|
else
|
||||||
m_alignment = alignment;
|
m_alignment = alignment;
|
||||||
|
|
||||||
CreateTable();
|
UpdateDisbatcher(UpdateType::Full);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryViewWidget::ToggleHighlights(bool enabled)
|
||||||
|
{
|
||||||
|
// m_highlight_color should hold the current highlight color even when disabled, so it can
|
||||||
|
// be used if re-enabled. If modifying the enabled alpha, change in .h file as well.
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
m_highlight_color.setAlpha(100);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Treated as being interchangable with Qt::transparent.
|
||||||
|
m_highlight_color.setAlpha(0);
|
||||||
|
|
||||||
|
// Immediately remove highlights when paused.
|
||||||
|
for (int i = 0; i < m_table->rowCount(); i++)
|
||||||
|
{
|
||||||
|
for (int c = 0; c < m_data_columns; c++)
|
||||||
|
m_table->item(i, c + MISC_COLUMNS)->setBackground(m_highlight_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryViewWidget::SetHighlightColor()
|
||||||
|
{
|
||||||
|
// Could allow custom alphas to be set, which would change fade-out rate.
|
||||||
|
QColor color = QColorDialog::getColor(m_highlight_color);
|
||||||
|
if (!color.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool enabled = m_highlight_color.alpha() != 0;
|
||||||
|
m_highlight_color = color;
|
||||||
|
m_highlight_color.setAlpha(enabled ? 100 : 0);
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Immediately update colors. Only useful for playing with colors while paused.
|
||||||
|
for (int i = 0; i < m_table->rowCount(); i++)
|
||||||
|
{
|
||||||
|
for (int c = 0; c < m_data_columns; c++)
|
||||||
|
{
|
||||||
|
auto* item = m_table->item(i, c + MISC_COLUMNS);
|
||||||
|
// Get current cell alpha state.
|
||||||
|
color.setAlpha(item->background().color().alpha());
|
||||||
|
m_table->item(i, c + MISC_COLUMNS)->setBackground(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryViewWidget::SetBPType(BPType type)
|
void MemoryViewWidget::SetBPType(BPType type)
|
||||||
@ -793,7 +970,7 @@ void MemoryViewWidget::SetAddress(u32 address)
|
|||||||
|
|
||||||
m_address = address;
|
m_address = address;
|
||||||
|
|
||||||
Update();
|
UpdateDisbatcher(UpdateType::Addresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryViewWidget::SetBPLoggingEnabled(bool enabled)
|
void MemoryViewWidget::SetBPLoggingEnabled(bool enabled)
|
||||||
@ -916,7 +1093,7 @@ void MemoryViewWidget::ScrollbarActionTriggered(int action)
|
|||||||
// User is currently dragging the scrollbar.
|
// User is currently dragging the scrollbar.
|
||||||
// Adjust the memory view by the exact drag difference.
|
// Adjust the memory view by the exact drag difference.
|
||||||
m_address += difference * m_bytes_per_row;
|
m_address += difference * m_bytes_per_row;
|
||||||
Update();
|
UpdateDisbatcher(UpdateType::Addresses);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -931,7 +1108,7 @@ void MemoryViewWidget::ScrollbarActionTriggered(int action)
|
|||||||
m_address += (difference < 0 ? -1 : 1) * m_bytes_per_row * m_table->rowCount();
|
m_address += (difference < 0 ? -1 : 1) * m_bytes_per_row * m_table->rowCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
Update();
|
UpdateDisbatcher(UpdateType::Addresses);
|
||||||
// Manually reset the draggable part of the bar back to the center.
|
// Manually reset the draggable part of the bar back to the center.
|
||||||
m_scrollbar->setSliderPosition(SCROLLBAR_CENTER);
|
m_scrollbar->setSliderPosition(SCROLLBAR_CENTER);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
@ -22,6 +24,21 @@ class CPUThreadGuard;
|
|||||||
class System;
|
class System;
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
||||||
|
// Captures direct editing of the table.
|
||||||
|
class TableEditDelegate : public QStyledItemDelegate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TableEditDelegate(QObject* parent) : QStyledItemDelegate(parent) {}
|
||||||
|
|
||||||
|
void setModelData(QWidget* editor, QAbstractItemModel* model,
|
||||||
|
const QModelIndex& index) const override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void editFinished(const int row, const int column, const QString& text) const;
|
||||||
|
};
|
||||||
|
|
||||||
class MemoryViewTable;
|
class MemoryViewTable;
|
||||||
|
|
||||||
class MemoryViewWidget final : public QWidget
|
class MemoryViewWidget final : public QWidget
|
||||||
@ -54,10 +71,21 @@ public:
|
|||||||
WriteOnly
|
WriteOnly
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class UpdateType
|
||||||
|
{
|
||||||
|
Full,
|
||||||
|
Addresses,
|
||||||
|
Values,
|
||||||
|
Auto,
|
||||||
|
};
|
||||||
|
|
||||||
explicit MemoryViewWidget(Core::System& system, QWidget* parent = nullptr);
|
explicit MemoryViewWidget(Core::System& system, QWidget* parent = nullptr);
|
||||||
|
|
||||||
void CreateTable();
|
void CreateTable();
|
||||||
|
void UpdateDisbatcher(UpdateType type = UpdateType::Addresses);
|
||||||
void Update();
|
void Update();
|
||||||
|
void UpdateOnFrameEnd();
|
||||||
|
void GetValues();
|
||||||
void UpdateFont(const QFont& font);
|
void UpdateFont(const QFont& font);
|
||||||
void ToggleBreakpoint(u32 addr, bool row);
|
void ToggleBreakpoint(u32 addr, bool row);
|
||||||
|
|
||||||
@ -65,6 +93,8 @@ public:
|
|||||||
void SetAddressSpace(AddressSpace::Type address_space);
|
void SetAddressSpace(AddressSpace::Type address_space);
|
||||||
AddressSpace::Type GetAddressSpace() const;
|
AddressSpace::Type GetAddressSpace() const;
|
||||||
void SetDisplay(Type type, int bytes_per_row, int alignment, bool dual_view);
|
void SetDisplay(Type type, int bytes_per_row, int alignment, bool dual_view);
|
||||||
|
void ToggleHighlights(bool enabled);
|
||||||
|
void SetHighlightColor();
|
||||||
void SetBPType(BPType type);
|
void SetBPType(BPType type);
|
||||||
void SetAddress(u32 address);
|
void SetAddress(u32 address);
|
||||||
void SetFocus() const;
|
void SetFocus() const;
|
||||||
@ -72,6 +102,7 @@ public:
|
|||||||
void SetBPLoggingEnabled(bool enabled);
|
void SetBPLoggingEnabled(bool enabled);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void AutoUpdate();
|
||||||
void ShowCode(u32 address);
|
void ShowCode(u32 address);
|
||||||
void RequestWatch(QString name, u32 address);
|
void RequestWatch(QString name, u32 address);
|
||||||
|
|
||||||
@ -81,10 +112,9 @@ private:
|
|||||||
void OnCopyHex(u32 addr);
|
void OnCopyHex(u32 addr);
|
||||||
void UpdateBreakpointTags();
|
void UpdateBreakpointTags();
|
||||||
void UpdateColumns();
|
void UpdateColumns();
|
||||||
void UpdateColumns(const Core::CPUThreadGuard* guard);
|
|
||||||
void ScrollbarActionTriggered(int action);
|
void ScrollbarActionTriggered(int action);
|
||||||
void ScrollbarSliderReleased();
|
void ScrollbarSliderReleased();
|
||||||
QString ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type);
|
std::optional<QString> ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type);
|
||||||
|
|
||||||
Core::System& m_system;
|
Core::System& m_system;
|
||||||
|
|
||||||
@ -95,6 +125,9 @@ private:
|
|||||||
BPType m_bp_type = BPType::ReadWrite;
|
BPType m_bp_type = BPType::ReadWrite;
|
||||||
bool m_do_log = true;
|
bool m_do_log = true;
|
||||||
u32 m_address = 0x80000000;
|
u32 m_address = 0x80000000;
|
||||||
|
std::pair<u32, u32> m_address_range;
|
||||||
|
std::map<u32, std::optional<QString>> m_values;
|
||||||
|
std::map<u32, std::optional<QString>> m_values_dual_view;
|
||||||
u32 m_address_highlight = 0;
|
u32 m_address_highlight = 0;
|
||||||
int m_font_width = 0;
|
int m_font_width = 0;
|
||||||
int m_font_vspace = 0;
|
int m_font_vspace = 0;
|
||||||
@ -102,6 +135,8 @@ private:
|
|||||||
int m_alignment = 16;
|
int m_alignment = 16;
|
||||||
int m_data_columns;
|
int m_data_columns;
|
||||||
bool m_dual_view = false;
|
bool m_dual_view = false;
|
||||||
|
std::mutex m_updating;
|
||||||
|
QColor m_highlight_color = QColor(120, 255, 255, 100);
|
||||||
|
|
||||||
friend class MemoryViewTable;
|
friend class MemoryViewTable;
|
||||||
};
|
};
|
||||||
|
@ -66,9 +66,13 @@ MemoryWidget::MemoryWidget(Core::System& system, QWidget* parent)
|
|||||||
connect(&Settings::Instance(), &Settings::DebugModeToggled, this,
|
connect(&Settings::Instance(), &Settings::DebugModeToggled, this,
|
||||||
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsMemoryVisible()); });
|
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsMemoryVisible()); });
|
||||||
|
|
||||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &MemoryWidget::Update);
|
connect(this, &QDockWidget::visibilityChanged, this, [this](bool visible) {
|
||||||
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &MemoryWidget::Update);
|
// Stop auto-update if MemoryView is tabbed out.
|
||||||
|
if (visible)
|
||||||
|
RegisterAfterFrameEventCallback();
|
||||||
|
else
|
||||||
|
RemoveAfterFrameEventCallback();
|
||||||
|
});
|
||||||
LoadSettings();
|
LoadSettings();
|
||||||
|
|
||||||
ConnectWidgets();
|
ConnectWidgets();
|
||||||
@ -250,12 +254,34 @@ void MemoryWidget::CreateWidgets()
|
|||||||
// Sidebar top menu
|
// Sidebar top menu
|
||||||
QMenuBar* menubar = new QMenuBar(sidebar);
|
QMenuBar* menubar = new QMenuBar(sidebar);
|
||||||
menubar->setNativeMenuBar(false);
|
menubar->setNativeMenuBar(false);
|
||||||
|
QMenu* menu_views = new QMenu(tr("&View"), menubar);
|
||||||
|
menubar->addMenu(menu_views);
|
||||||
|
|
||||||
QMenu* menu_import = new QMenu(tr("&Import"), menubar);
|
QMenu* menu_import = new QMenu(tr("&Import"), menubar);
|
||||||
menu_import->addAction(tr("&Load file to current address"), this,
|
menu_import->addAction(tr("&Load file to current address"), this,
|
||||||
&MemoryWidget::OnSetValueFromFile);
|
&MemoryWidget::OnSetValueFromFile);
|
||||||
menubar->addMenu(menu_import);
|
menubar->addMenu(menu_import);
|
||||||
|
|
||||||
|
auto* auto_update_action =
|
||||||
|
menu_views->addAction(tr("Auto update memory values"), this, [this](bool checked) {
|
||||||
|
m_auto_update_enabled = checked;
|
||||||
|
if (checked)
|
||||||
|
RegisterAfterFrameEventCallback();
|
||||||
|
else
|
||||||
|
RemoveAfterFrameEventCallback();
|
||||||
|
});
|
||||||
|
auto_update_action->setCheckable(true);
|
||||||
|
auto_update_action->setChecked(true);
|
||||||
|
|
||||||
|
auto* highlight_update_action =
|
||||||
|
menu_views->addAction(tr("Highlight recently changed values"), this,
|
||||||
|
[this](bool checked) { m_memory_view->ToggleHighlights(checked); });
|
||||||
|
highlight_update_action->setCheckable(true);
|
||||||
|
highlight_update_action->setChecked(true);
|
||||||
|
|
||||||
|
menu_views->addAction(tr("Highlight color"), this,
|
||||||
|
[this] { m_memory_view->SetHighlightColor(); });
|
||||||
|
|
||||||
QMenu* menu_export = new QMenu(tr("&Export"), menubar);
|
QMenu* menu_export = new QMenu(tr("&Export"), menubar);
|
||||||
menu_export->addAction(tr("Dump &MRAM"), this, &MemoryWidget::OnDumpMRAM);
|
menu_export->addAction(tr("Dump &MRAM"), this, &MemoryWidget::OnDumpMRAM);
|
||||||
menu_export->addAction(tr("Dump &ExRAM"), this, &MemoryWidget::OnDumpExRAM);
|
menu_export->addAction(tr("Dump &ExRAM"), this, &MemoryWidget::OnDumpExRAM);
|
||||||
@ -340,19 +366,43 @@ void MemoryWidget::ConnectWidgets()
|
|||||||
void MemoryWidget::closeEvent(QCloseEvent*)
|
void MemoryWidget::closeEvent(QCloseEvent*)
|
||||||
{
|
{
|
||||||
Settings::Instance().SetMemoryVisible(false);
|
Settings::Instance().SetMemoryVisible(false);
|
||||||
|
RemoveAfterFrameEventCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryWidget::showEvent(QShowEvent* event)
|
void MemoryWidget::showEvent(QShowEvent* event)
|
||||||
{
|
{
|
||||||
|
if (m_auto_update_enabled)
|
||||||
|
RegisterAfterFrameEventCallback();
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MemoryWidget::hideEvent(QHideEvent* event)
|
||||||
|
{
|
||||||
|
RemoveAfterFrameEventCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryWidget::RegisterAfterFrameEventCallback()
|
||||||
|
{
|
||||||
|
m_VI_end_field_event = VIEndFieldEvent::Register([this] { AutoUpdateTable(); }, "MemoryWidget");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryWidget::RemoveAfterFrameEventCallback()
|
||||||
|
{
|
||||||
|
m_VI_end_field_event.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryWidget::AutoUpdateTable()
|
||||||
|
{
|
||||||
|
m_memory_view->UpdateOnFrameEnd();
|
||||||
|
}
|
||||||
|
|
||||||
void MemoryWidget::Update()
|
void MemoryWidget::Update()
|
||||||
{
|
{
|
||||||
if (!isVisible())
|
if (!isVisible())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_memory_view->Update();
|
m_memory_view->UpdateDisbatcher(MemoryViewWidget::UpdateType::Addresses);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include <QDockWidget>
|
#include <QDockWidget>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "VideoCommon/VideoEvents.h"
|
||||||
|
|
||||||
class MemoryViewWidget;
|
class MemoryViewWidget;
|
||||||
class QCheckBox;
|
class QCheckBox;
|
||||||
@ -76,7 +77,11 @@ private:
|
|||||||
void FindValue(bool next);
|
void FindValue(bool next);
|
||||||
|
|
||||||
void closeEvent(QCloseEvent*) override;
|
void closeEvent(QCloseEvent*) override;
|
||||||
|
void hideEvent(QHideEvent* event) override;
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
|
void RegisterAfterFrameEventCallback();
|
||||||
|
void RemoveAfterFrameEventCallback();
|
||||||
|
void AutoUpdateTable();
|
||||||
|
|
||||||
Core::System& m_system;
|
Core::System& m_system;
|
||||||
|
|
||||||
@ -109,4 +114,7 @@ private:
|
|||||||
QRadioButton* m_bp_read_only;
|
QRadioButton* m_bp_read_only;
|
||||||
QRadioButton* m_bp_write_only;
|
QRadioButton* m_bp_write_only;
|
||||||
QCheckBox* m_bp_log_check;
|
QCheckBox* m_bp_log_check;
|
||||||
|
Common::EventHook m_VI_end_field_event;
|
||||||
|
|
||||||
|
bool m_auto_update_enabled = true;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user