OXYGENHUB
◆ main openmsx / src / imgui / DebuggableEditor.cc
36.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "DebuggableEditor.hh"
2
3
#include "ImGuiCpp.hh"
4
#include "ImGuiDebugger.hh"
5
#include "ImGuiManager.hh"
6
#include "ImGuiOpenFile.hh"
7
#include "ImGuiSettings.hh"
8
#include "ImGuiUtils.hh"
9
#include "Shortcuts.hh"
10
11
#include "CommandException.hh"
12
#include "Debuggable.hh"
13
#include "Debugger.hh"
14
#include "Display.hh"
15
#include "File.hh"
16
#include "Interpreter.hh"
17
#include "MSXMotherBoard.hh"
18
#include "SymbolManager.hh"
19
#include "TclObject.hh"
20
#include "VideoSystem.hh"
21
22
#include "enumerate.hh"
23
#include "narrow.hh"
24
#include "unreachable.hh"
25
26
#include "CustomFont.h"
27
#include "imgui_stdlib.h"
28
29
#include <algorithm>
30
#include <array>
31
#include <bit>
32
#include <cassert>
33
#include <cstdint>
34
#include <cstdio>
35
#include <expected>
36
#include <span>
37
38
namespace openmsx {
39
40
using namespace std::literals;
41
42
static constexpr int MidColsCount = 8; // extra spacing between every mid-cols.
43
static constexpr auto HighlightColor = IM_COL32(255, 255, 255, 50); // background color of highlighted bytes.
44
static constexpr auto HighlightSymbolColor = IM_COL32(148, 95, 35, 255); // background color of known symbol bytes.
45
46
DebuggableEditor::DebuggableEditor(ImGuiManager& manager_, std::string_view debuggableName_, size_t index)
47
	: ImGuiPart(manager_)
48
	, symbolManager(manager.getReactor().getSymbolManager())
49
	, debuggableNameSize(debuggableName_.size())
50
	, title(strCat(debuggableName_, strCat_if(index, " (", index + 1, ')')))
51
{
52
}
53
54
void DebuggableEditor::save(ImGuiTextBuffer& buf)
55
{
56
	savePersistent(buf, *this, persistentElements);
57
}
58
59
void DebuggableEditor::loadLine(std::string_view name, zstring_view value)
60
{
61
	loadOnePersistent(name, value, *this, persistentElements);
62
	parseSearchString(searchString);
63
}
64
65
void DebuggableEditor::loadEnd()
66
{
67
	updateAddr = true;
68
}
69
70
DebuggableEditor::Sizes DebuggableEditor::calcSizes(unsigned memSize) const
71
{
72
	Sizes s;
73
	const auto& style = ImGui::GetStyle();
74
75
	s.addrDigitsCount = 0;
76
	for (unsigned n = memSize - 1; n > 0; n >>= 4) {
77
		++s.addrDigitsCount;
78
	}
79
80
	s.lineHeight = ImGui::GetTextLineHeight();
81
	s.glyphWidth = ImGui::CalcTextSize("F").x + 1;            // We assume the font is mono-space
82
	s.hexCellWidth = truncf(s.glyphWidth * 2.5f);             // "FF " we include trailing space in the width to easily catch clicks everywhere
83
	s.spacingBetweenMidCols = truncf(s.hexCellWidth * 0.25f); // Every 'MidColsCount' columns we add a bit of extra spacing
84
	s.posHexStart = float(s.addrDigitsCount + 2) * s.glyphWidth;
85
	auto posHexEnd = s.posHexStart + (s.hexCellWidth * float(columns));
86
	s.posAsciiStart = s.posAsciiEnd = posHexEnd;
87
	if (showAscii) {
88
		int numMacroColumns = (columns + MidColsCount - 1) / MidColsCount;
89
		s.posAsciiStart = posHexEnd + s.glyphWidth + float(numMacroColumns) * s.spacingBetweenMidCols;
90
		s.posAsciiEnd = s.posAsciiStart + float(columns) * s.glyphWidth;
91
	}
92
	s.windowWidth = s.posAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.glyphWidth;
93
	return s;
94
}
95
96
void DebuggableEditor::makeSnapshot(MSXMotherBoard& motherBoard)
97
{
98
	auto& debugger = motherBoard.getDebugger();
99
	if (auto* debuggable = debugger.findDebuggable(getDebuggableName())) {
100
		makeSnapshot(*debuggable);
101
	}
102
}
103
void DebuggableEditor::makeSnapshot(Debuggable& debuggable)
104
{
105
	snapshot.resize(debuggable.getSize());
106
	debuggable.readBlock(0, snapshot);
107
}
108
109
void DebuggableEditor::paint(MSXMotherBoard* motherBoard)
110
{
111
	if (!open || !motherBoard) return;
112
	auto& debugger = motherBoard->getDebugger();
113
	auto* debuggable = debugger.findDebuggable(getDebuggableName());
114
	if (!debuggable) return;
115
116
	unsigned memSize = debuggable->getSize();
117
	columns = std::min(columns, narrow<int>(memSize));
118
	auto s = calcSizes(memSize);
119
120
	if (showExportWindow) {
121
		drawExport(s, *debuggable);
122
	}
123
124
	im::ScopedFont sf(manager.fontMono);
125
126
	ImGui::SetNextWindowSize(ImVec2(s.windowWidth, s.windowWidth * 0.60f), ImGuiCond_FirstUseEver);
127
128
	im::Window(title.c_str(), &open, ImGuiWindowFlags_NoScrollbar, [&]{
129
		if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) &&
130
		    ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
131
			ImGui::OpenPopup("context");
132
		}
133
		drawContents(s, *debuggable, memSize);
134
	});
