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 "Settings.hxx"
19#include "DataGridWidget.hxx"
20#include "EditTextWidget.hxx"
21#include "FrameBuffer.hxx"
22#include "GuiObject.hxx"
23#include "OSystem.hxx"
24#include "Debugger.hxx"
25#include "RiotDebug.hxx"
26#include "PopUpWidget.hxx"
27#include "ToggleBitWidget.hxx"
28#include "Widget.hxx"
29
30#include "NullControlWidget.hxx"
31#include "JoystickWidget.hxx"
32#include "PaddleWidget.hxx"
33#include "BoosterWidget.hxx"
34#include "DrivingWidget.hxx"
35#include "GenesisWidget.hxx"
36#include "KeyboardWidget.hxx"
37#include "AtariVoxWidget.hxx"
38#include "SaveKeyWidget.hxx"
39#include "AmigaMouseWidget.hxx"
40#include "AtariMouseWidget.hxx"
41#include "TrakBallWidget.hxx"
42
43#include "RiotWidget.hxx"
44
45// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
46RiotWidget::RiotWidget(GuiObject* boss, const GUI::Font& lfont,
47 const GUI::Font& nfont,
48 int x, int y, int w, int h)
49 : Widget(boss, lfont, x, y, w, h),
50 CommandSender(boss)
51{
52 const int fontWidth = lfont.getMaxCharWidth(),
53 fontHeight = lfont.getFontHeight(),
54 lineHeight = lfont.getLineHeight();
55 int xpos = 10, ypos = 25, lwidth = 8 * fontWidth, col = 0;
56 StaticTextWidget* t;
57 VariantList items;
58
59 // Set the strings to be used in the various bit registers
60 // We only do this once because it's the state that changes, not the strings
61 StringList off, on;
62 for(int i = 0; i < 8; ++i)
63 {
64 off.push_back("0");
65 on.push_back("1");
66 }
67
68#define CREATE_IO_REGS(desc, bits, bitsID, editable) \
69 t = new StaticTextWidget(boss, lfont, xpos, ypos+2, lwidth, fontHeight,\
70 desc, TextAlign::Left); \
71 xpos += t->getWidth() + 5; \
72 bits = new ToggleBitWidget(boss, nfont, xpos, ypos, 8, 1); \
73 bits->setTarget(this); \
74 bits->setID(bitsID); \
75 if(editable) addFocusWidget(bits); else bits->setEditable(false); \
76 xpos += bits->getWidth() + 5; \
77 bits->setList(off, on);
78
79 // SWCHA bits in 'poke' mode
80 CREATE_IO_REGS("SWCHA(W)", mySWCHAWriteBits, kSWCHABitsID, true)
81 col = xpos + 20; // remember this for adding widgets to the second column
82
83 // SWACNT bits
84 xpos = 10; ypos += lineHeight + 5;
85 CREATE_IO_REGS("SWACNT", mySWACNTBits, kSWACNTBitsID, true)
86
87 // SWCHA bits in 'peek' mode
88 xpos = 10; ypos += lineHeight + 5;
89 CREATE_IO_REGS("SWCHA(R)", mySWCHAReadBits, kSWCHARBitsID, true)
90
91 // SWCHB bits in 'poke' mode
92 xpos = 10; ypos += 2 * lineHeight;
93 CREATE_IO_REGS("SWCHB(W)", mySWCHBWriteBits, kSWCHBBitsID, true)
94
95 // SWBCNT bits
96 xpos = 10; ypos += lineHeight + 5;
97 CREATE_IO_REGS("SWBCNT", mySWBCNTBits, kSWBCNTBitsID, true)
98
99 // SWCHB bits in 'peek' mode
100 xpos = 10; ypos += lineHeight + 5;
101 CREATE_IO_REGS("SWCHB(R)", mySWCHBReadBits, 0, false)
102
103 // Timer registers (R/W)
104 const char* const writeNames[] = { "TIM1T", "TIM8T", "TIM64T", "T1024T" };
105 xpos = 10; ypos += 2*lineHeight;
106 for(int row = 0; row < 4; ++row)
107 {
108 t = new StaticTextWidget(boss, lfont, xpos, ypos + row*lineHeight + 2,
109 lwidth, fontHeight, writeNames[row], TextAlign::Left);
110 }
111 xpos += t->getWidth() + 5;
112 myTimWrite = new DataGridWidget(boss, nfont, xpos, ypos, 1, 4, 2, 8, Common::Base::F_16);
113 myTimWrite->setTarget(this);
114 myTimWrite->setID(kTimWriteID);
115 addFocusWidget(myTimWrite);
116
117 // Timer registers (RO)
118 const char* const readNames[] = { "INTIM", "TIMINT", "Total Clks", "INTIM Clks", "Divider" };
119 xpos = 10; ypos += myTimWrite->getHeight() + lineHeight / 2;
120 for(int row = 0; row < 5; ++row)
121 {
122 t = new StaticTextWidget(boss, lfont, xpos, ypos + row*lineHeight + 2,
123 10*fontWidth, fontHeight, readNames[row], TextAlign::Left);
124 }
125 xpos += t->getWidth() + 5;
126 myTimRead = new DataGridWidget(boss, nfont, xpos, ypos, 1, 4, 8, 32, Common::Base::F_16);
127 myTimRead->setTarget(this);
128 myTimRead->setEditable(false);
129
130 ypos += myTimRead->getHeight() - 1;
131 myTimDivider = new DataGridWidget(boss, nfont, xpos, ypos, 1, 1, 4, 32, Common::Base::F_10_4);
132 myTimDivider->setTarget(this);
133 myTimDivider->setEditable(false);
134
135 // Controller ports
136 xpos = col; ypos = 10;
137 myLeftControl = addControlWidget(boss, lfont, xpos, ypos,
138 instance().console().leftController());
139 xpos += myLeftControl->getWidth() + 15;
140 myRightControl = addControlWidget(boss, lfont, xpos, ypos,
141 instance().console().rightController());
142
143 // TIA INPTx registers (R), left port
144 const char* const contLeftReadNames[] = { "INPT0", "INPT1", "INPT4" };
145 xpos = col; ypos += myLeftControl->getHeight() + 2 * lineHeight;
146 for(int row = 0; row < 3; ++row)
147 {
148 new StaticTextWidget(boss, lfont, xpos, ypos + row*lineHeight + 2,
149 5*fontWidth, fontHeight, contLeftReadNames[row], TextAlign::Left);
150 }
151 xpos += 5*fontWidth + 5;
152 myLeftINPT = new DataGridWidget(boss, nfont, xpos, ypos, 1, 3, 2, 8, Common::Base::F_16);
153 myLeftINPT->setTarget(this);
154 myLeftINPT->setEditable(false);
155
156 // TIA INPTx registers (R), right port
157 const char* const contRightReadNames[] = { "INPT2", "INPT3", "INPT5" };
158 xpos = col + myLeftControl->getWidth() + 15;
159 for(int row = 0; row < 3; ++row)
160 {
161 new StaticTextWidget(boss, lfont, xpos, ypos + row*lineHeight + 2,
162 5*fontWidth, fontHeight, contRightReadNames[row], TextAlign::Left);
163 }
164 xpos += 5*fontWidth + 5;
165 myRightINPT = new DataGridWidget(boss, nfont, xpos, ypos, 1, 3, 2, 8, Common::Base::F_16);
166 myRightINPT->setTarget(this);
167 myRightINPT->setEditable(false);
168
169 // TIA INPTx VBLANK bits (D6-latch, D7-dump) (R)
170 xpos = col + 20; ypos += myLeftINPT->getHeight() + lineHeight;
171 myINPTLatch = new CheckboxWidget(boss, lfont, xpos, ypos, "INPT latch (VBlank D6)");
172 myINPTLatch->setTarget(this);
173 myINPTLatch->setEditable(false);
174 ypos += lineHeight + 5;
175 myINPTDump = new CheckboxWidget(boss, lfont, xpos, ypos, "INPT dump to gnd (VBlank D7)");
176 myINPTDump->setTarget(this);
177 myINPTDump->setEditable(false);
178
179 // PO & P1 difficulty switches
180 int pwidth = lfont.getStringWidth("B/easy");
181 lwidth = lfont.getStringWidth("P0 Diff ");
182 xpos = col; ypos += 2 * lineHeight;
183 int col2_ypos = ypos;
184 items.clear();
185 VarList::push_back(items, "B/easy", "b");
186 VarList::push_back(items, "A/hard", "a");
187 myP0Diff = new PopUpWidget(boss, lfont, xpos, ypos, pwidth, lineHeight, items,
188 "P0 Diff ", lwidth, kP0DiffChanged);
189 myP0Diff->setTarget(this);
190 addFocusWidget(myP0Diff);
191 ypos += myP0Diff->getHeight() + 5;
192 myP1Diff = new PopUpWidget(boss, lfont, xpos, ypos, pwidth, lineHeight, items,
193 "P1 Diff ", lwidth, kP1DiffChanged);
194 myP1Diff->setTarget(this);
195 addFocusWidget(myP1Diff);
196
197 // TV Type
198 ypos += myP1Diff->getHeight() + 5;
199 items.clear();
200 VarList::push_back(items, "B&W", "bw");
201 VarList::push_back(items, "Color", "color");
202 myTVType = new PopUpWidget(boss, lfont, xpos, ypos, pwidth, lineHeight, items,
203 "TV Type ", lwidth, kTVTypeChanged);
204 myTVType->setTarget(this);
205 addFocusWidget(myTVType);
206
207 // 2600/7800 mode
208 lwidth = lfont.getStringWidth("Console") + 29;
209 pwidth = lfont.getStringWidth("Atari 2600") + 4;
210 new StaticTextWidget(boss, lfont, 10, ypos+1, "Console");
211 myConsole = new EditTextWidget(boss, lfont, 10 + lwidth, ypos - 1, pwidth, lineHeight);
212 myConsole->setEditable(false, true);
213 addFocusWidget(myConsole);
214
215 // Select and Reset
216 xpos += myP0Diff->getWidth() + 20; ypos = col2_ypos + 1;
217 mySelect = new CheckboxWidget(boss, lfont, xpos, ypos, "Select",
218 CheckboxWidget::kCheckActionCmd);
219 mySelect->setID(kSelectID);
220 mySelect->setTarget(this);
221 addFocusWidget(mySelect);
222
223 ypos += myP0Diff->getHeight() + 5;
224 myReset = new CheckboxWidget(boss, lfont, xpos, ypos, "Reset",
225 CheckboxWidget::kCheckActionCmd);
226 myReset->setID(kResetID);
227 myReset->setTarget(this);
228 addFocusWidget(myReset);
229
230 ypos += myP0Diff->getHeight() + 5;
231 myPause = new CheckboxWidget(boss, lfont, xpos, ypos, "Pause",
232 CheckboxWidget::kCheckActionCmd);
233 myPause->setID(kPauseID);
234 myPause->setTarget(this);
235 addFocusWidget(myPause);
236}
237
238// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
239void RiotWidget::loadConfig()
240{
241#define IO_REGS_UPDATE(bits, s_bits) \
242 changed.clear(); \
243 for(uInt32 i = 0; i < state.s_bits.size(); ++i) \
244 changed.push_back(state.s_bits[i] != oldstate.s_bits[i]); \
245 bits->setState(state.s_bits, changed);
246
247 IntArray alist;
248 IntArray vlist;
249 BoolArray changed;
250
251 // We push the enumerated items as addresses, and deal with the real
252 // address in the callback (handleCommand)
253 Debugger& dbg = instance().debugger();
254 RiotDebug& riot = dbg.riotDebug();
255 const RiotState& state = static_cast<const RiotState&>(riot.getState());
256 const RiotState& oldstate = static_cast<const RiotState&>(riot.getOldState());
257
258 // Update the SWCHA register booleans (poke mode)
259 IO_REGS_UPDATE(mySWCHAWriteBits, swchaWriteBits)
260
261 // Update the SWACNT register booleans
262 IO_REGS_UPDATE(mySWACNTBits, swacntBits)
263
264 // Update the SWCHA register booleans (peek mode)
265 IO_REGS_UPDATE(mySWCHAReadBits, swchaReadBits)
266
267 // Update the SWCHB register booleans (poke mode)
268 IO_REGS_UPDATE(mySWCHBWriteBits, swchbWriteBits)
269
270 // Update the SWBCNT register booleans
271 IO_REGS_UPDATE(mySWBCNTBits, swbcntBits)
272
273 // Update the SWCHB register booleans (peek mode)
274 IO_REGS_UPDATE(mySWCHBReadBits, swchbReadBits)
275
276 // Update TIA INPTx registers
277 alist.clear(); vlist.clear(); changed.clear();
278 alist.push_back(0); vlist.push_back(state.INPT0);
279 changed.push_back(state.INPT0 != oldstate.INPT0);
280 alist.push_back(1); vlist.push_back(state.INPT1);
281 changed.push_back(state.INPT1 != oldstate.INPT1);
282 alist.push_back(4); vlist.push_back(state.INPT4);
283 changed.push_back(state.INPT4 != oldstate.INPT4);
284 myLeftINPT->setList(alist, vlist, changed);
285 alist.clear(); vlist.clear(); changed.clear();
286 alist.push_back(2); vlist.push_back(state.INPT2);
287 changed.push_back(state.INPT2 != oldstate.INPT2);
288 alist.push_back(3); vlist.push_back(state.INPT3);
289 changed.push_back(state.INPT3 != oldstate.INPT3);
290 alist.push_back(5); vlist.push_back(state.INPT5);
291 changed.push_back(state.INPT5 != oldstate.INPT5);
292 myRightINPT->setList(alist, vlist, changed);
293
294 // Update TIA VBLANK bits
295 myINPTLatch->setState(riot.vblank(6), state.INPTLatch != oldstate.INPTLatch);
296 myINPTDump->setState(riot.vblank(7), state.INPTDump != oldstate.INPTDump);
297
298 // Update timer write registers
299 alist.clear(); vlist.clear(); changed.clear();
300 alist.push_back(kTim1TID); vlist.push_back(state.TIM1T);
301 changed.push_back(state.TIM1T != oldstate.TIM1T);
302 alist.push_back(kTim8TID); vlist.push_back(state.TIM8T);
303 changed.push_back(state.TIM8T != oldstate.TIM8T);
304 alist.push_back(kTim64TID); vlist.push_back(state.TIM64T);
305 changed.push_back(state.TIM64T != oldstate.TIM64T);
306 alist.push_back(kTim1024TID); vlist.push_back(state.T1024T);
307 changed.push_back(state.T1024T != oldstate.T1024T);
308 myTimWrite->setList(alist, vlist, changed);
309
310 // Update timer read registers
311 alist.clear(); vlist.clear(); changed.clear();
312 alist.push_back(0); vlist.push_back(state.INTIM);
313 changed.push_back(state.INTIM != oldstate.INTIM);
314 alist.push_back(0); vlist.push_back(state.TIMINT);
315 changed.push_back(state.TIMINT != oldstate.TIMINT);
316 alist.push_back(0); vlist.push_back(state.TIMCLKS);
317 changed.push_back(state.TIMCLKS != oldstate.TIMCLKS);
318 alist.push_back(0); vlist.push_back(state.INTIMCLKS);
319 changed.push_back(state.INTIMCLKS != oldstate.INTIMCLKS);
320 myTimRead->setList(alist, vlist, changed);
321
322 alist.clear(); vlist.clear(); changed.clear();
323 alist.push_back(0); vlist.push_back(state.TIMDIV);
324 changed.push_back(state.TIMDIV != oldstate.TIMDIV);
325 myTimDivider->setList(alist, vlist, changed);
326
327 // Console switches (inverted, since 'selected' in the UI
328 // means 'grounded' in the system)
329 myP0Diff->setSelectedIndex(riot.diffP0(), state.swchbReadBits[1] != oldstate.swchbReadBits[1]);
330 myP1Diff->setSelectedIndex(riot.diffP1(), state.swchbReadBits[0] != oldstate.swchbReadBits[0]);
331
332 bool devSettings = instance().settings().getBool("dev.settings");
333 myConsole->setText(instance().settings().getString(devSettings ? "dev.console" : "plr.console") == "7800" ? "Atari 7800" : "Atari 2600");
334 myConsole->setEditable(false, true);
335
336 myTVType->setSelectedIndex(riot.tvType(), state.swchbReadBits[4] != oldstate.swchbReadBits[4]);
337 myPause->setState(!riot.tvType(), state.swchbReadBits[4] != oldstate.swchbReadBits[4]);
338 mySelect->setState(!riot.select(), state.swchbReadBits[6] != oldstate.swchbReadBits[6]);
339 myReset->setState(!riot.reset(), state.swchbReadBits[7] != oldstate.swchbReadBits[7]);
340
341 myLeftControl->loadConfig();
342 myRightControl->loadConfig();
343
344 handleConsole();
345}
346
347// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
348void RiotWidget::handleCommand(CommandSender* sender, int cmd, int data, int id)
349{
350 int value = -1;
351 RiotDebug& riot = instance().debugger().riotDebug();
352
353 switch(cmd)
354 {
355 case DataGridWidget::kItemDataChangedCmd:
356 switch(id)
357 {
358 case kTimWriteID:
359 switch(myTimWrite->getSelectedAddr())
360 {
361 case kTim1TID:
362 riot.tim1T(myTimWrite->getSelectedValue());
363 break;
364 case kTim8TID:
365 riot.tim8T(myTimWrite->getSelectedValue());
366 break;
367 case kTim64TID:
368 riot.tim64T(myTimWrite->getSelectedValue());
369 break;
370 case kTim1024TID:
371 riot.tim1024T(myTimWrite->getSelectedValue());
372 break;
373 }
374 break;
375 }
376 break;
377
378 case ToggleWidget::kItemDataChangedCmd:
379 switch(id)
380 {
381 case kSWCHABitsID:
382 value = Debugger::get_bits(mySWCHAWriteBits->getState());
383 riot.swcha(value & 0xff);
384 break;
385 case kSWACNTBitsID:
386 value = Debugger::get_bits(mySWACNTBits->getState());
387 riot.swacnt(value & 0xff);
388 break;
389 case kSWCHBBitsID:
390 value = Debugger::get_bits(mySWCHBWriteBits->getState());
391 riot.swchb(value & 0xff);
392 break;
393 case kSWBCNTBitsID:
394 value = Debugger::get_bits(mySWBCNTBits->getState());
395 riot.swbcnt(value & 0xff);
396 break;
397 case kSWCHARBitsID:
398 {
399 value = Debugger::get_bits(mySWCHAReadBits->getState());
400 ControllerLowLevel lport(instance().console().leftController());
401 ControllerLowLevel rport(instance().console().rightController());
402 lport.setPin(Controller::DigitalPin::One, value & 0b00010000);
403 lport.setPin(Controller::DigitalPin::Two, value & 0b00100000);
404 lport.setPin(Controller::DigitalPin::Three, value & 0b01000000);
405 lport.setPin(Controller::DigitalPin::Four, value & 0b10000000);
406 rport.setPin(Controller::DigitalPin::One, value & 0b00000001);
407 rport.setPin(Controller::DigitalPin::Two, value & 0b00000010);
408 rport.setPin(Controller::DigitalPin::Three, value & 0b00000100);
409 rport.setPin(Controller::DigitalPin::Four, value & 0b00001000);
410 break;
411 }
412 }
413 break;
414
415 case CheckboxWidget::kCheckActionCmd:
416 switch(id)
417 {
418 case kSelectID:
419 riot.select(!mySelect->getState());
420 break;
421 case kResetID:
422 riot.reset(!myReset->getState());
423 break;
424 case kPauseID:
425 handleConsole();
426 break;
427 }
428 break;
429
430 case kP0DiffChanged:
431 riot.diffP0(myP0Diff->getSelectedTag().toString() != "b");
432 break;
433
434 case kP1DiffChanged:
435 riot.diffP1(myP1Diff->getSelectedTag().toString() != "b");
436 break;
437
438 case kTVTypeChanged:
439 handleConsole();
440 break;
441 }
442}
443
444// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
445ControllerWidget* RiotWidget::addControlWidget(GuiObject* boss, const GUI::Font& font,
446 int x, int y, Controller& controller)
447{
448 switch(controller.type())
449 {
450 case Controller::Type::AmigaMouse:
451 return new AmigaMouseWidget(boss, font, x, y, controller);
452 case Controller::Type::AtariMouse:
453 return new AtariMouseWidget(boss, font, x, y, controller);
454 case Controller::Type::AtariVox:
455 return new AtariVoxWidget(boss, font, x, y, controller);
456 case Controller::Type::BoosterGrip:
457 return new BoosterWidget(boss, font, x, y, controller);
458 case Controller::Type::Driving:
459 return new DrivingWidget(boss, font, x, y, controller);
460 case Controller::Type::Genesis:
461 return new GenesisWidget(boss, font, x, y, controller);
462 case Controller::Type::Joystick:
463 return new JoystickWidget(boss, font, x, y, controller);
464 case Controller::Type::Keyboard:
465 return new KeyboardWidget(boss, font, x, y, controller);
466// case Controller::Type::KidVid: // TODO - implement this
467// case Controller::Type::MindLink: // TODO - implement this
468 case Controller::Type::Paddles:
469 return new PaddleWidget(boss, font, x, y, controller);
470 case Controller::Type::SaveKey:
471 return new SaveKeyWidget(boss, font, x, y, controller);
472 case Controller::Type::TrakBall:
473 return new TrakBallWidget(boss, font, x, y, controller);
474 default:
475 return new NullControlWidget(boss, font, x, y, controller);
476 }
477}
478
479// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
480void RiotWidget::handleConsole()
481{
482 RiotDebug& riot = instance().debugger().riotDebug();
483 bool devSettings = instance().settings().getBool("dev.settings");
484 bool is7800 = instance().settings().getString(devSettings ? "dev.console" : "plr.console") == "7800";
485
486 myTVType->setEnabled(!is7800);
487 myPause->setEnabled(is7800);
488 if(is7800)
489 myTVType->setSelectedIndex(myPause->getState() ? 0 : 1);
490 else
491 myPause->setState(myTVType->getSelected() == 0);
492 riot.tvType(myTVType->getSelected());
493}
494