e8555c89 Andy McCall 1 month ago
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.h>
13
#include < imgui_stdlib.h>
14
15
#include < cassert>
16
17
namespace openmsx {
18
19
using namespace std:: literals;
20
21
ImGuiWatchExpr:: ImGuiWatchExpr ( ImGuiManager& manager_ )
22
: ImGuiPart ( manager_ )
23
, symbolManager ( manager. getReactor ( ) . getSymbolManager ( ) )
24
{
25
}
26
27
void 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
36
void ImGuiWatchExpr:: loadStart ( )
37
{
38
watches. clear ( ) ;
39
}
40
41
void ImGuiWatchExpr:: loadLine ( std:: string_view name , zstring_view value )
42
{
43
if ( loadOnePersistent ( name, value, * this , persistentElements ) ) {
44
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
57
void ImGuiWatchExpr:: paint ( MSXMotherBoard* )
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" , 5 , flags, [ & ] {
77
ImGui:: TableSetupScrollFreeze ( 0 , 1 ) ;
78
ImGui:: TableSetupColumn ( " id" , ImGuiTableColumnFlags_DefaultHide) ;
79
ImGui:: TableSetupColumn ( " description" ) ;
80
ImGui:: TableSetupColumn ( " expression" ) ;
81
ImGui:: TableSetupColumn ( " format" ) ;
82
ImGui:: TableSetupColumn ( " result" , ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_NoHide) ;
83
ImGui:: TableHeadersRow ( ) ;
84
checkSort ( ) ;
85
86
im:: ID_for_range ( watches. size ( ) , [ & ] ( int row) {
87
drawRow ( row ) ;
88
} ) ;
89
} ) ;
90
} ) ;
91
ImGui:: SameLine ( ) ;
92
im:: Group ( [ & ] {
93
if ( ImGui:: Button ( " Add" ) ) {
94
selectedRow = narrow < int > ( watches. size ( ) ) ;
95
watches. emplace_back ( ) ;
96
}
97
im:: Disabled ( selectedRow < 0 || selectedRow >= narrow < int > ( watches. size ( ) ) , [ & ] {
98
if ( ImGui:: Button ( " Remove" ) ) {
99
watches. erase ( watches. begin ( ) + selectedRow ) ;
100
if ( selectedRow == narrow < int > ( watches. size ( ) ) ) {
101
selectedRow = - 1 ;
102
}
103
}
104
} ) ;
105
ImGui:: Dummy ( { 0 , 20 } ) ;
106
if ( ImGui:: SmallButton ( " Examples" ) ) {
107
watches. emplace_back (
108
" peek at fixed address" ,
109
" [peek 0xfcaf]" ,
110
TclObject ( " screen=%d " ) ) ;
111
watches. emplace_back (
112
" VDP command executing" ,
113
" [debug read \" VDP status regs\" 2] & 1" ,
114
TclObject ( ) ) ;
115
watches. emplace_back (
116
" PSG enable-channel status" ,
117
" [debug read \" PSG regs\" 7]" ,
118
TclObject ( " %08b" ) ) ;
119
watches. emplace_back (
120
" The following 2 require an appropriate symbol file" ,
121
" " ,
122
TclObject ( " " ) ) ;
123
watches. emplace_back (
124
" value of 'myLabel'" ,
125
" $sym(myLabel)" ,
126
TclObject ( " 0x%04x " ) ) ;
127
watches. emplace_back (
128
" peek at symbolic address" ,
129
" [peek16 $sym(numItems)]" ,
130
TclObject ( " %d items" ) ) ;
131
}
132
ImGui:: Dummy ( { 0 , 0 } ) ;
133
HelpMarker ( R "( The given (Tcl) expressions are continuously evaluated and displayed in a nicely formatted way.
134
Press the 'Examples' button to see some examples.
135
136
There are 4 columns:
137
* description: optional description for the expression
138
* expression: the actual Tcl expression, see below
139
* format: optional format specifier, see below
140
* result: this shows the result of evaluating 'expression'
141
142
Add a new entry via the 'Add' button, then fill in the appropriate fields.
143
To remove an entry, first select a row (e.g. click on the corresponding 'result' cell), then press the 'Remove' button.
144
145
You can sort the table by clicking the column headers.
146
You can reorder the columns by dragging the column headers.
147
You 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.
148
149
The expression can be any Tcl expression, some examples:
150
* [peek 0x1234]: monitor a byte at a specific address
151
* [reg SP] < 0xe000: check that stack hasn't grown too large
152
(below address 0xe000)
153
If you have debug-symbols loaded (via the 'Symbol manager'), you can use them like:
154
* [peek16 $sym(mySymbol)]: monitor 16-bit value at 'mySymbol'
155
There's a shorthand notation to peek 8-bit values:
156
* <integer> -> [peek <integer>]
157
* <symbol> -> [peek $sym(<symbol>)]
158
159
In the format column you can optionally enter a format-specifier (for the Tcl 'format' command). Some examples:
160
* 0x%04x: 4-digit hex with leading zeros and '0x' prefix
161
* %08b: 8-bit binary value
162
* %d items: decimal value followed by the string " items"
163
)" ) ;
164
} ) ;
165
} ) ;
166
}
167
168
static void tooWideToolTip ( float available , zstring_view str )
169
{
170
if ( str. empty ( ) ) return ;
171
auto width = ImGui:: CalcTextSize ( str ) . x ;
172
width += 2.0f * ImGui:: GetStyle ( ) . FramePadding . x ;
173
if ( width >= available) {
174
simpleToolTip ( str ) ;
175
}
176
}
177
178
void ImGuiWatchExpr:: refreshSymbols ( )
179
{
180
181
for ( auto & watch : watches) {
182
watch. expression . reset ( ) ;
183
}
184
}
185
186
std:: expected< TclObject, std:: string> ImGuiWatchExpr:: evalExpr ( WatchExpr& watch , Interpreter& interp ) const
187
{
188
if ( watch. exprStr . empty ( ) ) return { } ;
189
190
if ( ! watch. expression ) {
191
if ( auto addr = symbolManager. parseSymbolOrValue ( watch. exprStr ) ) {
192
193
watch. expression = TclObject ( tmpStrCat ( " [peek " , * addr, ' ]' ) ) ;
194
} else {
195
196
watch. expression = watch. exprStr ;
197
}
198
}
199
assert ( watch. expression ) ;
200
201
try {
202
return watch. expression -> eval ( interp ) ;
203
} catch ( CommandException& e) {
204
return std:: unexpected ( e. getMessage ( ) ) ;
205
}
206
}
207
208
void ImGuiWatchExpr:: drawRow ( int row )
209
{
210
auto & interp = manager. getInterpreter ( ) ;
211
auto & watch = watches[ row] ;
212
213
214
auto exprVal = evalExpr ( watch, interp ) ;
215
216
217
std:: expected< TclObject, std:: string> formatted;
218
if ( ! watch. format . getString ( ) . empty ( ) ) {
219
auto frmtCmd = makeTclList ( " format" , watch. format , exprVal ? * exprVal : TclObject ( " 0" ) ) ;
220
try {
221
formatted = frmtCmd. executeCommand ( interp ) ;
222
} catch ( CommandException& e) {
223
formatted = std:: unexpected ( e. getMessage ( ) ) ;
224
}
225
} else {
226
formatted = exprVal ? * exprVal : TclObject ( ) ;
227
}
228
229
const auto & display = exprVal ? ( formatted ? formatted-> getString ( ) : exprVal-> getString ( ) )
230
: exprVal. error ( ) ;
231
232
if ( ImGui:: TableNextColumn ( ) ) {
233
ImGui:: TextUnformatted ( watch. getIdStr ( ) ) ;
234
}
235
if ( ImGui:: TableNextColumn ( ) ) {
236
auto pos = ImGui:: GetCursorPos ( ) ;
237
const auto & style = ImGui:: GetStyle ( ) ;
238
float rowHeight = 2.0f * style. FramePadding . y ;
239
rowHeight += ImGui:: CalcTextSize ( display ) . y ;
240
if ( ImGui:: Selectable ( " ##selection" , selectedRow == row,
241
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
242
{ 0.0f , rowHeight} ) ) {
243
selectedRow = row;
244
}
245
ImGui:: SetCursorPos ( pos ) ;
246
247
auto avail = ImGui:: GetContentRegionAvail ( ) . x ;
248
ImGui:: SetNextItemWidth ( - FLT_MIN) ;
249
ImGui:: InputText ( " ##desc" , & watch. description ) ;
250
tooWideToolTip ( avail, watch. description ) ;
251
}
252
if ( ImGui:: TableNextColumn ( ) ) {
253
im:: StyleColor ( ! exprVal, ImGuiCol_Text, getColor ( imColor:: ERROR ) , [ & ] {
254
im:: ScopedFont sf ( manager. fontMono ) ;
255
auto avail = ImGui:: GetContentRegionAvail ( ) . x ;
256
ImGui:: SetNextItemWidth ( - FLT_MIN) ;
257
if ( ImGui:: InputText ( " ##expr" , & watch. exprStr ) ) {
258
watch. expression . reset ( ) ;
259
}
260
tooWideToolTip ( avail, watch. exprStr ) ;
261
} ) ;
262
}
263
if ( ImGui:: TableNextColumn ( ) ) {
264
im:: StyleColor ( ! formatted, ImGuiCol_Text, getColor ( imColor:: ERROR ) , [ & ] {
265
im:: ScopedFont sf ( manager. fontMono ) ;
266
auto avail = ImGui:: GetContentRegionAvail ( ) . x ;
267
ImGui:: SetNextItemWidth ( - FLT_MIN) ;
268
auto str = std:: string ( watch. format . getString ( ) ) ;
269
if ( ImGui:: InputText ( " ##format" , & str) ) {
270
watch. format = str;
271
}
272
if ( formatted) {
273
tooWideToolTip ( avail, str ) ;
274
} else {
275
simpleToolTip ( formatted. error ( ) ) ;
276
}
277
} ) ;
278
}
279
if ( ImGui:: TableNextColumn ( ) ) {
280
im:: ScopedFont sf ( manager. fontMono ) ;
281
auto avail = ImGui:: GetContentRegionAvail ( ) . x ;
282
im:: StyleColor ( ! exprVal, ImGuiCol_Text, getColor ( imColor:: ERROR ) , [ & ] {
283
ImGui:: TextUnformatted ( display ) ;
284
tooWideToolTip ( avail, display ) ;
285
} ) ;
286
}
287
}
288
289
void ImGuiWatchExpr:: checkSort ( )
290
{
291
auto * sortSpecs = ImGui:: TableGetSortSpecs ( ) ;
292
if ( ! sortSpecs-> SpecsDirty ) return ;
293
294
sortSpecs-> SpecsDirty = false ;
295
if ( sortSpecs-> SpecsCount == 0 ) return ;
296
assert ( sortSpecs-> SpecsCount == 1 ) ;
297
assert ( sortSpecs-> Specs ) ;
298
assert ( sortSpecs-> Specs -> SortOrder == 0 ) ;
299
300
switch ( sortSpecs-> Specs -> ColumnIndex ) {
301
case 0 :
302
sortUpDown_T ( watches, sortSpecs, & WatchExpr:: id ) ;
303
break ;
304
case 1 :
305
sortUpDown_String ( watches, sortSpecs, & WatchExpr:: description ) ;
306
break ;
307
case 2 :
308
sortUpDown_String ( watches, sortSpecs, [ ] ( const auto & item) { return item. exprStr ; } ) ;
309
break ;
310
case 3 :
311
sortUpDown_String ( watches, sortSpecs, [ ] ( const auto & item) { return item. format . getString ( ) ; } ) ;
312
break ;
313
default :
314
UNREACHABLE;
315
}
316
}
317
318
}