MemoryViewWidget refactor memory table.

Don't re-create the table on every update.
This commit is contained in:
TryTwo 2022-09-30 15:01:56 -07:00
parent 9ca1c0f533
commit 6f4f4e057e
2 changed files with 241 additions and 233 deletions

View File

@ -33,6 +33,8 @@
// 120; i.e., 120 units * 1/8 = 15 degrees." (http://doc.qt.io/qt-5/qwheelevent.html#angleDelta)
constexpr double SCROLL_FRACTION_DEGREES = 15.;
// Number of columns that don't contain the value of a memory address.
constexpr int MISC_COLUMNS = 2;
constexpr auto USER_ROLE_IS_ROW_BREAKPOINT_CELL = Qt::UserRole;
constexpr auto USER_ROLE_CELL_ADDRESS = Qt::UserRole + 1;
constexpr auto USER_ROLE_VALUE_TYPE = Qt::UserRole + 2;
@ -55,6 +57,9 @@ public:
setShowGrid(false);
setContextMenuPolicy(Qt::CustomContextMenu);
setSelectionMode(SingleSelection);
setTextElideMode(Qt::TextElideMode::ElideNone);
setRowCount(30);
setColumnCount(8);
connect(this, &MemoryViewTable::customContextMenuRequested, m_view,
&MemoryViewWidget::OnContextMenu);
@ -64,7 +69,7 @@ public:
void resizeEvent(QResizeEvent* event) override
{
QTableWidget::resizeEvent(event);
m_view->Update();
m_view->CreateTable();
}
void keyPressEvent(QKeyEvent* event) override
@ -175,11 +180,12 @@ MemoryViewWidget::MemoryViewWidget(QWidget* parent) : QWidget(parent)
this->setLayout(layout);
connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &MemoryViewWidget::UpdateFont);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] { Update(); });
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &MemoryViewWidget::Update);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
&MemoryViewWidget::UpdateColumns);
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &MemoryViewWidget::UpdateColumns);
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &MemoryViewWidget::Update);
// Also calls update.
// Also calls create table.
UpdateFont();
}
@ -196,7 +202,8 @@ void MemoryViewWidget::UpdateFont()
m_font_width = fm.width(QLatin1Char('0'));
#endif
m_table->setFont(Settings::Instance().GetDebugFont());
Update();
CreateTable();
}
constexpr int GetTypeSize(MemoryViewWidget::Type type)
@ -258,269 +265,265 @@ constexpr int GetCharacterCount(MemoryViewWidget::Type type)
}
}
void MemoryViewWidget::Update()
void MemoryViewWidget::CreateTable()
{
const QSignalBlocker blocker(m_table);
m_table->clearSelection();
u32 address = m_address;
address = Common::AlignDown(address, m_alignment);
const int data_columns = m_bytes_per_row / GetTypeSize(m_type);
if (m_dual_view)
m_table->setColumnCount(2 + 2 * data_columns);
else
m_table->setColumnCount(2 + data_columns);
if (m_table->rowCount() == 0)
m_table->setRowCount(1);
m_table->clearContents();
// This sets all row heights and determines horizontal ascii spacing.
// Could be placed in UpdateFont() but doesn't apply correctly unless called more.
m_table->verticalHeader()->setDefaultSectionSize(m_font_vspace - 1);
m_table->verticalHeader()->setMinimumSectionSize(m_font_vspace - 1);
m_table->horizontalHeader()->setMinimumSectionSize(m_font_width * 2);
m_table->setTextElideMode(Qt::TextElideMode::ElideNone);
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
const QSignalBlocker blocker(m_table);
// Set column and row parameters.
// Span is the number of unique memory values covered in one row.
const int data_span = m_bytes_per_row / GetTypeSize(m_type);
m_data_columns = m_dual_view ? data_span * 2 : data_span;
const int total_columns = MISC_COLUMNS + m_data_columns;
// Calculate (roughly) how many rows will fit in our table
const int rows =
std::round((m_table->height() / static_cast<float>(m_table->rowHeight(0))) - 0.25);
m_table->setColumnCount(total_columns);
m_table->setRowCount(rows);
m_table->setColumnWidth(0, m_table->rowHeight(0));
for (int i = 0; i < rows; i++)
{
u32 row_address = address - ((m_table->rowCount() / 2) * m_bytes_per_row) + i * m_bytes_per_row;
auto* bp_item = new QTableWidgetItem;
bp_item->setFlags(Qt::ItemIsEnabled);
bp_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, true);
bp_item->setData(USER_ROLE_CELL_ADDRESS, row_address);
bp_item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(Type::Null));
m_table->setItem(i, 0, bp_item);
auto* row_item =
new QTableWidgetItem(QStringLiteral("%1").arg(row_address, 8, 16, QLatin1Char('0')));
row_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
row_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false);
row_item->setData(USER_ROLE_CELL_ADDRESS, row_address);
row_item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(Type::Null));
m_table->setItem(i, 1, row_item);
if (row_address == address)
row_item->setSelected(true);
if (Core::GetState() != Core::State::Paused || !accessors->IsValidAddress(row_address))
{
for (int c = 2; c < m_table->columnCount(); c++)
{
auto* item = new QTableWidgetItem(QStringLiteral("-"));
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false);
item->setData(USER_ROLE_CELL_ADDRESS, row_address);
item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(Type::Null));
m_table->setItem(i, c, item);
}
continue;
}
}
int starting_column = 2;
// Get optional dual-view type
std::optional<Type> left_type = std::nullopt;
if (m_dual_view)
{
// Match left columns to number of right columns.
Type left_type = Type::Hex32;
if (GetTypeSize(m_type) == 1)
left_type = Type::Hex8;
else if (GetTypeSize(m_type) == 2)
left_type = Type::Hex16;
else if (GetTypeSize(m_type) == 8)
left_type = Type::Hex64;
UpdateColumns(left_type, starting_column);
const int column_count = m_bytes_per_row / GetTypeSize(left_type);
// Update column width
for (int i = starting_column; i < starting_column + column_count - 1; i++)
m_table->setColumnWidth(i, m_font_width * GetCharacterCount(left_type));
// Extra spacing between dual views.
m_table->setColumnWidth(starting_column + column_count - 1,
m_font_width * (GetCharacterCount(left_type) + 2));
starting_column += column_count;
else
left_type = Type::Hex32;
}
UpdateColumns(m_type, starting_column);
// Create cells and add data that won't be changing.
// Breakpoint buttons
auto* bp_item = new QTableWidgetItem;
bp_item->setFlags(Qt::ItemIsEnabled);
bp_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, true);
bp_item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(Type::Null));
// Row Addresses
auto* row_item = new QTableWidgetItem(QStringLiteral("-"));
row_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
row_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false);
row_item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(Type::Null));
// Data item
auto* item = new QTableWidgetItem(QStringLiteral("-"));
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false);
for (int i = 0; i < rows; i++)
{
m_table->setItem(i, 0, bp_item->clone());
m_table->setItem(i, 1, row_item->clone());
for (int c = 0; c < m_data_columns; c++)
{
if (left_type && c < data_span)
{
item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(left_type.value()));
}
else
{
item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(m_type));
// Left type will never be these.
auto text_alignment = Qt::AlignLeft;
if (m_type == Type::Signed32 || m_type == Type::Unsigned32 || m_type == Type::Signed16 ||
m_type == Type::Unsigned16 || m_type == Type::Signed8 || m_type == Type::Unsigned8)
{
text_alignment = Qt::AlignRight;
}
item->setTextAlignment(text_alignment | Qt::AlignVCenter);
}
m_table->setItem(i, c + MISC_COLUMNS, item->clone());
}
}
// Update column width
int start_fill = MISC_COLUMNS;
if (left_type)
{
const int width_left = m_font_width * GetCharacterCount(left_type.value());
for (int i = 0; i < data_span - 1; i++)
m_table->setColumnWidth(i + start_fill, width_left);
// Extra spacing between dual views.
m_table->setColumnWidth(start_fill + data_span - 1, width_left + m_font_width * 2);
start_fill += data_span;
}
// If dual-view, updates the right-side columns only.
const int width = m_font_width * GetCharacterCount(m_type);
for (int i = start_fill; i < total_columns; i++)
m_table->setColumnWidth(i, width);
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();
// Update addresses
const u32 address = Common::AlignDown(m_address, m_alignment);
u32 row_address = address - (m_table->rowCount() / 2) * m_bytes_per_row;
const int data_span = m_bytes_per_row / GetTypeSize(m_type);
for (int i = 0; i < m_table->rowCount(); i++, row_address += m_bytes_per_row)
{
auto* bp_item = m_table->item(i, 0);
bp_item->setData(USER_ROLE_CELL_ADDRESS, row_address);
auto* row_item = m_table->item(i, 1);
row_item->setText(QStringLiteral("%1").arg(row_address, 8, 16, QLatin1Char('0')));
row_item->setData(USER_ROLE_CELL_ADDRESS, row_address);
if (row_address == address)
row_item->setSelected(true);
for (int c = 0; c < m_data_columns; c++)
{
auto* item = m_table->item(i, c + MISC_COLUMNS);
u32 item_address;
if (m_dual_view && c >= data_span)
item_address = row_address + (c - data_span) * GetTypeSize(m_type);
else
item_address = row_address + c * GetTypeSize(m_type);
item->setData(USER_ROLE_CELL_ADDRESS, item_address);
}
}
UpdateColumns();
UpdateBreakpointTags();
m_table->setColumnWidth(0, m_table->rowHeight(0));
for (int i = starting_column; i <= m_table->columnCount(); i++)
m_table->setColumnWidth(i, m_font_width * GetCharacterCount(m_type));
m_table->viewport()->update();
m_table->update();
update();
}
void MemoryViewWidget::UpdateColumns(Type type, int first_column)
void MemoryViewWidget::UpdateColumns()
{
if (Core::GetState() != Core::State::Paused)
// Check if table is created
if (m_table->item(1, 1) == nullptr)
return;
const int data_columns = m_bytes_per_row / GetTypeSize(type);
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
auto text_alignment = Qt::AlignLeft;
if (type == Type::Signed32 || type == Type::Unsigned32 || type == Type::Signed16 ||
type == Type::Unsigned16 || type == Type::Signed8 || type == Type::Unsigned8)
{
text_alignment = Qt::AlignRight;
}
const QSignalBlocker blocker(m_table);
for (int i = 0; i < m_table->rowCount(); i++)
{
u32 row_address = m_table->item(i, 1)->data(USER_ROLE_CELL_ADDRESS).toUInt();
if (!accessors->IsValidAddress(row_address))
continue;
auto update_values = [&](auto value_to_string) {
for (int c = 0; c < data_columns; c++)
{
auto* cell_item = new QTableWidgetItem;
cell_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
cell_item->setTextAlignment(text_alignment);
const u32 cell_address = row_address + c * GetTypeSize(type);
m_table->setItem(i, first_column + c, cell_item);
if (accessors->IsValidAddress(cell_address))
{
cell_item->setText(value_to_string(cell_address));
cell_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false);
cell_item->setData(USER_ROLE_CELL_ADDRESS, cell_address);
cell_item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(type));
}
else
{
cell_item->setText(QStringLiteral("-"));
cell_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false);
cell_item->setData(USER_ROLE_CELL_ADDRESS, cell_address);
cell_item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(Type::Null));
}
}
};
switch (type)
for (int c = 0; c < m_data_columns; c++)
{
case Type::Hex8:
update_values([&accessors](u32 address) {
const u8 value = accessors->ReadU8(address);
return QStringLiteral("%1").arg(value, 2, 16, QLatin1Char('0'));
});
break;
case Type::ASCII:
update_values([&accessors](u32 address) {
const char value = accessors->ReadU8(address);
return IsPrintableCharacter(value) ? QString{QChar::fromLatin1(value)} :
QString{QChar::fromLatin1('.')};
});
break;
case Type::Hex16:
update_values([&accessors](u32 address) {
const u16 value = accessors->ReadU16(address);
return QStringLiteral("%1").arg(value, 4, 16, QLatin1Char('0'));
});
break;
case Type::Hex32:
update_values([&accessors](u32 address) {
const u32 value = accessors->ReadU32(address);
return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0'));
});
break;
case Type::Hex64:
update_values([&accessors](u32 address) {
const u64 value = accessors->ReadU64(address);
return QStringLiteral("%1").arg(value, 16, 16, QLatin1Char('0'));
});
break;
case Type::Unsigned8:
update_values(
[&accessors](u32 address) { return QString::number(accessors->ReadU8(address)); });
break;
case Type::Unsigned16:
update_values(
[&accessors](u32 address) { return QString::number(accessors->ReadU16(address)); });
break;
case Type::Unsigned32:
update_values(
[&accessors](u32 address) { return QString::number(accessors->ReadU32(address)); });
break;
case Type::Signed8:
update_values([&accessors](u32 address) {
return QString::number(Common::BitCast<s8>(accessors->ReadU8(address)));
});
break;
case Type::Signed16:
update_values([&accessors](u32 address) {
return QString::number(Common::BitCast<s16>(accessors->ReadU16(address)));
});
break;
case Type::Signed32:
update_values([&accessors](u32 address) {
return QString::number(Common::BitCast<s32>(accessors->ReadU32(address)));
});
break;
case Type::Float32:
update_values([&accessors](u32 address) {
QString string = QString::number(accessors->ReadF32(address), 'g', 4);
// Align to first digit.
if (!string.startsWith(QLatin1Char('-')))
string.prepend(QLatin1Char(' '));
auto* cell_item = m_table->item(i, c + MISC_COLUMNS);
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());
return string;
});
break;
case Type::Double:
update_values([&accessors](u32 address) {
QString string =
QString::number(Common::BitCast<double>(accessors->ReadU64(address)), 'g', 4);
// Align to first digit.
if (!string.startsWith(QLatin1Char('-')))
string.prepend(QLatin1Char(' '));
return string;
});
break;
cell_item->setText(ValueToString(cell_address, type));
}
}
}
QString MemoryViewWidget::ValueToString(u32 address, Type type)
{
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
if (!accessors->IsValidAddress(address) || Core::GetState() != Core::State::Paused)
return QStringLiteral("-");
switch (type)
{
case Type::Hex8:
{
const u8 value = accessors->ReadU8(address);
return QStringLiteral("%1").arg(value, 2, 16, QLatin1Char('0'));
}
case Type::ASCII:
{
const char value = accessors->ReadU8(address);
return IsPrintableCharacter(value) ? QString{QChar::fromLatin1(value)} :
QString{QChar::fromLatin1('.')};
}
case Type::Hex16:
{
const u16 value = accessors->ReadU16(address);
return QStringLiteral("%1").arg(value, 4, 16, QLatin1Char('0'));
}
case Type::Hex32:
{
const u32 value = accessors->ReadU32(address);
return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0'));
}
case Type::Hex64:
{
const u64 value = accessors->ReadU64(address);
return QStringLiteral("%1").arg(value, 16, 16, QLatin1Char('0'));
}
case Type::Unsigned8:
return QString::number(accessors->ReadU8(address));
case Type::Unsigned16:
return QString::number(accessors->ReadU16(address));
case Type::Unsigned32:
return QString::number(accessors->ReadU32(address));
case Type::Signed8:
return QString::number(Common::BitCast<s8>(accessors->ReadU8(address)));
case Type::Signed16:
return QString::number(Common::BitCast<s16>(accessors->ReadU16(address)));
case Type::Signed32:
return QString::number(Common::BitCast<s32>(accessors->ReadU32(address)));
case Type::Float32:
{
QString string = QString::number(accessors->ReadF32(address), 'g', 4);
// Align to first digit.
if (!string.startsWith(QLatin1Char('-')))
string.prepend(QLatin1Char(' '));
return string;
}
case Type::Double:
{
QString string = QString::number(Common::BitCast<double>(accessors->ReadU64(address)), 'g', 4);
// Align to first digit.
if (!string.startsWith(QLatin1Char('-')))
string.prepend(QLatin1Char(' '));
return string;
}
default:
return QStringLiteral("-");
}
}
void MemoryViewWidget::UpdateBreakpointTags()
{
if (Core::GetState() != Core::State::Paused)
return;
for (int i = 0; i < m_table->rowCount(); i++)
{
bool row_breakpoint = false;
for (int c = 2; c < m_table->columnCount(); c++)
for (int c = 0; c < m_data_columns; c++)
{
// Pull address from cell itself, helpful for dual column view.
auto cell = m_table->item(i, c);
u32 address = cell->data(USER_ROLE_CELL_ADDRESS).toUInt();
auto cell = m_table->item(i, c + MISC_COLUMNS);
const u32 address = cell->data(USER_ROLE_CELL_ADDRESS).toUInt();
if (address == 0)
{
@ -528,14 +531,16 @@ void MemoryViewWidget::UpdateBreakpointTags()
continue;
}
// In dual view the only sizes that dont match up on both left and right views are for
// Double, which uses two or four columns of hex32.
if (m_address_space == AddressSpace::Type::Effective &&
PowerPC::memchecks.GetMemCheck(address, GetTypeSize(m_type)) != nullptr)
{
row_breakpoint = true;
cell->setBackground(Qt::red);
}
else
{
cell->setBackground(Qt::white);
}
}
if (row_breakpoint)
@ -545,6 +550,10 @@ void MemoryViewWidget::UpdateBreakpointTags()
Resources::GetScaledThemeIcon("debugger_breakpoint")
.pixmap(QSize(m_table->rowHeight(0) - 3, m_table->rowHeight(0) - 3)));
}
else
{
m_table->item(i, 0)->setData(Qt::DecorationRole, QIcon());
}
}
}
@ -724,7 +733,7 @@ void MemoryViewWidget::SetDisplay(Type type, int bytes_per_row, int alignment, b
else
m_alignment = alignment;
Update();
CreateTable();
}
void MemoryViewWidget::SetBPType(BPType type)
@ -815,25 +824,21 @@ void MemoryViewWidget::OnContextMenu(const QPoint& pos)
if (!item_selected || item_selected->data(USER_ROLE_IS_ROW_BREAKPOINT_CELL).toBool())
return;
const bool item_has_value =
item_selected->data(USER_ROLE_VALUE_TYPE).toInt() != static_cast<int>(Type::Null);
const u32 addr = item_selected->data(USER_ROLE_CELL_ADDRESS).toUInt();
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
const bool item_has_value =
item_selected->data(USER_ROLE_VALUE_TYPE).toInt() != static_cast<int>(Type::Null) &&
accessors->IsValidAddress(addr);
auto* menu = new QMenu(this);
menu->addAction(tr("Copy Address"), this, [this, addr] { OnCopyAddress(addr); });
auto* copy_hex = menu->addAction(tr("Copy Hex"), this, [this, addr] { OnCopyHex(addr); });
copy_hex->setEnabled(item_has_value);
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
copy_hex->setEnabled(item_has_value && Core::GetState() != Core::State::Uninitialized &&
accessors->IsValidAddress(addr));
auto* copy_value = menu->addAction(tr("Copy Value"), this, [this, &pos] {
// Re-fetch the item in case the underlying table has refreshed since the menu was opened.
auto* item = m_table->itemAt(pos);
if (item && item->data(USER_ROLE_VALUE_TYPE).toInt() != static_cast<int>(Type::Null))
QApplication::clipboard()->setText(item->text());
auto* copy_value = menu->addAction(tr("Copy Value"), this, [this, item_selected] {
QApplication::clipboard()->setText(item_selected->text());
});
copy_value->setEnabled(item_has_value);

View File

@ -49,6 +49,7 @@ public:
explicit MemoryViewWidget(QWidget* parent = nullptr);
void CreateTable();
void Update();
void UpdateFont();
void ToggleBreakpoint(u32 addr, bool row);
@ -72,9 +73,10 @@ private:
void OnCopyAddress(u32 addr);
void OnCopyHex(u32 addr);
void UpdateBreakpointTags();
void UpdateColumns(Type type, int first_column);
void UpdateColumns();
void ScrollbarActionTriggered(int action);
void ScrollbarSliderReleased();
QString ValueToString(u32 address, Type type);
MemoryViewTable* m_table;
QScrollBar* m_scrollbar;
@ -82,11 +84,12 @@ private:
Type m_type = Type::Hex32;
BPType m_bp_type = BPType::ReadWrite;
bool m_do_log = true;
u32 m_address = 0;
u32 m_address = 0x80000000;
int m_font_width = 0;
int m_font_vspace = 0;
int m_bytes_per_row = 16;
int m_alignment = 16;
int m_data_columns;
bool m_dual_view = false;
friend class MemoryViewTable;