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