openMSX
ImGuiWatchExpr.cc
Go to the documentation of this file.
1#include "ImGuiWatchExpr.hh"
2
3#include "ImGuiCpp.hh"
4#include "ImGuiManager.hh"
5#include "ImGuiUtils.hh"
6
7#include "CommandException.hh"
8#include "SymbolManager.hh"
9
10#include "narrow.hh"
11
12#include <imgui_stdlib.h>
13#include <imgui.h>
14
15#include <cassert>
16
17namespace openmsx {
18
19using namespace std::literals;
20
22 : ImGuiPart(manager_)
23 , symbolManager(manager.getReactor().getSymbolManager())
24{
25}
26
27void ImGuiWatchExpr::save(ImGuiTextBuffer& buf)
28{
29 savePersistent(buf, *this, persistentElements);
30 for (const auto& watch : watches) {
31 auto out = makeTclList(watch.description, watch.exprStr, watch.format);
32 buf.appendf("watch=%s\n", out.getString().c_str());
33 }
34}
35
37{
38 watches.clear();
39}
40
41void ImGuiWatchExpr::loadLine(std::string_view name, zstring_view value)
42{
43 if (loadOnePersistent(name, value, *this, persistentElements)) {
44 // already handled
45 } else if (name == "watch"sv) {
46 TclObject in(value);
47 if (in.size() == 3) {
48 WatchExpr result;
49 result.description = in.getListIndexUnchecked(0).getString();
50 result.exprStr = in.getListIndexUnchecked(1).getString();
51 result.format = in.getListIndexUnchecked(2);
52 watches.push_back(std::move(result));
53 }
54 }
55}
56
58{
59 if (!show) return;
60
61 ImGui::SetNextWindowSize(gl::vec2{35, 15} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
62 im::Window("Watch expression", &show, [&]{
63 const auto& style = ImGui::GetStyle();
64 auto width = style.ItemSpacing.x + 2.0f * style.FramePadding.x + ImGui::CalcTextSize("Examples").x;
65 im::Child("child", {-width, 0}, [&] {
66 int flags = ImGuiTableFlags_Resizable
67 | ImGuiTableFlags_Reorderable
68 | ImGuiTableFlags_Hideable
69 | ImGuiTableFlags_Sortable
70 | ImGuiTableFlags_RowBg
71 | ImGuiTableFlags_BordersV
72 | ImGuiTableFlags_BordersOuter
73 | ImGuiTableFlags_SizingStretchProp
74 | ImGuiTableFlags_SortTristate
75 | ImGuiTableFlags_ScrollY;
76 im::Table("table", 4, flags, [&]{
77 ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
78 ImGui::TableSetupColumn("description");
79 ImGui::TableSetupColumn("expression");
80 ImGui::TableSetupColumn("format");
81 ImGui::TableSetupColumn("result", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_NoHide);
82 ImGui::TableHeadersRow();
83 checkSort();
84
85 im::ID_for_range(watches.size(), [&](int row) {
86 drawRow(row);
87 });
88 });
89 });
90 ImGui::SameLine();
91 im::Group([&] {
92 if (ImGui::Button("Add")) {
93 selectedRow = narrow<int>(watches.size()); // select created row
94 watches.emplace_back();
95 }
96 im::Disabled(selectedRow < 0 || selectedRow >= narrow<int>(watches.size()), [&]{
97 if (ImGui::Button("Remove")) {
98 watches.erase(watches.begin() + selectedRow);
99 if (selectedRow == narrow<int>(watches.size())) {
100 selectedRow = -1;
101 }
102 }
103 });
104 ImGui::Dummy({0, 20});
105 if (ImGui::SmallButton("Examples")) {
106 watches.emplace_back(
107 "peek at fixed address",
108 "[peek 0xfcaf]",
109 TclObject("screen=%d"));
110 watches.emplace_back(
111 "VDP command executing",
112 "[debug read \"VDP status regs\" 2] & 1",
113 TclObject());
114 watches.emplace_back(
115 "PSG enable-channel status",
116 "[debug read \"PSG regs\" 7]",
117 TclObject("%08b"));
118 watches.emplace_back(
119 "The following 2 require an appropriate symbol file",
120 "",
121 TclObject(""));
122 watches.emplace_back(
123 "value of 'myLabel'",
124 "$sym(myLabel)",
125 TclObject("0x%04x"));
126 watches.emplace_back(
127 "peek at symbolic address",
128 "[peek16 $sym(numItems)]",
129 TclObject("%d items"));
130 }
131 ImGui::Dummy({0, 0}); // want help marker on the next line
132 HelpMarker(R"(The given (Tcl) expressions are continuously evaluated and displayed in a nicely formatted way.
133Press the 'Examples' button to see some examples.
134
135There are 4 columns:
136* description: optional description for the expression
137* expression: the actual Tcl expression, see below
138* format: optional format specifier, see below
139* result: this shows the result of evaluating 'expression'
140
141Add a new entry via the 'Add' button, then fill in the appropriate fields.
142To remove an entry, first select a row (e.g. click on the corresponding 'result' cell), then press the 'Remove' button.
143
144You can sort the table by clicking the column headers.
145You can reorder the columns by dragging the column headers.
146You can hide columns via the right-click context menu on a column header. For example, once configured, you may want to hide the 'expression' and 'format' columns.
147
148The expression can be any Tcl expression, some examples:
149* [peek 0x1234]: monitor a byte at a specific address
150* [reg SP] < 0xe000: check that stack hasn't grown too large
151 (below address 0xe000)
152If you have debug-symbols loaded (via the 'Symbol manager'), you can use them like:
153* [peek16 $sym(mySymbol)]: monitor 16-bit value at 'mySymbol'
154There's a shorthand notation to peek 8-bit values:
155* <integer> -> [peek <integer>]
156* <symbol> -> [peek $sym(<symbol>)]
157
158In the format column you can optionally enter a format-specifier (for the Tcl 'format' command). Some examples:
159* 0x%04x: 4-digit hex with leading zeros and '0x' prefix
160* %08b: 8-bit binary value
161* %d items: decimal value followed by the string " items"
162)");
163 });
164 });
165}
166
167static void tooWideToolTip(float available, zstring_view str)
168{
169 if (str.empty()) return;
170 auto width = ImGui::CalcTextSize(str).x;
171 width += 2.0f * ImGui::GetStyle().FramePadding.x;
172 if (width >= available) {
173 simpleToolTip(str);
174 }
175}
176
177void ImGuiWatchExpr::refreshSymbols()
178{
179 // symbols changed, expression might have used those symbols
180 for (auto& watch : watches) {
181 watch.expression.reset(); // drop cache
182 }
183}
184
185std::expected<TclObject, std::string> ImGuiWatchExpr::evalExpr(WatchExpr& watch, Interpreter& interp) const
186{
187 if (watch.exprStr.empty()) return {};
188
189 if (!watch.expression) {
190 if (auto addr = symbolManager.parseSymbolOrValue(watch.exprStr)) {
191 // expression is a symbol or an integer -> rewrite
192 watch.expression = TclObject(tmpStrCat("[peek ", *addr, ']'));
193 } else {
194 // keep original expression
195 watch.expression = watch.exprStr;
196 }
197 }
198 assert(watch.expression);
199
200 try {
201 return watch.expression->eval(interp);
202 } catch (CommandException& e) {
203 return std::unexpected(e.getMessage());
204 }
205}
206
207void ImGuiWatchExpr::drawRow(int row)
208{
209 auto& interp = manager.getInterpreter();
210 auto& watch = watches[row];
211
212 // evaluate 'expression'
213 auto exprVal = evalExpr(watch, interp);
214
215 // format the result
216 std::expected<TclObject, std::string> formatted;
217 if (!watch.format.getString().empty()) {
218 auto frmtCmd = makeTclList("format", watch.format, exprVal ? *exprVal : TclObject("0"));
219 try {
220 formatted = frmtCmd.executeCommand(interp);
221 } catch (CommandException& e) {
222 formatted = std::unexpected(e.getMessage());
223 }
224 } else {
225 formatted = exprVal ? *exprVal : TclObject();
226 }
227
228 const auto& display = exprVal ? (formatted ? formatted->getString() : exprVal->getString())
229 : exprVal.error();
230
231 if (ImGui::TableNextColumn()) { // description
232 auto pos = ImGui::GetCursorPos();
233 const auto& style = ImGui::GetStyle();
234 float rowHeight = 2.0f * style.FramePadding.y;
235 rowHeight += ImGui::CalcTextSize(display).y;
236 if (ImGui::Selectable("##selection", selectedRow == row,
237 ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
238 {0.0f, rowHeight})) {
239 selectedRow = row;
240 }
241 ImGui::SetCursorPos(pos);
242
243 auto avail = ImGui::GetContentRegionAvail().x;
244 ImGui::SetNextItemWidth(-FLT_MIN);
245 ImGui::InputText("##desc", &watch.description);
246 tooWideToolTip(avail, watch.description);
247 }
248 if (ImGui::TableNextColumn()) { // expression
249 im::StyleColor(!exprVal, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
250 im::ScopedFont sf(manager.fontMono);
251 auto avail = ImGui::GetContentRegionAvail().x;
252 ImGui::SetNextItemWidth(-FLT_MIN);
253 if (ImGui::InputText("##expr", &watch.exprStr)) {
254 watch.expression.reset();
255 }
256 tooWideToolTip(avail, watch.exprStr);
257 });
258 }
259 if (ImGui::TableNextColumn()) { // format
260 im::StyleColor(!formatted, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
261 im::ScopedFont sf(manager.fontMono);
262 auto avail = ImGui::GetContentRegionAvail().x;
263 ImGui::SetNextItemWidth(-FLT_MIN);
264 auto str = std::string(watch.format.getString());
265 if (ImGui::InputText("##format", &str)) {
266 watch.format = str;
267 }
268 if (formatted) {
269 tooWideToolTip(avail, str);
270 } else {
271 simpleToolTip(formatted.error());
272 }
273 });
274 }
275 if (ImGui::TableNextColumn()) { // result
276 im::ScopedFont sf(manager.fontMono);
277 auto avail = ImGui::GetContentRegionAvail().x;
278 im::StyleColor(!exprVal, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
279 ImGui::TextUnformatted(display);
280 tooWideToolTip(avail, display);
281 });
282 }
283}
284
285void ImGuiWatchExpr::checkSort()
286{
287 auto* sortSpecs = ImGui::TableGetSortSpecs();
288 if (!sortSpecs->SpecsDirty) return;
289
290 sortSpecs->SpecsDirty = false;
291 if (sortSpecs->SpecsCount == 0) return;
292 assert(sortSpecs->SpecsCount == 1);
293 assert(sortSpecs->Specs);
294 assert(sortSpecs->Specs->SortOrder == 0);
295
296 switch (sortSpecs->Specs->ColumnIndex) {
297 case 0: // description
298 sortUpDown_String(watches, sortSpecs, &WatchExpr::description);
299 break;
300 case 1: // expression
301 sortUpDown_String(watches, sortSpecs, [](const auto& item) { return item.exprStr; });
302 break;
303 case 2: // format
304 sortUpDown_String(watches, sortSpecs, [](const auto& item) { return item.format.getString(); });
305 break;
306 default:
308 }
309}
310
311} // namespace openmsx
void save(ImGuiTextBuffer &buf) override
ImGuiWatchExpr(ImGuiManager &manager)
void loadStart() override
void loadLine(std::string_view name, zstring_view value) override
void paint(MSXMotherBoard *motherBoard) override
TclObject executeCommand(Interpreter &interp, bool compile=false)
Interpret this TclObject as a command and execute it.
Definition TclObject.cc:248
TclObject getListIndexUnchecked(unsigned index) const
Definition TclObject.cc:182
unsigned size() const
Definition TclObject.hh:179
zstring_view getString() const
Definition TclObject.cc:141
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr auto empty() const
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:39
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:26
constexpr double e
Definition Math.hh:21
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
Definition ImGuiCpp.hh:455
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
void StyleColor(bool active, Args &&...args)
Definition ImGuiCpp.hh:175
void Child(const char *str_id, const ImVec2 &size, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:110
void Disabled(bool b, std::invocable<> auto next)
Definition ImGuiCpp.hh:506
void Group(std::invocable<> auto next)
Definition ImGuiCpp.hh:236
void ID_for_range(std::integral auto count, std::invocable< int > auto next)
Definition ImGuiCpp.hh:281
This file implemented 3 utility functions:
Definition Autofire.cc:11
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void sortUpDown_String(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
void simpleToolTip(std::string_view desc)
Definition ImGuiUtils.hh:79
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
void HelpMarker(std::string_view desc)
Definition ImGuiUtils.cc:23
ImU32 getColor(imColor col)
TclObject makeTclList(Args &&... args)
Definition TclObject.hh:293
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
#define UNREACHABLE