135
}
136
137
[[nodiscard]] static char formatAsciiData(uint8_t val)
138
{
139
	return (val < 32 || val >= 128) ? '.' : char(val);
140
}
141
142
void DebuggableEditor::drawExport(const Sizes& s, Debuggable& debuggable)
143
{
144
	auto& interp = manager.getInterpreter();
145
	const auto& style = ImGui::GetStyle();
146
147
	im::Window(tmpStrCat("Export ", title).c_str(), &showExportWindow, [&]{
148
		if (ImGui::IsWindowAppearing()) {
149
			exportBegin = strCat("0x", formatAddr(s, 0));
150
			exportEnd   = strCat("0x", formatAddr(s, debuggable.getSize() - 1));
151
		}
152
153
		std::optional<unsigned> begin, end;
154
		std::string formatError;
155
156
		ImGui::SeparatorText("Input");
157
158
		ImGui::RadioButton("All", &exportRange, RANGE_ALL);
159
		if (exportRange == RANGE_ALL) {
160
			begin = 0;
161
			end = debuggable.getSize() - 1;
162
		}
163
164
		ImGui::RadioButton("Range   ", &exportRange, RANGE_RANGE);
165
		auto handleAddress = [&](std::string_view label, std::string& text, int lowerBound) {
166
			std::optional<unsigned> addr;
167
			std::string error;
168
			try {
169
				auto i = TclObject(text).eval(interp).getInt(interp);
170
				if ((i < 0) || (i >= narrow<int>(debuggable.getSize()))) {
171
					error = "out of range";
172
				} else if (i < lowerBound) {
173
					error = "'end' smaller than 'begin'";
174
				} else {
175
					addr = narrow<unsigned>(i);
176
				}
177
			} catch (MSXException& e) {
178
				error = e.getMessage();
179
			}
180
			ImGui::SameLine();
181
			ImGui::TextUnformatted(label);
182
			ImGui::SameLine();
183
			ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5.0f);
184
			im::StyleColor(!addr, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
185
				ImGui::InputText(tmpStrCat("##", label).c_str(), &text);
186
				if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
187
					text = strCat("0x", formatAddr(s, currentAddr));
188
				}
189
				simpleToolTip([&]{
190
					return addr ? strCat("0x", formatAddr(s, *addr))
191
					            : error;
192
				});
193
			});
194
			return addr;
195
		};
196
		im::Disabled(exportRange != RANGE_RANGE, [&]{
197
			begin = handleAddress("begin:", exportBegin, 0);
198
			end =   handleAddress("end:", exportEnd, begin.value_or(0));
199
		});
200
		HelpMarker("Both 'begin' and 'end' are inclusive.\n"
201
		           "You can enter an expression line '0x100 + 7' or '[reg HL]'.\n"
202
		           "Right-click to enter the current cursor position.");
203
204
		ImGui::SeparatorText("Format");
205
206
		ImGui::RadioButton("Raw", &exportFormatted, EXPORT_RAW);
207
		HelpMarker("Produce binary (unformatted) output");
208
209
		ImGui::RadioButton("Formatted", &exportFormatted, EXPORT_FORMATTED);
210
		HelpMarker("Produce formatted ASCII output");
211
		im::DisabledIndent(exportFormatted != EXPORT_FORMATTED, [&]{
212
			ImGui::TextUnformatted("Line");
213
			HelpMarker("Formatting options per line");
214
			im::Indent([&]{
215
				ImGui::TextUnformatted("Prefix:");
216
				ImGui::SameLine();
217
				ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
218
				ImGui::InputText("##prefix", &exportPrefix);
219
				HelpMarker("Output a prefix at the start of each line.\n"
220
					"For example 'db ', note the space after 'db'.");
221
222
				ImGui::TextUnformatted("Columns:");
223
				ImGui::SameLine();
224
				ImGui::SetNextItemWidth(9.0f * s.glyphWidth + 2.0f * style.FramePadding.x);
225
				if (ImGui::InputInt("##columns", &exportColumns, 1, 0)) {
226
					exportColumns = std::clamp(exportColumns, 1, MAX_COLUMNS);
227
				}
228
				HelpMarker("Output N columns of comma-separated values.");
229
230
				ImGui::TextUnformatted("Suffix:");
231
				ImGui::SameLine();
232
				ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
233
				ImGui::InputText("##suffix", &exportSuffix);
234
				HelpMarker("Output a suffix at the end of each line.\n"
235
					"This might be useful if you want a trailing comma.");
236
			});
237
			ImGui::TextUnformatted("Value");
238
			HelpMarker("Formatting options per value (multiple per line)");
239
			im::Indent([&]{
240
				ImGui::RadioButton("Bin", &exportFormat, FORMAT_BIN);
241
				HelpMarker("8 digit binary with prefix '0b'\n"
242
					"corresponding to custom format '0b%08b'");
243
				ImGui::SameLine();
244
				ImGui::RadioButton("Dec", &exportFormat, FORMAT_DEC);
245
				HelpMarker("3 digit decimal right padded with spaces\n"
246
					"corresponding to custom format '%3d'");
247
				ImGui::SameLine();
248
				ImGui::RadioButton("Hex", &exportFormat, FORMAT_HEX);
249
				HelpMarker("2 digit hexadecimal\n"
250
					"corresponding to custom format '0x%02X'");
251
				ImGui::SameLine();
252
				ImGui::RadioButton("ASCII", &exportFormat, FORMAT_ASCII);
253
				HelpMarker("As visible in the ASCII view.");
254
				try {
255
					if (exportFormatted == EXPORT_FORMATTED) {
256
						makeTclList("format", exportCustomFormat, 0).executeCommand(interp);
257
					}
258
				} catch (MSXException& e) {
259
					formatError = e.getMessage();
260
				}
261
				ImGui::RadioButton("Custom", &exportFormat, FORMAT_CUSTOM);
262
				ImGui::SameLine();
263
				im::Disabled(exportFormat != FORMAT_CUSTOM, [&]{
264
					im::StyleColor(!formatError.empty(), ImGuiCol_Text, getColor(imColor::ERROR), [&]{
265
						ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
266
						ImGui::InputText("##custom", &exportCustomFormat);
267
						if (!formatError.empty()) {
268
							simpleToolTip(formatError);
269
						}
270
					});
271
				});
272
				HelpMarker("format string passed to the Tcl 'format' command");
273
			});
274
		});
275
		std::string_view formatStr = (exportFormat == FORMAT_BIN) ? "0b%08b"sv
276
		                           : (exportFormat == FORMAT_DEC) ? "%3d"sv
