JSON-based controller descriptions
[lsnes.git] / src / platform / wxwidgets / editor-macro.cpp
blobcaa97b6c3187e8ab7337f6a21c6c8f0abfc6bec0
1 #include "core/controller.hpp"
2 #include "core/command.hpp"
3 #include "core/dispatch.hpp"
4 #include "core/mainloop.hpp"
5 #include "core/moviedata.hpp"
6 #include "core/project.hpp"
7 #include "library/zip.hpp"
8 #include "library/minmax.hpp"
9 #include "library/json.hpp"
11 #include "platform/wxwidgets/platform.hpp"
12 #include "platform/wxwidgets/loadsave.hpp"
14 #include <wx/wx.h>
15 #include <wx/event.h>
16 #include <wx/control.h>
17 #include <wx/combobox.h>
19 namespace
21 std::map<unsigned, const port_controller*> get_controller_set()
23 std::map<unsigned, const port_controller*> r;
24 const port_type_set& s = controls.get_blank().porttypes();
25 for(unsigned i = 0; i < s.number_of_controllers(); i++) {
26 auto g = s.lcid_to_pcid(i);
27 if(g.first < 0)
28 continue;
29 port_controller_set* pcs = s.port_type(g.first).controller_info;
30 if(g.second >= pcs->controllers.size())
31 continue;
32 r[i] = &pcs->controllers[g.second];
34 return r;
37 std::string summarize_controller(unsigned lcid, const JSON::node& c)
39 std::ostringstream s;
40 bool first = true;
41 uint64_t acnt = 0;
42 s << "#" << (lcid + 1) << " [";
43 for(auto i : c) {
44 if(i.type() == JSON::number)
45 acnt = max(acnt, i.as_uint() + 1);
46 if(i.type() != JSON::string)
47 continue;
48 if(!first)
49 s << ", ";
50 first = false;
51 s << i.as_string8();
53 if(acnt) {
54 if(!first)
55 s << ", ";
56 if(acnt == 1)
57 s << "1 analog";
58 else
59 s << acnt << " analogs";
61 s << "]";
62 return s.str();
65 class wxeditor_macro_1 : public wxDialog
67 public:
68 wxeditor_macro_1(wxWindow* parent, const std::string& title, const controller_macro& m);
69 void on_ok(wxCommandEvent& e);
70 void on_cancel(wxCommandEvent& e);
71 void on_macro_edit(wxCommandEvent& e);
72 controller_macro get_macro() { return curmacro; }
73 private:
74 std::vector<wxCheckBox*> enabled;
75 std::vector<wxTextCtrl*> macros;
76 wxButton* okbutton;
77 wxButton* cancelbutton;
78 wxRadioButton* rb_overwrite;
79 wxRadioButton* rb_or;
80 wxRadioButton* rb_xor;
81 controller_macro curmacro;
82 bool constructing;
85 wxeditor_macro_1::wxeditor_macro_1(wxWindow* parent, const std::string& title, const controller_macro& m)
86 : wxDialog(parent, wxID_ANY, towxstring(title), wxDefaultPosition, wxSize(-1, -1))
88 constructing = true;
89 curmacro = m;
90 Centre();
91 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
92 SetSizer(top_s);
94 size_t ctrlsize = 0;
95 for(auto i : curmacro.macros)
96 ctrlsize = max(ctrlsize, (size_t)(i.first + 1));
97 enabled.resize(ctrlsize);
98 macros.resize(ctrlsize);
99 for(auto i : curmacro.macros) {
100 top_s->Add(new wxStaticText(this, wxID_ANY, towxstring(summarize_controller(i.first,
101 i.second._descriptor))), 0, wxGROW);
102 wxBoxSizer* tmp = new wxBoxSizer(wxHORIZONTAL);
103 tmp->Add(enabled[i.first] = new wxCheckBox(this, wxID_ANY, wxT("Enabled")), 0, wxGROW);
104 enabled[i.first]->SetValue(curmacro.macros.count(i.first) &&
105 curmacro.macros[i.first].enabled);
106 tmp->Add(macros[i.first] = new wxTextCtrl(this, wxID_ANY,
107 towxstring(curmacro.macros.count(i.first) ? curmacro.macros[i.first].orig : ""),
108 wxDefaultPosition, wxSize(400, -1)), 0, wxGROW);
109 macros[i.first]->Connect(wxEVT_COMMAND_TEXT_UPDATED,
110 wxCommandEventHandler(wxeditor_macro_1::on_macro_edit), NULL, this);
111 top_s->Add(tmp, 1, wxGROW);
114 wxBoxSizer* tmp2 = new wxBoxSizer(wxHORIZONTAL);
115 rb_overwrite = new wxRadioButton(this, wxID_HIGHEST + 1, wxT("Overwrite"), wxDefaultPosition,
116 wxDefaultSize, wxRB_GROUP);
117 rb_or = new wxRadioButton(this, wxID_HIGHEST + 1, wxT("OR"));
118 rb_xor = new wxRadioButton(this, wxID_HIGHEST + 1, wxT("XOR"));
119 tmp2->Add(rb_overwrite, 0, wxGROW);
120 tmp2->Add(rb_or, 0, wxGROW);
121 tmp2->Add(rb_xor, 0, wxGROW);
122 top_s->Add(tmp2, 0, wxGROW);
124 switch(curmacro.amode) {
125 case controller_macro_data::AM_OVERWRITE: rb_overwrite->SetValue(true); break;
126 case controller_macro_data::AM_OR: rb_or->SetValue(true); break;
127 case controller_macro_data::AM_XOR: rb_xor->SetValue(true); break;
130 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
131 pbutton_s->AddStretchSpacer();
132 pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
133 pbutton_s->Add(cancelbutton = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW);
134 okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
135 wxCommandEventHandler(wxeditor_macro_1::on_ok), NULL, this);
136 cancelbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
137 wxCommandEventHandler(wxeditor_macro_1::on_cancel), NULL, this);
138 top_s->Add(pbutton_s, 0, wxGROW);
140 top_s->SetSizeHints(this);
141 Fit();
143 constructing = false;
144 Show();
147 void wxeditor_macro_1::on_ok(wxCommandEvent& e)
149 controller_macro m;
150 if(rb_overwrite->GetValue()) m.amode = controller_macro_data::AM_OVERWRITE;
151 if(rb_or->GetValue()) m.amode = controller_macro_data::AM_OR;
152 if(rb_xor->GetValue()) m.amode = controller_macro_data::AM_XOR;
153 try {
154 for(auto i : curmacro.macros) {
155 if(!macros[i.first])
156 continue;
157 m.macros[i.first] = controller_macro_data(tostdstring(macros[i.first]->GetValue()),
158 curmacro.macros[i.first].get_descriptor(), i.first);
159 m.macros[i.first].enabled = enabled[i.first]->GetValue();
161 } catch(std::exception& e) {
162 wxMessageBox(towxstring(e.what()), _T("Error parsing macro"), wxICON_EXCLAMATION | wxOK,
163 this);
164 return;
166 curmacro = m;
167 EndModal(wxID_OK);
170 void wxeditor_macro_1::on_cancel(wxCommandEvent& e)
172 EndModal(wxID_CANCEL);
175 void wxeditor_macro_1::on_macro_edit(wxCommandEvent& e)
177 if(constructing)
178 return;
179 bool ret = true;
180 auto c = get_controller_set();
181 for(auto i : curmacro.macros) {
182 if(!controller_macro_data::syntax_check(tostdstring(macros[i.first]->GetValue()).c_str(),
183 curmacro.macros[i.first].get_descriptor()))
184 ret = false;
186 okbutton->Enable(ret);
190 class wxeditor_macro : public wxDialog
192 public:
193 wxeditor_macro(wxWindow* parent);
194 bool ShouldPreventAppExit() const;
195 void on_close(wxCommandEvent& e);
196 void on_change(wxCommandEvent& e);
197 void on_add(wxCommandEvent& e);
198 void on_edit(wxCommandEvent& e);
199 void on_rename(wxCommandEvent& e);
200 void on_delete(wxCommandEvent& e);
201 void on_load(wxCommandEvent& e);
202 void on_save(wxCommandEvent& e);
203 private:
204 bool do_edit(const std::string& mname, controller_macro& m);
205 void update();
206 wxButton* closebutton;
207 wxButton* addbutton;
208 wxButton* editbutton;
209 wxButton* renamebutton;
210 wxButton* deletebutton;
211 wxButton* savebutton;
212 wxButton* loadbutton;
213 wxListBox* macros;
214 std::vector<std::string> macronames;
217 bool wxeditor_macro::ShouldPreventAppExit() const
219 return false;
222 wxeditor_macro::wxeditor_macro(wxWindow* parent)
223 : wxDialog(parent, wxID_ANY, wxT("lsnes: Edit macros"), wxDefaultPosition, wxSize(-1, -1))
225 Centre();
226 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
227 SetSizer(top_s);
229 top_s->Add(macros = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(300, 400), 0, NULL,
230 wxLB_SINGLE), 1, wxGROW);
231 macros->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
232 wxCommandEventHandler(wxeditor_macro::on_change), NULL, this);
234 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
235 pbutton_s->Add(addbutton = new wxButton(this, wxID_ANY, wxT("Add")), 0, wxGROW);
236 pbutton_s->Add(editbutton = new wxButton(this, wxID_ANY, wxT("Edit")), 0, wxGROW);
237 pbutton_s->Add(renamebutton = new wxButton(this, wxID_ANY, wxT("Rename")), 0, wxGROW);
238 pbutton_s->Add(deletebutton = new wxButton(this, wxID_ANY, wxT("Delete")), 0, wxGROW);
239 pbutton_s->Add(savebutton = new wxButton(this, wxID_ANY, wxT("Save")), 0, wxGROW);
240 pbutton_s->Add(loadbutton = new wxButton(this, wxID_ANY, wxT("Load")), 0, wxGROW);
241 pbutton_s->AddStretchSpacer();
242 pbutton_s->Add(closebutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
243 addbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
244 wxCommandEventHandler(wxeditor_macro::on_add), NULL, this);
245 editbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
246 wxCommandEventHandler(wxeditor_macro::on_edit), NULL, this);
247 renamebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
248 wxCommandEventHandler(wxeditor_macro::on_rename), NULL, this);
249 deletebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
250 wxCommandEventHandler(wxeditor_macro::on_delete), NULL, this);
251 savebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
252 wxCommandEventHandler(wxeditor_macro::on_save), NULL, this);
253 loadbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
254 wxCommandEventHandler(wxeditor_macro::on_load), NULL, this);
255 closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
256 wxCommandEventHandler(wxeditor_macro::on_close), NULL, this);
257 top_s->Add(pbutton_s, 0, wxGROW);
259 top_s->SetSizeHints(this);
260 Fit();
262 update();
263 Show();
266 void wxeditor_macro::update()
268 std::set<std::string> macro_list = controls.enumerate_macro();
269 std::string current;
270 int sel = macros->GetSelection();
271 int selpos = -1;
272 int idx = 0;
273 if(sel != wxNOT_FOUND)
274 current = macronames[sel];
275 macros->Clear();
276 macronames.clear();
277 for(auto i : macro_list) {
278 macronames.push_back(i);
279 macros->Append(towxstring(i));
280 if(i == current)
281 selpos = idx;
282 idx++;
284 if(!macro_list.empty()) {
285 if(selpos >= 0)
286 macros->SetSelection(selpos);
287 if(sel < idx)
288 macros->SetSelection(sel);
289 else
290 macros->SetSelection(idx - 1);
292 wxCommandEvent e;
293 on_change(e);
296 void wxeditor_macro::on_close(wxCommandEvent& e)
298 EndModal(wxID_OK);
301 void wxeditor_macro::on_change(wxCommandEvent& e)
303 int sel = macros->GetSelection();
304 editbutton->Enable(sel != wxNOT_FOUND);
305 deletebutton->Enable(sel != wxNOT_FOUND);
306 savebutton->Enable(sel != wxNOT_FOUND);
309 void wxeditor_macro::on_delete(wxCommandEvent& e)
311 int sel = macros->GetSelection();
312 if(sel == wxNOT_FOUND)
313 return;
314 std::string macro = macronames[sel];
315 controls.erase_macro(macro);
316 update();
319 void wxeditor_macro::on_add(wxCommandEvent& e)
321 try {
322 std::string mname = pick_text(this, "Name new macro", "Enter name for the new macro:", "");
323 if(mname == "")
324 return;
325 controller_macro _macro;
326 _macro.amode = controller_macro_data::AM_XOR;
327 auto c = get_controller_set();
328 for(auto i : c) {
329 _macro.macros[i.first] = controller_macro_data("",
330 controller_macro_data::make_descriptor(*i.second), i.first);
331 _macro.macros[i.first].enabled = false;
333 if(do_edit("", _macro))
334 controls.set_macro(mname, _macro);
335 } catch(canceled_exception& e) {
336 } catch(std::exception& e) {
337 wxMessageBox(towxstring(e.what()), _T("Error creating macro"), wxICON_EXCLAMATION | wxOK, this);
339 update();
342 void wxeditor_macro::on_edit(wxCommandEvent& e)
344 int sel = macros->GetSelection();
345 if(sel == wxNOT_FOUND)
346 return;
347 std::string macro = macronames[sel];
348 controller_macro _macro;
349 try {
350 _macro = controls.get_macro(macro);
351 } catch(...) {
352 return;
354 if(do_edit(macro, _macro))
355 controls.set_macro(macro, _macro);
358 void wxeditor_macro::on_rename(wxCommandEvent& e)
360 int sel = macros->GetSelection();
361 if(sel == wxNOT_FOUND)
362 return;
363 std::string macro = macronames[sel];
364 try {
365 std::string mname = pick_text(this, "Rename macro", "Enter new name for the macro:", "");
366 if(mname == "")
367 return;
368 controls.rename_macro(macro, mname);
369 } catch(canceled_exception& e) {
370 } catch(std::exception& e) {
371 wxMessageBox(towxstring(e.what()), _T("Error renaming macro"), wxICON_EXCLAMATION | wxOK, this);
373 update();
376 void wxeditor_macro::on_load(wxCommandEvent& e)
378 try {
379 std::string mname = pick_text(this, "Name new macro", "Enter name for the new macro:", "");
380 if(mname == "")
381 return;
382 std::string file = choose_file_load(this, "Load macro from", project_otherpath(), filetype_macro);
383 std::vector<char> contents = read_file_relative(file, "");
384 controller_macro m(JSON::node(std::string(contents.begin(), contents.end())));
385 controls.set_macro(mname, m);
386 } catch(canceled_exception& e) {
387 } catch(std::exception& e) {
388 wxMessageBox(towxstring(e.what()), _T("Error loading macro"), wxICON_EXCLAMATION | wxOK, this);
390 update();
393 void wxeditor_macro::on_save(wxCommandEvent& e)
395 int sel = macros->GetSelection();
396 if(sel == wxNOT_FOUND)
397 return;
398 std::string macro = macronames[sel];
399 controller_macro* _macro;
400 try {
401 _macro = &controls.get_macro(macro);
402 } catch(...) {
403 return;
405 std::string mdata = _macro->serialize().serialize();
406 //Okay, have the macro data, now prompt for file and save.
407 try {
408 std::string tfile = choose_file_save(this, "Save macro to", project_otherpath(), filetype_macro);
409 std::ofstream f(tfile);
410 f << mdata;
411 if(!f)
412 wxMessageBox(towxstring("Error saving macro"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
413 } catch(canceled_exception& e) {
417 bool wxeditor_macro::do_edit(const std::string& mname, controller_macro& m)
419 wxeditor_macro_1* editor;
420 bool ret;
422 try {
423 std::string title;
424 if(mname != "")
425 title = "Editing macro " + mname;
426 else
427 title = "Create a new macro";
428 editor = new wxeditor_macro_1(this, title, m);
429 ret = (editor->ShowModal() == wxID_OK);
430 if(ret)
431 m = editor->get_macro();
432 } catch(...) {
434 editor->Destroy();
435 return ret;
438 void wxeditor_macro_display(wxWindow* parent)
440 modal_pause_holder hld;
441 wxDialog* editor;
442 try {
443 editor = new wxeditor_macro(parent);
444 editor->ShowModal();
445 } catch(...) {
447 editor->Destroy();