@@ -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 @@ class MemoryViewTable final : public QTableWidget
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 @@ class MemoryViewTable final : public QTableWidget
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,284 +265,282 @@ 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));
// Get optional dual-view type
std::optional<Type> left_type = std::nullopt;
m_table->setItem (i, 0 , bp_item);
if (m_dual_view)
{
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;
else
left_type = Type::Hex32;
}
auto * row_item =
new QTableWidgetItem (QStringLiteral (" %1" ).arg (row_address, 8 , 16 , QLatin1Char (' 0' )));
// 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_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));
// 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));
m_table->setItem (i, 1 , row_item);
// Data item
auto * item = new QTableWidgetItem (QStringLiteral (" -" ));
item->setFlags (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
item->setData (USER_ROLE_IS_ROW_BREAKPOINT_CELL, false );
if (row_address == address)
row_item->setSelected (true );
for (int i = 0 ; i < rows; i++)
{
m_table->setItem (i, 0 , bp_item->clone ());
m_table->setItem (i, 1 , row_item->clone ());
if ( Core::GetState () != Core::State::Paused || !accessors-> IsValidAddress (row_address) )
for ( int c = 0 ; c < m_data_columns; c++ )
{
for (int c = 2 ; c < m_table->columnCount (); c++)
if (left_type && c < data_span)
{
item->setData (USER_ROLE_VALUE_TYPE, static_cast <int >(left_type.value ()));
}
else
{
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));
item->setData (USER_ROLE_VALUE_TYPE, static_cast <int >(m_type));
m_table->setItem (i, c, item);
// 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);
}
continue ;
m_table-> setItem (i, c + MISC_COLUMNS, item-> clone ()) ;
}
}
int starting_column = 2 ;
// Update column width
int start_fill = MISC_COLUMNS;
if (left_type)
{
const int width_left = m_font_width * GetCharacterCount (left_type.value ());
if (m_dual_view)
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)
{
// 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;
auto * bp_item = m_table->item (i, 0 );
bp_item->setData (USER_ROLE_CELL_ADDRESS, row_address);
UpdateColumns (left_type, starting_column);
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);
const int column_count = m_bytes_per_row / GetTypeSize (left_type);
if (row_address == address)
row_item->setSelected (true );
// 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) );
for ( int c = 0 ; c < m_data_columns; c++)
{
auto * item = m_table->item (i, c + MISC_COLUMNS );
// Extra spacing between dual views.
m_table->setColumnWidth (starting_column + column_count - 1 ,
m_font_width * (GetCharacterCount (left_type) + 2 ));
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);
starting_column += column_count;
item->setData (USER_ROLE_CELL_ADDRESS, item_address);
}
}
UpdateColumns (m_type, starting_column );
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 ;
for (int c = 0 ; c < m_data_columns; c++)
{
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 ());
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);
cell_item->setText (ValueToString (cell_address, type));
}
}
}
const u32 cell_address = row_address + c * GetTypeSize (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 (" -" );
m_table->setItem (i, first_column + c, cell_item);
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 (' ' ));
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)
{
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 (' ' ));
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 ;
}
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 )
{
row_breakpoint = false ;
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);