277
		                           : (exportFormat == FORMAT_HEX) ? "0x%02X"sv
278
		                           : exportCustomFormat;
279
280
		ImGui::SeparatorText("Output");
281
282
		ImGui::RadioButton("Clipboard", &exportDestination, OUTPUT_CLIPBOARD);
283
284
		ImGui::RadioButton("File", &exportDestination, OUTPUT_FILE);
285
		ImGui::SameLine();
286
		im::Disabled(exportDestination != OUTPUT_FILE, [&]{
287
			ImGui::SetNextItemWidth(ImGui::GetFontSize() * 18.0f);
288
			ImGui::InputText("##filename", &exportFilename);
289
			ImGui::SameLine();
290
			if (ImGui::Button(ICON_IGFD_FOLDER_OPEN)) {
291
				manager.openFile->selectNewFile(
292
					strCat("Select output file for ", title), "",
293
					[&](const auto& fn) { exportFilename = fn; });
294
			}
295
		});
296
297
		bool ok = begin && end &&
298
		          (exportFormat != FORMAT_CUSTOM || formatError.empty()) &&
299
			  (exportDestination != OUTPUT_FILE || !exportFilename.empty());
300
		im::Disabled(!ok, [&]{
301
			if (ImGui::Button("Export")) {
302
				auto fetch = [&](unsigned address) { return debuggable.read(address); };
303
				try {
304
					auto output = (exportFormatted == EXPORT_FORMATTED)
305
						? ((exportFormat == FORMAT_ASCII)
306
							? formatToString(fetch, *begin, *end, exportPrefix, exportColumns, exportSuffix, {},
307
								[](uint8_t val) { return std::string(1, formatAsciiData(val)); })
308
							: formatToString(fetch, *begin, *end, exportPrefix, exportColumns, exportSuffix, ", ",
309
								formatStr, interp))
310
						: rawToString(fetch, *begin, *end);
311
					if (exportDestination == OUTPUT_CLIPBOARD) {
312
						manager.getReactor().getDisplay().getVideoSystem().setClipboardText(output);
313
					} else {
314
						File file(exportFilename, File::OpenMode::TRUNCATE);
315
						file.write(std::span(std::bit_cast<const uint8_t*>(output.data()), output.size()));
316
					}
317
					exportStatus = "Export successful";
318
					exportStatusTimeout = 3.0f;
319
				} catch (MSXException& e) {
320
					exportStatus = e.getMessage();
321
					exportStatusTimeout = 5.0f;
322
					manager.printError(e.getMessage());
323
				}
324
			}
325
		});
326
		if (exportStatusTimeout > 0.0f) {
327
			const auto& io = ImGui::GetIO();
328
			exportStatusTimeout -= io.DeltaTime;
329
330
			ImGui::SameLine();
331
			ImGui::TextUnformatted(exportStatus);
332
		}
333
334
	});
335
}
336
337
[[nodiscard]] static unsigned DataTypeGetSize(ImGuiDataType dataType)
338
{
339
	std::array<unsigned, 8> sizes = { 1, 1, 2, 2, 4, 4, 8, 8 };
340
	assert(dataType >= 0 && dataType < 8);
341
	return sizes[dataType];
342
}
343
344
[[nodiscard]] static std::optional<int> parseHexDigit(char c)
345
{
346
	if ('0' <= c && c <= '9') return c - '0';
347
	if ('a' <= c && c <= 'f') return c - 'a' + 10;
348
	if ('A' <= c && c <= 'F') return c - 'A' + 10;
349
	return std::nullopt;
350
}
351
352
[[nodiscard]] static std::optional<uint8_t> parseDataValue(std::string_view str)
353
{
354
	if (str.size() == 1) {
355
		return parseHexDigit(str[0]);
356
	} else if (str.size() == 2) {
357
		if (auto digit0 = parseHexDigit(str[0])) {
358
			if (auto digit1 = parseHexDigit(str[1])) {
359
				return 16 * *digit0 + *digit1;
360
			}
361
		}
362
	}
363
	return std::nullopt;
364
}
365
366
[[nodiscard]] static std::expected<unsigned, std::string> parseAddressExpr(
367
	std::string_view str, const SymbolManager& symbolManager, Interpreter& interp)
368
{
369
	if (str.empty()) return 0;
370
371
	// TODO linear search, probably OK for now, but can be improved if it turns out to be a problem
372
	// Note: limited to 16-bit, but larger values trigger an errors and are then handled below, so that's fine
373
	if (auto addr = symbolManager.parseSymbolOrValue(str)) {
374
		return *addr;
375
	}
376
377
	try {
378
		return TclObject(str).eval(interp).getInt(interp);
379
	} catch (CommandException& e) {
380
		return std::unexpected(e.getMessage());
381
	}
382
}
383
384
[[nodiscard]] static std::string formatData(uint8_t val)
385
{
386
	return strCat(hex_string<2, HexCase::upper>(val));
387
}
388
389
[[nodiscard]] std::string DebuggableEditor::formatAddr(const Sizes& s, unsigned addr) const
390
{
391
	return strCat(hex_string<HexCase::upper>(Digits{size_t(s.addrDigitsCount)}, addr));
392
}
393
void DebuggableEditor::setStrings(const Sizes& s, Debuggable& debuggable)
394
{
395
	addrStr = strCat("0x", formatAddr(s, currentAddr));
396
	auto b = debuggable.read(currentAddr);
397
	if (dataEditingActive == HEX  ) dataInput = formatData(b);
398
	if (dataEditingActive == ASCII) dataInput = std::string(1, formatAsciiData(b));
399
}
400
bool DebuggableEditor::setAddr(const Sizes& s, Debuggable& debuggable, unsigned memSize, unsigned addr)
401
{
402
	addr = std::min(addr, memSize - 1);
403
	if (currentAddr == addr) return false;
404
	currentAddr = addr;
405
	setStrings(s, debuggable);
406
	return true;
407
}
408
void DebuggableEditor::scrollAddr(const Sizes& s, Debuggable& debuggable, unsigned memSize, unsigned addr, bool forceScroll)
409
{
410
	if (setAddr(s, debuggable, memSize, addr) || forceScroll) {
411
		im::Child("##scrolling", [&]{
412
			int row = narrow<int>(currentAddr) / columns;
413
			ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + float(row) * ImGui::GetTextLineHeight());
414
		});
415
	}
