OXYGENHUB
◆ main openmsx / src / imgui / ImGuiBreakPoints.cc
23.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "ImGuiBreakPoints.hh"
2
3
#include "ImGuiCpp.hh"
4
#include "ImGuiDebugger.hh"
5
#include "ImGuiManager.hh"
6
#include "ImGuiUtils.hh"
7
8
#include "BreakPoint.hh"
9
#include "DebugCondition.hh"
10
#include "Debugger.hh"
11
#include "Interpreter.hh"
12
#include "MSXCPUInterface.hh"
13
#include "MSXMotherBoard.hh"
14
#include "Reactor.hh"
15
#include "SymbolManager.hh"
16
#include "WatchPoint.hh"
17
18
#include "narrow.hh"
19
#include "one_of.hh"
20
#include "strCat.hh"
21
#include "unreachable.hh"
22
23
#include <imgui.h>
24
#include <imgui_stdlib.h>
25
26
#include <cstdint>
27
#include <tuple>
28
#include <vector>
29
30
using namespace std::literals;
31
32
namespace openmsx {
33
34
ImGuiBreakPoints::ImGuiBreakPoints(ImGuiManager& manager_)
35
	: ImGuiPart(manager_)
36
	, symbolManager(manager.getReactor().getSymbolManager())
37
{
38
}
39
40
void ImGuiBreakPoints::save(ImGuiTextBuffer& buf)
41
{
42
	savePersistent(buf, *this, persistentElements);
43
}
44
45
void ImGuiBreakPoints::loadLine(std::string_view name, zstring_view value)
46
{
47
	loadOnePersistent(name, value, *this, persistentElements);
48
}
49
50
void ImGuiBreakPoints::paint(MSXMotherBoard* motherBoard)
51
{
52
	if (!motherBoard || !show) return;
53
54
	ImGui::SetNextWindowSize(gl::vec2{25, 14} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
55
	im::Window("Breakpoints", &show, [&]{
56
		im::TabBar("tabs", [&]{
57
			auto& cpuInterface = motherBoard->getCPUInterface();
58
			im::TabItem("Breakpoints", [&]{
59
				paintTab<BreakPoint>(cpuInterface);
60
			});
61
			im::TabItem("Watchpoints", [&]{
62
				paintTab<WatchPoint>(cpuInterface);
63
			});
64
			im::TabItem("Conditions", [&]{
65
				paintTab<DebugCondition>(cpuInterface);
66
			});
67
		});
68
	});
69
}
70
71
template<typename T> static std::string_view getCheckCmd();
72
template<> std::string_view getCheckCmd<BreakPoint>()     { return "pc_in_slot"; }
73
template<> std::string_view getCheckCmd<WatchPoint>()     { return "watch_in_slot"; }
74
template<> std::string_view getCheckCmd<DebugCondition>() { return "pc_in_slot"; }
75
76
ParsedSlotCond::ParsedSlotCond(std::string_view checkCmd, std::string_view cond)
77
	: rest(cond) // for when we fail to match a slot-expression
78
{
79
	size_t pos = 0;
80
	auto end = cond.size();
81
	std::optional<int> o_ps;
82
	std::optional<int> o_ss;
83
	std::optional<int> o_seg;
84
85
	auto next = [&](std::string_view s) {
86
		if (cond.substr(pos).starts_with(s)) {
87
			pos += s.size();
88
			return true;
89
		}
90
		return false;
91
	};
92
	auto done = [&]{
93
		bool stop = cond.substr(pos) == "]";
94
		if (stop || (next("] && (") && cond.ends_with(')'))) {
95
			if (stop) {
96
				rest.clear();
97
			} else {
98
				rest = cond.substr(pos, cond.size() - pos - 1);
99
			}
100
			if (o_ps)  { hasPs  = true; ps  = *o_ps;  }
101
			if (o_ss)  { hasSs  = true; ss  = *o_ss;  }
102
			if (o_seg) { hasSeg = true; seg = narrow<uint8_t>(*o_seg); }
103
			return true;
104
		}
105
		return false;
106
	};
107
	auto isDigit = [](char c) { return ('0' <= c) && (c <= '9'); };
108
	auto getInt = [&](unsigned max) -> std::optional<int> {
109
		unsigned i = 0;
110
		if ((pos == end) || !isDigit(cond[pos])) return {};
111
		while ((pos != end) && isDigit(cond[pos])) {
112
			i = 10 * i + (cond[pos] - '0');
113
			++pos;
114
		}
115
		if (i >= max) return {};
116
		return i;
117
	};
118
119
	if (!next(tmpStrCat('[', checkCmd, ' '))) return; // no slot
120
	o_ps = getInt(4);
121
	if (!o_ps) return; // invalid ps
122
	if (done()) return; // ok, only ps
123
	if (!next(" ")) return; // invalid separator
124
	if (!next("X")) {
125
		o_ss = getInt(4);
126
		if (!o_ss) return; // invalid ss
127
	}
128
	if (done()) return; // ok, ps + ss
129
	if (!next(" ")) return; // invalid separator
130
	o_seg = getInt(256);
131
	if (!o_seg) return; // invalid seg
132
	if (done()) return; // ok, ps + ss + seg
133
	// invalid terminator
134
}
135
136
TemporaryString ParsedSlotCond::toTclExpression(std::string_view checkCmd) const
137
{
138
	return tmpStrCat(
139
		strCat_if(hasPs, '[', checkCmd, ' ', ps,
140
		                 strCat_if(hasSs, ' ', ss).else_if(hasSeg, " X"),
141
		                 strCat_if(hasSeg, ' ', seg),
142
		                 ']',
143
		                 strCat_if(!rest.empty(), " && (", rest, ')'))
144
		.else_(rest));
145
}
146
147
TemporaryString ParsedSlotCond::toDisplayString() const
148
{
149
	return tmpStrCat(
150
		strCat_if(hasPs, "Slot:", ps, '-',
151
		                 strCat_if(hasSs, ss).else_('X'),
152
		                 strCat_if(hasSeg, ',', seg),
153
		                 strCat_if(!rest.empty(), " && ", rest))
154
		.else_(rest));
155
}
156
157
template<typename> struct BaseBpType;
158
template<> struct BaseBpType<BreakPoint>                  { using type = BreakPoint; };
159
template<> struct BaseBpType<std::shared_ptr<WatchPoint>> { using type = WatchPoint; };
160
template<> struct BaseBpType<DebugCondition>              { using type = DebugCondition; };
161
template<typename T> using BaseBpType_t = typename BaseBpType<T>::type;
162
163
template<typename Item> struct HasAddress : std::true_type {};
164
template<> struct HasAddress<DebugCondition> : std::false_type {};
165
166
template<typename Item> struct AllowEmptyCond : std::true_type {};
167
template<> struct AllowEmptyCond<DebugCondition> : std::false_type {};
168
169
static std::vector<BreakPoint>& getItems(BreakPoint*, MSXCPUInterface&)
170
{
171
	return MSXCPUInterface::getBreakPoints();
172
}
173
static std::vector<std::shared_ptr<WatchPoint>>& getItems(WatchPoint*, MSXCPUInterface& cpuInterface)
174
{
175
	return cpuInterface.getWatchPoints();
176
}
177
static std::vector<DebugCondition>& getItems(DebugCondition*, MSXCPUInterface&)
178
{
179
	return MSXCPUInterface::getConditions();
180
}
181
182
template<typename T> static void createNew(MSXCPUInterface& cpuInterface, Interpreter& interp, std::optional<uint16_t> addr);
183
template<> void createNew<BreakPoint>(MSXCPUInterface& cpuInterface, Interpreter& interp, std::optional<uint16_t> addr)
184
{
185
	BreakPoint bp;
186
	if (addr) {
187
		bp.setAddress(interp, TclObject(tmpStrCat("0x", hex_string<4>(*addr))));
188
	}
189
	cpuInterface.insertBreakPoint(std::move(bp));
190
}
191
template<> void createNew<WatchPoint>(MSXCPUInterface& cpuInterface, Interpreter&, std::optional<uint16_t>)
192
{
193
	cpuInterface.setWatchPoint(std::make_shared<WatchPoint>());
194
}
195
template<> void createNew<DebugCondition>(MSXCPUInterface& cpuInterface, Interpreter&, std::optional<uint16_t>)
196
{
197
	cpuInterface.setCondition(DebugCondition());
198
}
199
200
template<typename T> static void remove(MSXCPUInterface& cpuInterface, unsigned id);
201
template<> void remove<BreakPoint>(MSXCPUInterface& cpuInterface, unsigned id) {
202
	cpuInterface.removeBreakPoint(id);
203
}
204
template<> void remove<WatchPoint>(MSXCPUInterface& cpuInterface, unsigned id) {
205
	cpuInterface.removeWatchPoint(id);
206
}
207
template<> void remove<DebugCondition>(MSXCPUInterface& cpuInterface, unsigned id) {
208
	cpuInterface.removeCondition(id);
209
}
210
211
[[nodiscard]] static unsigned getId(const BreakPoint& bp)                  { return bp.getId(); }
212
[[nodiscard]] static unsigned getId(const std::shared_ptr<WatchPoint>& wp) { return wp->getId(); }
213
[[nodiscard]] static unsigned getId(const DebugCondition& cond)            { return cond.getId(); }
214
215
[[nodiscard]] static bool getEnabled(const BreakPoint& bp)                  { return bp.isEnabled(); }
216
[[nodiscard]] static bool getEnabled(const std::shared_ptr<WatchPoint>& wp) { return wp->isEnabled(); }
217
[[nodiscard]] static bool getEnabled(const DebugCondition& cond)            { return cond.isEnabled(); }
218
static void setEnabled(BreakPoint& bp,                  bool e) { bp.setEnabled(e); }
219
static void setEnabled(std::shared_ptr<WatchPoint>& wp, bool e) { wp->setEnabled(e); }
220
static void setEnabled(DebugCondition& cond,            bool e) { cond.setEnabled(e); }
221
222
[[nodiscard]] static std::optional<uint16_t> getAddress(const BreakPoint& bp)                  { return bp.getAddress(); }
223
[[nodiscard]] static std::optional<uint16_t> getAddress(const std::shared_ptr<WatchPoint>& wp) { return wp->getBeginAddress(); }
224
[[nodiscard]] static std::optional<uint16_t> getAddress(const DebugCondition&)                 { return {}; }
225
226
[[nodiscard]] static TclObject getAddressString(const BreakPoint& bp)                  { return bp.getAddressString(); }
227
[[nodiscard]] static TclObject getAddressString(const std::shared_ptr<WatchPoint>& wp) { return wp->getBeginAddressString(); }
228
[[nodiscard]] static TclObject getAddressString(const DebugCondition&)                 { return {}; }
229
230
[[nodiscard]] static TclObject getEndAddressString(const BreakPoint&)                     { return {}; }
231
[[nodiscard]] static TclObject getEndAddressString(const std::shared_ptr<WatchPoint>& wp) { return wp->getEndAddressString(); }
232
233
[[nodiscard]] static TclObject getCondition(const BreakPoint& bp)                  { return bp.getCondition(); }
234
[[nodiscard]] static TclObject getCondition(const std::shared_ptr<WatchPoint>& wp) { return wp->getCondition(); }
235
[[nodiscard]] static TclObject getCondition(const DebugCondition& cond)            { return cond.getCondition(); }
236
static void setCondition(BreakPoint& bp,                  const TclObject& c) { bp.setCondition(c); }
237
static void setCondition(std::shared_ptr<WatchPoint>& wp, const TclObject& c) { wp->setCondition(c); }
238
static void setCondition(DebugCondition& cond,            const TclObject& c) { cond.setCondition(c); }
239
240
[[nodiscard]] static TclObject getCommand(const BreakPoint& bp)                  { return bp.getCommand(); }
241
[[nodiscard]] static TclObject getCommand(const std::shared_ptr<WatchPoint>& wp) { return wp->getCommand(); }
242
[[nodiscard]] static TclObject getCommand(const DebugCondition& cond)            { return cond.getCommand(); }
243
static void setCommand(BreakPoint& bp,                  const TclObject& c) { bp.setCommand(c); }
244
static void setCommand(std::shared_ptr<WatchPoint>& wp, const TclObject& c) { wp->setCommand(c); }
245
static void setCommand(DebugCondition& cond,            const TclObject& c) { cond.setCommand(c); }
246
247
[[nodiscard]] static bool getOnce(const BreakPoint& bp)                  { return bp.onlyOnce(); }
248
[[nodiscard]] static bool getOnce(const std::shared_ptr<WatchPoint>& wp) { return wp->onlyOnce(); }
249
[[nodiscard]] static bool getOnce(const DebugCondition& cond)            { return cond.onlyOnce(); }
250
static void setOnce(BreakPoint& bp,                  bool o) { bp.setOnce(o); }
251
static void setOnce(std::shared_ptr<WatchPoint>& wp, bool o) { wp->setOnce(o); }
252
static void setOnce(DebugCondition& cond,            bool o) { cond.setOnce(o); }
253
254
struct DummyScopedChange {};
255
[[nodiscard]] static DummyScopedChange getScopedChange(BreakPoint&, MSXCPUInterface&) { return {}; }
256
[[nodiscard]] static auto getScopedChange(std::shared_ptr<WatchPoint>& wp, MSXCPUInterface& cpuInterface) {
257
	return cpuInterface.getScopedChangeWatchpoint(wp);
258
}
259
[[nodiscard]] static DummyScopedChange getScopedChange(DebugCondition&, MSXCPUInterface&) { return {}; }
260
261
template<typename Item>
262
static void checkSort(std::vector<Item>& items)
263
{
264
	using Type = BaseBpType_t<Item>;
265
	constexpr bool isWatchPoint = std::is_same_v<Type, WatchPoint>;
266
	constexpr bool hasAddress = HasAddress<Type>{};
267
268
	auto* sortSpecs = ImGui::TableGetSortSpecs();
269
	if (!sortSpecs->SpecsDirty) return;
270
271
	sortSpecs->SpecsDirty = false;
272
	assert(sortSpecs->SpecsCount == 1);
273
	assert(sortSpecs->Specs);
274
	assert(sortSpecs->Specs->SortOrder == 0);
275
276
	switch (sortSpecs->Specs->ColumnIndex) {
277
	case 0: // enable
278
		sortUpDown_T(items, sortSpecs, [](const auto& item) { return getEnabled(item); });
279
		break;
280
	case 1: // type
281
		if constexpr (isWatchPoint) {
282
			sortUpDown_T(items, sortSpecs, &WatchPoint::getType);
283
		}
284
		break;
285
	case 2: // addr
286
		if constexpr (hasAddress) {
287
			sortUpDown_T(items, sortSpecs, [](const auto& item) {
288
				return std::tuple(getAddressString   (item).getString(),
289
				                  getEndAddressString(item).getString());
290
			});
291
		}
292
		break;
293
	case 3: // cond
294
		sortUpDown_String(items, sortSpecs, [](const auto& item) { return getCondition(item).getString(); });
295
		break;
296
	case 4: // action
297
		sortUpDown_String(items, sortSpecs, [](const auto& item) { return getCommand(item).getString(); });
298
		break;
299
	default:
300
		UNREACHABLE;
301
	}
302
}
303
304
template<typename Type>
305
[[nodiscard]] static std::string isValidCond(std::string_view cond, Interpreter& interp)
306
{
307
	if (cond.empty()) {
308
		return AllowEmptyCond<Type>{} ? std::string{}
309
		                              : std::string("cannot be empty");
310
	}
311
	return interp.parseExpressionError(cond);
312
}
313
314
[[nodiscard]] static std::string isValidCmd(std::string_view cmd, Interpreter& interp)
315
{
316
	if (cmd.empty()) return {}; // ok
317
	return interp.parseCommandError(cmd);
318
}
319
320
std::string ImGuiBreakPoints::displayAddr(const TclObject& addr) const
321
{
322
	auto str = addr.getString();
323
	if (auto symbol = [&] -> std::optional<std::string_view> {
324
		if (str.ends_with(')')) {
325
			if (str.starts_with("$sym("))   return str.substr(5, str.size() - 6);
326
			if (str.starts_with("$::sym(")) return str.substr(7, str.size() - 8);
327
		}
328
		return {};
329
	}()) {
330
		if (symbolManager.lookupSymbol(*symbol)) {
331
			return std::string(*symbol);
332
		}
333
	}
334
	return std::string(str);
335
}
336
337
std::string ImGuiBreakPoints::parseDisplayAddress(std::string_view str) const
338
{
339
	if (symbolManager.lookupSymbol(str)) {
340
		return strCat("$sym(", str, ')');
341
	}
342
	return std::string(str);
343
}
344
345
template<typename Item>
346
void ImGuiBreakPoints::drawRow(MSXCPUInterface& cpuInterface, int row, Item& item)
347
{
348
	using Type = BaseBpType_t<Item>;
349
	constexpr bool isWatchPoint = std::is_same_v<Type, WatchPoint>;
350
	constexpr bool isBreakPoint = std::is_same_v<Type, BreakPoint>;
351
352
	auto& interp = manager.getInterpreter();
353
	const auto& style = ImGui::GetStyle();
354
	float rowHeight = 2.0f * style.FramePadding.y + ImGui::GetTextLineHeight();
355
356
	auto setRedBg = [](bool valid) {
357
		if (valid) return;
358
		ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, getColor(imColor::RED_BG));
359
	};
360
361
	if (ImGui::TableNextColumn()) { // enable
362
		auto pos = ImGui::GetCursorPos();
363
		if (ImGui::Selectable("##selection", selectedRow == row,
364
				ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
365
				{0.0f, rowHeight})) {
366
			selectedRow = row;
367
		}
368
		ImGui::SetCursorPos(pos);
369
370
		bool enabled = getEnabled(item);
371
		if (ImGui::Checkbox("##enabled", &enabled)) {
372
			auto sc = getScopedChange(item, cpuInterface); (void)sc;
373
			setEnabled(item, enabled);
374
		}
375
		if (ImGui::IsItemActive()) selectedRow = row;
376
	}
377
	if (ImGui::TableNextColumn()) { // type
378
		if constexpr (isWatchPoint) {
379
			ImGui::SetNextItemWidth(-FLT_MIN);
380
			auto wpType = static_cast<int>(item->getType());
381
			if (ImGui::Combo("##type", &wpType, "read IO\000write IO\000read memory\000write memory\000")) {
382
				auto sc = getScopedChange(item, cpuInterface); (void)sc;
383
				item->setType(interp, static_cast<WatchPoint::Type>(wpType));
384
			}
385
			if (ImGui::IsItemActive()) selectedRow = row;
386
		}
387
	}
388
	if (ImGui::TableNextColumn()) { // address
389
		std::string addrStr = displayAddr(getAddressString(item));
390
		ImGui::SetNextItemWidth(-FLT_MIN);
391
		if constexpr (isWatchPoint) {
392
			std::string parseError = item->parseAddressError(interp);
393
			setRedBg(parseError.empty());
394
395
			auto pos = ImGui::GetCursorPos();
396
			auto endAddrStr = displayAddr(item->getEndAddressString());
397
			auto displayAddr = tmpStrCat(addrStr, strCat_if(!endAddrStr.empty(), "...", endAddrStr));
398
			im::Font(manager.fontMono, [&]{
399
				ImGui::TextUnformatted(displayAddr);
400
			});
401
			ImGui::SetCursorPos(pos);
402
			if (ImGui::InvisibleButton("##range-button", {-FLT_MIN, rowHeight})) {
403
				ImGui::OpenPopup("range-popup");
404
			}
405
406
			auto addr = item->getBeginAddress();
407
			if (parseError.empty() && addr) {
408
				simpleToolTip([&]{
409
					auto endAddr = item->getEndAddress();
410
					return tmpStrCat(
411
						"0x", hex_string<4>(*addr),
412
						strCat_if(endAddr && *endAddr != *addr, "...0x", STRCAT_LAZY(hex_string<4>(*endAddr))));
413
				});
414
			} else {
415
				simpleToolTip(parseError);
416
			}
417
418
			if (ImGui::IsItemActive()) selectedRow = row;
419
			im::Popup("range-popup", [&]{
420
				editRange(cpuInterface, item, addrStr, endAddrStr);
421
			});
422
		} else if constexpr (isBreakPoint) {
423
			std::string parseError = item.parseAddressError(interp);
424
			setRedBg(parseError.empty());
425
426
			im::Font(manager.fontMono, [&]{
427
				if (ImGui::InputText("##addr", &addrStr)) {
428
					auto sc = getScopedChange(item, cpuInterface); (void)sc;
429
					item.setAddress(interp, TclObject(parseDisplayAddress(addrStr)));
430
				}
431
			});
432
433
			auto addr = item.getAddress();
434
			if (parseError.empty() && addr) {
435
				simpleToolTip([&]{ return strCat("0x", hex_string<4>(*addr)); });
436
			} else {
437
				simpleToolTip(parseError);
438
			}
439
440
			im::PopupContextItem("context menu", [&]{
441
				if (ImGui::MenuItem("Show in Disassembly", nullptr, nullptr, addr.has_value())) {
442
					manager.debugger->setGotoTarget(*addr);
443
				}
444
			});
445
446
			if (ImGui::IsItemActive()) selectedRow = row;
447
		}
448
	}
449
	if (ImGui::TableNextColumn()) { // condition
450
		std::string_view cond = getCondition(item).getString();
451
		std::string parseError = isValidCond<Type>(cond, interp);
452
		setRedBg(parseError.empty());
453
		auto checkCmd = getCheckCmd<Type>();
454
		ParsedSlotCond slot(checkCmd, cond);
455
		auto pos = ImGui::GetCursorPos();
456
		im::Font(manager.fontMono, [&]{
457
			ImGui::TextUnformatted(slot.toDisplayString());
458
		});
459
		ImGui::SetCursorPos(pos);
460
		if (ImGui::InvisibleButton("##cond-button", {-FLT_MIN, rowHeight})) {
461
			ImGui::OpenPopup("cond-popup");
462
		}
463
		if (ImGui::IsItemActive()) selectedRow = row;
464
		simpleToolTip(parseError);
465
		im::Popup("cond-popup", [&]{
466
			if (editCondition(slot)) {
467
				setCondition(item, TclObject(slot.toTclExpression(checkCmd)));
468
			}
469
		});
470
	}
471
	if (ImGui::TableNextColumn()) { // action
472
		std::string cmd{getCommand(item).getString()};
473
		std::string parseError = isValidCmd(cmd, interp);
474
		setRedBg(parseError.empty());
475
		im::Font(manager.fontMono, [&]{
476
			ImGui::SetNextItemWidth(-FLT_MIN);
477
			if (ImGui::InputText("##cmd", &cmd)) {
478
				setCommand(item, TclObject(cmd));
479
			}
480
			if (ImGui::IsItemActive()) selectedRow = row;
481
		});
482
		simpleToolTip(parseError);
483
	}
484
	if (ImGui::TableNextColumn()) { // once
485
		bool once = getOnce(item);
486
		if (ImGui::Checkbox("##once", &once)) {
487
			setOnce(item, once);
488
		}
489
		if (ImGui::IsItemActive()) selectedRow = row;
490
	}
491
}
492
493
void ImGuiBreakPoints::paintBpTab(MSXCPUInterface& cpuInterface, uint16_t addr)
494
{
495
	paintTab<BreakPoint>(cpuInterface, addr);
496
}
497
498
template<typename Type>
499
void ImGuiBreakPoints::paintTab(MSXCPUInterface& cpuInterface, std::optional<uint16_t> addr)
500
{
501
	constexpr bool isWatchPoint = std::is_same_v<Type, WatchPoint>;
502
	constexpr bool isCondition  = std::is_same_v<Type, DebugCondition>;
503
	bool hasAddress = HasAddress<Type>{} && !addr; // don't draw address-column if filtering a specific address
504
	Type* tag = nullptr;
505
	auto& items = getItems(tag, cpuInterface);
506
507
	int flags = ImGuiTableFlags_RowBg |
508
		ImGuiTableFlags_BordersV |
509
		ImGuiTableFlags_BordersOuter |
510
		ImGuiTableFlags_Resizable |
511
		ImGuiTableFlags_Sortable |
512
		ImGuiTableFlags_Hideable |
513
		ImGuiTableFlags_Reorderable |
514
		ImGuiTableFlags_ContextMenuInBody |
515
		ImGuiTableFlags_SizingStretchProp;
516
	if (!addr) {
517
		flags |= ImGuiTableFlags_ScrollY
518
		      |  ImGuiTableFlags_ScrollX;
519
	}
520
	const auto& style = ImGui::GetStyle();
521
	auto width = style.ItemSpacing.x + 2.0f * style.FramePadding.x + ImGui::CalcTextSize("Remove").x;
522
	bool disableRemove = true;
523
	int count = 0;
524
	int lastDrawnRow = -1; // should only be used when count=1
525
	im::Table("items", 6, flags, {-width, 0}, [&]{
526
		ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
527
		ImGui::TableSetupColumn("Enable", ImGuiTableColumnFlags_WidthFixed);
528
		int typeFlags = isWatchPoint ? ImGuiTableColumnFlags_NoHide : ImGuiTableColumnFlags_Disabled;
529
		ImGui::TableSetupColumn("Type", typeFlags);
530
		int addressFlags = hasAddress ? ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_DefaultSort
531
		                              : ImGuiTableColumnFlags_Disabled;
532
		ImGui::TableSetupColumn("Address", addressFlags);
533
		ImGui::TableSetupColumn("Condition", isCondition ? ImGuiTableColumnFlags_NoHide : 0);
534
		ImGui::TableSetupColumn("Action", addr ? 0 : ImGuiTableColumnFlags_DefaultHide);
535
		ImGui::TableSetupColumn("Once", ImGuiTableColumnFlags_DefaultHide);
536
		ImGui::TableHeadersRow();
537
538
		checkSort(items);
539
540
		im::ID_for_range(items.size(), [&](int row) {
541
			if (!addr || (getAddress(items[row]) == addr)) {
542
				++count; lastDrawnRow = row;
543
				if (row == selectedRow) disableRemove = false;
544
				drawRow(cpuInterface, row, items[row]);
545
			}
546
		});
547
	});
548
	if (count == 1) disableRemove = false;
549
	ImGui::SameLine();
550
	im::Group([&] {
551
		if (ImGui::Button("Add")) {
552
			createNew<Type>(cpuInterface, manager.getInterpreter(), addr);
553
			selectedRow = -1;
554
		}
555
		im::Disabled(disableRemove, [&]{
556
			if (ImGui::Button("Remove")) {
557
				int removeRow = (count == 1) ? lastDrawnRow : selectedRow;
558
				auto it = items.begin() + removeRow;
559
				remove<Type>(cpuInterface, getId(*it));
560
				selectedRow = -1;
561
			}
562
		});
563
		if (!addr) {
564
			ImGui::Spacing();
565
			im::Disabled(items.empty() ,[&]{
566
				if (ImGui::Button("Clear")) {
567
					while (!items.empty()) {
568
						remove<Type>(cpuInterface, getId(items.back()));
569
					}
570
				}
571
			});
572
		}
573
	});
574
}
575
576
void ImGuiBreakPoints::editRange(MSXCPUInterface& cpuInterface, std::shared_ptr<WatchPoint>& wp, std::string& begin, std::string& end)
577
{
578
	ImGui::TextUnformatted("address range"sv);
579
	im::Indent([&]{
580
		const auto& style = ImGui::GetStyle();
581
		auto pos = ImGui::GetCursorPos().x + ImGui::CalcTextSize("end: (?)").x + 2.0f * style.ItemSpacing.x;
582
583
		ImGui::AlignTextToFramePadding();
584
		ImGui::TextUnformatted("begin:  "sv);
585
		ImGui::SameLine(pos);
586
		im::Font(manager.fontMono, [&]{
587
			if (ImGui::InputText("##begin", &begin)) {
588
				auto& interp = manager.getInterpreter();
589
				auto sc = getScopedChange(wp, cpuInterface); (void)sc;
590
				wp->setBeginAddressString(interp, TclObject(parseDisplayAddress(begin)));
591
			}
592
		});
593
594
		ImGui::AlignTextToFramePadding();
595
		ImGui::TextUnformatted("end:"sv);
596
		HelpMarker("End address is included in the range.\n"
597
		           "Leave empty for a single address.");
598
		ImGui::SameLine(pos);
599
		im::Font(manager.fontMono, [&]{
600
			if (ImGui::InputText("##end", &end)) {
601
				auto& interp = manager.getInterpreter();
602
				auto sc = getScopedChange(wp, cpuInterface); (void)sc;
603
				wp->setEndAddressString(interp, TclObject(parseDisplayAddress(end)));
604
			}
605
		});
606
	});
607
}
608
609
bool ImGuiBreakPoints::editCondition(ParsedSlotCond& slot)
610
{
611
	bool changed = false;
612
	ImGui::TextUnformatted("slot"sv);
613
	im::Indent([&]{
614
		const auto& style = ImGui::GetStyle();
615
		auto pos = ImGui::GetCursorPos().x + ImGui::GetFrameHeight() +
616
		           ImGui::CalcTextSize("secondary").x + 2.0f * style.ItemSpacing.x;
617
618
		changed |= ImGui::Checkbox("primary", &slot.hasPs);
619
		ImGui::SameLine(pos);
620
		im::Disabled(!slot.hasPs, [&]{
621
			changed |= ImGui::Combo("##ps", &slot.ps, "0\0001\0002\0003\000");
622
623
			changed |= ImGui::Checkbox("secondary", &slot.hasSs);
624
			ImGui::SameLine(pos);
625
			im::Disabled(!slot.hasSs, [&]{
626
				changed |= ImGui::Combo("##ss", &slot.ss, "0\0001\0002\0003\000");
627
			});
628
629
			changed |= ImGui::Checkbox("segment", &slot.hasSeg);
630
			ImGui::SameLine(pos);
631
			im::Disabled(!slot.hasSeg, [&]{
632
				uint8_t one = 1;
633
				changed |= ImGui::InputScalar("##seg", ImGuiDataType_U8, &slot.seg, &one);
634
			});
635
		});
636
	});
637
	ImGui::TextUnformatted("Tcl expression"sv);
638
	im::Indent([&]{
639
		im::Font(manager.fontMono, [&]{
640
			ImGui::SetNextItemWidth(-FLT_MIN);
641
			changed |= ImGui::InputText("##cond", &slot.rest);
642
		});
643
	});
644
	return changed;
645
}
646
647
void ImGuiBreakPoints::refreshSymbols()
648
{
649
	auto& interp = manager.getInterpreter();
650
	for (auto& bp : MSXCPUInterface::getBreakPoints()) {
651
		bp.evaluateAddress(interp);
652
	}
653
	if (auto* motherBoard = manager.getReactor().getMotherBoard()) {
654
		auto& cpuInterface = motherBoard->getCPUInterface();
655
		for (auto& wp : cpuInterface.getWatchPoints()) {
656
			auto sc = getScopedChange(wp, cpuInterface);
657
			wp->evaluateAddress(interp);
658
		}
659
	}
660
	// nothing for DebugCondition
661
}
662
663
} // namespace openmsx