-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve UX for MemViewer and better code flexibility #8
Conversation
- Use smaller font - The Home and End keys jumps to and selects the first and last byte respectively - Fix calculation error when setting scrollbar upon pressing End - Always set `m_carretBetweenHex` to false when navigating using the keyboard - Fix selection jumping when pressing Left or Right when at the edges of the row - Refactor to use defines for number of rows, columns and bytes shown - Add support for clicking the ASCII table and typing in it - Refactored `writeHexCharacterToSelectedMemory` to be usable for ASCII too - Ignore non-left click events (you could middleclick a value to select it) - Play system beep when a non-hex character is edited - Update selection coords when scrolling
I will do a more extensive review later when I have time, but for now, I am just going to give 2 comments. About 1, I do not like the change. The reason is because a common theme I noticed with different hex viewers and more specifically Cheat Engine (which this program aims to replace when using with Dolphin) is that their hex viewer tends to be really difficult for me to look at for hours in a row simply because it's a lot of condensed data, in a rather small font (which really is hindered by the fact that I have a myopia). Putting a bigger font was done for that reason, to ease the fact that it's a lot of info to read in a little space. Ideally, the best would be to make the size configurable, but considering how little settings this program may need (the only I can think of are the updates and freeze timers) and how less needed it is than other features, I prefer to not do it now and just make eveyrone happy by putting the font slightly larger. It does make the number of possible rows to display slightly lower because of the added space, but in practice, what I noticed is you really don't need to consult that much memory in one sight often. I noticed this after using Cheat Engine for years on multiple games so I am not really concerned about the added space. Which leads me to my second comment, change number 6, despite what I jsut said, I do approve, I agreed the code needed that refactor which means I would have done it eventually. However, what I do not approve is the use of define, I would rather have them be a class wide constant simply because it provides better type checking and I don't feel a preprocessor is a necessity here. Also, in my opinion, writting The other changes doesn't sound something I would have a problem with, but like I said, I still need to do an extensive review on them. Overall, this PR sounds really nice :) |
Agreed with both comments. I'll make both the font size and the defines and make them member variables. For the font size, what do you think about having a Ctrl+Scroll feature to increase/decrease the font size? Many editors work that way, but maybe it's not that obvious for this memory viewer. Otherwise maybe a A- and A+ button in the corner? |
I am fine, as long as the window size adapts itself and there is a limit because if you zoom too much, this might be bad, same for too much out. I actually never thought of that, the code should even account for this because it's binded all to the EM size so you would just need to recalculate that once and redraw. |
The window adapts itself nicely already, I only had to move the calculations from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than these changes, make the #define
class wide const
and everything is LGTM.
Source/GUI/MemViewer/MemViewer.cpp
Outdated
m_editingHex = false; | ||
} | ||
else | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put the return
inside braces here. I am okay to omit the braces, but only if the entire seqeunce of if
, else if
and else
don't have them. This is not the case here and even if return
is trivial, it's still a bit jarring to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the new changes this can be removed entirely. Clicking the empty space is checked before this block now.
int x = event->pos().x(); | ||
int y = event->pos().y(); | ||
if (x >= m_rowHeaderWidth && x <= m_rowHeaderWidth + m_hexAreaWidth && y >= m_columnHeaderHeight) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing this actually doesn't work, you can select the first of the row if you click on the last hex character of the row header and it really does not ignore clicks on the header (clickedPosY
is calculated using integer division while the left member, if you click on the column header, will always be equal or less then the right member which means, it's always 0 here). After testing a bit, this is the correct way to do this:
if (!(x >= m_rowHeaderWidth &&
x <= m_rowHeaderWidth + m_hexAreaWidth + spacing + (m_charWidthEm * VISIBLE_COLS) &&
y >= m_columnHeaderHeight && y <= m_columnHeaderHeight + m_charHeight * VISIBLE_ROWS))
return;
It assumes spacing
is declared a line above and obviously, replace the define constant by whatever name you give to the class wide const
. Note also the addition of && y <= m_columnHeaderHeight + m_charHeight * VISIBLE_ROWS
, this is because while testing this, I found a bug that is affecting master where if you click JUST RIGHT you can select an invisible row at the bottom, I found this PR fitting for the fix so I put it in there. Essentially, this condition ensure that whatever you clicked in is in the hex area or the ascii area, the entire rectangle.
You are free to put a comment there if it's confusing, but if you do go with this, remove the test you do on clickedPosY
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well spotted. Clicking the row, column headers or area between the two tables was due to the devision. I didn't take into account that any value between 0 and -width would return 0. I think a nicer way than having those concatinated conditions is to check if the relative click position is < 0 rather than checking the index.
Source/GUI/MemViewer/MemViewer.cpp
Outdated
{ | ||
m_byteSelectedPosX = 15; | ||
m_byteSelectedPosY = 15; | ||
m_byteSelectedPosY = 0; | ||
scrollContentsBy(0, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The scroll should be BEFORE assigning m_byteSelectedPosY
because if you set it THEN scroll, it's going to be on the last column of the same row instead of being the last column of the previous row. Since you do it first for the right arrow key, do so here to remain consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah, the change that makes the selection move when scrolling changed this behaviour. Actually this entire part should be refactored, it's not taking the variable number of rows and columns into account.
Source/GUI/MemViewer/MemViewer.cpp
Outdated
@@ -166,7 +216,7 @@ bool MemViewer::handleNaviguationKey(const int key) | |||
{ | |||
scrollContentsBy(0, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, you have to do m_byteSelectedPosY = 0;
as you no longer stay the selection to the same place everytime. Do so after the scroll, this will make the selection jump one row above,
Source/GUI/MemViewer/MemViewer.cpp
Outdated
@@ -177,7 +227,7 @@ bool MemViewer::handleNaviguationKey(const int key) | |||
{ | |||
scrollContentsBy(0, -1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, but you assign 15 this time, fixes the same thing, but down this time.
Source/GUI/MemViewer/MemViewer.cpp
Outdated
#define VISIBLE_COLS 16 // Should be a multiple of 16, or the header doesn't make sense | ||
#define VISIBLE_ROWS 16 | ||
#define NUM_BYTES (VISIBLE_COLS * VISIBLE_ROWS) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last commit says the defines were replaced, but I still see them here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to push the latest changes, should be good now.
- Correctly check click location for hex and ASCII tables - Simplify logic in `handleNavigationKey` - Replace defines for table dimensions with const class members
c657269
to
08ab53d
Compare
Source/GUI/MemViewer/MemViewer.cpp
Outdated
|
||
// Ignore clicking the padding at the bottom | ||
if (clickedPosY >= m_numRows) | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this way of doing this for 2 reasons: 1: it doesn't consider the horizontal space after the ascii area and second, it's honestly more confusing to read to make sure this work because essentially, what you want to do is test that the click was inside the grid and having all these conditions separated doesn't make that part obvious. I would rather keep the 4 conditions I mentioned because they all set each boundaries of the grid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The area to the right doesn't really need to be checked, since it touches the scroll bar, even when zoomed in very far, but (Actually scrap that, that's not the case anymore.) I do agree it would be nicer to do.
I don't agree on the readibility. I'd rather see conditions simply comparing two variables than having a bunch of additions within the condition. That's something I'm working on now. The code that you suggested earlier didn't match the areas exactly (pink box in the screenshot).
I'll combine the two, by storing the left/right and top/bottom in variables, then checking if the clicked locaiton is in either of those areas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, sounds fine with better variable labels.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've got the correct area calculations for the hex table now:
int spacing = m_charWidthEm / 2;
int hexAreaLeft = m_rowHeaderWidth - spacing / 2;
int hexAreaRight = m_rowHeaderWidth + m_hexAreaWidth - spacing / 2;
int asciiAreaLeft = (m_hexAsciiSeparatorPosX + spacing);
int asciiAreaRight = asciiAreaLeft + m_charWidthEm * m_numColumns;
int AreaTop = m_columnHeaderHeight + m_charHeight - fontMetrics().overlinePos();
int AreaBottom = AreaTop + m_charHeight * m_numRows;
int cellWidth = m_charWidthEm * 2 + spacing;
int cellHeight = m_charHeight;
The click area includes half of the spacing between each byte. Now to get the values for the ascii table and simplyfy it a bit, then I'll get this PR nice and clean! :P
…n the stack. Previously, these dialogs were constructed dynamically in the heap, and were never deleted explicitly; only when their parent QObject was destroyed at a much later time would they be deleted. This deferred destruction was in fact masking a segmentation fault caused by a double-free: the entry in the dialog was not extracted via `stealEntry()`; instead the reference was taking from the `entryCopy` variable (same pointer), resulting in the entry getting deleted twice, eventually. Reproduction steps: - Double click on an watch entry to bring up the **Edit Watch** dialog. - Make any edit and press **OK**. - Quit the application gracefully to force the destruction of the dialog. - A segmentation fault would be produced: ``` (gdb) bt #0 QArrayDataPointer<char16_t>::deref() (this=<optimised out>, this=0x558b5d1a35c0) at /usr/include/c++/11/bits/atomic_base.h:392 aldelaro5#1 QArrayDataPointer<char16_t>::~QArrayDataPointer() (this=0x558b5d1a35c0, __in_chrg=<optimised out>) at /usr/include/x86_64-linux-gnu/qt6/QtCore/qarraydatapointer.h:129 aldelaro5#2 QString::~QString() (this=0x558b5d1a35c0, __in_chrg=<optimised out>) at /usr/include/x86_64-linux-gnu/qt6/QtCore/qstring.h:1302 aldelaro5#3 MemWatchEntry::~MemWatchEntry() (this=0x558b5d1a35c0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/MemoryWatch/MemWatchEntry.cpp:48 aldelaro5#4 0x0000558b5c517787 in DlgAddWatchEntry::~DlgAddWatchEntry() (this=0x558b5d1be8f0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/Dialogs/DlgAddWatchEntry.cpp:31 aldelaro5#5 0x0000558b5c51782d in DlgAddWatchEntry::~DlgAddWatchEntry() (this=0x558b5d1be8f0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/Dialogs/DlgAddWatchEntry.cpp:32 aldelaro5#6 0x00007fde5556599b in QObjectPrivate::deleteChildren() () at /lib/x86_64-linux-gnu/libQt6Core.so.6 aldelaro5#7 0x00007fde5624cab8 in QWidget::~QWidget() () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#8 0x0000558b5c51d0cd in MemWatchWidget::~MemWatchWidget() (this=0x558b5cd068e0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/MemWatchWidget.cpp:41 aldelaro5#9 0x0000558b5c5367cd in MainWindow::~MainWindow() (this=0x7ffc70ef10f0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MainWindow.cpp:56 aldelaro5#10 0x0000558b5c4f1ad7 in main(int, char**) (argc=<optimised out>, argv=<optimised out>) at /w/dolphin-memory-engine/Source/main.cpp:56 ``` # Please enter the commit message for your changes. Lines starting # with '#' will be kept; you may remove them yourself if you want to. # An empty message aborts the commit. # # Date: Fri May 17 22:10:10 2024 +0100 # # On branch watch_entry_dialog_segfault # Changes to be committed: # modified: Source/GUI/MemViewer/MemViewer.cpp # modified: Source/GUI/MemWatcher/MemWatchWidget.cpp #
…n the stack. Previously, these dialogs were constructed dynamically in the heap, and were never deleted explicitly; only when their parent QObject was destroyed at a much later time would they be deleted. This deferred destruction was in fact masking a segmentation fault caused by a double-free: the entry in the dialog was not extracted via `stealEntry()`; instead the reference was taking from the `entryCopy` variable (same pointer), resulting in the entry getting deleted twice, eventually. Reproduction steps: - Double click on an watch entry to bring up the **Edit Watch** dialog. - Make any edit and press **OK**. - Quit the application gracefully to force the destruction of the dialog. - A segmentation fault would be produced: ``` (gdb) bt #0 QArrayDataPointer<char16_t>::deref() (this=<optimised out>, this=0x558b5d1a35c0) at /usr/include/c++/11/bits/atomic_base.h:392 aldelaro5#1 QArrayDataPointer<char16_t>::~QArrayDataPointer() (this=0x558b5d1a35c0, __in_chrg=<optimised out>) at /usr/include/x86_64-linux-gnu/qt6/QtCore/qarraydatapointer.h:129 aldelaro5#2 QString::~QString() (this=0x558b5d1a35c0, __in_chrg=<optimised out>) at /usr/include/x86_64-linux-gnu/qt6/QtCore/qstring.h:1302 aldelaro5#3 MemWatchEntry::~MemWatchEntry() (this=0x558b5d1a35c0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/MemoryWatch/MemWatchEntry.cpp:48 aldelaro5#4 0x0000558b5c517787 in DlgAddWatchEntry::~DlgAddWatchEntry() (this=0x558b5d1be8f0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/Dialogs/DlgAddWatchEntry.cpp:31 aldelaro5#5 0x0000558b5c51782d in DlgAddWatchEntry::~DlgAddWatchEntry() (this=0x558b5d1be8f0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/Dialogs/DlgAddWatchEntry.cpp:32 aldelaro5#6 0x00007fde5556599b in QObjectPrivate::deleteChildren() () at /lib/x86_64-linux-gnu/libQt6Core.so.6 aldelaro5#7 0x00007fde5624cab8 in QWidget::~QWidget() () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#8 0x0000558b5c51d0cd in MemWatchWidget::~MemWatchWidget() (this=0x558b5cd068e0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/MemWatchWidget.cpp:41 aldelaro5#9 0x0000558b5c5367cd in MainWindow::~MainWindow() (this=0x7ffc70ef10f0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MainWindow.cpp:56 aldelaro5#10 0x0000558b5c4f1ad7 in main(int, char**) (argc=<optimised out>, argv=<optimised out>) at /w/dolphin-memory-engine/Source/main.cpp:56 ```
…n the stack. Previously, these dialogs were constructed dynamically in the heap, and were never deleted explicitly; only when their parent QObject was destroyed at a much later time would they be deleted. This deferred destruction was in fact masking a segmentation fault caused by a double-free: the entry in the dialog was not extracted via `stealEntry()`; instead the reference was taken from the `entryCopy` variable (same pointer), resulting in the entry getting deleted twice, eventually. Reproduction steps: - Double-click on an watch entry to bring up the **Edit Watch** dialog. - Make any edit and press **OK**. - Quit the application gracefully to force the destruction of the dialog. - A segmentation fault would be produced: ``` (gdb) bt #0 QArrayDataPointer<char16_t>::deref() (this=<optimised out>, this=0x558b5d1a35c0) at /usr/include/c++/11/bits/atomic_base.h:392 aldelaro5#1 QArrayDataPointer<char16_t>::~QArrayDataPointer() (this=0x558b5d1a35c0, __in_chrg=<optimised out>) at /usr/include/x86_64-linux-gnu/qt6/QtCore/qarraydatapointer.h:129 aldelaro5#2 QString::~QString() (this=0x558b5d1a35c0, __in_chrg=<optimised out>) at /usr/include/x86_64-linux-gnu/qt6/QtCore/qstring.h:1302 aldelaro5#3 MemWatchEntry::~MemWatchEntry() (this=0x558b5d1a35c0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/MemoryWatch/MemWatchEntry.cpp:48 aldelaro5#4 0x0000558b5c517787 in DlgAddWatchEntry::~DlgAddWatchEntry() (this=0x558b5d1be8f0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/Dialogs/DlgAddWatchEntry.cpp:31 aldelaro5#5 0x0000558b5c51782d in DlgAddWatchEntry::~DlgAddWatchEntry() (this=0x558b5d1be8f0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/Dialogs/DlgAddWatchEntry.cpp:32 aldelaro5#6 0x00007fde5556599b in QObjectPrivate::deleteChildren() () at /lib/x86_64-linux-gnu/libQt6Core.so.6 aldelaro5#7 0x00007fde5624cab8 in QWidget::~QWidget() () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#8 0x0000558b5c51d0cd in MemWatchWidget::~MemWatchWidget() (this=0x558b5cd068e0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/MemWatchWidget.cpp:41 aldelaro5#9 0x0000558b5c5367cd in MainWindow::~MainWindow() (this=0x7ffc70ef10f0, __in_chrg=<optimised out>) at /w/dolphin-memory-engine/Source/GUI/MainWindow.cpp:56 aldelaro5#10 0x0000558b5c4f1ad7 in main(int, char**) (argc=<optimised out>, argv=<optimised out>) at /w/dolphin-memory-engine/Source/main.cpp:56 ```
…group node is right-clicked. This was a regression in aldelaro5#167. Group nodes do not have a watch entry associated with them. When a group node was right-clicked, a null pointer would be fatally dereferenced: ``` #0 MemWatchEntry::isBoundToPointer() const (this=this@entry=0x0) at /w/dolphin-memory-engine/Source/MemoryWatch/MemWatchEntry.cpp:77 aldelaro5#1 0x0000561a4ab96c94 in MemWatchWidget::onMemWatchContextMenuRequested(QPoint const&) (this=0x561a4c28c580, pos=...) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/MemWatchWidget.cpp:280 aldelaro5#2 0x00007f56ac7be023 in () at /lib/x86_64-linux-gnu/libQt6Core.so.6 aldelaro5#3 0x00007f56ad489889 in QWidget::customContextMenuRequested(QPoint const&) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#4 0x00007f56ad4a6020 in QWidget::event(QEvent*) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#5 0x00007f56ad541406 in QFrame::event(QEvent*) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#6 0x00007f56ac765818 in QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Core.so.6 aldelaro5#7 0x00007f56ad44bd25 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#8 0x00007f56ad454c5e in QApplication::notify(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#9 0x00007f56ac765a58 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Core.so.6 aldelaro5#10 0x00007f56ad4b796c in () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#11 0x00007f56ad4ba635 in () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#12 0x00007f56ad44bd36 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 aldelaro5#13 0x00007f56ac765a58 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Core.so.6 aldelaro5#14 0x00007f56acd0a6bf in QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent*) () at /lib/x86_64-linux-gnu/libQt6Gui.so.6 aldelaro5#15 0x00007f56acd52c8c in QWindowSystemInterface::sendWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) () at /lib/x86_64-linux-gnu/libQt6Gui.so.6 aldelaro5#16 0x00007f56a8c7686e in () at /usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/../../../libQt6XcbQpa.so.6 aldelaro5#17 0x00007f56abe32d3b in g_main_context_dispatch () at /lib/x86_64-linux-gnu/libglib-2.0.so.0 aldelaro5#18 0x00007f56abe882b8 in () at /lib/x86_64-linux-gnu/libglib-2.0.so.0 aldelaro5#19 0x00007f56abe303e3 in g_main_context_iteration () at /lib/x86_64-linux-gnu/libglib-2.0.so.0 aldelaro5#20 0x00007f56ac98deae in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () at /lib/x86_64-linux-gnu/libQt6Core.so.6 aldelaro5#21 0x00007f56ac772adb in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () at /lib/x86_64-linux-gnu/libQt6Core.so.6 aldelaro5#22 0x00007f56ac76e0f3 in QCoreApplication::exec() () at /lib/x86_64-linux-gnu/libQt6Core.so.6 aldelaro5#23 0x0000561a4ab66e8c in main(int, char**) (argc=<optimised out>, argv=<optimised out>) at /w/dolphin-memory-engine/Source/main.cpp:54 ``` Fixes aldelaro5#170.
…group node is right-clicked. This was a regression in #167. Group nodes do not have a watch entry associated with them. When a group node was right-clicked, a null pointer would be fatally dereferenced: ``` #0 MemWatchEntry::isBoundToPointer() const (this=this@entry=0x0) at /w/dolphin-memory-engine/Source/MemoryWatch/MemWatchEntry.cpp:77 #1 0x0000561a4ab96c94 in MemWatchWidget::onMemWatchContextMenuRequested(QPoint const&) (this=0x561a4c28c580, pos=...) at /w/dolphin-memory-engine/Source/GUI/MemWatcher/MemWatchWidget.cpp:280 #2 0x00007f56ac7be023 in () at /lib/x86_64-linux-gnu/libQt6Core.so.6 #3 0x00007f56ad489889 in QWidget::customContextMenuRequested(QPoint const&) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 #4 0x00007f56ad4a6020 in QWidget::event(QEvent*) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 #5 0x00007f56ad541406 in QFrame::event(QEvent*) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 #6 0x00007f56ac765818 in QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Core.so.6 #7 0x00007f56ad44bd25 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 #8 0x00007f56ad454c5e in QApplication::notify(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 #9 0x00007f56ac765a58 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Core.so.6 #10 0x00007f56ad4b796c in () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 #11 0x00007f56ad4ba635 in () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 #12 0x00007f56ad44bd36 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Widgets.so.6 #13 0x00007f56ac765a58 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt6Core.so.6 #14 0x00007f56acd0a6bf in QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent*) () at /lib/x86_64-linux-gnu/libQt6Gui.so.6 #15 0x00007f56acd52c8c in QWindowSystemInterface::sendWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) () at /lib/x86_64-linux-gnu/libQt6Gui.so.6 #16 0x00007f56a8c7686e in () at /usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/../../../libQt6XcbQpa.so.6 #17 0x00007f56abe32d3b in g_main_context_dispatch () at /lib/x86_64-linux-gnu/libglib-2.0.so.0 #18 0x00007f56abe882b8 in () at /lib/x86_64-linux-gnu/libglib-2.0.so.0 #19 0x00007f56abe303e3 in g_main_context_iteration () at /lib/x86_64-linux-gnu/libglib-2.0.so.0 #20 0x00007f56ac98deae in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () at /lib/x86_64-linux-gnu/libQt6Core.so.6 #21 0x00007f56ac772adb in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () at /lib/x86_64-linux-gnu/libQt6Core.so.6 #22 0x00007f56ac76e0f3 in QCoreApplication::exec() () at /lib/x86_64-linux-gnu/libQt6Core.so.6 #23 0x0000561a4ab66e8c in main(int, char**) (argc=<optimised out>, argv=<optimised out>) at /w/dolphin-memory-engine/Source/main.cpp:54 ``` Fixes #170.
I originalle started with the idea to add a "Right-click Hex -> Add To Watch" option, but found a lot of other things that could be improved first 😄
Copied from the commit:
m_carretBetweenHex
to false when navigating using the keyboardwriteHexCharacterToSelectedMemory
to be usable for ASCII tooSome notes to clarify:
Before:
After (with extra rows just because):