416
}
417
418
void DebuggableEditor::drawContents(const Sizes& s, Debuggable& debuggable, unsigned memSize)
419
{
420
	const auto& style = ImGui::GetStyle();
421
	if (updateAddr) {
422
		updateAddr = false;
423
		scrollAddr(s, debuggable, memSize, currentAddr, true);
424
	} else {
425
		// still clip addr (for the unlikely case that 'memSize' got smaller)
426
		setAddr(s, debuggable, memSize, currentAddr);
427
	}
428
429
	float footerHeight = 0.0f;
430
	if (showAddress) {
431
		footerHeight += style.ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
432
	}
433
	if (showSearch) {
434
		footerHeight += style.ItemSpacing.y + 2 * ImGui::GetFrameHeightWithSpacing();
435
	}
436
	if (showDataPreview) {
437
		footerHeight += style.ItemSpacing.y + ImGui::GetFrameHeightWithSpacing() + 3 * ImGui::GetTextLineHeightWithSpacing();
438
	}
439
	if (showSymbolInfo) {
440
		footerHeight += style.ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
441
	}
442
	// We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
443
	// This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
444
	int cFlags = ImGuiWindowFlags_NoMove;
445
	// note: with ImGuiWindowFlags_NoNav it happens occasionally that (rapid) cursor-input is passed to the underlying MSX window
446
	//    without ImGuiWindowFlags_NoNav PgUp/PgDown work, but they are ALSO interpreted as openMSX hotkeys,
447
	//                                   though other windows have the same problem.
448
	//flags |= ImGuiWindowFlags_NoNav;
449
	cFlags |= ImGuiWindowFlags_HorizontalScrollbar;
450
	ImGui::BeginChild("##scrolling", ImVec2(0, -footerHeight), ImGuiChildFlags_None, cFlags);
451
	ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
452
	ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
453
454
	std::optional<unsigned> nextAddr;
455
456
	// Draw vertical separator
457
	auto* drawList = ImGui::GetWindowDrawList();
458
	ImVec2 windowPos = ImGui::GetWindowPos();
459
	if (showAscii) {
460
		drawList->AddLine(ImVec2(windowPos.x + s.posAsciiStart - s.glyphWidth, windowPos.y),
461
		                  ImVec2(windowPos.x + s.posAsciiStart - s.glyphWidth, windowPos.y + 9999),
462
		                  ImGui::GetColorU32(ImGuiCol_Border));
463
	}
464
465
	auto handleInput = [&](unsigned addr, int width, auto formatData, auto parseData, int extraFlags = 0) {
466
		// Display text input on current byte
467
		if (dataEditingTakeFocus) {
468
			ImGui::SetKeyboardFocusHere();
469
			setStrings(s, debuggable);
470
		}
471
		struct UserData {
472
			// TODO: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here.
473
			static int Callback(ImGuiInputTextCallbackData* data) {
474
				auto* userData = static_cast<UserData*>(data->UserData);
475
				if (userData->cursorPos != -1) { // reset cursor
476
					data->CursorPos = userData->cursorPos;
477
				}
478
				if (!data->HasSelection()) {
479
					userData->cursorPos = data->CursorPos;
480
				}
481
				if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) {
482
					// When not editing a byte, always refresh its InputText content pulled from underlying memory data
483
					// (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there)
484
					data->DeleteChars(0, data->BufTextLen);
485
					userData->format(data);
486
					//data->InsertChars(0, ...);
487
					//data->SelectionEnd = width;
488
					data->SelectionStart = 0;
489
					data->CursorPos = 0;
490
				}
491
				return 0;
492
			}
493
			std::function<void(ImGuiInputTextCallbackData* data)> format;
494
			int cursorPos = -1; // Output
495
		};
496
		UserData userData;
497
		userData.format = formatData;
498
		if (resetCursor) {
499
			resetCursor = false;
500
			userData.cursorPos = 0;
501
		}
502
		ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue
503
					  | ImGuiInputTextFlags_AutoSelectAll
504
					  | ImGuiInputTextFlags_NoHorizontalScroll
505
					  | ImGuiInputTextFlags_CallbackAlways
506
					  | ImGuiInputTextFlags_AlwaysOverwrite
507
					  | extraFlags;
508
		ImGui::SetNextItemWidth(s.glyphWidth * float(width));
509
		bool dataWrite = false;
510
		im::ID(int(addr), [&]{
511
			if (ImGui::InputText("##data", &dataInput, flags, UserData::Callback, &userData)) {
512
				dataWrite = true;
513
			} else if (!ImGui::IsItemActive()) {
514
				setStrings(s, debuggable);
515
			}
516
		});
517
		// Move cursor but only apply on next frame so scrolling will be synchronized (because currently we can't change the scrolling while the window is being rendered)
518
		if (addrMode == CURSOR && ImGui::IsItemActive()) {
519
			if (ImGui::IsKeyChordPressed(ImGuiKey_Home | ImGuiMod_Ctrl)) {
520
				nextAddr = 0;
521
				updateAddr = true; // force scroll
522
			} else if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
523
				nextAddr = currentAddr - (currentAddr % unsigned(columns));
524
			}
525
			if (ImGui::IsKeyChordPressed(ImGuiKey_End | ImGuiMod_Ctrl)) {
526
				nextAddr = memSize - 1;
527
				updateAddr = true; // force scroll
528
				// ImGui::InputText() already reacted to 'End' by placing the edit cursor at the end.
529
				// And normally that's a signal to write the value to memory. We don't want that.
530
				resetCursor = true;
531
			} else if (ImGui::IsKeyPressed(ImGuiKey_End)) {
532
				auto tmp = currentAddr - (currentAddr % unsigned(columns)) + columns - 1;
533
				nextAddr = std::min(tmp, memSize - 1);
534
				resetCursor = true;
535
			}
536
			if ((int(currentAddr) >= columns) &&
537
			    ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
538
				nextAddr = currentAddr - columns;
539
			}
540
			if ((int(currentAddr) < int(memSize - columns)) &&
541
			    ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
542
				nextAddr = currentAddr + columns;
543
			}
544
			if ((int(currentAddr) > 0) &&
545
			    ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
546
				nextAddr = currentAddr - 1;
547
			}
548
			if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
549
				// always set nextAddr, also when already on the very last address
550
				// this prevents doing a 'dataWrite'
551
				nextAddr = std::min(currentAddr + 1, memSize - 1);
552
				resetCursor = true; // see comment on 'End'
553
			}
