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 "bspf.hxx"
19
20#include "Cheat.hxx"
21#include "CheatManager.hxx"
22#include "CheckListWidget.hxx"
23#include "DialogContainer.hxx"
24#include "Dialog.hxx"
25#include "Font.hxx"
26#include "InputTextDialog.hxx"
27#include "OSystem.hxx"
28#include "Props.hxx"
29#include "Widget.hxx"
30
31#include "CheatCodeDialog.hxx"
32
33// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
34CheatCodeDialog::CheatCodeDialog(OSystem& osystem, DialogContainer& parent,
35 const GUI::Font& font)
36 : Dialog(osystem, parent, font, "Cheat codes")
37{
38 const int lineHeight = font.getLineHeight(),
39 fontWidth = font.getMaxCharWidth(),
40 buttonWidth = font.getStringWidth("Defaults") + 20,
41 buttonHeight = font.getLineHeight() + 4;
42 const int HBORDER = 10;
43 const int VBORDER = 10 + _th;
44 int xpos, ypos;
45 WidgetArray wid;
46 ButtonWidget* b;
47
48 // Set real dimensions
49 _w = 45 * fontWidth + HBORDER * 2;
50 _h = 11 * (lineHeight + 4) + VBORDER;
51
52 // List of cheats, with checkboxes to enable/disable
53 xpos = HBORDER; ypos = VBORDER;
54 myCheatList =
55 new CheckListWidget(this, font, xpos, ypos, _w - buttonWidth - HBORDER * 2 - 8,
56 _h - 2*buttonHeight - VBORDER);
57 myCheatList->setEditable(false);
58 wid.push_back(myCheatList);
59
60 xpos += myCheatList->getWidth() + 8; ypos = VBORDER;
61
62 b = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight,
63 "Add" + ELLIPSIS, kAddCheatCmd);
64 wid.push_back(b);
65 ypos += lineHeight + 8;
66
67 myEditButton =
68 new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight,
69 "Edit" + ELLIPSIS, kEditCheatCmd);
70 wid.push_back(myEditButton);
71 ypos += lineHeight + 8;
72
73 myRemoveButton =
74 new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight,
75 "Remove", kRemCheatCmd);
76 wid.push_back(myRemoveButton);
77 ypos += lineHeight + 8 * 3;
78
79 b = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight,
80 "One shot" + ELLIPSIS, kAddOneShotCmd);
81 wid.push_back(b);
82
83 // Inputbox which will pop up when adding/editing a cheat
84 StringList labels;
85 labels.push_back("Name ");
86 labels.push_back("Code (hex) ");
87 myCheatInput = make_unique<InputTextDialog>(this, font, labels, "Cheat code");
88 myCheatInput->setTarget(this);
89
90 // Add filtering for each textfield
91 EditableWidget::TextFilter f0 = [](char c) {
92 return isprint(c) && c != '\"' && c != ':';
93 };
94 myCheatInput->setTextFilter(f0, 0);
95
96 EditableWidget::TextFilter f1 = [](char c) {
97 return (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
98 };
99 myCheatInput->setTextFilter(f1, 1);
100
101 addToFocusList(wid);
102
103 // Add OK and Cancel buttons
104 wid.clear();
105 addOKCancelBGroup(wid, font);
106 addBGroupToFocusList(wid);
107}
108
109// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
110CheatCodeDialog::~CheatCodeDialog()
111{
112}
113
114// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
115void CheatCodeDialog::loadConfig()
116{
117 // Load items from CheatManager
118 // Note that the items are always in the same order/number as given in
119 // the CheatManager, so the arrays will be one-to-one
120 StringList l;
121 BoolArray b;
122
123 const CheatList& list = instance().cheat().list();
124 for(const auto& c: list)
125 {
126 l.push_back(c->name());
127 b.push_back(bool(c->enabled()));
128 }
129 myCheatList->setList(l, b);
130
131 // Redraw the list, auto-selecting the first item if possible
132 myCheatList->setSelected(l.size() > 0 ? 0 : -1);
133
134 bool enabled = (list.size() > 0);
135 myEditButton->setEnabled(enabled);
136 myRemoveButton->setEnabled(enabled);
137}
138
139// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
140void CheatCodeDialog::saveConfig()
141{
142 // Inspect checkboxes for enable/disable codes
143 const CheatList& list = instance().cheat().list();
144 for(uInt32 i = 0; i < myCheatList->getList().size(); ++i)
145 {
146 if(myCheatList->getState(i))
147 list[i]->enable();
148 else
149 list[i]->disable();
150 }
151}
152
153// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
154void CheatCodeDialog::addCheat()
155{
156 myCheatInput->show(); // Center input dialog over entire screen
157 myCheatInput->setText("", 0);
158 myCheatInput->setText("", 1);
159 myCheatInput->setMessage("");
160 myCheatInput->setFocus(0);
161 myCheatInput->setEmitSignal(kCheatAdded);
162}
163
164// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
165void CheatCodeDialog::editCheat()
166{
167 int idx = myCheatList->getSelected();
168 if(idx < 0)
169 return;
170
171 const CheatList& list = instance().cheat().list();
172 const string& name = list[idx]->name();
173 const string& code = list[idx]->code();
174
175 myCheatInput->show(); // Center input dialog over entire screen
176 myCheatInput->setText(name, 0);
177 myCheatInput->setText(code, 1);
178 myCheatInput->setMessage("");
179 myCheatInput->setFocus(1);
180 myCheatInput->setEmitSignal(kCheatEdited);
181}
182
183// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
184void CheatCodeDialog::removeCheat()
185{
186 instance().cheat().remove(myCheatList->getSelected());
187 loadConfig(); // reload the cheat list
188}
189
190// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
191void CheatCodeDialog::addOneShotCheat()
192{
193 myCheatInput->show(); // Center input dialog over entire screen
194 myCheatInput->setText("One-shot cheat", 0);
195 myCheatInput->setText("", 1);
196 myCheatInput->setMessage("");
197 myCheatInput->setFocus(1);
198 myCheatInput->setEmitSignal(kOneShotCheatAdded);
199}
200
201// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
202void CheatCodeDialog::handleCommand(CommandSender* sender, int cmd,
203 int data, int id)
204{
205 switch(cmd)
206 {
207 case GuiObject::kOKCmd:
208 saveConfig();
209 close();
210 break;
211
212 case GuiObject::kCloseCmd:
213 close();
214 break;
215
216 case ListWidget::kDoubleClickedCmd:
217 editCheat();
218 break;
219
220 case kAddCheatCmd:
221 addCheat();
222 break;
223
224 case kEditCheatCmd:
225 editCheat();
226 break;
227
228 case kCheatAdded:
229 {
230 const string& name = myCheatInput->getResult(0);
231 const string& code = myCheatInput->getResult(1);
232 if(instance().cheat().isValidCode(code))
233 {
234 myCheatInput->close();
235 instance().cheat().add(name, code);
236 loadConfig(); // show changes onscreen
237 }
238 else
239 myCheatInput->setMessage("Invalid code");
240 break;
241 }
242
243 case kCheatEdited:
244 {
245 const string& name = myCheatInput->getResult(0);
246 const string& code = myCheatInput->getResult(1);
247 bool enable = myCheatList->getSelectedState();
248 int idx = myCheatList->getSelected();
249 if(instance().cheat().isValidCode(code))
250 {
251 myCheatInput->close();
252 instance().cheat().add(name, code, enable, idx);
253 loadConfig(); // show changes onscreen
254 }
255 else
256 myCheatInput->setMessage("Invalid code");
257 break;
258 }
259
260 case kRemCheatCmd:
261 removeCheat();
262 break;
263
264 case kAddOneShotCmd:
265 addOneShotCheat();
266 break;
267
268 case kOneShotCheatAdded:
269 {
270 const string& name = myCheatInput->getResult(0);
271 const string& code = myCheatInput->getResult(1);
272 if(instance().cheat().isValidCode(code))
273 {
274 myCheatInput->close();
275 instance().cheat().addOneShot(name, code);
276 }
277 else
278 myCheatInput->setMessage("Invalid code");
279 break;
280 }
281
282 default:
283 Dialog::handleCommand(sender, cmd, data, 0);
284 break;
285 }
286}
287