Rewind movie to beginning function
[lsnes.git] / src / plat-wxwidgets / mainwindow.cpp
blob6fdf790db2ccc613cb7890fdb9971e109fb06ca7
1 #include "lsnes.hpp"
3 #include "core/command.hpp"
4 #include "core/controller.hpp"
5 #include "core/controllerframe.hpp"
6 #include "core/dispatch.hpp"
7 #include "core/framebuffer.hpp"
8 #include "core/framerate.hpp"
9 #include "core/lua.hpp"
10 #include "core/mainloop.hpp"
11 #include "core/memorywatch.hpp"
12 #include "core/misc.hpp"
13 #include "core/moviedata.hpp"
14 #include "core/window.hpp"
15 #include "core/zip.hpp"
17 #include <vector>
18 #include <string>
20 #include "plat-wxwidgets/menu_dump.hpp"
21 #include "plat-wxwidgets/platform.hpp"
22 #include "plat-wxwidgets/window_mainwindow.hpp"
24 #define MAXCONTROLLERS MAX_PORTS * MAX_CONTROLLERS_PER_PORT
26 extern "C"
28 #ifndef UINT64_C
29 #define UINT64_C(val) val##ULL
30 #endif
31 #include <libswscale/swscale.h>
34 enum
36 wxID_PAUSE = wxID_HIGHEST + 1,
37 wxID_FRAMEADVANCE,
38 wxID_SUBFRAMEADVANCE,
39 wxID_NEXTPOLL,
40 wxID_ERESET,
41 wxID_AUDIO_ENABLED,
42 wxID_SHOW_AUDIO_STATUS,
43 wxID_AUDIODEV_FIRST,
44 wxID_AUDIODEV_LAST = wxID_AUDIODEV_FIRST + 255,
45 wxID_SAVE_STATE,
46 wxID_SAVE_MOVIE,
47 wxID_LOAD_STATE,
48 wxID_LOAD_STATE_RO,
49 wxID_LOAD_STATE_RW,
50 wxID_LOAD_STATE_P,
51 wxID_LOAD_MOVIE,
52 wxID_RUN_SCRIPT,
53 wxID_RUN_LUA,
54 wxID_EVAL_LUA,
55 wxID_SAVE_SCREENSHOT,
56 wxID_READONLY_MODE,
57 wxID_EDIT_AUTHORS,
58 wxID_AUTOHOLD_FIRST,
59 wxID_AUTOHOLD_LAST = wxID_AUTOHOLD_FIRST + 127,
60 wxID_EDIT_AXES,
61 wxID_EDIT_SETTINGS,
62 wxID_EDIT_KEYBINDINGS,
63 wxID_EDIT_ALIAS,
64 wxID_EDIT_MEMORYWATCH,
65 wxID_SAVE_MEMORYWATCH,
66 wxID_LOAD_MEMORYWATCH,
67 wxID_DUMP_FIRST,
68 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
69 wxID_REWIND_MOVIE
73 namespace
75 unsigned char* screen_buffer;
76 uint32_t old_width;
77 uint32_t old_height;
78 bool main_window_dirty;
79 struct thread* emulation_thread;
81 wxString getname()
83 std::string windowname = "lsnes rr" + lsnes_version + "[" + bsnes_core_version + "]";
84 return towxstring(windowname);
87 struct emu_args
89 struct loaded_rom* rom;
90 struct moviefile* initial;
91 bool load_has_to_succeed;
94 void* emulator_main(void* _args)
96 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
97 try {
98 our_rom = args->rom;
99 struct moviefile* movie = args->initial;
100 bool has_to_succeed = args->load_has_to_succeed;
101 platform::flush_command_queue();
102 main_loop(*our_rom, *movie, has_to_succeed);
103 signal_program_exit();
104 } catch(std::bad_alloc& e) {
105 OOM_panic();
106 } catch(std::exception& e) {
107 messages << "FATAL: " << e.what() << std::endl;
108 platform::fatal_error();
110 return NULL;
113 void join_emulator_thread()
115 emulation_thread->join();
118 void handle_wx_mouse(wxMouseEvent& e)
120 static uint32_t mask = 0;
121 if(e.LeftDown())
122 mask |= 1;
123 if(e.LeftUp())
124 mask &= ~1;
125 if(e.MiddleDown())
126 mask |= 2;
127 if(e.MiddleUp())
128 mask &= ~2;
129 if(e.RightDown())
130 mask |= 4;
131 if(e.RightUp())
132 mask &= ~4;
133 send_mouse_click(e.GetX(), e.GetY(), mask);
136 bool is_readonly_mode()
138 bool ret;
139 runemufn([&ret]() { ret = movb.get_movie().readonly_mode(); });
140 return ret;
143 bool UI_get_autohold(unsigned pid, unsigned idx)
145 bool ret;
146 runemufn([&ret, pid, idx]() { ret = controls.autohold(pid, idx); });
147 return ret;
150 void UI_change_autohold(unsigned pid, unsigned idx, bool newstate)
152 runemufn([pid, idx, newstate]() { controls.autohold(pid, idx, newstate); });
155 int UI_controller_index_by_logical(unsigned lid)
157 int ret;
158 runemufn([&ret, lid]() { ret = controls.lcid_to_pcid(lid); });
159 return ret;
162 int UI_button_id(unsigned pcid, unsigned lidx)
164 int ret;
165 runemufn([&ret, pcid, lidx]() { ret = controls.button_id(pcid, lidx); });
166 return ret;
169 class controller_autohold_menu : public wxMenu
171 public:
172 controller_autohold_menu(unsigned lid, enum devicetype_t dtype);
173 void change_type();
174 bool is_dummy();
175 void on_select(wxCommandEvent& e);
176 void update(unsigned pid, unsigned ctrlnum, bool newstate);
177 private:
178 unsigned our_lid;
179 wxMenuItem* entries[MAX_LOGICAL_BUTTONS];
180 unsigned enabled_entries;
183 class autohold_menu : public wxMenu
185 public:
186 autohold_menu(wxwin_mainwindow* win);
187 void reconfigure();
188 void on_select(wxCommandEvent& e);
189 void update(unsigned pid, unsigned ctrlnum, bool newstate);
190 private:
191 controller_autohold_menu* menus[MAXCONTROLLERS];
192 wxMenuItem* entries[MAXCONTROLLERS];
195 class sound_select_menu : public wxMenu
197 public:
198 sound_select_menu(wxwin_mainwindow* win);
199 void update(const std::string& dev);
200 void on_select(wxCommandEvent& e);
201 private:
202 std::map<std::string, wxMenuItem*> items;
203 std::map<int, std::string> devices;
206 class sound_select_menu;
208 class broadcast_listener : public information_dispatch
210 public:
211 broadcast_listener(wxwin_mainwindow* win);
212 void set_sound_select(sound_select_menu* sdev);
213 void set_autohold_menu(autohold_menu* ah);
214 void on_sound_unmute(bool unmute) throw();
215 void on_sound_change(const std::string& dev) throw();
216 void on_mode_change(bool readonly) throw();
217 void on_autohold_update(unsigned pid, unsigned ctrlnum, bool newstate);
218 void on_autohold_reconfigure();
219 private:
220 wxwin_mainwindow* mainw;
221 sound_select_menu* sounddev;
222 autohold_menu* ahmenu;
225 controller_autohold_menu::controller_autohold_menu(unsigned lid, enum devicetype_t dtype)
227 platform::set_modal_pause(true);
228 our_lid = lid;
229 for(unsigned i = 0; i < MAX_LOGICAL_BUTTONS; i++) {
230 int id = wxID_AUTOHOLD_FIRST + MAX_LOGICAL_BUTTONS * lid + i;
231 entries[i] = AppendCheckItem(id, towxstring(get_logical_button_name(i)));
233 change_type();
234 platform::set_modal_pause(false);
237 void controller_autohold_menu::change_type()
239 enabled_entries = 0;
240 int pid = controls.lcid_to_pcid(our_lid);
241 for(unsigned i = 0; i < MAX_LOGICAL_BUTTONS; i++) {
242 int pidx = -1;
243 if(pid >= 0)
244 pidx = controls.button_id(pid, i);
245 if(pidx >= 0) {
246 entries[i]->Check(pid > 0 && UI_get_autohold(pid, pidx));
247 entries[i]->Enable();
248 enabled_entries++;
249 } else {
250 entries[i]->Check(false);
251 entries[i]->Enable(false);
256 bool controller_autohold_menu::is_dummy()
258 return !enabled_entries;
261 void controller_autohold_menu::on_select(wxCommandEvent& e)
263 int x = e.GetId();
264 if(x < wxID_AUTOHOLD_FIRST + our_lid * MAX_LOGICAL_BUTTONS || x >= wxID_AUTOHOLD_FIRST *
265 (our_lid + 1) * MAX_LOGICAL_BUTTONS) {
266 return;
268 unsigned lidx = (x - wxID_AUTOHOLD_FIRST) % MAX_LOGICAL_BUTTONS;
269 platform::set_modal_pause(true);
270 int pid = controls.lcid_to_pcid(our_lid);
271 if(pid < 0 || !entries[lidx]) {
272 platform::set_modal_pause(false);
273 return;
275 int pidx = controls.button_id(pid, lidx);
276 if(pidx < 0) {
277 platform::set_modal_pause(false);
278 return;
280 //Autohold change on pid=pid, ctrlindx=idx, state
281 bool newstate = entries[lidx]->IsChecked();
282 UI_change_autohold(pid, pidx, newstate);
283 platform::set_modal_pause(false);
286 void controller_autohold_menu::update(unsigned pid, unsigned ctrlnum, bool newstate)
288 platform::set_modal_pause(true);
289 int pid2 = UI_controller_index_by_logical(our_lid);
290 if(pid2 < 0 || static_cast<unsigned>(pid) != pid2) {
291 platform::set_modal_pause(false);
292 return;
294 for(unsigned i = 0; i < MAX_LOGICAL_BUTTONS; i++) {
295 int idx = UI_button_id(pid2, i);
296 if(idx < 0 || static_cast<unsigned>(idx) != ctrlnum)
297 continue;
298 entries[i]->Check(newstate);
300 platform::set_modal_pause(false);
304 autohold_menu::autohold_menu(wxwin_mainwindow* win)
306 for(unsigned i = 0; i < MAXCONTROLLERS; i++) {
307 std::ostringstream str;
308 str << "Controller #&" << (i + 1);
309 menus[i] = new controller_autohold_menu(i, DT_NONE);
310 entries[i] = AppendSubMenu(menus[i], towxstring(str.str()));
311 entries[i]->Enable(!menus[i]->is_dummy());
313 win->Connect(wxID_AUTOHOLD_FIRST, wxID_AUTOHOLD_LAST, wxEVT_COMMAND_MENU_SELECTED,
314 wxCommandEventHandler(autohold_menu::on_select), NULL, this);
315 reconfigure();
318 void autohold_menu::reconfigure()
320 platform::set_modal_pause(true);
321 for(unsigned i = 0; i < MAXCONTROLLERS; i++) {
322 menus[i]->change_type();
323 entries[i]->Enable(!menus[i]->is_dummy());
325 platform::set_modal_pause(false);
328 void autohold_menu::on_select(wxCommandEvent& e)
330 for(unsigned i = 0; i < MAXCONTROLLERS; i++)
331 menus[i]->on_select(e);
334 void autohold_menu::update(unsigned pid, unsigned ctrlnum, bool newstate)
336 for(unsigned i = 0; i < MAXCONTROLLERS; i++)
337 menus[i]->update(pid, ctrlnum, newstate);
340 sound_select_menu::sound_select_menu(wxwin_mainwindow* win)
342 std::string curdev = platform::get_sound_device();
343 int j = wxID_AUDIODEV_FIRST;
344 for(auto i : platform::get_sound_devices()) {
345 items[i.first] = AppendRadioItem(j, towxstring(i.first + "(" + i.second + ")"));
346 devices[j] = i.first;
347 if(i.first == curdev)
348 items[i.first]->Check();
349 win->Connect(j, wxEVT_COMMAND_MENU_SELECTED,
350 wxCommandEventHandler(sound_select_menu::on_select), NULL, this);
351 j++;
355 void sound_select_menu::update(const std::string& dev)
357 items[dev]->Check();
360 void _do_sound_select(void* args)
362 std::string* x = reinterpret_cast<std::string*>(args);
363 platform::set_sound_device(*x);
366 void sound_select_menu::on_select(wxCommandEvent& e)
368 std::string devname = devices[e.GetId()];
369 if(devname != "")
370 runemufn([devname]() { platform::set_sound_device(devname); });
373 broadcast_listener::broadcast_listener(wxwin_mainwindow* win)
374 : information_dispatch("wxwidgets-broadcast-listener")
376 mainw = win;
379 void broadcast_listener::set_sound_select(sound_select_menu* sdev)
381 sounddev = sdev;
384 void broadcast_listener::set_autohold_menu(autohold_menu* ah)
386 ahmenu = ah;
389 void broadcast_listener::on_sound_unmute(bool unmute) throw()
391 runuifun([unmute, mainw]() { mainw->menu_check(wxID_AUDIO_ENABLED, unmute); });
394 void broadcast_listener::on_sound_change(const std::string& dev) throw()
396 runuifun([dev, sounddev]() { if(sounddev) sounddev->update(dev); });
399 void broadcast_listener::on_mode_change(bool readonly) throw()
401 runuifun([readonly, mainw]() { mainw->menu_check(wxID_READONLY_MODE, readonly); });
404 void broadcast_listener::on_autohold_update(unsigned pid, unsigned ctrlnum, bool newstate)
406 runuifun([pid, ctrlnum, newstate, ahmenu]() { ahmenu->update(pid, ctrlnum, newstate); });
409 void broadcast_listener::on_autohold_reconfigure()
411 runuifun([ahmenu]() { ahmenu->reconfigure(); });
414 void _set_readonly(void* args)
416 bool s = *reinterpret_cast<bool*>(args);
417 movb.get_movie().readonly_mode(s);
418 if(!s)
419 lua_callback_do_readwrite();
420 update_movie_state();
423 struct keyentry_mod_data
425 wxCheckBox* pressed;
426 wxCheckBox* unmasked;
427 unsigned tmpflags;
430 class wxdialog_keyentry : public wxDialog
432 public:
433 wxdialog_keyentry(wxWindow* parent);
434 void on_change_setting(wxCommandEvent& e);
435 void on_ok(wxCommandEvent& e);
436 void on_cancel(wxCommandEvent& e);
437 std::string getkey();
438 private:
439 std::map<std::string, keyentry_mod_data> modifiers;
440 wxComboBox* mainkey;
441 wxButton* ok;
442 wxButton* cancel;
445 wxdialog_keyentry::wxdialog_keyentry(wxWindow* parent)
446 : wxDialog(parent, wxID_ANY, wxT("Specify key"), wxDefaultPosition, wxSize(-1, -1))
448 std::vector<wxString> keych;
449 std::set<std::string> mods, keys;
451 runemufn([&mods, &keys]() { mods = modifier::get_set(); keys = keygroup::get_keys(); });
452 Centre();
453 wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0);
454 SetSizer(top_s);
456 wxFlexGridSizer* t_s = new wxFlexGridSizer(mods.size() + 1, 3, 0, 0);
457 for(auto i : mods) {
458 t_s->Add(new wxStaticText(this, wxID_ANY, towxstring(i)), 0, wxGROW);
459 keyentry_mod_data m;
460 t_s->Add(m.pressed = new wxCheckBox(this, wxID_ANY, wxT("Pressed")), 0, wxGROW);
461 t_s->Add(m.unmasked = new wxCheckBox(this, wxID_ANY, wxT("Unmasked")), 0, wxGROW);
462 m.pressed->Disable();
463 modifiers[i] = m;
464 m.pressed->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED,
465 wxCommandEventHandler(wxdialog_keyentry::on_change_setting), NULL, this);
466 m.unmasked->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED,
467 wxCommandEventHandler(wxdialog_keyentry::on_change_setting), NULL, this);
469 for(auto i : keys)
470 keych.push_back(towxstring(i));
471 t_s->Add(new wxStaticText(this, wxID_ANY, wxT("Key")), 0, wxGROW);
472 t_s->Add(mainkey = new wxComboBox(this, wxID_ANY, keych[0], wxDefaultPosition, wxDefaultSize,
473 keych.size(), &keych[0], wxCB_READONLY), 1, wxGROW);
474 mainkey->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED,
475 wxCommandEventHandler(wxdialog_keyentry::on_change_setting), NULL, this);
476 top_s->Add(t_s);
478 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
479 pbutton_s->AddStretchSpacer();
480 pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
481 pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW);
482 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
483 wxCommandEventHandler(wxdialog_keyentry::on_ok), NULL, this);
484 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
485 wxCommandEventHandler(wxdialog_keyentry::on_cancel), NULL, this);
486 top_s->Add(pbutton_s, 0, wxGROW);
488 t_s->SetSizeHints(this);
489 top_s->SetSizeHints(this);
490 Fit();
493 #define TMPFLAG_UNMASKED 65
494 #define TMPFLAG_UNMASKED_LINK_CHILD 2
495 #define TMPFLAG_UNMASKED_LINK_PARENT 68
496 #define TMPFLAG_PRESSED 8
497 #define TMPFLAG_PRESSED_LINK_CHILD 16
498 #define TMPFLAG_PRESSED_LINK_PARENT 32
500 void wxdialog_keyentry::on_change_setting(wxCommandEvent& e)
502 for(auto& i : modifiers)
503 i.second.tmpflags = 0;
504 for(auto& i : modifiers) {
505 modifier* m = NULL;
506 try {
507 m = &modifier::lookup(i.first);
508 } catch(...) {
509 i.second.pressed->Disable();
510 i.second.unmasked->Disable();
511 continue;
513 std::string j = m->linked_name();
514 if(i.second.unmasked->GetValue())
515 i.second.tmpflags |= TMPFLAG_UNMASKED;
516 if(j != "") {
517 if(modifiers[j].unmasked->GetValue())
518 i.second.tmpflags |= TMPFLAG_UNMASKED_LINK_PARENT;
519 if(i.second.unmasked->GetValue())
520 modifiers[j].tmpflags |= TMPFLAG_UNMASKED_LINK_CHILD;
522 if(i.second.pressed->GetValue())
523 i.second.tmpflags |= TMPFLAG_PRESSED;
524 if(j != "") {
525 if(modifiers[j].pressed->GetValue())
526 i.second.tmpflags |= TMPFLAG_PRESSED_LINK_PARENT;
527 if(i.second.pressed->GetValue())
528 modifiers[j].tmpflags |= TMPFLAG_PRESSED_LINK_CHILD;
531 for(auto& i : modifiers) {
532 //Unmasked is to be enabled if neither unmasked link flag is set.
533 if(i.second.tmpflags & ((TMPFLAG_UNMASKED_LINK_CHILD | TMPFLAG_UNMASKED_LINK_PARENT) & ~64)) {
534 i.second.unmasked->SetValue(false);
535 i.second.unmasked->Disable();
536 } else
537 i.second.unmasked->Enable();
538 //Pressed is to be enabled if:
539 //- This modifier is unmasked or parent is unmasked.
540 //- Parent nor child is not pressed.
541 if(((i.second.tmpflags & (TMPFLAG_UNMASKED | TMPFLAG_UNMASKED_LINK_PARENT |
542 TMPFLAG_PRESSED_LINK_CHILD | TMPFLAG_PRESSED_LINK_PARENT)) & 112) == 64)
543 i.second.pressed->Enable();
544 else {
545 i.second.pressed->SetValue(false);
546 i.second.pressed->Disable();
551 void wxdialog_keyentry::on_ok(wxCommandEvent& e)
553 EndModal(wxID_OK);
556 void wxdialog_keyentry::on_cancel(wxCommandEvent& e)
558 EndModal(wxID_CANCEL);
561 std::string wxdialog_keyentry::getkey()
563 std::string x;
564 bool f;
565 f = true;
566 for(auto i : modifiers) {
567 if(i.second.pressed->GetValue()) {
568 if(!f)
569 x = x + ",";
570 f = false;
571 x = x + i.first;
574 x = x + "/";
575 f = true;
576 for(auto i : modifiers) {
577 if(i.second.unmasked->GetValue()) {
578 if(!f)
579 x = x + ",";
580 f = false;
581 x = x + i.first;
584 x = x + "|" + tostdstring(mainkey->GetValue());
585 return x;
589 void boot_emulator(loaded_rom& rom, moviefile& movie)
591 try {
592 struct emu_args* a = new emu_args;
593 a->rom = &rom;
594 a->initial = &movie;
595 a->load_has_to_succeed = false;
596 platform::set_modal_pause(true);
597 emulation_thread = &thread::create(emulator_main, a);
598 main_window = new wxwin_mainwindow();
599 main_window->Show();
600 platform::set_modal_pause(false);
601 } catch(std::bad_alloc& e) {
602 OOM_panic();
606 wxwin_mainwindow::panel::panel(wxWindow* win)
607 : wxPanel(win)
609 initialize_wx_keyboard();
610 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
611 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
612 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
613 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
614 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
615 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
616 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
617 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
618 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
619 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
620 SetMinSize(wxSize(512, 448));
623 void wxwin_mainwindow::menu_start(wxString name)
625 while(!upper.empty())
626 upper.pop();
627 current_menu = new wxMenu();
628 menubar->Append(current_menu, name);
631 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
633 while(!upper.empty())
634 upper.pop();
635 menubar->Append(menu, name);
636 current_menu = NULL;
639 void wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
641 current_menu->AppendSubMenu(menu, name);
644 void wxwin_mainwindow::menu_entry(int id, wxString name)
646 current_menu->Append(id, name);
647 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
648 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
651 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
653 checkitems[id] = current_menu->AppendCheckItem(id, name);
654 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
655 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
658 void wxwin_mainwindow::menu_start_sub(wxString name)
660 wxMenu* old = current_menu;
661 upper.push(current_menu);
662 current_menu = new wxMenu();
663 old->AppendSubMenu(current_menu, name);
666 void wxwin_mainwindow::menu_end_sub(wxString name)
668 current_menu = upper.top();
669 upper.pop();
672 bool wxwin_mainwindow::menu_ischecked(int id)
674 if(checkitems.count(id))
675 return checkitems[id]->IsChecked();
676 else
677 return false;
680 void wxwin_mainwindow::menu_check(int id, bool newstate)
682 if(checkitems.count(id))
683 return checkitems[id]->Check(newstate);
684 else
685 return;
688 void wxwin_mainwindow::menu_separator()
690 current_menu->AppendSeparator();
693 void wxwin_mainwindow::panel::request_paint()
695 Refresh();
698 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
700 render_framebuffer();
701 static struct SwsContext* ctx;
702 uint8_t* srcp[1];
703 int srcs[1];
704 uint8_t* dstp[1];
705 int dsts[1];
706 wxPaintDC dc(this);
707 if(!screen_buffer || main_screen.width != old_width || main_screen.height != old_height) {
708 if(screen_buffer)
709 delete[] screen_buffer;
710 screen_buffer = new unsigned char[main_screen.width * main_screen.height * 3];
711 old_height = main_screen.height;
712 old_width = main_screen.width;
713 uint32_t w = main_screen.width;
714 uint32_t h = main_screen.height;
715 if(w && h)
716 ctx = sws_getCachedContext(ctx, w, h, PIX_FMT_RGBA, w, h, PIX_FMT_BGR24, SWS_POINT |
717 SWS_CPU_CAPS_MMX2, NULL, NULL, NULL);
718 if(w < 512)
719 w = 512;
720 if(h < 448)
721 h = 448;
722 SetMinSize(wxSize(w, h));
723 main_window->Fit();
725 srcs[0] = 4 * main_screen.width;
726 dsts[0] = 3 * main_screen.width;
727 srcp[0] = reinterpret_cast<unsigned char*>(main_screen.memory);
728 dstp[0] = screen_buffer;
729 memset(screen_buffer, 0, main_screen.width * main_screen.height * 3);
730 uint64_t t1 = get_utime();
731 if(main_screen.width && main_screen.height)
732 sws_scale(ctx, srcp, srcs, 0, main_screen.height, dstp, dsts);
733 uint64_t t2 = get_utime();
734 wxBitmap bmp(wxImage(main_screen.width, main_screen.height, screen_buffer, true));
735 uint64_t t3 = get_utime();
736 dc.DrawBitmap(bmp, 0, 0, false);
737 main_window_dirty = false;
740 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
742 //Blank.
745 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
747 handle_wx_keyboard(e, true);
750 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
752 handle_wx_keyboard(e, false);
755 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
757 handle_wx_mouse(e);
760 wxwin_mainwindow::wxwin_mainwindow()
761 : wxFrame(NULL, wxID_ANY, getname(), wxDefaultPosition, wxSize(-1, -1),
762 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX)
764 broadcast_listener* blistener = new broadcast_listener(this);
765 Centre();
766 wxFlexGridSizer* toplevel = new wxFlexGridSizer(1, 1, 0, 0);
767 toplevel->Add(gpanel = new panel(this), 1, wxGROW);
768 toplevel->SetSizeHints(this);
769 SetSizer(toplevel);
770 Fit();
771 gpanel->SetFocus();
772 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
773 menubar = new wxMenuBar;
774 SetMenuBar(menubar);
776 //TOP-level accels: ACFOS.
777 //System menu: (ACFOS)EMNPQRU
778 menu_start(wxT("&System"));
779 menu_entry(wxID_FRAMEADVANCE, wxT("Fra&me advance"));
780 menu_entry(wxID_SUBFRAMEADVANCE, wxT("S&ubframe advance"));
781 menu_entry(wxID_NEXTPOLL, wxT("&Next poll"));
782 menu_entry(wxID_PAUSE, wxT("&Pause/Unpause"));
783 menu_separator();
784 menu_entry(wxID_ERESET, wxT("&Reset"));
785 menu_separator();
786 menu_entry(wxID_EDIT_AUTHORS, wxT("&Edit game name && authors"));
787 menu_separator();
788 menu_entry(wxID_EXIT, wxT("&Quit"));
789 menu_separator();
790 menu_entry(wxID_ABOUT, wxT("About"));
791 //File menu: (ACFOS)DELMNPRTUVW
792 menu_start(wxT("&File"));
793 menu_entry_check(wxID_READONLY_MODE, wxT("Reado&nly mode"));
794 menu_check(wxID_READONLY_MODE, is_readonly_mode());
795 menu_separator();
796 menu_entry(wxID_SAVE_STATE, wxT("Save stat&e"));
797 menu_entry(wxID_SAVE_MOVIE, wxT("Sa&ve movie"));
798 menu_separator();
799 menu_entry(wxID_LOAD_STATE, wxT("&Load state"));
800 menu_entry(wxID_LOAD_STATE_RO, wxT("Loa&d state (readonly)"));
801 menu_entry(wxID_LOAD_STATE_RW, wxT("Load s&tate (read-write)"));
802 menu_entry(wxID_LOAD_STATE_P, wxT("Load state (&preserve)"));
803 menu_entry(wxID_LOAD_MOVIE, wxT("Load &movie"));
804 menu_entry(wxID_REWIND_MOVIE, wxT("Re&wind movie"));
805 menu_separator();
806 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Save sc&reenshot"));
807 menu_separator();
808 menu_special_sub(wxT("D&ump video"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
809 wxID_DUMP_FIRST, wxID_DUMP_LAST)));
810 //Autohold menu: (ACFOS)
811 menu_special(wxT("&Autohold"), reinterpret_cast<autohold_menu*>(ahmenu = new autohold_menu(this)));
812 blistener->set_autohold_menu(reinterpret_cast<autohold_menu*>(ahmenu));
813 //Scripting menu: (ACFOS)ERU
814 menu_start(wxT("S&cripting"));
815 menu_entry(wxID_RUN_SCRIPT, wxT("&Run script"));
816 if(lua_supported) {
817 menu_separator();
818 menu_entry(wxID_EVAL_LUA, wxT("&Evaluate Lua statement"));
819 menu_entry(wxID_RUN_LUA, wxT("R&un Lua script"));
821 menu_separator();
822 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch"));
823 menu_separator();
824 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch"));
825 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch"));
826 //Settings menu: (ACFOS)
827 menu_start(wxT("Settings"));
828 menu_entry(wxID_EDIT_AXES, wxT("Configure axes"));
829 menu_entry(wxID_EDIT_SETTINGS, wxT("Configure settings"));
830 menu_entry(wxID_EDIT_KEYBINDINGS, wxT("Configure keybindings"));
831 menu_entry(wxID_EDIT_ALIAS, wxT("Configure aliases"));
832 if(platform::sound_initialized()) {
833 //Sound menu: (ACFOS)EHU
834 menu_start(wxT("S&ound"));
835 menu_entry_check(wxID_AUDIO_ENABLED, wxT("So&unds enabled"));
836 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
837 menu_entry(wxID_SHOW_AUDIO_STATUS, wxT("S&how audio status"));
838 menu_separator();
839 menu_special_sub(wxT("S&elect sound device"), reinterpret_cast<sound_select_menu*>(sounddev =
840 new sound_select_menu(this)));
841 blistener->set_sound_select(reinterpret_cast<sound_select_menu*>(sounddev));
845 void wxwin_mainwindow::request_paint()
847 gpanel->Refresh();
850 void wxwin_mainwindow::on_close(wxCloseEvent& e)
852 //Veto it for now, latter things will delete it.
853 e.Veto();
854 platform::queue("quit-emulator");
857 void wxwin_mainwindow::notify_update() throw()
859 if(!main_window_dirty) {
860 main_window_dirty = true;
861 gpanel->Refresh();
865 void wxwin_mainwindow::notify_exit() throw()
867 join_emulator_thread();
868 Destroy();
871 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
873 wxFileDialog* d;
874 wxTextEntryDialog* d2;
875 std::string filename;
876 bool s;
877 switch(e.GetId()) {
878 case wxID_FRAMEADVANCE:
879 platform::queue("+advance-frame");
880 platform::queue("-advance-frame");
881 return;
882 case wxID_SUBFRAMEADVANCE:
883 platform::queue("+advance-poll");
884 platform::queue("-advance-poll");
885 return;
886 case wxID_NEXTPOLL:
887 platform::queue("advance-skiplag");
888 return;
889 case wxID_PAUSE:
890 platform::queue("pause-emulator");
891 return;
892 case wxID_RESET:
893 platform::queue("reset");
894 return;
895 case wxID_EXIT:
896 platform::queue("quit-emulator");
897 return;
898 case wxID_AUDIO_ENABLED:
899 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
900 return;
901 case wxID_SHOW_AUDIO_STATUS:
902 platform::queue("show-sound-status");
903 return;
904 case wxID_LOAD_MOVIE:
905 d = new wxFileDialog(this, wxT("Load Movie"), wxT("."));
906 if(d->ShowModal() == wxID_OK)
907 filename = tostdstring(d->GetPath());
908 d->Destroy();
909 if(filename == "")
910 break;
911 platform::queue("load-movie " + filename);
912 break;
913 case wxID_LOAD_STATE:
914 d = new wxFileDialog(this, wxT("Load State"), wxT("."));
915 if(d->ShowModal() == wxID_OK)
916 filename = tostdstring(d->GetPath());
917 d->Destroy();
918 if(filename == "")
919 break;
920 platform::queue("load " + filename);
921 break;
922 case wxID_LOAD_STATE_RO:
923 d = new wxFileDialog(this, wxT("Load State (Read-Only)"), wxT("."));
924 if(d->ShowModal() == wxID_OK)
925 filename = tostdstring(d->GetPath());
926 d->Destroy();
927 if(filename == "")
928 break;
929 platform::queue("load-readonly " + filename);
930 break;
931 case wxID_LOAD_STATE_RW:
932 d = new wxFileDialog(this, wxT("Load State (Read-Write)"), wxT("."));
933 if(d->ShowModal() == wxID_OK)
934 filename = tostdstring(d->GetPath());
935 d->Destroy();
936 if(filename == "")
937 break;
938 platform::queue("load-state " + filename);
939 break;
940 case wxID_REWIND_MOVIE:
941 platform::queue("rewind-movie");
942 break;
943 case wxID_SAVE_MOVIE:
944 d = new wxFileDialog(this, wxT("Save Movie"), wxT("."));
945 if(d->ShowModal() == wxID_OK)
946 filename = tostdstring(d->GetPath());
947 d->Destroy();
948 if(filename == "")
949 break;
950 platform::queue("save-movie " + filename);
951 break;
952 case wxID_SAVE_STATE:
953 d = new wxFileDialog(this, wxT("Save State"), wxT("."));
954 if(d->ShowModal() == wxID_OK)
955 filename = tostdstring(d->GetPath());
956 d->Destroy();
957 if(filename == "")
958 break;
959 platform::queue("save-state " + filename);
960 break;
961 case wxID_SAVE_SCREENSHOT:
962 d = new wxFileDialog(this, wxT("Save Screenshot"), wxT("."));
963 if(d->ShowModal() == wxID_OK)
964 filename = tostdstring(d->GetPath());
965 d->Destroy();
966 if(filename == "")
967 break;
968 platform::queue("take-screenshot " + filename);
969 break;
970 case wxID_RUN_SCRIPT:
971 d = new wxFileDialog(this, wxT("Select Script"), wxT("."));
972 if(d->ShowModal() == wxID_OK)
973 filename = tostdstring(d->GetPath());
974 d->Destroy();
975 if(filename == "")
976 break;
977 platform::queue("run-script " + filename);
978 break;
979 case wxID_RUN_LUA:
980 d = new wxFileDialog(this, wxT("Select Lua Script"), wxT("."));
981 if(d->ShowModal() == wxID_OK)
982 filename = tostdstring(d->GetPath());
983 d->Destroy();
984 if(filename == "")
985 break;
986 platform::queue("run-lua " + filename);
987 break;
988 case wxID_EVAL_LUA:
989 d2 = new wxTextEntryDialog(this, wxT("Enter Lua statement:"), wxT("Evaluate Lua"));
990 if(d2->ShowModal() == wxID_OK)
991 filename = tostdstring(d2->GetValue());
992 d2->Destroy();
993 platform::queue("evaluate-lua " + filename);
994 break;
995 case wxID_READONLY_MODE:
996 s = menu_ischecked(wxID_READONLY_MODE);
997 platform::queue(_set_readonly, &s, true);
998 break;
999 case wxID_EDIT_AXES:
1000 wxeditor_axes_display(this);
1001 break;
1002 case wxID_EDIT_AUTHORS:
1003 wxeditor_authors_display(this);
1004 break;
1005 case wxID_EDIT_SETTINGS:
1006 wxeditor_settings_display(this);
1007 break;
1008 case wxID_EDIT_KEYBINDINGS:
1009 menu_edit_keybindings(e);
1010 break;
1011 case wxID_EDIT_ALIAS:
1012 menu_edit_aliases(e);
1013 break;
1014 case wxID_EDIT_MEMORYWATCH:
1015 menu_edit_memorywatch(e);
1016 break;
1017 case wxID_SAVE_MEMORYWATCH:
1018 menu_save_memorywatch(e);
1019 break;
1020 case wxID_LOAD_MEMORYWATCH:
1021 menu_load_memorywatch(e);
1022 break;
1023 case wxID_ABOUT: {
1024 std::ostringstream str;
1025 str << "Version: lsnes rr" << lsnes_version << std::endl;
1026 str << "Revision: " << lsnes_git_revision << std::endl;
1027 str << "Core: " << bsnes_core_version << std::endl;
1028 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1030 break;
1034 #define NEW_KEYBINDING "A new binding..."
1035 #define NEW_ALIAS "A new alias..."
1036 #define NEW_WATCH "A new watch..."
1038 void wxwin_mainwindow::menu_edit_keybindings(wxCommandEvent& e)
1040 platform::set_modal_pause(true);
1041 std::set<std::string> bind;
1042 runemufn([&bind]() { bind = keymapper::get_bindings(); });
1043 std::vector<wxString> choices;
1044 choices.push_back(wxT(NEW_KEYBINDING));
1045 for(auto i : bind)
1046 choices.push_back(towxstring(i));
1047 wxSingleChoiceDialog* d = new wxSingleChoiceDialog(this, wxT("Select keybinding to edit"),
1048 wxT("Select binding"), choices.size(), &choices[0]);
1049 if(d->ShowModal() == wxID_CANCEL) {
1050 d->Destroy();
1051 platform::set_modal_pause(false);
1052 return;
1054 std::string key = tostdstring(d->GetStringSelection());
1055 d->Destroy();
1056 if(key == NEW_KEYBINDING) {
1057 wxdialog_keyentry* d2 = new wxdialog_keyentry(this);
1058 //wxTextEntryDialog* d2 = new wxTextEntryDialog(this, wxT("Enter key for binding:"),
1059 // wxT("Edit binding"), wxT(""));
1060 if(d2->ShowModal() == wxID_CANCEL) {
1061 d2->Destroy();
1062 platform::set_modal_pause(false);
1063 return;
1065 key = d2->getkey();
1066 //key = tostdstring(d2->GetValue());
1067 d2->Destroy();
1069 std::string old_command_value;
1070 runemufn([&old_command_value, key]() { old_command_value = keymapper::get_command_for(key); });
1071 wxTextEntryDialog* d4 = new wxTextEntryDialog(this, wxT("Enter new command for binding:"), wxT("Edit binding"),
1072 towxstring(old_command_value));
1073 if(d4->ShowModal() == wxID_CANCEL) {
1074 d4->Destroy();
1075 platform::set_modal_pause(false);
1076 return;
1078 bool fault = false;
1079 std::string faulttext;
1080 std::string newcommand = tostdstring(d4->GetValue());
1081 runemufn([&fault, &faulttext, key, newcommand]() {
1082 try {
1083 keymapper::bind_for(key, newcommand);
1084 } catch(std::exception& e) {
1087 if(fault) {
1088 wxMessageDialog* d3 = new wxMessageDialog(this, towxstring(std::string("Can't bind key: ") +
1089 faulttext), wxT("Error"), wxOK | wxICON_EXCLAMATION);
1090 d3->ShowModal();
1091 d3->Destroy();
1093 d4->Destroy();
1094 platform::set_modal_pause(false);
1097 void wxwin_mainwindow::menu_edit_aliases(wxCommandEvent& e)
1099 platform::set_modal_pause(true);
1100 std::set<std::string> bind;
1101 runemufn([&bind]() { bind = command::get_aliases(); });
1102 std::vector<wxString> choices;
1103 choices.push_back(wxT(NEW_ALIAS));
1104 for(auto i : bind)
1105 choices.push_back(towxstring(i));
1106 wxSingleChoiceDialog* d = new wxSingleChoiceDialog(this, wxT("Select alias to edit"),
1107 wxT("Select alias"), choices.size(), &choices[0]);
1108 if(d->ShowModal() == wxID_CANCEL) {
1109 d->Destroy();
1110 platform::set_modal_pause(false);
1111 return;
1113 std::string alias = tostdstring(d->GetStringSelection());
1114 d->Destroy();
1115 if(alias == NEW_ALIAS) {
1116 wxTextEntryDialog* d2 = new wxTextEntryDialog(this, wxT("Enter name for the new alias:"),
1117 wxT("Enter alias name"));
1118 if(d2->ShowModal() == wxID_CANCEL) {
1119 d2->Destroy();
1120 platform::set_modal_pause(false);
1121 return;
1123 alias = tostdstring(d2->GetValue());
1124 d2->Destroy();
1125 if(!command::valid_alias_name(alias)) {
1126 wxMessageDialog* d3 = new wxMessageDialog(this, towxstring(std::string("Not a valid alias "
1127 "name: ") + alias), wxT("Error"), wxOK | wxICON_EXCLAMATION);
1128 d3->ShowModal();
1129 d3->Destroy();
1130 platform::set_modal_pause(false);
1131 return;
1134 std::string old_alias_value = command::get_alias_for(alias);
1135 wxTextEntryDialog* d4 = new wxTextEntryDialog(this, wxT("Enter new commands for alias:"), wxT("Edit alias"),
1136 towxstring(old_alias_value), wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE);
1137 if(d4->ShowModal() == wxID_CANCEL) {
1138 d4->Destroy();
1139 platform::set_modal_pause(false);
1140 return;
1142 std::string newcmd = tostdstring(d4->GetValue());
1143 runemufn([alias, newcmd]() { command::set_alias_for(alias, newcmd); });
1144 d4->Destroy();
1145 platform::set_modal_pause(false);
1148 void wxwin_mainwindow::menu_load_memorywatch(wxCommandEvent& e)
1150 platform::set_modal_pause(true);
1151 std::set<std::string> old_watches;
1152 runemufn([&old_watches]() { old_watches = get_watches(); });
1153 std::map<std::string, std::string> new_watches;
1154 std::string filename;
1156 wxFileDialog* d = new wxFileDialog(this, towxstring("Choose memory watch file"), wxT("."));
1157 if(d->ShowModal() == wxID_CANCEL) {
1158 d->Destroy();
1159 platform::set_modal_pause(false);
1160 return;
1162 filename = tostdstring(d->GetPath());
1163 d->Destroy();
1164 //Did we pick a .zip file?
1165 try {
1166 zip_reader zr(filename);
1167 std::vector<wxString> files;
1168 for(auto i : zr)
1169 files.push_back(towxstring(i));
1170 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(this, wxT("Select file within .zip"),
1171 wxT("Select member"), files.size(), &files[0]);
1172 if(d2->ShowModal() == wxID_CANCEL) {
1173 d2->Destroy();
1174 platform::set_modal_pause(false);
1175 return;
1177 filename = filename + "/" + tostdstring(d2->GetStringSelection());
1178 d2->Destroy();
1179 } catch(...) {
1180 //Ignore error.
1183 try {
1184 std::istream& in = open_file_relative(filename, "");
1185 while(in) {
1186 std::string wname;
1187 std::string wexpr;
1188 std::getline(in, wname);
1189 std::getline(in, wexpr);
1190 new_watches[wname] = wexpr;
1192 delete &in;
1193 } catch(std::exception& e) {
1194 wxMessageDialog* d3 = new wxMessageDialog(this, towxstring(std::string("Can't load memory "
1195 "watch: ") + e.what()), wxT("Error"), wxOK | wxICON_EXCLAMATION);
1196 d3->ShowModal();
1197 d3->Destroy();
1198 platform::set_modal_pause(false);
1199 return;
1202 runemufn([&new_watches, &old_watches]() {
1203 for(auto i : new_watches)
1204 set_watchexpr_for(i.first, i.second);
1205 for(auto i : old_watches)
1206 if(!new_watches.count(i))
1207 set_watchexpr_for(i, "");
1209 platform::set_modal_pause(false);
1212 void wxwin_mainwindow::menu_save_memorywatch(wxCommandEvent& e)
1214 platform::set_modal_pause(true);
1215 std::set<std::string> old_watches;
1216 runemufn([&old_watches]() { old_watches = get_watches(); });
1217 std::string filename;
1219 wxFileDialog* d = new wxFileDialog(this, towxstring("Save watches to file"), wxT("."));
1220 if(d->ShowModal() == wxID_CANCEL) {
1221 d->Destroy();
1222 platform::set_modal_pause(false);
1223 return;
1225 filename = tostdstring(d->GetPath());
1226 d->Destroy();
1228 std::ofstream out(filename.c_str());
1229 for(auto i : old_watches)
1230 out << i << std::endl << get_watchexpr_for(i) << std::endl;
1231 out.close();
1232 platform::set_modal_pause(false);
1236 void wxwin_mainwindow::menu_edit_memorywatch(wxCommandEvent& e)
1238 platform::set_modal_pause(true);
1239 std::set<std::string> bind;
1240 runemufn([&bind]() { bind = get_watches(); });
1241 std::vector<wxString> choices;
1242 choices.push_back(wxT(NEW_WATCH));
1243 for(auto i : bind)
1244 choices.push_back(towxstring(i));
1245 wxSingleChoiceDialog* d = new wxSingleChoiceDialog(this, wxT("Select watch to edit"),
1246 wxT("Select watch"), choices.size(), &choices[0]);
1247 if(d->ShowModal() == wxID_CANCEL) {
1248 d->Destroy();
1249 platform::set_modal_pause(false);
1250 return;
1252 std::string watch = tostdstring(d->GetStringSelection());
1253 d->Destroy();
1254 if(watch == NEW_WATCH) {
1255 wxTextEntryDialog* d2 = new wxTextEntryDialog(this, wxT("Enter name for the new watch:"),
1256 wxT("Enter watch name"));
1257 if(d2->ShowModal() == wxID_CANCEL) {
1258 d2->Destroy();
1259 platform::set_modal_pause(false);
1260 return;
1262 watch = tostdstring(d2->GetValue());
1263 d2->Destroy();
1265 std::string old_watch_value = get_watchexpr_for(watch);
1266 wxTextEntryDialog* d4 = new wxTextEntryDialog(this, wxT("Enter new expression for watch:"), wxT("Edit watch"),
1267 towxstring(old_watch_value), wxOK | wxCANCEL | wxCENTRE);
1268 if(d4->ShowModal() == wxID_CANCEL) {
1269 d4->Destroy();
1270 platform::set_modal_pause(false);
1271 return;
1273 std::string newexpr = tostdstring(d4->GetValue());
1274 runemufn([watch, newexpr]() { set_watchexpr_for(watch, newexpr); });
1275 platform::set_modal_pause(false);
1276 d4->Destroy();