554
			if (!switchedTab && ImGui::IsKeyPressed(ImGuiKey_Tab)) {
555
				dataEditingActive = (dataEditingActive == HEX) ? ASCII : HEX;
556
				nextAddr = currentAddr;
557
				switchedTab = true; // to prevent switching back already in the next frame
558
			} else {
559
				switchedTab = false;
560
			}
561
		}
562
		dataEditingTakeFocus = false;
563
		dataWrite |= userData.cursorPos >= width;
564
		if (nextAddr) {
565
			dataWrite = false;
566
			dataEditingTakeFocus = true;
567
		}
568
		if (dataWrite) {
569
			if (auto value = parseData(dataInput)) {
570
				debuggable.write(addr, *value);
571
				assert(!nextAddr);
572
				nextAddr = currentAddr + 1;
573
			}
574
		}
575
	};
576
577
	auto& debugger = manager.debugger;
578
	if ((snapshot.size() != memSize) && debugger->needSnapshot()) {
579
		makeSnapshot(debuggable);
580
	}
581
	bool drawChanges = (snapshot.size() == memSize) && debugger->needDrawChanges();
582
	auto changedColor = debugger->getChangesColor();
583
	auto greyColor = getColor(imColor::TEXT_DISABLED);
584
585
	const auto totalLineCount = int((memSize + columns - 1) / columns);
586
	im::ListClipper(totalLineCount, -1, s.lineHeight, [&](int line) {
587
		auto addr = unsigned(line) * columns;
588
		ImGui::StrCat(formatAddr(s, addr), ':');
589
590
		auto previewDataTypeSize = DataTypeGetSize(previewDataType);
591
		auto inside = [](unsigned a, unsigned start, unsigned size) {
592
			return (start <= a) && (a < (start + size));
593
		};
594
		auto highLightDataPreview = [&](unsigned a) {
595
			return inside(a, currentAddr, previewDataTypeSize);
596
		};
597
		auto highLightSearch = [&](unsigned a) {
598
			if (!searchPattern) return false;
599
			auto len = narrow<unsigned>(searchPattern->size());
600
			if (searchHighlight == static_cast<int>(SearchHighlight::SINGLE)) {
601
				if (searchResult) {
602
					return inside(a, *searchResult, len);
603
				}
604
			} else if (searchHighlight == static_cast<int>(SearchHighlight::ALL)) {
605
				int start = std::max(0, int(a - len + 1));
606
				for (unsigned i = start; i <= a; ++i) {
607
					if (match(debuggable, memSize, i)) return true;
608
				}
609
			}
610
			return false;
611
		};
612
		auto highLight = [&](unsigned a) {
613
			return highLightDataPreview(a) || highLightSearch(a);
614
		};
615
616
		// Draw Hexadecimal
617
		for (int n = 0; n < columns && addr < memSize; ++n, ++addr) {
618
			int macroColumn = n / MidColsCount;
619
			float bytePosX = s.posHexStart + float(n) * s.hexCellWidth
620
					+ float(macroColumn) * s.spacingBetweenMidCols;
621
			ImGui::SameLine(bytePosX);
622
623
			// Draw highlight
624
			if (highLight(addr)) {
625
				ImVec2 pos = ImGui::GetCursorScreenPos();
626
				float highlightWidth = s.glyphWidth * 2;
627
				if (highLight(addr + 1)) {
628
					highlightWidth = s.hexCellWidth;
629
					if (n > 0 && (n + 1) < columns && ((n + 1) % MidColsCount) == 0) {
630
						highlightWidth += s.spacingBetweenMidCols;
631
					}
632
				}
633
				drawList->AddRectFilled(pos, ImVec2(pos.x + highlightWidth, pos.y + s.lineHeight), HighlightColor);
634
			}
635
636
			// Draw symbol highlight
637
			if (showSymbolInfo) {
638
				auto symbol = symbolManager.lookupValue(narrow_cast<uint16_t>(addr));
639
				if (!symbol.empty()) {
640
					float highlightWidth = s.glyphWidth * 2;
641
					ImVec2 pos = ImGui::GetCursorScreenPos();
642
					drawList->AddRectFilled(pos, ImVec2(pos.x + highlightWidth, pos.y + s.lineHeight), HighlightSymbolColor);
643
				}
644
			}
645
646
			if (currentAddr == addr && (dataEditingActive == HEX)) {
647
				handleInput(addr, 2,
648
					[&](ImGuiInputTextCallbackData* data) { // format
649
						auto valStr = formatData(debuggable.read(addr));
650
						data->InsertChars(0, valStr.data(), valStr.data() + valStr.size());
651
						data->SelectionEnd = 2;
652
					},
653
					[&](std::string_view data) { // parse
654
						return parseDataValue(data);
655
					},
656
					ImGuiInputTextFlags_CharsHexadecimal);
657
			} else {
658
				uint8_t b = debuggable.read(addr);
659
				bool changed = drawChanges && (b != snapshot[addr]);
660
				bool grey = (b == 0) && greyOutZeroes;
661
				im::StyleColor(changed || grey, ImGuiCol_Text, changed ? changedColor : greyColor, [&]{
662
					ImGui::StrCat(formatData(b), ' ');
663
				});
664
				if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
665
					dataEditingActive = HEX;
666
					dataEditingTakeFocus = true;
667
					nextAddr = addr;
668
				}
669
			}
670
		}
671
672
		if (showAscii) {
673
			// Draw ASCII values
674
			ImGui::SameLine(s.posAsciiStart);
675
			gl::vec2 pos = ImGui::GetCursorPos();
676
			gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
677
			addr = unsigned(line) * columns;
678
679
			im::ID(line, [&]{
680
				// handle via a single full-width button, this ensures we don't miss
681
				// clicks because they fall in between two chars
682
				if (ImGui::InvisibleButton("ascii", ImVec2(s.posAsciiEnd - s.posAsciiStart, s.lineHeight))) {
683
					dataEditingActive = ASCII;
684
					dataEditingTakeFocus = true;
685
					nextAddr = addr + unsigned((ImGui::GetIO().MousePos.x - scrnPos.x) / s.glyphWidth);
686
				}
687
			});
688
689
			for (int n = 0; n < columns && addr < memSize; ++n, ++addr) {
690
				if (highLight(addr)) {
691
					auto start = scrnPos + gl::vec2(float(n) * s.glyphWidth, 0.0f);
692
					drawList->AddRectFilled(start, start + gl::vec2(s.glyphWidth, s.lineHeight), ImGui::GetColorU32(HighlightColor));
693
				}
694
695
				ImGui::SetCursorPos(pos);
696
				if (currentAddr == addr && (dataEditingActive == ASCII)) {
697
					handleInput(addr, 1,
698
						[&](ImGuiInputTextCallbackData* data) { // format
699
							char valChar = formatAsciiData(debuggable.read(addr));
700
							data->InsertChars(0, &valChar, &valChar + 1);
701
							data->SelectionEnd = 1;
702
						},
703
						[&](std::string_view data) -> std::optional<uint8_t> { // parse
704
							if (data.empty()) return {};
705
							uint8_t b = data[0];
706
							if (b < 32 || b >= 128) return {};
707
							return b;
708
						});
709
				} else {
710
					uint8_t c = debuggable.read(addr);
711
					bool changed = drawChanges && (c != snapshot[addr]);
712
					char display = formatAsciiData(c);
713
					bool grey = display != char(c);
714
					im::StyleColor(changed || grey, ImGuiCol_Text, changed ? changedColor : greyColor, [&]{
715
						ImGui::TextUnformatted(&display, &display + 1);
716
					});
717
				}
718
				pos.x += s.glyphWidth;
719
			}
720
		}
721
	});
