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
185ImGuiWatchExpr::EvalResult ImGuiWatchExpr::evalExpr(WatchExpr& watch, Interpreter& interp) const
186{
187 EvalResult r; // TODO c++23 std::expected might be a good fit here
188 if (watch.exprStr.empty()) return r;
189
190 if (!watch.expression) {
191 if (auto addr = symbolManager.parseSymbolOrValue(watch.exprStr)) {
192 // expression is a symbol or an integer -> rewrite
193 watch.expression = TclObject(tmpStrCat("[peek ", *addr, ']'));
194 } else {
195 // keep original expression
196 watch.expression = watch.exprStr;
197 }
198 }
199 assert(watch.expression);
200
201 try {
202 r.result = watch.expression->eval(interp);
203 } catch (CommandException& e) {
204 r.error = e.getMessage();
205 }
206 return r;
207}
208
209void ImGuiWatchExpr::drawRow(int row)
210{
211 auto& interp = manager.getInterpreter();
212 auto& watch = watches[row];
213
214 // evaluate 'expression'
215 auto [result, exprError_] = evalExpr(watch, interp);
216 auto& exprError = exprError_; // clang workaround
217 bool validExpr = exprError.empty();
218
219 // format the result
220 TclObject frmtResult = result; // also fallback for error in format
221 std::string frmtError;
222 if (!watch.format.getString().empty()) {
223 auto frmtCmd = makeTclList("format", watch.format, validExpr ? result : TclObject("0"));
224 try {
225 frmtResult = frmtCmd.executeCommand(interp);
226 } catch (CommandException& e) {
227 frmtError = e.getMessage();
228 }
229 }
230 bool validFrmt = frmtError.empty();
231
232 if (ImGui::TableNextColumn()) { // description
233 auto pos = ImGui::GetCursorPos();
234 const auto& style = ImGui::GetStyle();
235 float rowHeight = 2.0f * style.FramePadding.y;
236 rowHeight += ImGui::CalcTextSize(validExpr ? frmtResult.getString() : exprError).y;
237 if (ImGui::Selectable("##selection", selectedRow == row,
238 ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
239 {0.0f, rowHeight})) {
240 selectedRow = row;
241 }
242 ImGui::SetCursorPos(pos);
243
244 auto avail = ImGui::GetContentRegionAvail().x;
245 ImGui::SetNextItemWidth(-FLT_MIN);
246 ImGui::InputText("##desc", &watch.description);
247 tooWideToolTip(avail, watch.description);
248 }
249 if (ImGui::TableNextColumn()) { // expression
250 im::StyleColor(!validExpr, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
251 im::ScopedFont sf(manager.fontMono);
252 auto avail = ImGui::GetContentRegionAvail().x;
253 ImGui::SetNextItemWidth(-FLT_MIN);
254 if (ImGui::InputText("##expr", &watch.exprStr)) {
255 watch.expression.reset();
256 }
257 tooWideToolTip(avail, watch.exprStr);
258 });
259 }
260 if (ImGui::TableNextColumn()) { // format
261 im::StyleColor(!validFrmt, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
262 im::ScopedFont sf(manager.fontMono);
263 auto avail = ImGui::GetContentRegionAvail().x;
264 ImGui::SetNextItemWidth(-FLT_MIN);
265 auto str = std::string(watch.format.getString());
266 if (ImGui::InputText("##format", &str)) {
267 watch.format = str;
268 }
269 if (validFrmt) {
270 tooWideToolTip(avail, str);
271 } else {
272 simpleToolTip(frmtError);
273 }
274 });
275 }
276 if (ImGui::TableNextColumn()) { // result
277 im::ScopedFont sf(manager.fontMono);
278 auto avail = ImGui::GetContentRegionAvail().x;
279 if (validExpr) {
280 const auto& str = frmtResult.getString();
282 tooWideToolTip(avail, str);
283 } else {
284 im::StyleColor(ImGuiCol_Text, getColor(imColor::ERROR), [&]{
285 ImGui::TextUnformatted(exprError);
286 tooWideToolTip(avail, exprError);
287 });
288 }
289 }
290}
291
292void ImGuiWatchExpr::checkSort()
293{
294 auto* sortSpecs = ImGui::TableGetSortSpecs();
295 if (!sortSpecs->SpecsDirty) return;
296
297 sortSpecs->SpecsDirty = false;
298 if (sortSpecs->SpecsCount == 0) return;
299 assert(sortSpecs->SpecsCount == 1);
300 assert(sortSpecs->Specs);
301 assert(sortSpecs->Specs->SortOrder == 0);
302
303 switch (sortSpecs->Specs->ColumnIndex) {
304 case 0: // description
305 sortUpDown_String(watches, sortSpecs, &WatchExpr::description);
306 break;
307 case 1: // expression
308 sortUpDown_String(watches, sortSpecs, [](const auto& item) { return item.exprStr; });
309 break;
310 case 2: // format
311 sortUpDown_String(watches, sortSpecs, [](const auto& item) { return item.format.getString(); });
312 break;
313 default:
315 }
316}
317
318} // 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:177
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:37
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:24
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:479
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
void Child(const char *str_id, const ImVec2 &size, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:110
void StyleColor(ImGuiCol idx1, ImVec4 col1, ImGuiCol idx2, ImVec4 col2, std::invocable<> auto next)
Definition ImGuiCpp.hh:162
void Disabled(bool b, std::invocable<> auto next)
Definition ImGuiCpp.hh:530
void Group(std::invocable<> auto next)
Definition ImGuiCpp.hh:256
void ID_for_range(int count, std::invocable< int > auto next)
Definition ImGuiCpp.hh:301
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:66
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