Support hiding the status panel
[lsnes.git] / src / plat-wxwidgets / mainwindow.cpp
blob6a17913de2c57e36b074c47c894b314f4afe981e
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 "library/string.hpp"
16 #include "library/zip.hpp"
18 #include <vector>
19 #include <string>
21 #include "plat-wxwidgets/menu_dump.hpp"
22 #include "plat-wxwidgets/platform.hpp"
23 #include "plat-wxwidgets/window_mainwindow.hpp"
24 #include "plat-wxwidgets/window_status.hpp"
26 #define MAXCONTROLLERS MAX_PORTS * MAX_CONTROLLERS_PER_PORT
28 extern "C"
30 #ifndef UINT64_C
31 #define UINT64_C(val) val##ULL
32 #endif
33 #include <libswscale/swscale.h>
36 enum
38 wxID_PAUSE = wxID_HIGHEST + 1,
39 wxID_FRAMEADVANCE,
40 wxID_SUBFRAMEADVANCE,
41 wxID_NEXTPOLL,
42 wxID_ERESET,
43 wxID_AUDIO_ENABLED,
44 wxID_SHOW_AUDIO_STATUS,
45 wxID_AUDIODEV_FIRST,
46 wxID_AUDIODEV_LAST = wxID_AUDIODEV_FIRST + 255,
47 wxID_SAVE_STATE,
48 wxID_SAVE_MOVIE,
49 wxID_LOAD_STATE,
50 wxID_LOAD_STATE_RO,
51 wxID_LOAD_STATE_RW,
52 wxID_LOAD_STATE_P,
53 wxID_LOAD_MOVIE,
54 wxID_RUN_SCRIPT,
55 wxID_RUN_LUA,
56 wxID_EVAL_LUA,
57 wxID_SAVE_SCREENSHOT,
58 wxID_READONLY_MODE,
59 wxID_EDIT_AUTHORS,
60 wxID_AUTOHOLD_FIRST,
61 wxID_AUTOHOLD_LAST = wxID_AUTOHOLD_FIRST + 1023,
62 wxID_EDIT_AXES,
63 wxID_EDIT_SETTINGS,
64 wxID_EDIT_KEYBINDINGS,
65 wxID_EDIT_ALIAS,
66 wxID_EDIT_MEMORYWATCH,
67 wxID_SAVE_MEMORYWATCH,
68 wxID_LOAD_MEMORYWATCH,
69 wxID_DUMP_FIRST,
70 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
71 wxID_REWIND_MOVIE,
72 wxID_EDIT_JUKEBOX,
73 wxID_MEMORY_SEARCH,
74 wxID_CANCEL_SAVES,
75 wxID_EDIT_HOTKEYS,
76 wxID_SHOW_STATUS
80 namespace
82 unsigned char* screen_buffer;
83 uint32_t old_width;
84 uint32_t old_height;
85 bool main_window_dirty;
86 struct thread* emulation_thread;
88 wxString getname()
90 std::string windowname = "lsnes rr" + lsnes_version + "[" + bsnes_core_version + "]";
91 return towxstring(windowname);
94 struct emu_args
96 struct loaded_rom* rom;
97 struct moviefile* initial;
98 bool load_has_to_succeed;
101 void* emulator_main(void* _args)
103 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
104 try {
105 our_rom = args->rom;
106 struct moviefile* movie = args->initial;
107 bool has_to_succeed = args->load_has_to_succeed;
108 platform::flush_command_queue();
109 main_loop(*our_rom, *movie, has_to_succeed);
110 signal_program_exit();
111 } catch(std::bad_alloc& e) {
112 OOM_panic();
113 } catch(std::exception& e) {
114 messages << "FATAL: " << e.what() << std::endl;
115 platform::fatal_error();
117 return NULL;
120 void join_emulator_thread()
122 emulation_thread->join();
125 keygroup mouse_x("mouse_x", keygroup::KT_MOUSE);
126 keygroup mouse_y("mouse_y", keygroup::KT_MOUSE);
127 keygroup mouse_l("mouse_left", keygroup::KT_KEY);
128 keygroup mouse_m("mouse_center", keygroup::KT_KEY);
129 keygroup mouse_r("mouse_right", keygroup::KT_KEY);
130 keygroup mouse_i("mouse_inwindow", keygroup::KT_KEY);
132 void handle_wx_mouse(wxMouseEvent& e)
134 platform::queue(keypress(modifier_set(), mouse_x, e.GetX()));
135 platform::queue(keypress(modifier_set(), mouse_y, e.GetY()));
136 if(e.Entering())
137 platform::queue(keypress(modifier_set(), mouse_i, 1));
138 if(e.Leaving())
139 platform::queue(keypress(modifier_set(), mouse_i, 0));
140 if(e.LeftDown())
141 platform::queue(keypress(modifier_set(), mouse_l, 1));
142 if(e.LeftUp())
143 platform::queue(keypress(modifier_set(), mouse_l, 0));
144 if(e.MiddleDown())
145 platform::queue(keypress(modifier_set(), mouse_m, 1));
146 if(e.MiddleUp())
147 platform::queue(keypress(modifier_set(), mouse_m, 0));
148 if(e.RightDown())
149 platform::queue(keypress(modifier_set(), mouse_r, 1));
150 if(e.RightUp())
151 platform::queue(keypress(modifier_set(), mouse_r, 0));
154 bool is_readonly_mode()
156 bool ret;
157 runemufn([&ret]() { ret = movb.get_movie().readonly_mode(); });
158 return ret;
161 bool UI_get_autohold(unsigned pid, unsigned idx)
163 bool ret;
164 runemufn([&ret, pid, idx]() { ret = controls.autohold(pid, idx); });
165 return ret;
168 void UI_change_autohold(unsigned pid, unsigned idx, bool newstate)
170 runemufn([pid, idx, newstate]() { controls.autohold(pid, idx, newstate); });
173 int UI_controller_index_by_logical(unsigned lid)
175 int ret;
176 runemufn([&ret, lid]() { ret = controls.lcid_to_pcid(lid); });
177 return ret;
180 int UI_button_id(unsigned pcid, unsigned lidx)
182 int ret;
183 runemufn([&ret, pcid, lidx]() { ret = controls.button_id(pcid, lidx); });
184 return ret;
187 class controller_autohold_menu : public wxMenu
189 public:
190 controller_autohold_menu(unsigned lid, enum devicetype_t dtype);
191 void change_type();
192 bool is_dummy();
193 void on_select(wxCommandEvent& e);
194 void update(unsigned pid, unsigned ctrlnum, bool newstate);
195 private:
196 unsigned our_lid;
197 wxMenuItem* entries[MAX_LOGICAL_BUTTONS];
198 unsigned enabled_entries;
201 class autohold_menu : public wxMenu
203 public:
204 autohold_menu(wxwin_mainwindow* win);
205 void reconfigure();
206 void on_select(wxCommandEvent& e);
207 void update(unsigned pid, unsigned ctrlnum, bool newstate);
208 private:
209 controller_autohold_menu* menus[MAXCONTROLLERS];
210 wxMenuItem* entries[MAXCONTROLLERS];
213 class sound_select_menu : public wxMenu
215 public:
216 sound_select_menu(wxwin_mainwindow* win);
217 void update(const std::string& dev);
218 void on_select(wxCommandEvent& e);
219 private:
220 std::map<std::string, wxMenuItem*> items;
221 std::map<int, std::string> devices;
224 class sound_select_menu;
226 class broadcast_listener : public information_dispatch
228 public:
229 broadcast_listener(wxwin_mainwindow* win);
230 void set_sound_select(sound_select_menu* sdev);
231 void set_autohold_menu(autohold_menu* ah);
232 void on_sound_unmute(bool unmute) throw();
233 void on_sound_change(const std::string& dev) throw();
234 void on_mode_change(bool readonly) throw();
235 void on_autohold_update(unsigned pid, unsigned ctrlnum, bool newstate);
236 void on_autohold_reconfigure();
237 private:
238 wxwin_mainwindow* mainw;
239 sound_select_menu* sounddev;
240 autohold_menu* ahmenu;
243 controller_autohold_menu::controller_autohold_menu(unsigned lid, enum devicetype_t dtype)
245 modal_pause_holder hld;
246 our_lid = lid;
247 for(unsigned i = 0; i < MAX_LOGICAL_BUTTONS; i++) {
248 int id = wxID_AUTOHOLD_FIRST + MAX_LOGICAL_BUTTONS * lid + i;
249 entries[i] = AppendCheckItem(id, towxstring(get_logical_button_name(i)));
251 change_type();
254 void controller_autohold_menu::change_type()
256 enabled_entries = 0;
257 int pid = controls.lcid_to_pcid(our_lid);
258 for(unsigned i = 0; i < MAX_LOGICAL_BUTTONS; i++) {
259 int pidx = -1;
260 if(pid >= 0)
261 pidx = controls.button_id(pid, i);
262 if(pidx >= 0) {
263 entries[i]->Check(pid > 0 && UI_get_autohold(pid, pidx));
264 entries[i]->Enable();
265 enabled_entries++;
266 } else {
267 entries[i]->Check(false);
268 entries[i]->Enable(false);
273 bool controller_autohold_menu::is_dummy()
275 return !enabled_entries;
278 void controller_autohold_menu::on_select(wxCommandEvent& e)
280 int x = e.GetId();
281 if(x < wxID_AUTOHOLD_FIRST + our_lid * MAX_LOGICAL_BUTTONS || x >= wxID_AUTOHOLD_FIRST *
282 (our_lid + 1) * MAX_LOGICAL_BUTTONS) {
283 return;
285 unsigned lidx = (x - wxID_AUTOHOLD_FIRST) % MAX_LOGICAL_BUTTONS;
286 modal_pause_holder hld;
287 int pid = controls.lcid_to_pcid(our_lid);
288 if(pid < 0 || !entries[lidx])
289 return;
290 int pidx = controls.button_id(pid, lidx);
291 if(pidx < 0)
292 return;
293 //Autohold change on pid=pid, ctrlindx=idx, state
294 bool newstate = entries[lidx]->IsChecked();
295 UI_change_autohold(pid, pidx, newstate);
298 void controller_autohold_menu::update(unsigned pid, unsigned ctrlnum, bool newstate)
300 modal_pause_holder hld;
301 int pid2 = UI_controller_index_by_logical(our_lid);
302 if(pid2 < 0 || static_cast<unsigned>(pid) != pid2)
303 return;
304 for(unsigned i = 0; i < MAX_LOGICAL_BUTTONS; i++) {
305 int idx = UI_button_id(pid2, i);
306 if(idx < 0 || static_cast<unsigned>(idx) != ctrlnum)
307 continue;
308 entries[i]->Check(newstate);
313 autohold_menu::autohold_menu(wxwin_mainwindow* win)
315 for(unsigned i = 0; i < MAXCONTROLLERS; i++) {
316 std::ostringstream str;
317 str << "Controller #&" << (i + 1);
318 menus[i] = new controller_autohold_menu(i, DT_NONE);
319 entries[i] = AppendSubMenu(menus[i], towxstring(str.str()));
320 entries[i]->Enable(!menus[i]->is_dummy());
322 win->Connect(wxID_AUTOHOLD_FIRST, wxID_AUTOHOLD_LAST, wxEVT_COMMAND_MENU_SELECTED,
323 wxCommandEventHandler(autohold_menu::on_select), NULL, this);
324 reconfigure();
327 void autohold_menu::reconfigure()
329 modal_pause_holder hld;
330 for(unsigned i = 0; i < MAXCONTROLLERS; i++) {
331 menus[i]->change_type();
332 entries[i]->Enable(!menus[i]->is_dummy());
336 void autohold_menu::on_select(wxCommandEvent& e)
338 for(unsigned i = 0; i < MAXCONTROLLERS; i++)
339 menus[i]->on_select(e);
342 void autohold_menu::update(unsigned pid, unsigned ctrlnum, bool newstate)
344 for(unsigned i = 0; i < MAXCONTROLLERS; i++)
345 menus[i]->update(pid, ctrlnum, newstate);
348 sound_select_menu::sound_select_menu(wxwin_mainwindow* win)
350 std::string curdev = platform::get_sound_device();
351 int j = wxID_AUDIODEV_FIRST;
352 for(auto i : platform::get_sound_devices()) {
353 items[i.first] = AppendRadioItem(j, towxstring(i.first + "(" + i.second + ")"));
354 devices[j] = i.first;
355 if(i.first == curdev)
356 items[i.first]->Check();
357 win->Connect(j, wxEVT_COMMAND_MENU_SELECTED,
358 wxCommandEventHandler(sound_select_menu::on_select), NULL, this);
359 j++;
363 void sound_select_menu::update(const std::string& dev)
365 items[dev]->Check();
368 void sound_select_menu::on_select(wxCommandEvent& e)
370 std::string devname = devices[e.GetId()];
371 if(devname != "")
372 runemufn([devname]() { platform::set_sound_device(devname); });
375 broadcast_listener::broadcast_listener(wxwin_mainwindow* win)
376 : information_dispatch("wxwidgets-broadcast-listener")
378 mainw = win;
381 void broadcast_listener::set_sound_select(sound_select_menu* sdev)
383 sounddev = sdev;
386 void broadcast_listener::set_autohold_menu(autohold_menu* ah)
388 ahmenu = ah;
391 void broadcast_listener::on_sound_unmute(bool unmute) throw()
393 runuifun([unmute, mainw]() { mainw->menu_check(wxID_AUDIO_ENABLED, unmute); });
396 void broadcast_listener::on_sound_change(const std::string& dev) throw()
398 runuifun([dev, sounddev]() { if(sounddev) sounddev->update(dev); });
401 void broadcast_listener::on_mode_change(bool readonly) throw()
403 runuifun([readonly, mainw]() { mainw->menu_check(wxID_READONLY_MODE, readonly); });
406 void broadcast_listener::on_autohold_update(unsigned pid, unsigned ctrlnum, bool newstate)
408 runuifun([pid, ctrlnum, newstate, ahmenu]() { ahmenu->update(pid, ctrlnum, newstate); });
411 void broadcast_listener::on_autohold_reconfigure()
413 runuifun([ahmenu]() { ahmenu->reconfigure(); });
417 void boot_emulator(loaded_rom& rom, moviefile& movie)
419 try {
420 struct emu_args* a = new emu_args;
421 a->rom = &rom;
422 a->initial = &movie;
423 a->load_has_to_succeed = false;
424 modal_pause_holder hld;
425 emulation_thread = &thread::create(emulator_main, a);
426 main_window = new wxwin_mainwindow();
427 main_window->Show();
428 } catch(std::bad_alloc& e) {
429 OOM_panic();
433 wxwin_mainwindow::panel::panel(wxWindow* win)
434 : wxPanel(win)
436 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
437 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
438 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
439 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
440 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
441 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
442 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
443 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
444 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
445 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
446 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
447 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
448 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
449 SetMinSize(wxSize(512, 448));
452 void wxwin_mainwindow::menu_start(wxString name)
454 while(!upper.empty())
455 upper.pop();
456 current_menu = new wxMenu();
457 menubar->Append(current_menu, name);
460 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
462 while(!upper.empty())
463 upper.pop();
464 menubar->Append(menu, name);
465 current_menu = NULL;
468 void wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
470 current_menu->AppendSubMenu(menu, name);
473 void wxwin_mainwindow::menu_entry(int id, wxString name)
475 current_menu->Append(id, name);
476 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
477 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
480 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
482 checkitems[id] = current_menu->AppendCheckItem(id, name);
483 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
484 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
487 void wxwin_mainwindow::menu_start_sub(wxString name)
489 wxMenu* old = current_menu;
490 upper.push(current_menu);
491 current_menu = new wxMenu();
492 old->AppendSubMenu(current_menu, name);
495 void wxwin_mainwindow::menu_end_sub(wxString name)
497 current_menu = upper.top();
498 upper.pop();
501 bool wxwin_mainwindow::menu_ischecked(int id)
503 if(checkitems.count(id))
504 return checkitems[id]->IsChecked();
505 else
506 return false;
509 void wxwin_mainwindow::menu_check(int id, bool newstate)
511 if(checkitems.count(id))
512 return checkitems[id]->Check(newstate);
513 else
514 return;
517 void wxwin_mainwindow::menu_separator()
519 current_menu->AppendSeparator();
522 void wxwin_mainwindow::panel::request_paint()
524 Refresh();
527 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
529 render_framebuffer();
530 static struct SwsContext* ctx;
531 uint8_t* srcp[1];
532 int srcs[1];
533 uint8_t* dstp[1];
534 int dsts[1];
535 wxPaintDC dc(this);
536 if(!screen_buffer || main_screen.width != old_width || main_screen.height != old_height) {
537 if(screen_buffer)
538 delete[] screen_buffer;
539 screen_buffer = new unsigned char[main_screen.width * main_screen.height * 3];
540 old_height = main_screen.height;
541 old_width = main_screen.width;
542 uint32_t w = main_screen.width;
543 uint32_t h = main_screen.height;
544 if(w && h)
545 ctx = sws_getCachedContext(ctx, w, h, PIX_FMT_RGBA, w, h, PIX_FMT_BGR24, SWS_POINT |
546 SWS_CPU_CAPS_MMX2, NULL, NULL, NULL);
547 if(w < 512)
548 w = 512;
549 if(h < 448)
550 h = 448;
551 SetMinSize(wxSize(w, h));
552 main_window->Fit();
554 srcs[0] = 4 * main_screen.width;
555 dsts[0] = 3 * main_screen.width;
556 srcp[0] = reinterpret_cast<unsigned char*>(main_screen.memory);
557 dstp[0] = screen_buffer;
558 memset(screen_buffer, 0, main_screen.width * main_screen.height * 3);
559 uint64_t t1 = get_utime();
560 if(main_screen.width && main_screen.height)
561 sws_scale(ctx, srcp, srcs, 0, main_screen.height, dstp, dsts);
562 uint64_t t2 = get_utime();
563 wxBitmap bmp(wxImage(main_screen.width, main_screen.height, screen_buffer, true));
564 uint64_t t3 = get_utime();
565 dc.DrawBitmap(bmp, 0, 0, false);
566 main_window_dirty = false;
569 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
571 //Blank.
574 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
576 handle_wx_keyboard(e, true);
579 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
581 handle_wx_keyboard(e, false);
584 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
586 handle_wx_mouse(e);
589 wxwin_mainwindow::wxwin_mainwindow()
590 : wxFrame(NULL, wxID_ANY, getname(), wxDefaultPosition, wxSize(-1, -1),
591 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX)
593 broadcast_listener* blistener = new broadcast_listener(this);
594 Centre();
595 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
596 toplevel->Add(gpanel = new panel(this), 1, wxGROW);
597 toplevel->Add(spanel = new wxwin_status::panel(this, 20), 1, wxGROW);
598 spanel_shown = true;
599 toplevel->SetSizeHints(this);
600 SetSizer(toplevel);
601 Fit();
602 gpanel->SetFocus();
603 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
604 menubar = new wxMenuBar;
605 SetMenuBar(menubar);
607 //TOP-level accels: ACFOS.
608 //System menu: (ACFOS)EMNPQRU
609 menu_start(wxT("&System"));
610 menu_entry(wxID_FRAMEADVANCE, wxT("Fra&me advance"));
611 menu_entry(wxID_SUBFRAMEADVANCE, wxT("S&ubframe advance"));
612 menu_entry(wxID_NEXTPOLL, wxT("&Next poll"));
613 menu_entry(wxID_PAUSE, wxT("&Pause/Unpause"));
614 menu_separator();
615 menu_entry(wxID_ERESET, wxT("&Reset"));
616 menu_separator();
617 menu_entry(wxID_EDIT_AUTHORS, wxT("&Edit game name && authors"));
618 menu_separator();
619 menu_entry(wxID_EXIT, wxT("&Quit"));
620 menu_separator();
621 menu_entry(wxID_ABOUT, wxT("About"));
622 //File menu: (ACFOS)DEILMNPRTUVW
623 menu_start(wxT("&File"));
624 menu_entry_check(wxID_READONLY_MODE, wxT("Reado&nly mode"));
625 menu_check(wxID_READONLY_MODE, is_readonly_mode());
626 menu_separator();
627 menu_entry(wxID_SAVE_STATE, wxT("Save stat&e"));
628 menu_entry(wxID_SAVE_MOVIE, wxT("Sa&ve movie"));
629 menu_separator();
630 menu_entry(wxID_LOAD_STATE, wxT("&Load state"));
631 menu_entry(wxID_LOAD_STATE_RO, wxT("Loa&d state (readonly)"));
632 menu_entry(wxID_LOAD_STATE_RW, wxT("Load s&tate (read-write)"));
633 menu_entry(wxID_LOAD_STATE_P, wxT("Load state (&preserve)"));
634 menu_entry(wxID_LOAD_MOVIE, wxT("Load &movie"));
635 menu_entry(wxID_REWIND_MOVIE, wxT("Re&wind movie"));
636 menu_separator();
637 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pend&ing saves"));
638 menu_separator();
639 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Save sc&reenshot"));
640 menu_separator();
641 menu_special_sub(wxT("D&ump video"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
642 wxID_DUMP_FIRST, wxID_DUMP_LAST)));
643 //Autohold menu: (ACFOS)
644 menu_special(wxT("&Autohold"), reinterpret_cast<autohold_menu*>(ahmenu = new autohold_menu(this)));
645 blistener->set_autohold_menu(reinterpret_cast<autohold_menu*>(ahmenu));
646 //Scripting menu: (ACFOS)ERU
647 menu_start(wxT("S&cripting"));
648 menu_entry(wxID_RUN_SCRIPT, wxT("&Run script"));
649 if(lua_supported) {
650 menu_separator();
651 menu_entry(wxID_EVAL_LUA, wxT("&Evaluate Lua statement"));
652 menu_entry(wxID_RUN_LUA, wxT("R&un Lua script"));
654 menu_separator();
655 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch"));
656 menu_separator();
657 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch"));
658 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch"));
659 menu_separator();
660 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search"));
661 //Settings menu: (ACFOS)
662 menu_start(wxT("Settings"));
663 menu_entry(wxID_EDIT_AXES, wxT("Configure axes"));
664 menu_entry(wxID_EDIT_SETTINGS, wxT("Configure settings"));
665 menu_entry(wxID_EDIT_HOTKEYS, wxT("Configure hotkeys"));
666 menu_entry(wxID_EDIT_KEYBINDINGS, wxT("Configure keybindings"));
667 menu_entry(wxID_EDIT_ALIAS, wxT("Configure aliases"));
668 menu_entry(wxID_EDIT_JUKEBOX, wxT("Configure jukebox"));
669 menu_separator();
670 menu_entry_check(wxID_SHOW_STATUS, wxT("Show status panel"));
671 menu_check(wxID_SHOW_STATUS, true);
672 if(platform::sound_initialized()) {
673 //Sound menu: (ACFOS)EHU
674 menu_start(wxT("S&ound"));
675 menu_entry_check(wxID_AUDIO_ENABLED, wxT("So&unds enabled"));
676 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
677 menu_entry(wxID_SHOW_AUDIO_STATUS, wxT("S&how audio status"));
678 menu_separator();
679 menu_special_sub(wxT("S&elect sound device"), reinterpret_cast<sound_select_menu*>(sounddev =
680 new sound_select_menu(this)));
681 blistener->set_sound_select(reinterpret_cast<sound_select_menu*>(sounddev));
685 void wxwin_mainwindow::request_paint()
687 gpanel->Refresh();
690 void wxwin_mainwindow::on_close(wxCloseEvent& e)
692 //Veto it for now, latter things will delete it.
693 e.Veto();
694 platform::queue("quit-emulator");
697 void wxwin_mainwindow::notify_update() throw()
699 if(!main_window_dirty) {
700 main_window_dirty = true;
701 gpanel->Refresh();
705 void wxwin_mainwindow::notify_update_status() throw()
707 if(!spanel->dirty) {
708 spanel->dirty = true;
709 spanel->Refresh();
713 void wxwin_mainwindow::notify_exit() throw()
715 join_emulator_thread();
716 Destroy();
719 #define NEW_KEYBINDING "A new binding..."
720 #define NEW_ALIAS "A new alias..."
721 #define NEW_WATCH "A new watch..."
723 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
725 try {
726 handle_menu_click_cancelable(e);
727 } catch(canceled_exception& e) {
728 //Ignore.
729 } catch(std::bad_alloc& e) {
730 OOM_panic();
731 } catch(std::exception& e) {
732 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
736 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
738 std::string filename;
739 bool s;
740 switch(e.GetId()) {
741 case wxID_FRAMEADVANCE:
742 platform::queue("+advance-frame");
743 platform::queue("-advance-frame");
744 return;
745 case wxID_SUBFRAMEADVANCE:
746 platform::queue("+advance-poll");
747 platform::queue("-advance-poll");
748 return;
749 case wxID_NEXTPOLL:
750 platform::queue("advance-skiplag");
751 return;
752 case wxID_PAUSE:
753 platform::queue("pause-emulator");
754 return;
755 case wxID_ERESET:
756 platform::queue("reset");
757 return;
758 case wxID_EXIT:
759 platform::queue("quit-emulator");
760 return;
761 case wxID_AUDIO_ENABLED:
762 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
763 return;
764 case wxID_SHOW_AUDIO_STATUS:
765 platform::queue("show-sound-status");
766 return;
767 case wxID_CANCEL_SAVES:
768 platform::queue("cancel-saves");
769 return;
770 case wxID_LOAD_MOVIE:
771 platform::queue("load-movie " + pick_file(this, "Load Movie", "."));
772 return;
773 case wxID_LOAD_STATE:
774 platform::queue("load " + pick_file(this, "Load State", "."));
775 return;
776 case wxID_LOAD_STATE_RO:
777 platform::queue("load-readonly " + pick_file(this, "Load State (Read-Only)", "."));
778 return;
779 case wxID_LOAD_STATE_RW:
780 platform::queue("load-state " + pick_file(this, "Load State (Read-Write)", "."));
781 return;
782 case wxID_LOAD_STATE_P:
783 platform::queue("load-preserve " + pick_file(this, "Load State (Preserve)", "."));
784 return;
785 case wxID_REWIND_MOVIE:
786 platform::queue("rewind-movie");
787 return;
788 case wxID_SAVE_MOVIE:
789 platform::queue("save-movie " + pick_file(this, "Save Movie", "."));
790 return;
791 case wxID_SAVE_STATE:
792 platform::queue("save-state " + pick_file(this, "Save State", "."));
793 return;
794 case wxID_SAVE_SCREENSHOT:
795 platform::queue("take-screenshot " + pick_file(this, "Save State", "."));
796 return;
797 case wxID_RUN_SCRIPT:
798 platform::queue("run-script " + pick_file_member(this, "Select Script", "."));
799 return;
800 case wxID_RUN_LUA:
801 platform::queue("run-lua " + pick_file(this, "Select Lua Script", "."));
802 return;
803 case wxID_EVAL_LUA:
804 platform::queue("evaluate-lua " + pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
805 return;
806 case wxID_READONLY_MODE:
807 s = menu_ischecked(wxID_READONLY_MODE);
808 runemufn([s]() {
809 movb.get_movie().readonly_mode(s);
810 if(!s)
811 lua_callback_do_readwrite();
812 update_movie_state();
814 return;
815 case wxID_EDIT_AXES:
816 wxeditor_axes_display(this);
817 return;
818 case wxID_EDIT_AUTHORS:
819 wxeditor_authors_display(this);
820 return;
821 case wxID_EDIT_SETTINGS:
822 wxeditor_settings_display(this);
823 return;
824 case wxID_EDIT_HOTKEYS:
825 wxeditor_hotkeys_display(this);
826 return;
827 case wxID_EDIT_KEYBINDINGS: {
828 modal_pause_holder hld;
829 std::set<std::string> bind;
830 runemufn([&bind]() { bind = keymapper::get_bindings(); });
831 std::vector<std::string> choices;
832 choices.push_back(NEW_KEYBINDING);
833 for(auto i : bind)
834 choices.push_back(i);
835 std::string key = pick_among(this, "Select binding", "Select keybinding to edit", choices);
836 if(key == NEW_KEYBINDING)
837 key = wxeditor_keyselect(this, false);
838 std::string old_command_value;
839 runemufn([&old_command_value, key]() { old_command_value = keymapper::get_command_for(key); });
840 std::string newcommand = pick_text(this, "Edit binding", "Enter new command for binding:",
841 old_command_value);
842 bool fault = false;
843 std::string faulttext;
844 runemufn([&fault, &faulttext, key, newcommand]() {
845 try {
846 keymapper::bind_for(key, newcommand);
847 } catch(std::exception& e) {
848 fault = true;
849 faulttext = e.what();
852 if(fault)
853 show_message_ok(this, "Error", "Can't bind key: " + faulttext, wxICON_EXCLAMATION);
854 return;
856 case wxID_EDIT_ALIAS: {
857 modal_pause_holder hld;
858 std::set<std::string> bind;
859 runemufn([&bind]() { bind = command::get_aliases(); });
860 std::vector<std::string> choices;
861 choices.push_back(NEW_ALIAS);
862 for(auto i : bind)
863 choices.push_back(i);
864 std::string alias = pick_among(this, "Select alias", "Select alias to edit", choices);
865 if(alias == NEW_ALIAS) {
866 alias = pick_text(this, "Enter alias name", "Enter name for the new alias:");
867 if(!command::valid_alias_name(alias)) {
868 show_message_ok(this, "Error", "Not a valid alias name: " + alias,
869 wxICON_EXCLAMATION);
870 throw canceled_exception();
873 std::string old_alias_value;
874 runemufn([alias, &old_alias_value]() { old_alias_value = command::get_alias_for(alias); });
875 std::string newcmd = pick_text(this, "Edit alias", "Enter new commands for alias:",
876 old_alias_value, true);
877 runemufn([alias, newcmd]() { command::set_alias_for(alias, newcmd); });
878 return;
880 case wxID_EDIT_JUKEBOX: {
881 modal_pause_holder hld;
882 std::vector<std::string> new_jukebox;
883 std::string x;
884 runemufn([&x]() {
885 for(auto i : get_jukebox_names())
886 x = x + i + "\n";
888 x = pick_text(this, "Configure jukebox", "List jukebox entries", x, true);
889 while(x != "") {
890 size_t split = x.find_first_of("\n");
891 std::string l;
892 if(split < x.length()) {
893 l = x.substr(0, split);
894 x = x.substr(split + 1);
895 } else {
896 l = x;
897 x = "";
899 istrip_CR(l);
900 if(l != "")
901 new_jukebox.push_back(l);
903 runemufn([&new_jukebox]() { set_jukebox_names(new_jukebox); });
904 notify_update_status();
905 return;
907 case wxID_EDIT_MEMORYWATCH: {
908 modal_pause_holder hld;
909 std::set<std::string> bind;
910 runemufn([&bind]() { bind = get_watches(); });
911 std::vector<std::string> choices;
912 choices.push_back(NEW_WATCH);
913 for(auto i : bind)
914 choices.push_back(i);
915 std::string watch = pick_among(this, "Select watch", "Select watch to edit", choices);
916 if(watch == NEW_WATCH)
917 watch = pick_text(this, "Enter watch name", "Enter name for the new watch:");
918 std::string newexpr = pick_text(this, "Edit watch", "Enter new expression for watch:",
919 get_watchexpr_for(watch));
920 runemufn([watch, newexpr]() { set_watchexpr_for(watch, newexpr); });
921 return;
923 case wxID_SAVE_MEMORYWATCH: {
924 modal_pause_holder hld;
925 std::set<std::string> old_watches;
926 runemufn([&old_watches]() { old_watches = get_watches(); });
927 std::string filename = pick_file(this, "Save watches to file", ".");
928 std::ofstream out(filename.c_str());
929 for(auto i : old_watches) {
930 std::string val;
931 runemufn([i, &val]() { val = get_watchexpr_for(i); });
932 out << i << std::endl << val << std::endl;
934 out.close();
935 return;
937 case wxID_LOAD_MEMORYWATCH: {
938 modal_pause_holder hld;
939 std::set<std::string> old_watches;
940 runemufn([&old_watches]() { old_watches = get_watches(); });
941 std::map<std::string, std::string> new_watches;
942 std::string filename = pick_file_member(this, "Choose memory watch file", ".");
944 try {
945 std::istream& in = open_file_relative(filename, "");
946 while(in) {
947 std::string wname;
948 std::string wexpr;
949 std::getline(in, wname);
950 std::getline(in, wexpr);
951 new_watches[wname] = wexpr;
953 delete &in;
954 } catch(std::exception& e) {
955 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
956 wxICON_EXCLAMATION);
957 return;
960 runemufn([&new_watches, &old_watches]() {
961 for(auto i : new_watches)
962 set_watchexpr_for(i.first, i.second);
963 for(auto i : old_watches)
964 if(!new_watches.count(i))
965 set_watchexpr_for(i, "");
967 return;
969 case wxID_MEMORY_SEARCH:
970 wxwindow_memorysearch_display();
971 return;
972 case wxID_ABOUT: {
973 std::ostringstream str;
974 str << "Version: lsnes rr" << lsnes_version << std::endl;
975 str << "Revision: " << lsnes_git_revision << std::endl;
976 str << "Core: " << bsnes_core_version << std::endl;
977 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
978 return;
980 case wxID_SHOW_STATUS: {
981 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
982 if(newstate && !spanel_shown)
983 toplevel->Add(spanel);
984 else if(!newstate && spanel_shown)
985 toplevel->Detach(spanel);
986 spanel_shown = newstate;
987 toplevel->Layout();
988 toplevel->SetSizeHints(this);
989 Fit();
990 return;