722
	ImGui::PopStyleVar(2);
723
	ImGui::EndChild();
724
725
	if (nextAddr) {
726
		setAddr(s, debuggable, memSize, *nextAddr);
727
		dataEditingTakeFocus = true;
728
		addrMode = CURSOR;
729
	}
730
731
	if (showAddress) {
732
		bool forceScroll = ImGui::IsWindowAppearing();
733
734
		ImGui::Separator();
735
		ImGui::AlignTextToFramePadding();
736
		ImGui::TextUnformatted("Address");
737
		ImGui::SameLine();
738
		ImGui::SetNextItemWidth(2.0f * style.FramePadding.x + ImGui::CalcTextSize("Expression").x + ImGui::GetFrameHeight());
739
		if (ImGui::Combo("##mode", &addrMode, "Cursor\0Expression\0Link BC\0Link DE\0Link HL\0Link IX\0Link IY\0")) {
740
			dataEditingTakeFocus = true;
741
			forceScroll = true;
742
			if (addrMode >=2) {
743
				static constexpr std::array linkExpr = {
744
					"[reg bc]", "[reg de]", "[reg hl]", "[reg ix]", "[reg iy]"
745
				};
746
				addrExpr = linkExpr[addrMode - 2];
747
				addrMode = EXPRESSION;
748
			}
749
		}
750
		ImGui::SameLine();
751
752
		std::string* as = addrMode == CURSOR ? &addrStr : &addrExpr;
753
		auto addr = parseAddressExpr(*as, symbolManager, manager.getInterpreter());
754
		im::StyleColor(!addr, ImGuiCol_Text, getColor(imColor::ERROR), [&] {
755
			if (addrMode == EXPRESSION && addr) {
756
				scrollAddr(s, debuggable, memSize, *addr, forceScroll);
757
			}
758
			if (manager.getShortcuts().checkShortcut(Shortcuts::ID::HEX_GOTO_ADDR)) {
759
				ImGui::SetKeyboardFocusHere();
760
			}
761
			ImGui::SetNextItemWidth(15.0f * ImGui::GetFontSize());
762
			if (ImGui::InputText("##addr", as, ImGuiInputTextFlags_EnterReturnsTrue)) {
763
				if (auto addr2 = parseAddressExpr(addrStr, symbolManager, manager.getInterpreter())) {
764
					scrollAddr(s, debuggable, memSize, *addr2, forceScroll);
765
					dataEditingTakeFocus = true;
766
				}
767
			}
768
			simpleToolTip([&]{
769
				return addr ? strCat("0x", formatAddr(s, *addr))
770
				            : addr.error();
771
			});
772
		});
773
		im::Font(manager.fontProp, [&]{
774
			HelpMarker("Address-mode:\n"
775
				"  Cursor: view the cursor position\n"
776
				"  Expression: continuously re-evaluate an expression and view that address\n"
777
				"\n"
778
				"Addresses can be entered as:\n"
779
				"  Decimal or hexadecimal values (e.g. 0x1234)\n"
780
				"  A calculation like 0x1234 + 7*22\n"
781
				"  The name of a label (e.g. CHPUT)\n"
782
				"  A Tcl expression (e.g. [reg hl] to follow the content of register HL)\n"
783
				"\n"
784
				"Right-click to configure this view.");
785
		});
786
	}
787
	if (showSearch) {
788
		ImGui::Separator();
789
		drawSearch(s, debuggable, memSize);
790
	}
791
	if (showDataPreview) {
792
		ImGui::Separator();
793
		drawPreviewLine(s, debuggable, memSize);
794
	}
795
	if (showSymbolInfo) {
796
		ImGui::Separator();
797
		ImGui::AlignTextToFramePadding();
798
		auto symbol = symbolManager.lookupValue(narrow_cast<uint16_t>(currentAddr));
799
		if (!symbol.empty()) {
800
			ImGui::Text("Current symbol: %s", symbol[0]->name.c_str());
801
		} else {
802
			ImGui::TextUnformatted("No symbol for this address defined"sv);
803
		}
804
	}
