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