Wxwidgets: Allow loading ROMs and movies from commandline
[lsnes.git] / src / platform / wxwidgets / mainwindow.cpp
blob6acd83b8d5cdc01b86cb3b4e830dca0103248023
1 #include "lsnes.hpp"
3 #include "core/emucore.hpp"
5 #include "core/command.hpp"
6 #include "core/controller.hpp"
7 #include "core/controllerframe.hpp"
8 #include "core/dispatch.hpp"
9 #include "core/framebuffer.hpp"
10 #include "core/framerate.hpp"
11 #include "core/loadlib.hpp"
12 #include "lua/lua.hpp"
13 #include "core/mainloop.hpp"
14 #include "core/memorywatch.hpp"
15 #include "core/misc.hpp"
16 #include "core/moviedata.hpp"
17 #include "core/settings.hpp"
18 #include "core/window.hpp"
19 #include "library/minmax.hpp"
20 #include "library/string.hpp"
21 #include "library/zip.hpp"
23 #include <wx/dnd.h>
25 #include <cmath>
26 #include <vector>
27 #include <string>
29 #include "platform/wxwidgets/menu_dump.hpp"
30 #include "platform/wxwidgets/platform.hpp"
31 #include "platform/wxwidgets/window_mainwindow.hpp"
32 #include "platform/wxwidgets/window_messages.hpp"
33 #include "platform/wxwidgets/window_status.hpp"
35 extern "C"
37 #ifndef UINT64_C
38 #define UINT64_C(val) val##ULL
39 #endif
40 #include <libswscale/swscale.h>
43 enum
45 wxID_PAUSE = wxID_HIGHEST + 1,
46 wxID_FRAMEADVANCE,
47 wxID_SUBFRAMEADVANCE,
48 wxID_NEXTPOLL,
49 wxID_ERESET,
50 wxID_AUDIO_ENABLED,
51 wxID_SHOW_AUDIO_STATUS,
52 wxID_AUDIODEV_FIRST,
53 wxID_AUDIODEV_LAST = wxID_AUDIODEV_FIRST + 255,
54 wxID_SAVE_STATE,
55 wxID_SAVE_MOVIE,
56 wxID_SAVE_SUBTITLES,
57 wxID_LOAD_STATE,
58 wxID_LOAD_STATE_RO,
59 wxID_LOAD_STATE_RW,
60 wxID_LOAD_STATE_P,
61 wxID_LOAD_MOVIE,
62 wxID_RUN_SCRIPT,
63 wxID_RUN_LUA,
64 wxID_RESET_LUA,
65 wxID_EVAL_LUA,
66 wxID_SAVE_SCREENSHOT,
67 wxID_READONLY_MODE,
68 wxID_EDIT_AUTHORS,
69 wxID_AUTOHOLD_FIRST,
70 wxID_AUTOHOLD_LAST = wxID_AUTOHOLD_FIRST + 1023,
71 wxID_EDIT_MEMORYWATCH,
72 wxID_SAVE_MEMORYWATCH,
73 wxID_LOAD_MEMORYWATCH,
74 wxID_EDIT_SUBTITLES,
75 wxID_DUMP_FIRST,
76 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
77 wxID_REWIND_MOVIE,
78 wxID_MEMORY_SEARCH,
79 wxID_CANCEL_SAVES,
80 wxID_SHOW_STATUS,
81 wxID_SET_SPEED,
82 wxID_SET_VOLUME,
83 wxID_SPEED_5,
84 wxID_SPEED_10,
85 wxID_SPEED_17,
86 wxID_SPEED_20,
87 wxID_SPEED_25,
88 wxID_SPEED_33,
89 wxID_SPEED_50,
90 wxID_SPEED_100,
91 wxID_SPEED_150,
92 wxID_SPEED_200,
93 wxID_SPEED_300,
94 wxID_SPEED_500,
95 wxID_SPEED_1000,
96 wxID_SPEED_TURBO,
97 wxID_LOAD_LIBRARY,
98 wxID_SETTINGS,
99 wxID_SETTINGS_HOTKEYS,
100 wxID_RELOAD_ROM_IMAGE,
101 wxID_LOAD_ROM_IMAGE,
102 wxID_NEW_MOVIE,
103 wxID_SHOW_MESSAGES,
107 double horizontal_scale_factor = 1.0;
108 double vertical_scale_factor = 1.0;
109 int scaling_flags = SWS_POINT;
111 namespace
113 std::string last_volume = "0dB";
114 unsigned char* screen_buffer;
115 uint32_t old_width;
116 uint32_t old_height;
117 int old_flags = SWS_POINT;
118 bool main_window_dirty;
119 struct thread* emulation_thread;
121 wxString getname()
123 std::string windowname = "lsnes rr" + lsnes_version + "[" + bsnes_core_version + "]";
124 return towxstring(windowname);
127 struct emu_args
129 struct loaded_rom* rom;
130 struct moviefile* initial;
131 bool load_has_to_succeed;
134 void* emulator_main(void* _args)
136 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
137 try {
138 our_rom = args->rom;
139 struct moviefile* movie = args->initial;
140 bool has_to_succeed = args->load_has_to_succeed;
141 platform::flush_command_queue();
142 main_loop(*our_rom, *movie, has_to_succeed);
143 signal_program_exit();
144 } catch(std::bad_alloc& e) {
145 OOM_panic();
146 } catch(std::exception& e) {
147 messages << "FATAL: " << e.what() << std::endl;
148 platform::fatal_error();
150 return NULL;
153 void join_emulator_thread()
155 emulation_thread->join();
158 keygroup mouse_x("mouse_x", "mouse", keygroup::KT_MOUSE);
159 keygroup mouse_y("mouse_y", "mouse", keygroup::KT_MOUSE);
160 keygroup mouse_l("mouse_left", "mouse", keygroup::KT_KEY);
161 keygroup mouse_m("mouse_center", "mouse", keygroup::KT_KEY);
162 keygroup mouse_r("mouse_right", "mouse", keygroup::KT_KEY);
163 keygroup mouse_i("mouse_inwindow", "mouse", keygroup::KT_KEY);
165 void handle_wx_mouse(wxMouseEvent& e)
167 platform::queue(keypress(modifier_set(), mouse_x, e.GetX() / horizontal_scale_factor));
168 platform::queue(keypress(modifier_set(), mouse_y, e.GetY() / vertical_scale_factor));
169 if(e.Entering())
170 platform::queue(keypress(modifier_set(), mouse_i, 1));
171 if(e.Leaving())
172 platform::queue(keypress(modifier_set(), mouse_i, 0));
173 if(e.LeftDown())
174 platform::queue(keypress(modifier_set(), mouse_l, 1));
175 if(e.LeftUp())
176 platform::queue(keypress(modifier_set(), mouse_l, 0));
177 if(e.MiddleDown())
178 platform::queue(keypress(modifier_set(), mouse_m, 1));
179 if(e.MiddleUp())
180 platform::queue(keypress(modifier_set(), mouse_m, 0));
181 if(e.RightDown())
182 platform::queue(keypress(modifier_set(), mouse_r, 1));
183 if(e.RightUp())
184 platform::queue(keypress(modifier_set(), mouse_r, 0));
187 bool is_readonly_mode()
189 bool ret;
190 runemufn([&ret]() { ret = movb.get_movie().readonly_mode(); });
191 return ret;
194 bool UI_get_autohold(unsigned pid, unsigned idx)
196 bool ret;
197 runemufn([&ret, pid, idx]() { ret = controls.autohold(pid, idx); });
198 return ret;
201 void UI_change_autohold(unsigned pid, unsigned idx, bool newstate)
203 runemufn([pid, idx, newstate]() { controls.autohold(pid, idx, newstate); });
206 int UI_controller_index_by_logical(unsigned lid)
208 int ret;
209 runemufn([&ret, lid]() { ret = controls.lcid_to_pcid(lid); });
210 return ret;
213 int UI_button_id(unsigned pcid, unsigned lidx)
215 int ret;
216 runemufn([&ret, pcid, lidx]() { ret = controls.button_id(pcid, lidx); });
217 return ret;
220 void set_speed(double target)
222 std::string v = (stringfmt() << target).str();
223 if(target < 0)
224 setting::set("targetfps", "infinite");
225 else
226 setting::set("targetfps", v);
229 class controller_autohold_menu : public wxMenu
231 public:
232 controller_autohold_menu(unsigned lid);
233 void change_type();
234 bool is_dummy();
235 void on_select(wxCommandEvent& e);
236 void update(unsigned pid, unsigned ctrlnum, bool newstate);
237 private:
238 unsigned our_lid;
239 int our_pid;
240 std::vector<wxMenuItem*> entries;
241 unsigned enabled_entries;
242 std::map<unsigned, int> pidxs;
243 std::vector<bool> autoholds;
246 class autohold_menu : public wxMenu
248 public:
249 autohold_menu(wxwin_mainwindow* win);
250 void reconfigure();
251 void on_select(wxCommandEvent& e);
252 void update(unsigned pid, unsigned ctrlnum, bool newstate);
253 private:
254 std::vector<controller_autohold_menu*> menus;
255 std::vector<wxMenuItem*> entries;
258 class sound_select_menu : public wxMenu
260 public:
261 sound_select_menu(wxwin_mainwindow* win);
262 void update(const std::string& dev);
263 void on_select(wxCommandEvent& e);
264 private:
265 std::map<std::string, wxMenuItem*> items;
266 std::map<int, std::string> devices;
269 class sound_select_menu;
271 class broadcast_listener : public information_dispatch
273 public:
274 broadcast_listener(wxwin_mainwindow* win);
275 void set_sound_select(sound_select_menu* sdev);
276 void set_autohold_menu(autohold_menu* ah);
277 void on_sound_unmute(bool unmute) throw();
278 void on_sound_change(const std::string& dev) throw();
279 void on_mode_change(bool readonly) throw();
280 void on_autohold_update(unsigned pid, unsigned ctrlnum, bool newstate);
281 void on_autohold_reconfigure();
282 private:
283 wxwin_mainwindow* mainw;
284 sound_select_menu* sounddev;
285 autohold_menu* ahmenu;
288 controller_autohold_menu::controller_autohold_menu(unsigned lid)
290 auto limits = get_core_logical_controller_limits();
291 entries.resize(limits.second);
292 modal_pause_holder hld;
293 our_lid = lid;
294 for(unsigned i = 0; i < limits.second; i++) {
295 int id = wxID_AUTOHOLD_FIRST + limits.second * lid + i;
296 entries[i] = AppendCheckItem(id, towxstring(get_logical_button_name(i)));
298 change_type();
301 void controller_autohold_menu::change_type()
303 enabled_entries = 0;
304 runuifun([&autoholds, &pidxs, our_pid]() {
305 auto limits = get_core_logical_controller_limits();
306 autoholds.resize(limits.second);
307 for(unsigned i = 0; i < limits.second; i++) {
308 pidxs[i] = -1;
309 if(our_pid >= 0)
310 pidxs[i] = controls.button_id(our_pid, i);
311 if(pidxs[i] >= 0)
312 autoholds[i] = (our_pid > 0 && controls.autohold(our_pid, pidxs[i]));
313 else
314 autoholds[i] = false;
317 our_pid = controls.lcid_to_pcid(our_lid);
318 for(auto i : pidxs) {
319 if(i.second >= 0) {
320 entries[i.first]->Check(autoholds[i.first]);
321 entries[i.first]->Enable();
322 enabled_entries++;
323 } else {
324 entries[i.first]->Check(false);
325 entries[i.first]->Enable(false);
330 bool controller_autohold_menu::is_dummy()
332 return !enabled_entries;
335 void controller_autohold_menu::on_select(wxCommandEvent& e)
337 auto limits = get_core_logical_controller_limits();
338 int x = e.GetId();
339 if(x < wxID_AUTOHOLD_FIRST + our_lid * limits.second || x >= wxID_AUTOHOLD_FIRST *
340 (our_lid + 1) * limits.second) {
341 return;
343 unsigned lidx = (x - wxID_AUTOHOLD_FIRST) % limits.second;
344 modal_pause_holder hld;
345 int pid = controls.lcid_to_pcid(our_lid);
346 if(pid < 0 || !entries[lidx])
347 return;
348 int pidx = controls.button_id(pid, lidx);
349 if(pidx < 0)
350 return;
351 //Autohold change on pid=pid, ctrlindx=idx, state
352 bool newstate = entries[lidx]->IsChecked();
353 UI_change_autohold(pid, pidx, newstate);
356 void controller_autohold_menu::update(unsigned pid, unsigned ctrlnum, bool newstate)
358 modal_pause_holder hld;
359 if(our_pid < 0 || static_cast<unsigned>(pid) != our_pid)
360 return;
361 auto limits = get_core_logical_controller_limits();
362 for(unsigned i = 0; i < limits.second; i++) {
363 if(pidxs[i] < 0 || static_cast<unsigned>(pidxs[i]) != ctrlnum)
364 continue;
365 entries[i]->Check(newstate);
370 autohold_menu::autohold_menu(wxwin_mainwindow* win)
372 auto limits = get_core_logical_controller_limits();
373 entries.resize(limits.first);
374 menus.resize(limits.first);
375 for(unsigned i = 0; i < limits.first; i++) {
376 std::ostringstream str;
377 str << "Controller #&" << (i + 1);
378 menus[i] = new controller_autohold_menu(i);
379 entries[i] = AppendSubMenu(menus[i], towxstring(str.str()));
380 entries[i]->Enable(!menus[i]->is_dummy());
382 win->Connect(wxID_AUTOHOLD_FIRST, wxID_AUTOHOLD_LAST, wxEVT_COMMAND_MENU_SELECTED,
383 wxCommandEventHandler(autohold_menu::on_select), NULL, this);
384 reconfigure();
387 void autohold_menu::reconfigure()
389 modal_pause_holder hld;
390 auto limits = get_core_logical_controller_limits();
391 for(unsigned i = 0; i < limits.first; i++) {
392 menus[i]->change_type();
393 entries[i]->Enable(!menus[i]->is_dummy());
397 void autohold_menu::on_select(wxCommandEvent& e)
399 auto limits = get_core_logical_controller_limits();
400 for(unsigned i = 0; i < limits.first; i++)
401 menus[i]->on_select(e);
404 void autohold_menu::update(unsigned pid, unsigned ctrlnum, bool newstate)
406 auto limits = get_core_logical_controller_limits();
407 for(unsigned i = 0; i < limits.first; i++)
408 menus[i]->update(pid, ctrlnum, newstate);
411 sound_select_menu::sound_select_menu(wxwin_mainwindow* win)
413 std::string curdev = platform::get_sound_device();
414 int j = wxID_AUDIODEV_FIRST;
415 for(auto i : platform::get_sound_devices()) {
416 items[i.first] = AppendRadioItem(j, towxstring(i.first + "(" + i.second + ")"));
417 devices[j] = i.first;
418 if(i.first == curdev)
419 items[i.first]->Check();
420 win->Connect(j, wxEVT_COMMAND_MENU_SELECTED,
421 wxCommandEventHandler(sound_select_menu::on_select), NULL, this);
422 j++;
426 void sound_select_menu::update(const std::string& dev)
428 items[dev]->Check();
431 void sound_select_menu::on_select(wxCommandEvent& e)
433 std::string devname = devices[e.GetId()];
434 if(devname != "")
435 runemufn([devname]() { platform::set_sound_device(devname); });
438 broadcast_listener::broadcast_listener(wxwin_mainwindow* win)
439 : information_dispatch("wxwidgets-broadcast-listener")
441 mainw = win;
444 void broadcast_listener::set_sound_select(sound_select_menu* sdev)
446 sounddev = sdev;
449 void broadcast_listener::set_autohold_menu(autohold_menu* ah)
451 ahmenu = ah;
454 void broadcast_listener::on_sound_unmute(bool unmute) throw()
456 runuifun([unmute, mainw]() { mainw->menu_check(wxID_AUDIO_ENABLED, unmute); });
459 void broadcast_listener::on_sound_change(const std::string& dev) throw()
461 runuifun([dev, sounddev]() { if(sounddev) sounddev->update(dev); });
464 void broadcast_listener::on_mode_change(bool readonly) throw()
466 runuifun([readonly, mainw]() { mainw->menu_check(wxID_READONLY_MODE, readonly); });
469 void broadcast_listener::on_autohold_update(unsigned pid, unsigned ctrlnum, bool newstate)
471 runuifun([pid, ctrlnum, newstate, ahmenu]() { ahmenu->update(pid, ctrlnum, newstate); });
474 void broadcast_listener::on_autohold_reconfigure()
476 runuifun([ahmenu]() { ahmenu->reconfigure(); });
479 path_setting moviepath_setting("moviepath");
480 path_setting rompath_setting("rompath");
482 std::string movie_path()
484 return setting::get("moviepath");
487 std::string rom_path()
489 return setting::get("rompath");
492 bool is_lsnes_movie(const std::string& filename)
494 try {
495 zip_reader r(filename);
496 std::istream& s = r["systemid"];
497 std::string s2;
498 std::getline(s, s2);
499 delete &s;
500 istrip_CR(s2);
501 return (s2 == "lsnes-rr1");
502 } catch(...) {
503 return false;
507 class loadfile : public wxFileDropTarget
509 public:
510 bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
512 if(filenames.Count() != 1)
513 return false;
514 if(is_lsnes_movie(tostdstring(filenames[0])))
515 platform::queue("load-smart " + tostdstring(filenames[0]));
516 else {
517 platform::queue("unpause-emulator");
518 platform::queue("reload-rom " + tostdstring(filenames[0]));
520 return true;
525 void boot_emulator(loaded_rom& rom, moviefile& movie)
527 try {
528 struct emu_args* a = new emu_args;
529 a->rom = &rom;
530 a->initial = &movie;
531 a->load_has_to_succeed = false;
532 modal_pause_holder hld;
533 emulation_thread = &thread::create(emulator_main, a);
534 main_window = new wxwin_mainwindow();
535 main_window->Show();
536 } catch(std::bad_alloc& e) {
537 OOM_panic();
541 wxwin_mainwindow::panel::panel(wxWindow* win)
542 : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS)
544 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
545 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
546 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
547 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
548 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
549 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
550 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
551 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
552 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
553 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
554 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
555 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
556 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
557 SetMinSize(wxSize(512, 448));
560 void wxwin_mainwindow::menu_start(wxString name)
562 while(!upper.empty())
563 upper.pop();
564 current_menu = new wxMenu();
565 menubar->Append(current_menu, name);
568 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
570 while(!upper.empty())
571 upper.pop();
572 menubar->Append(menu, name);
573 current_menu = NULL;
576 void wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
578 current_menu->AppendSubMenu(menu, name);
581 void wxwin_mainwindow::menu_entry(int id, wxString name)
583 current_menu->Append(id, name);
584 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
585 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
588 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
590 checkitems[id] = current_menu->AppendCheckItem(id, name);
591 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
592 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
595 void wxwin_mainwindow::menu_start_sub(wxString name)
597 wxMenu* old = current_menu;
598 upper.push(current_menu);
599 current_menu = new wxMenu();
600 old->AppendSubMenu(current_menu, name);
603 void wxwin_mainwindow::menu_end_sub()
605 current_menu = upper.top();
606 upper.pop();
609 bool wxwin_mainwindow::menu_ischecked(int id)
611 if(checkitems.count(id))
612 return checkitems[id]->IsChecked();
613 else
614 return false;
617 void wxwin_mainwindow::menu_check(int id, bool newstate)
619 if(checkitems.count(id))
620 return checkitems[id]->Check(newstate);
621 else
622 return;
625 void wxwin_mainwindow::menu_separator()
627 current_menu->AppendSeparator();
630 void wxwin_mainwindow::panel::request_paint()
632 Refresh();
635 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
637 render_framebuffer();
638 static struct SwsContext* ctx;
639 uint8_t* srcp[1];
640 int srcs[1];
641 uint8_t* dstp[1];
642 int dsts[1];
643 wxPaintDC dc(this);
644 uint32_t tw = main_screen.get_width() * horizontal_scale_factor + 0.5;
645 uint32_t th = main_screen.get_height() * vertical_scale_factor + 0.5;
646 if(!tw || !th) {
647 main_window_dirty = false;
648 return;
650 if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags) {
651 if(screen_buffer)
652 delete[] screen_buffer;
653 old_height = th;
654 old_width = tw;
655 old_flags = scaling_flags;
656 uint32_t w = main_screen.get_width();
657 uint32_t h = main_screen.get_height();
658 if(w && h)
659 ctx = sws_getCachedContext(ctx, w, h, PIX_FMT_RGBA, tw, th, PIX_FMT_BGR24, scaling_flags,
660 NULL, NULL, NULL);
661 tw = max(tw, static_cast<uint32_t>(128));
662 th = max(th, static_cast<uint32_t>(112));
663 screen_buffer = new unsigned char[tw * th * 3];
664 SetMinSize(wxSize(tw, th));
665 signal_resize_needed();
667 srcs[0] = 4 * main_screen.get_width();
668 dsts[0] = 3 * tw;
669 srcp[0] = reinterpret_cast<unsigned char*>(main_screen.rowptr(0));
670 dstp[0] = screen_buffer;
671 memset(screen_buffer, 0, tw * th * 3);
672 if(main_screen.get_width() && main_screen.get_height())
673 sws_scale(ctx, srcp, srcs, 0, main_screen.get_height(), dstp, dsts);
674 wxBitmap bmp(wxImage(tw, th, screen_buffer, true));
675 dc.DrawBitmap(bmp, 0, 0, false);
676 main_window_dirty = false;
679 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
681 //Blank.
684 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
686 handle_wx_keyboard(e, true);
689 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
691 handle_wx_keyboard(e, false);
694 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
696 handle_wx_mouse(e);
699 wxwin_mainwindow::wxwin_mainwindow()
700 : wxFrame(NULL, wxID_ANY, getname(), wxDefaultPosition, wxSize(-1, -1),
701 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX)
703 broadcast_listener* blistener = new broadcast_listener(this);
704 Centre();
705 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
706 toplevel->Add(gpanel = new panel(this), 1, wxGROW);
707 toplevel->Add(spanel = new wxwin_status::panel(this, gpanel, 20), 1, wxGROW);
708 spanel_shown = true;
709 toplevel->SetSizeHints(this);
710 SetSizer(toplevel);
711 Fit();
712 gpanel->SetFocus();
713 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
714 menubar = new wxMenuBar;
715 SetMenuBar(menubar);
717 menu_start(wxT("File"));
718 menu_start_sub(wxT("New"));
719 menu_entry(wxID_NEW_MOVIE, wxT("Movie..."));
720 menu_end_sub();
721 menu_start_sub(wxT("Load"));
722 menu_entry(wxID_LOAD_STATE, wxT("State..."));
723 menu_entry(wxID_LOAD_STATE_RO, wxT("State (readonly)..."));
724 menu_entry(wxID_LOAD_STATE_RW, wxT("State (read-write)..."));
725 menu_entry(wxID_LOAD_STATE_P, wxT("State (preserve input)..."));
726 menu_entry(wxID_LOAD_MOVIE, wxT("Movie..."));
727 if(load_library_supported) {
728 menu_separator();
729 menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + library_is_called));
731 menu_separator();
732 menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM"));
733 menu_entry(wxID_LOAD_ROM_IMAGE, wxT("ROM..."));
734 menu_end_sub();
735 menu_start_sub(wxT("Save"));
736 menu_entry(wxID_SAVE_STATE, wxT("State..."));
737 menu_entry(wxID_SAVE_MOVIE, wxT("Movie..."));
738 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Screenshot..."));
739 menu_entry(wxID_SAVE_SUBTITLES, wxT("Subtitles..."));
740 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pending saves"));
741 menu_end_sub();
742 menu_separator();
743 menu_entry(wxID_EXIT, wxT("Quit"));
745 menu_start(wxT("System"));
746 menu_entry(wxID_PAUSE, wxT("Pause/Unpause"));
747 menu_entry(wxID_FRAMEADVANCE, wxT("Step frame"));
748 menu_entry(wxID_SUBFRAMEADVANCE, wxT("Step subframe"));
749 menu_entry(wxID_NEXTPOLL, wxT("Step poll"));
750 menu_entry(wxID_ERESET, wxT("Reset"));
752 menu_start(wxT("Movie"));
753 menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode"));
754 menu_check(wxID_READONLY_MODE, is_readonly_mode());
755 menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors..."));
756 menu_entry(wxID_EDIT_SUBTITLES, wxT("Edit subtitles..."));
757 menu_separator();
758 menu_entry(wxID_REWIND_MOVIE, wxT("Rewind to start"));
760 //Autohold menu: (ACOS)
761 menu_special(wxT("Autohold"), reinterpret_cast<autohold_menu*>(ahmenu = new autohold_menu(this)));
762 blistener->set_autohold_menu(reinterpret_cast<autohold_menu*>(ahmenu));
764 menu_start(wxT("Speed"));
765 menu_entry(wxID_SPEED_5, wxT("1/20x"));
766 menu_entry(wxID_SPEED_10, wxT("1/10x"));
767 menu_entry(wxID_SPEED_17, wxT("1/6x"));
768 menu_entry(wxID_SPEED_20, wxT("1/5x"));
769 menu_entry(wxID_SPEED_25, wxT("1/4x"));
770 menu_entry(wxID_SPEED_33, wxT("1/3x"));
771 menu_entry(wxID_SPEED_50, wxT("1/2x"));
772 menu_entry(wxID_SPEED_100, wxT("1x"));
773 menu_entry(wxID_SPEED_150, wxT("1.5x"));
774 menu_entry(wxID_SPEED_200, wxT("2x"));
775 menu_entry(wxID_SPEED_300, wxT("3x"));
776 menu_entry(wxID_SPEED_500, wxT("5x"));
777 menu_entry(wxID_SPEED_1000, wxT("10x"));
778 menu_entry(wxID_SPEED_TURBO, wxT("Turbo"));
779 menu_entry(wxID_SET_SPEED, wxT("Set..."));
781 menu_start(wxT("Tools"));
782 menu_entry(wxID_RUN_SCRIPT, wxT("Run batch file..."));
783 if(lua_supported) {
784 menu_separator();
785 menu_entry(wxID_EVAL_LUA, wxT("Evaluate Lua statement..."));
786 menu_entry(wxID_RUN_LUA, wxT("Run Lua script..."));
787 menu_separator();
788 menu_entry(wxID_RESET_LUA, wxT("Reset Lua VM"));
790 menu_separator();
791 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch..."));
792 menu_separator();
793 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch..."));
794 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch..."));
795 menu_separator();
796 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
797 menu_separator();
798 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
799 wxID_DUMP_FIRST, wxID_DUMP_LAST)));
801 menu_start(wxT("Configure"));
802 menu_entry_check(wxID_SHOW_STATUS, wxT("Show/Hide status panel"));
803 menu_check(wxID_SHOW_STATUS, true);
804 menu_entry(wxID_SHOW_MESSAGES, wxT("Show messages"));
805 menu_entry(wxID_SETTINGS, wxT("Configure emulator..."));
806 menu_entry(wxID_SETTINGS_HOTKEYS, wxT("Configure hotkeys..."));
807 if(platform::sound_initialized()) {
808 menu_separator();
809 menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled"));
810 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
811 menu_entry(wxID_SET_VOLUME, wxT("Set Sound volume"));
812 menu_entry(wxID_SHOW_AUDIO_STATUS, wxT("Show audio status"));
813 menu_special_sub(wxT("Select sound device"), reinterpret_cast<sound_select_menu*>(sounddev =
814 new sound_select_menu(this)));
815 blistener->set_sound_select(reinterpret_cast<sound_select_menu*>(sounddev));
818 menu_start(wxT("Help"));
819 menu_entry(wxID_ABOUT, wxT("About..."));
821 gpanel->SetDropTarget(new loadfile());
822 spanel->SetDropTarget(new loadfile());
825 void wxwin_mainwindow::request_paint()
827 gpanel->Refresh();
830 void wxwin_mainwindow::on_close(wxCloseEvent& e)
832 //Veto it for now, latter things will delete it.
833 e.Veto();
834 platform::queue("quit-emulator");
837 void wxwin_mainwindow::notify_update() throw()
839 if(!main_window_dirty) {
840 main_window_dirty = true;
841 gpanel->Refresh();
845 void wxwin_mainwindow::notify_resized() throw()
847 toplevel->Layout();
848 toplevel->SetSizeHints(this);
849 Fit();
852 void wxwin_mainwindow::notify_update_status() throw()
854 if(!spanel->dirty) {
855 spanel->dirty = true;
856 spanel->Refresh();
860 void wxwin_mainwindow::notify_exit() throw()
862 wxwidgets_exiting = true;
863 join_emulator_thread();
864 Destroy();
867 #define NEW_KEYBINDING "A new binding..."
868 #define NEW_ALIAS "A new alias..."
869 #define NEW_WATCH "A new watch..."
871 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
873 try {
874 handle_menu_click_cancelable(e);
875 } catch(canceled_exception& e) {
876 //Ignore.
877 } catch(std::bad_alloc& e) {
878 OOM_panic();
879 } catch(std::exception& e) {
880 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
884 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
886 std::string filename;
887 bool s;
888 switch(e.GetId()) {
889 case wxID_FRAMEADVANCE:
890 platform::queue("+advance-frame");
891 platform::queue("-advance-frame");
892 return;
893 case wxID_SUBFRAMEADVANCE:
894 platform::queue("+advance-poll");
895 platform::queue("-advance-poll");
896 return;
897 case wxID_NEXTPOLL:
898 platform::queue("advance-skiplag");
899 return;
900 case wxID_PAUSE:
901 platform::queue("pause-emulator");
902 return;
903 case wxID_ERESET:
904 platform::queue("reset");
905 return;
906 case wxID_EXIT:
907 platform::queue("quit-emulator");
908 return;
909 case wxID_AUDIO_ENABLED:
910 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
911 return;
912 case wxID_SHOW_AUDIO_STATUS:
913 platform::queue("show-sound-status");
914 return;
915 case wxID_CANCEL_SAVES:
916 platform::queue("cancel-saves");
917 return;
918 case wxID_LOAD_MOVIE:
919 platform::queue("load-movie " + pick_file(this, "Load Movie", movie_path(), false));
920 return;
921 case wxID_LOAD_STATE:
922 platform::queue("load " + pick_file(this, "Load State", movie_path(), false));
923 return;
924 case wxID_LOAD_STATE_RO:
925 platform::queue("load-readonly " + pick_file(this, "Load State (Read-Only)", movie_path(), false));
926 return;
927 case wxID_LOAD_STATE_RW:
928 platform::queue("load-state " + pick_file(this, "Load State (Read-Write)", movie_path(), false));
929 return;
930 case wxID_LOAD_STATE_P:
931 platform::queue("load-preserve " + pick_file(this, "Load State (Preserve)", movie_path(), false));
932 return;
933 case wxID_REWIND_MOVIE:
934 platform::queue("rewind-movie");
935 return;
936 case wxID_SAVE_MOVIE:
937 platform::queue("save-movie " + pick_file(this, "Save Movie", movie_path(), true));
938 return;
939 case wxID_SAVE_SUBTITLES:
940 platform::queue("save-subtitle " + pick_file(this, "Save Subtitle (.sub)", movie_path(), true));
941 return;
942 case wxID_SAVE_STATE:
943 platform::queue("save-state " + pick_file(this, "Save State", movie_path(), true));
944 return;
945 case wxID_SAVE_SCREENSHOT:
946 platform::queue("take-screenshot " + pick_file(this, "Save Screenshot", movie_path(), true));
947 return;
948 case wxID_RUN_SCRIPT:
949 platform::queue("run-script " + pick_file_member(this, "Select Script", "."));
950 return;
951 case wxID_RUN_LUA:
952 platform::queue("run-lua " + pick_file(this, "Select Lua Script", ".", false));
953 return;
954 case wxID_RESET_LUA:
955 platform::queue("reset-lua");
956 return;
957 case wxID_EVAL_LUA:
958 platform::queue("evaluate-lua " + pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
959 return;
960 case wxID_READONLY_MODE:
961 s = menu_ischecked(wxID_READONLY_MODE);
962 runemufn([s]() {
963 movb.get_movie().readonly_mode(s);
964 if(!s)
965 lua_callback_do_readwrite();
966 update_movie_state();
968 return;
969 case wxID_EDIT_AUTHORS:
970 wxeditor_authors_display(this);
971 return;
972 case wxID_EDIT_SUBTITLES:
973 wxeditor_subtitles_display(this);
974 return;
975 case wxID_EDIT_MEMORYWATCH:
976 wxeditor_memorywatch_display(this);
977 return;
978 case wxID_SAVE_MEMORYWATCH: {
979 modal_pause_holder hld;
980 std::set<std::string> old_watches;
981 runemufn([&old_watches]() { old_watches = get_watches(); });
982 std::string filename = pick_file(this, "Save watches to file", ".", true);
983 std::ofstream out(filename.c_str());
984 for(auto i : old_watches) {
985 std::string val;
986 runemufn([i, &val]() { val = get_watchexpr_for(i); });
987 out << i << std::endl << val << std::endl;
989 out.close();
990 return;
992 case wxID_LOAD_MEMORYWATCH: {
993 modal_pause_holder hld;
994 std::set<std::string> old_watches;
995 runemufn([&old_watches]() { old_watches = get_watches(); });
996 std::map<std::string, std::string> new_watches;
997 std::string filename = pick_file_member(this, "Choose memory watch file", ".");
999 try {
1000 std::istream& in = open_file_relative(filename, "");
1001 while(in) {
1002 std::string wname;
1003 std::string wexpr;
1004 std::getline(in, wname);
1005 std::getline(in, wexpr);
1006 new_watches[strip_CR(wname)] = strip_CR(wexpr);
1008 delete &in;
1009 } catch(std::exception& e) {
1010 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
1011 wxICON_EXCLAMATION);
1012 return;
1015 runemufn([&new_watches, &old_watches]() {
1016 for(auto i : new_watches)
1017 set_watchexpr_for(i.first, i.second);
1018 for(auto i : old_watches)
1019 if(!new_watches.count(i))
1020 set_watchexpr_for(i, "");
1022 return;
1024 case wxID_MEMORY_SEARCH:
1025 wxwindow_memorysearch_display();
1026 return;
1027 case wxID_ABOUT: {
1028 std::ostringstream str;
1029 str << "Version: lsnes rr" << lsnes_version << std::endl;
1030 str << "Revision: " << lsnes_git_revision << std::endl;
1031 str << "Core: " << bsnes_core_version << std::endl;
1032 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1033 return;
1035 case wxID_SHOW_STATUS: {
1036 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
1037 if(newstate)
1038 spanel->Show();
1039 if(newstate && !spanel_shown)
1040 toplevel->Add(spanel, 1, wxGROW);
1041 else if(!newstate && spanel_shown)
1042 toplevel->Detach(spanel);
1043 if(!newstate)
1044 spanel->Hide();
1045 spanel_shown = newstate;
1046 toplevel->Layout();
1047 toplevel->SetSizeHints(this);
1048 Fit();
1049 return;
1051 case wxID_SET_SPEED: {
1052 bool bad = false;
1053 std::string value = setting::is_set("targetfps") ? setting::get("targetfps") : "";
1054 value = pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value);
1055 try {
1056 setting::set("targetfps", value);
1057 } catch(...) {
1058 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1060 return;
1062 case wxID_SET_VOLUME: {
1063 std::string value;
1064 regex_results r;
1065 double parsed = 1;
1066 value = pick_text(this, "Set volume", "Enter volume in absolute units, percentage (%) or dB:",
1067 last_volume);
1068 if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)", value))
1069 parsed = strtod(r[1].c_str(), NULL);
1070 else if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)%", value))
1071 parsed = strtod(r[1].c_str(), NULL) / 100;
1072 else if(r = regex("([+-]?([0-9]*.[0-9]+|[0-9]+))dB", value))
1073 parsed = pow(10, strtod(r[1].c_str(), NULL) / 20);
1074 else {
1075 wxMessageBox(wxT("Invalid volume"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1076 return;
1078 last_volume = value;
1079 runemufn([parsed]() { platform::global_volume = parsed; });
1080 return;
1082 case wxID_SPEED_5:
1083 set_speed(5);
1084 break;
1085 case wxID_SPEED_10:
1086 set_speed(10);
1087 break;
1088 case wxID_SPEED_17:
1089 set_speed(16.66666666666);
1090 break;
1091 case wxID_SPEED_20:
1092 set_speed(20);
1093 break;
1094 case wxID_SPEED_25:
1095 set_speed(25);
1096 break;
1097 case wxID_SPEED_33:
1098 set_speed(33.3333333333333);
1099 break;
1100 case wxID_SPEED_50:
1101 set_speed(50);
1102 break;
1103 case wxID_SPEED_100:
1104 set_speed(100);
1105 break;
1106 case wxID_SPEED_150:
1107 set_speed(150);
1108 break;
1109 case wxID_SPEED_200:
1110 set_speed(200);
1111 break;
1112 case wxID_SPEED_300:
1113 set_speed(300);
1114 break;
1115 case wxID_SPEED_500:
1116 set_speed(500);
1117 break;
1118 case wxID_SPEED_1000:
1119 set_speed(1000);
1120 break;
1121 case wxID_SPEED_TURBO:
1122 set_speed(-1);
1123 break;
1124 case wxID_LOAD_LIBRARY: {
1125 std::string name = std::string("load ") + library_is_called;
1126 load_library(pick_file(this, name, ".", false));
1127 break;
1129 case wxID_SETTINGS:
1130 wxsetingsdialog_display(this, false);
1131 break;
1132 case wxID_SETTINGS_HOTKEYS:
1133 wxsetingsdialog_display(this, true);
1134 break;
1135 case wxID_LOAD_ROM_IMAGE:
1136 filename = pick_file_member(this, "Select new ROM image", rom_path());
1137 platform::queue("unpause-emulator");
1138 platform::queue("reload-rom " + filename);
1139 return;
1140 case wxID_RELOAD_ROM_IMAGE:
1141 platform::queue("unpause-emulator");
1142 platform::queue("reload-rom");
1143 return;
1144 case wxID_NEW_MOVIE:
1145 show_projectwindow(this);
1146 return;
1147 case wxID_SHOW_MESSAGES:
1148 msg_window->reshow();
1149 return;