805
806
	im::Popup("context", [&]{
807
		im::ScopedFont sf(manager.fontProp);
808
		ImGui::SetNextItemWidth(8.5f * s.glyphWidth + 2.0f * style.FramePadding.x);
809
		if (ImGui::InputInt("Columns", &columns, 1, 0)) {
810
			columns = std::clamp(columns, 1, MAX_COLUMNS);
811
		}
812
		ImGui::Checkbox("Show Address bar", &showAddress);
813
		ImGui::Checkbox("Show Search pane", &showSearch);
814
		ImGui::Checkbox("Show Data Preview", &showDataPreview);
815
		ImGui::Checkbox("Show ASCII", &showAscii);
816
		ImGui::Checkbox("Show Symbol info", &showSymbolInfo);
817
		ImGui::Checkbox("Grey out zeroes", &greyOutZeroes);
818
		im::Menu("Highlight changes", [&]{
819
			debugger->configureChangesMenu();
820
		});
821
		ImGui::Separator();
822
		if (ImGui::MenuItem("Export...")) {
823
			showExportWindow = true;
824
		}
825
	});
826
	im::Popup("NotFound", [&]{
827
		ImGui::TextUnformatted("Not found");
828
	});
829
}
830
831
[[nodiscard]] static const char* DataTypeGetDesc(ImGuiDataType dataType)
832
{
833
	std::array<const char*, 8> desc = {
834
		"Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64"
835
	};
836
	assert(dataType >= 0 && dataType < 8);
837
	return desc[dataType];
838
}
839
840
template<typename T>
841
[[nodiscard]] static T read(std::span<const uint8_t> buf)
842
{
843
	assert(buf.size() >= sizeof(T));
844
	T t = 0;
845
	memcpy(&t, buf.data(), sizeof(T));
846
	return t;
847
}
848
849
static void formatDec(std::span<const uint8_t> buf, ImGuiDataType dataType)
850
{
851
	switch (dataType) {
852
	case ImGuiDataType_S8:
853
		ImGui::StrCat(read<int8_t>(buf));
854
		break;
855
	case ImGuiDataType_U8:
856
		ImGui::StrCat(read<uint8_t>(buf));
857
		break;
858
	case ImGuiDataType_S16:
859
		ImGui::StrCat(read<int16_t>(buf));
860
		break;
861
	case ImGuiDataType_U16:
862
		ImGui::StrCat(read<uint16_t>(buf));
863
		break;
864
	case ImGuiDataType_S32:
865
		ImGui::StrCat(read<int32_t>(buf));
866
		break;
867
	case ImGuiDataType_U32:
868
		ImGui::StrCat(read<uint32_t>(buf));
869
		break;
870
	case ImGuiDataType_S64:
871
		ImGui::StrCat(read<int64_t>(buf));
872
		break;
873
	case ImGuiDataType_U64:
874
		ImGui::StrCat(read<uint64_t>(buf));
875
		break;
876
	default:
877
		UNREACHABLE;
878
	}
879
}
880
881
static void formatHex(std::span<const uint8_t> buf, ImGuiDataType data_type)
882
{
883
	switch (data_type) {
884
	case ImGuiDataType_S8:
885
	case ImGuiDataType_U8:
886
		ImGui::StrCat(hex_string<2>(read<uint8_t>(buf)));
887
		break;
888
	case ImGuiDataType_S16:
889
	case ImGuiDataType_U16:
890
		ImGui::StrCat(hex_string<4>(read<uint16_t>(buf)));
891
		break;
892
	case ImGuiDataType_S32:
893
	case ImGuiDataType_U32:
894
		ImGui::StrCat(hex_string<8>(read<uint32_t>(buf)));
895
		break;
896
	case ImGuiDataType_S64:
897
	case ImGuiDataType_U64:
898
		ImGui::StrCat(hex_string<16>(read<uint64_t>(buf)));
899
		break;
900
	default:
901
		UNREACHABLE;
902
	}
903
}
904
905
static void formatBin(std::span<const uint8_t> buf)
906
{
907
	for (int i = int(buf.size()) - 1; i >= 0; --i) {
908
		ImGui::StrCat(bin_string<8>(buf[i]));
909
		if (i != 0) ImGui::SameLine();
910
	}
911
}
912
913
void DebuggableEditor::parseSearchString(std::string_view str)
914
{
915
	searchPattern.reset();
916
	searchResult.reset();
917
	std::vector<uint8_t> result;
918
919
	if (searchType == static_cast<int>(SearchType::ASCII)) {
920
		const auto* begin = std::bit_cast<const uint8_t*>(str.data());
921
		const auto* end = begin + str.size();
922
		result.assign(begin, end);
923
	} else {
924
		assert(searchType == static_cast<int>(SearchType::HEX));
925
		std::optional<int> partial;
926
		for (char c : str) {
927
			if (c == ' ') continue; // ignore space characters
928
			auto digit = parseHexDigit(c);
929
			if (!digit) return; // error: invalid hex digit
930
			if (partial) {
931
				result.push_back(narrow<uint8_t>(16 * *partial + *digit));
932
				partial.reset();
933
			} else {
934
				partial = *digit;
935
			}
936
		}
937
		if (partial) return; // error: odd number of hex digits
938
	}
939
940
	searchPattern = std::move(result);
941
}
942
943
void DebuggableEditor::drawSearch(const Sizes& s, Debuggable& debuggable, unsigned memSize)
944
{
945
	const auto& style = ImGui::GetStyle();
946
947
	bool doSearch = false;
948
	auto buttonSize = ImGui::CalcTextSize("Search").x + 2.0f * style.FramePadding.x;
949
	ImGui::SetNextItemWidth(-(buttonSize + style.WindowPadding.x));
950
	im::StyleColor(!searchPattern, ImGuiCol_Text, getColor(imColor::ERROR), [&] {
951
		auto callback = [](ImGuiInputTextCallbackData* data) {
952
			if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit) {
953
				auto& self = *static_cast<DebuggableEditor*>(data->UserData);
954
				self.parseSearchString(std::string_view(data->Buf, data->BufTextLen));
955
			}
956
			return 0;
957
		};
958
		ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue
959
		                          | ImGuiInputTextFlags_CallbackEdit;
960
		if (ImGui::InputText("##search_string", &searchString, flags, callback, this)) {
961
			doSearch = true; // pressed enter
962
		}
963
	});
