1 //============================================================================
2 //
3 // SSSS tt lll lll
4 // SS SS tt ll ll
5 // SS tttttt eeee ll ll aaaa
6 // SSSS tt ee ee ll ll aa
7 // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8 // SS SS tt ee ll ll aa aa
9 // SSSS ttt eeeee llll llll aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17
18 #include "DataGridRamWidget.hxx"
19 #include "EditTextWidget.hxx"
20 #include "GuiObject.hxx"
21 #include "InputTextDialog.hxx"
22 #include "OSystem.hxx"
23 #include "Debugger.hxx"
24 #include "CartDebug.hxx"
25 #include "Font.hxx"
26 #include "FBSurface.hxx"
27 #include "Widget.hxx"
28 #include "RamWidget.hxx"
29
30 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RamWidget(GuiObject * boss,const GUI::Font & lfont,const GUI::Font & nfont,int x,int y,int w,int h,uInt32 ramsize,uInt32 numrows,uInt32 pagesize,const string & helpAnchor)31 RamWidget::RamWidget(GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
32 int x, int y, int w, int h,
33 uInt32 ramsize, uInt32 numrows, uInt32 pagesize,
34 const string& helpAnchor)
35 : Widget(boss, lfont, x, y, w, h),
36 CommandSender(boss),
37 _nfont{nfont},
38 myFontWidth{lfont.getMaxCharWidth()},
39 myFontHeight{lfont.getFontHeight()},
40 myLineHeight{lfont.getLineHeight()},
41 myButtonHeight{static_cast<int>(myLineHeight * 1.25)},
42 myRamSize{ramsize},
43 myNumRows{numrows},
44 myPageSize{pagesize}
45 {
46 const int bwidth = lfont.getStringWidth("Compare " + ELLIPSIS),
47 bheight = myLineHeight + 2;
48 //const int VGAP = 4;
49 const int VGAP = myFontHeight / 4;
50 StaticTextWidget* s;
51 WidgetArray wid;
52
53 int ypos = y + myLineHeight;
54
55 // Add RAM grid (with scrollbar)
56 int xpos = x + _font.getStringWidth("xxxx");
57 bool useScrollbar = ramsize / numrows > 16;
58 myRamGrid = new DataGridRamWidget(_boss, *this, _nfont, xpos, ypos,
59 16, myNumRows, 2, 8, Common::Base::Fmt::_16, useScrollbar);
60 myRamGrid->setHelpAnchor(helpAnchor, true);
61 myRamGrid->setTarget(this);
62 myRamGrid->setID(kRamGridID);
63 addFocusWidget(myRamGrid);
64
65 // Create actions buttons to the left of the RAM grid
66 int bx = xpos + myRamGrid->getWidth() + 4;
67 int by = ypos;
68
69 myUndoButton = new ButtonWidget(boss, lfont, bx, by, bwidth, bheight,
70 "Undo", kUndoCmd);
71 myUndoButton->setHelpAnchor("M6532Search", true);
72 wid.push_back(myUndoButton);
73 myUndoButton->setTarget(this);
74
75 by += bheight + VGAP;
76 myRevertButton = new ButtonWidget(boss, lfont, bx, by, bwidth, bheight,
77 "Revert", kRevertCmd);
78 myRevertButton->setHelpAnchor("M6532Search", true);
79 wid.push_back(myRevertButton);
80 myRevertButton->setTarget(this);
81
82 by += bheight + VGAP * 6;
83 mySearchButton = new ButtonWidget(boss, lfont, bx, by, bwidth, bheight,
84 "Search" + ELLIPSIS, kSearchCmd);
85 mySearchButton->setHelpAnchor("M6532Search", true);
86 mySearchButton->setToolTip("Search and highlight found values.");
87 wid.push_back(mySearchButton);
88 mySearchButton->setTarget(this);
89
90 by += bheight + VGAP;
91 myCompareButton = new ButtonWidget(boss, lfont, bx, by, bwidth, bheight,
92 "Compare" + ELLIPSIS, kCmpCmd);
93 myCompareButton->setHelpAnchor("M6532Search", true);
94 myCompareButton->setToolTip("Compare highlighted values.");
95 wid.push_back(myCompareButton);
96 myCompareButton->setTarget(this);
97
98 by += bheight + VGAP;
99 myRestartButton = new ButtonWidget(boss, lfont, bx, by, bwidth, bheight,
100 "Reset", kRestartCmd);
101 myRestartButton->setHelpAnchor("M6532Search", true);
102 myRestartButton->setToolTip("Reset search/compare mode.");
103 wid.push_back(myRestartButton);
104 myRestartButton->setTarget(this);
105
106 addToFocusList(wid);
107
108 // Labels for RAM grid
109 myRamStart =
110 new StaticTextWidget(_boss, lfont, xpos - _font.getStringWidth("xxxx"),
111 ypos - myLineHeight,
112 lfont.getStringWidth("xxxx"), myFontHeight,
113 "00xx", TextAlign::Left);
114
115 for(int col = 0; col < 16; ++col)
116 {
117 new StaticTextWidget(_boss, lfont, xpos + col*myRamGrid->colWidth() + 8,
118 ypos - myLineHeight,
119 myFontWidth, myFontHeight,
120 Common::Base::toString(col, Common::Base::Fmt::_16_1),
121 TextAlign::Left);
122 }
123
124 uInt32 row;
125 for(row = 0; row < myNumRows; ++row)
126 {
127 myRamLabels[row] =
128 new StaticTextWidget(_boss, _font, xpos - _font.getStringWidth("x "),
129 ypos + row*myLineHeight + 2,
130 myFontWidth, myFontHeight, "", TextAlign::Left);
131 }
132
133 // For smaller grids, make sure RAM cell detail fields are below the RESET button
134 row = myNumRows < 8 ? 9 : myNumRows + 1;
135 ypos += (row - 1) * myLineHeight + VGAP * 2;
136
137 // We need to define these widgets from right to left since the leftmost
138 // one resizes as much as possible
139
140 // Add Binary display of selected RAM cell
141 xpos = x + w - 9.6 * myFontWidth - 9;
142 s = new StaticTextWidget(boss, lfont, xpos, ypos, "%");
143 myBinValue = new DataGridWidget(boss, nfont, s->getRight() + myFontWidth * 0.1, ypos-2,
144 1, 1, 8, 8, Common::Base::Fmt::_2);
145 myBinValue->setHelpAnchor(helpAnchor, true);
146 myBinValue->setTarget(this);
147 myBinValue->setID(kRamBinID);
148
149 // Add Decimal display of selected RAM cell
150 xpos -= 6.5 * myFontWidth;
151 s = new StaticTextWidget(boss, lfont, xpos, ypos, "#");
152 myDecValue = new DataGridWidget(boss, nfont, s->getRight(), ypos-2,
153 1, 1, 3, 8, Common::Base::Fmt::_10);
154 myDecValue->setHelpAnchor(helpAnchor, true);
155 myDecValue->setTarget(this);
156 myDecValue->setID(kRamDecID);
157
158 // Add Hex display of selected RAM cell
159 xpos -= 4.5 * myFontWidth;
160 myHexValue = new DataGridWidget(boss, nfont, xpos, ypos - 2,
161 1, 1, 2, 8, Common::Base::Fmt::_16);
162 myHexValue->setHelpAnchor(helpAnchor, true);
163 myHexValue->setTarget(this);
164 myHexValue->setID(kRamHexID);
165
166 addFocusWidget(myHexValue);
167 addFocusWidget(myDecValue);
168 addFocusWidget(myBinValue);
169
170 // Add Label of selected RAM cell
171 int xpos_r = xpos - myFontWidth * 1.5;
172 xpos = x;
173 s = new StaticTextWidget(boss, lfont, xpos, ypos, "Label");
174 xpos = s->getRight() + myFontWidth / 2;
175 myLabel = new EditTextWidget(boss, nfont, xpos, ypos-2, xpos_r-xpos,
176 myLineHeight);
177 myLabel->setEditable(false, true);
178
179 // Inputbox which will pop up when searching RAM
180 StringList labels = { "Value" };
181 myInputBox = make_unique<InputTextDialog>(boss, lfont, nfont, labels, " ");
182 myInputBox->setTarget(this);
183
184 // Start with these buttons disabled
185 myCompareButton->clearFlags(Widget::FLAG_ENABLED);
186 myRestartButton->clearFlags(Widget::FLAG_ENABLED);
187
188 // Calculate final height
189 if(_h == 0) _h = ypos + myLineHeight - y;
190 }
191
192 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
~RamWidget()193 RamWidget::~RamWidget()
194 {
195 }
196
197 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleCommand(CommandSender * sender,int cmd,int data,int id)198 void RamWidget::handleCommand(CommandSender* sender, int cmd, int data, int id)
199 {
200 // We simply change the values in the DataGridWidget
201 // It will then send the 'kDGItemDataChangedCmd' signal to change the actual
202 // memory location
203 int addr = 0, value = 0;
204
205 switch(cmd)
206 {
207 case DataGridWidget::kItemDataChangedCmd:
208 {
209 switch(id)
210 {
211 case kRamGridID:
212 addr = myRamGrid->getSelectedAddr();
213 value = myRamGrid->getSelectedValue();
214 break;
215
216 case kRamHexID:
217 addr = myRamGrid->getSelectedAddr();
218 value = myHexValue->getSelectedValue();
219 break;
220
221 case kRamDecID:
222 addr = myRamGrid->getSelectedAddr();
223 value = myDecValue->getSelectedValue();
224 break;
225
226 case kRamBinID:
227 addr = myRamGrid->getSelectedAddr();
228 value = myBinValue->getSelectedValue();
229 break;
230
231 default:
232 break;
233 }
234
235 uInt8 oldval = getValue(addr);
236 setValue(addr, value);
237
238 myUndoAddress = addr;
239 myUndoValue = oldval;
240
241 myRamGrid->setValueInternal(addr - myCurrentRamBank*myPageSize, value, true);
242 myHexValue->setValueInternal(0, value, true);
243 myDecValue->setValueInternal(0, value, true);
244 myBinValue->setValueInternal(0, value, true);
245
246 myRevertButton->setEnabled(true);
247 myUndoButton->setEnabled(true);
248 break;
249 }
250
251 case DataGridWidget::kSelectionChangedCmd:
252 {
253 addr = myRamGrid->getSelectedAddr();
254 value = myRamGrid->getSelectedValue();
255 bool changed = myRamGrid->getSelectedChanged();
256
257 myLabel->setText(getLabel(addr));
258 myHexValue->setValueInternal(0, value, changed);
259 myDecValue->setValueInternal(0, value, changed);
260 myBinValue->setValueInternal(0, value, changed);
261 break;
262 }
263
264 case kRevertCmd:
265 for(uInt32 i = 0; i < myOldValueList.size(); ++i)
266 setValue(i, myOldValueList[i]);
267 fillGrid(true);
268 break;
269
270 case kUndoCmd:
271 setValue(myUndoAddress, myUndoValue);
272 myUndoButton->setEnabled(false);
273 fillGrid(false);
274 break;
275
276 case kSearchCmd:
277 showInputBox(kSValEntered);
278 break;
279
280 case kCmpCmd:
281 showInputBox(kCValEntered);
282 break;
283
284 case kRestartCmd:
285 doRestart();
286 break;
287
288 case kSValEntered:
289 {
290 const string& result = doSearch(myInputBox->getResult());
291 if(result != "")
292 myInputBox->setMessage(result);
293 else
294 myInputBox->close();
295 break;
296 }
297
298 case kCValEntered:
299 {
300 const string& result = doCompare(myInputBox->getResult());
301 if(result != "")
302 myInputBox->setMessage(result);
303 else
304 myInputBox->close();
305 break;
306 }
307
308 case GuiObject::kSetPositionCmd:
309 myCurrentRamBank = data;
310 showSearchResults();
311 fillGrid(false);
312 break;
313
314 default:
315 break;
316 }
317 }
318
319 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setOpsWidget(DataGridOpsWidget * w)320 void RamWidget::setOpsWidget(DataGridOpsWidget* w)
321 {
322 myRamGrid->setOpsWidget(w);
323 myHexValue->setOpsWidget(w);
324 myBinValue->setOpsWidget(w);
325 myDecValue->setOpsWidget(w);
326 }
327
328 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
loadConfig()329 void RamWidget::loadConfig()
330 {
331 fillGrid(true);
332
333 int value = myRamGrid->getSelectedValue();
334 bool changed = myRamGrid->getSelectedChanged();
335
336 myHexValue->setValueInternal(0, value, changed);
337 myDecValue->setValueInternal(0, value, changed);
338 myBinValue->setValueInternal(0, value, changed);
339 }
340
341 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
fillGrid(bool updateOld)342 void RamWidget::fillGrid(bool updateOld)
343 {
344 IntArray alist;
345 IntArray vlist;
346 BoolArray changed;
347
348 uInt32 start = myCurrentRamBank * myPageSize;
349 fillList(start, myPageSize, alist, vlist, changed);
350
351 if(updateOld)
352 myOldValueList = currentRam(start);
353
354 myRamGrid->setNumRows(myRamSize / myPageSize);
355 myRamGrid->setList(alist, vlist, changed);
356 if(updateOld)
357 {
358 myRevertButton->setEnabled(false);
359 myUndoButton->setEnabled(false);
360 }
361
362 // Update RAM labels
363 uInt32 rport = readPort(start), page = rport & 0xf0;
364 string label = Common::Base::toString(rport, Common::Base::Fmt::_16_4);
365
366 label[2] = label[3] = 'x';
367 myRamStart->setLabel(label);
368 for(uInt32 row = 0; row < myNumRows; ++row, page += 0x10)
369 myRamLabels[row]->setLabel(Common::Base::toString(page>>4, Common::Base::Fmt::_16_1));
370 }
371
372 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
showInputBox(int cmd)373 void RamWidget::showInputBox(int cmd)
374 {
375 // Add inputbox in the middle of the RAM widget
376 uInt32 x = getAbsX() + ((getWidth() - myInputBox->getWidth()) >> 1);
377 uInt32 y = getAbsY() + ((getHeight() - myInputBox->getHeight()) >> 1);
378
379 myInputBox->show(x, y, dialog().surface().dstRect());
380 myInputBox->setText("");
381 myInputBox->setMessage("");
382 myInputBox->setToolTip(cmd == kSValEntered
383 ? "Enter search value (leave blank for all)."
384 : "Enter relative or absolute value\nto compare with searched values.");
385 myInputBox->setFocus(0);
386 myInputBox->setEmitSignal(cmd);
387 myInputBox->setTitle(cmd == kSValEntered ? "Search" : "Compare");
388 }
389
390 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
doSearch(const string & str)391 string RamWidget::doSearch(const string& str)
392 {
393 bool comparisonSearch = true;
394
395 if(str.length() == 0)
396 {
397 // An empty field means return all memory locations
398 comparisonSearch = false;
399 }
400 else if(str.find_first_of("+-", 0) != string::npos)
401 {
402 // Don't accept these characters here, only in compare
403 return "Invalid input +|-";
404 }
405
406 int searchVal = instance().debugger().stringToValue(str);
407
408 // Clear the search array of previous items
409 mySearchAddr.clear();
410 mySearchValue.clear();
411 mySearchState.clear();
412
413 // Now, search all memory locations for this value, and add it to the
414 // search array
415 const ByteArray& ram = currentRam(0);
416 bool hitfound = false;
417 for(uInt32 addr = 0; addr < ram.size(); ++addr)
418 {
419 int value = ram[addr];
420 if(comparisonSearch && searchVal != value)
421 {
422 mySearchState.push_back(false);
423 }
424 else
425 {
426 mySearchAddr.push_back(addr);
427 mySearchValue.push_back(value);
428 mySearchState.push_back(true);
429 hitfound = true;
430 }
431 }
432
433 // If we have some hits, enable the comparison methods
434 if(hitfound)
435 {
436 mySearchButton->setEnabled(false);
437 myCompareButton->setEnabled(true);
438 myRestartButton->setEnabled(true);
439 }
440
441 // Finally, show the search results in the list
442 showSearchResults();
443
444 return EmptyString;
445 }
446
447 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
doCompare(const string & str)448 string RamWidget::doCompare(const string& str)
449 {
450 bool comparativeSearch = false;
451 int searchVal = 0, offset = 0;
452
453 if(str.length() == 0)
454 return "Enter an absolute or comparative value";
455
456 // Do some pre-processing on the string
457 string::size_type pos = str.find_first_of("+-", 0);
458 if(pos > 0 && pos != string::npos)
459 {
460 // Only accept '+' or '-' at the start of the string
461 return "Input must be [+|-]NUM";
462 }
463
464 // A comparative search searches memory for locations that have changed by
465 // the specified amount, vs. for exact values
466 if(str[0] == '+' || str[0] == '-')
467 {
468 comparativeSearch = true;
469 bool negative = false;
470 if(str[0] == '-')
471 negative = true;
472
473 string tmp = str;
474 tmp.erase(0, 1); // remove the operator
475 offset = instance().debugger().stringToValue(tmp);
476 if(negative)
477 offset = -offset;
478 }
479 else
480 searchVal = instance().debugger().stringToValue(str);
481
482 // Now, search all memory locations previously 'found' for this value
483 const ByteArray& ram = currentRam(0);
484 bool hitfound = false;
485 IntArray tempAddrList, tempValueList;
486 mySearchState.clear();
487 for(uInt32 i = 0; i < ram.size(); ++i)
488 mySearchState.push_back(false);
489
490 for(uInt32 i = 0; i < mySearchAddr.size(); ++i)
491 {
492 if(comparativeSearch)
493 {
494 searchVal = mySearchValue[i] + offset;
495 if(searchVal < 0 || searchVal > 255)
496 continue;
497 }
498
499 int addr = mySearchAddr[i];
500 if(ram[addr] == searchVal)
501 {
502 tempAddrList.push_back(addr);
503 tempValueList.push_back(searchVal);
504 mySearchState[addr] = hitfound = true;
505 }
506 }
507
508 // Update the searchArray for the new addresses and data
509 mySearchAddr = tempAddrList;
510 mySearchValue = tempValueList;
511
512 // If we have some hits, enable the comparison methods
513 if(hitfound)
514 {
515 myCompareButton->setEnabled(true);
516 myRestartButton->setEnabled(true);
517 }
518
519 // Finally, show the search results in the list
520 showSearchResults();
521
522 return EmptyString;
523 }
524
525 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
doRestart()526 void RamWidget::doRestart()
527 {
528 // Erase all search buffers, reset to start mode
529 mySearchAddr.clear();
530 mySearchValue.clear();
531 mySearchState.clear();
532 showSearchResults();
533
534 mySearchButton->setEnabled(true);
535 myCompareButton->setEnabled(false);
536 myRestartButton->setEnabled(false);
537 }
538
539 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
showSearchResults()540 void RamWidget::showSearchResults()
541 {
542 // Only update the search results for the bank currently being shown
543 BoolArray temp;
544 uInt32 start = myCurrentRamBank * myPageSize;
545 if(mySearchState.size() == 0 || start > mySearchState.size())
546 {
547 for(uInt32 i = 0; i < myPageSize; ++i)
548 temp.push_back(false);
549 }
550 else
551 {
552 for(uInt32 i = start; i < start + myPageSize; ++i)
553 temp.push_back(mySearchState[i]);
554 }
555 myRamGrid->setHiliteList(temp);
556 }
557