964
	ImGui::SameLine();
965
	im::Disabled(!searchPattern, [&]{
966
		doSearch |= ImGui::Button("Search");
967
	});
968
	if (!searchPattern) {
969
		simpleToolTip("Must be an even number of hex digits, optionally separated by spaces");
970
	}
971
	if (searchPattern && doSearch) {
972
		search(s, debuggable, memSize);
973
	}
974
975
	auto arrowSize = ImGui::GetFrameHeight();
976
	auto extra = arrowSize + 2.0f * style.FramePadding.x;
977
	ImGui::AlignTextToFramePadding();
978
	ImGui::TextUnformatted("Type");
979
	ImGui::SameLine();
980
	ImGui::SetNextItemWidth(ImGui::CalcTextSize("ASCII").x + extra);
981
	if (ImGui::Combo("##search_type", &searchType, "Hex\0ASCII\0\0")) {
982
		parseSearchString(searchString);
983
	}
984
985
	ImGui::SameLine(0.0f, 2 * ImGui::GetFontSize());
986
	ImGui::TextUnformatted("Direction");
987
	ImGui::SameLine();
988
	ImGui::SetNextItemWidth(ImGui::CalcTextSize("Backwards").x + extra);
989
	ImGui::Combo("##search_direction", &searchDirection, "Forwards\0Backwards\0\0");
990
991
	ImGui::SameLine(0.0f, 2 * ImGui::GetFontSize());
992
	ImGui::TextUnformatted("Highlight");
993
	ImGui::SameLine();
994
	ImGui::SetNextItemWidth(ImGui::CalcTextSize("Single").x + extra);
995
	ImGui::Combo("##search_highlight", &searchHighlight, "None\0Single\0All\0\0");
996
}
997
998
bool DebuggableEditor::match(Debuggable& debuggable, unsigned memSize, unsigned addr)
999
{
1000
	assert(searchPattern);
1001
	if ((addr + searchPattern->size()) > memSize) return false;
1002
	for (auto [i, c] : enumerate(*searchPattern)) {
1003
		if (debuggable.read(narrow<unsigned>(addr + i)) != c) return false;
1004
	}
1005
	return true;
1006
}
1007
1008
void DebuggableEditor::search(const Sizes& s, Debuggable& debuggable, unsigned memSize)
1009
{
1010
	std::optional<unsigned> found;
1011
	auto test = [&](unsigned addr) {
1012
		if (match(debuggable, memSize, addr)) {
1013
			found = addr;
1014
			return true;
1015
		}
1016
		return false;
1017
	};
1018
	if (searchDirection == static_cast<int>(SearchDirection::FWD)) {
1019
		for (unsigned addr = currentAddr + 1; addr < memSize; ++addr) {
1020
			if (test(addr)) break;
1021
		}
1022
		if (!found) {
1023
			for (unsigned addr = 0; addr <= currentAddr; ++addr) {
1024
				if (test(addr)) break;
1025
			}
1026
		}
1027
	} else {
1028
		for (int addr = currentAddr - 1; addr > 0; --addr) {
1029
			if (test(unsigned(addr))) break;
1030
		}
1031
		if (!found) {
1032
			for (int addr = memSize - 1; addr >= int(currentAddr); --addr) {
1033
				if (test(unsigned(addr))) break;
1034
			}
1035
		}
1036
	}
1037
	if (found) {
1038
		searchResult = *found;
1039
		scrollAddr(s, debuggable, memSize, *found, false);
1040
		dataEditingTakeFocus = true;
1041
		addrMode = CURSOR;
1042
	} else {
1043
		searchResult.reset();
1044
		ImGui::OpenPopup("NotFound");
1045
	}
1046
}
1047
1048
void DebuggableEditor::drawPreviewLine(const Sizes& s, Debuggable& debuggable, unsigned memSize)
1049
{
1050
	const auto& style = ImGui::GetStyle();
1051
	ImGui::AlignTextToFramePadding();
1052
	ImGui::TextUnformatted("Preview as:"sv);
1053
	ImGui::SameLine();
1054
	ImGui::SetNextItemWidth((s.glyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
1055
	if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(previewDataType), ImGuiComboFlags_HeightLargest)) {
1056
		for (ImGuiDataType n = 0; n < 8; ++n) {
1057
			if (ImGui::Selectable(DataTypeGetDesc(n), previewDataType == n)) {
1058
				previewDataType = n;
1059
			}
1060
		}
1061
		ImGui::EndCombo();
1062
	}
1063
	ImGui::SameLine();
1064
	ImGui::SetNextItemWidth((s.glyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
1065
	ImGui::Combo("##combo_endianess", &previewEndianess, "LE\0BE\0\0");
1066
1067
	std::array<uint8_t, 8> dataBuf = {};
1068
	auto elemSize = DataTypeGetSize(previewDataType);
1069
	for (auto i : xrange(elemSize)) {
1070
		auto addr = currentAddr + i;
1071
		dataBuf[i] = (addr < memSize) ? debuggable.read(addr) : 0;
1072
	}
1073
1074
	static constexpr bool nativeIsLittle = std::endian::native == std::endian::little;
1075
	if (bool previewIsLittle = previewEndianess == LE;
1076
	    nativeIsLittle != previewIsLittle) {
1077
		std::reverse(dataBuf.begin(), dataBuf.begin() + elemSize);
1078
	}
1079
1080
	ImGui::TextUnformatted("Dec "sv);
1081
	ImGui::SameLine();
1082
	formatDec(dataBuf, previewDataType);
1083
1084
	ImGui::TextUnformatted("Hex "sv);
1085
	ImGui::SameLine();
1086
	formatHex(dataBuf, previewDataType);
1087
1088
	ImGui::TextUnformatted("Bin "sv);
1089
	ImGui::SameLine();
1090
	formatBin(subspan(dataBuf, 0, elemSize));
1091
}
1092
1093
} // namespace openmsx