Allow button display symbols to be Unicode characters
[lsnes.git] / src / platform / wxwidgets / mainwindow.cpp
bloba481a17cd121034f68ac487c08d19ae6c15eecf3
1 #include "lsnes.hpp"
3 #include <wx/dnd.h>
4 #include "platform/wxwidgets/menu_dump.hpp"
5 #include "platform/wxwidgets/platform.hpp"
6 #include "platform/wxwidgets/window_mainwindow.hpp"
7 #include "platform/wxwidgets/window_messages.hpp"
8 #include "platform/wxwidgets/window_status.hpp"
10 #include "core/audioapi.hpp"
11 #include "core/command.hpp"
12 #include "core/controller.hpp"
13 #include "core/controllerframe.hpp"
14 #include "core/dispatch.hpp"
15 #include "core/framebuffer.hpp"
16 #include "core/framerate.hpp"
17 #include "interface/romtype.hpp"
18 #include "core/loadlib.hpp"
19 #include "lua/lua.hpp"
20 #include "core/mainloop.hpp"
21 #include "core/memorywatch.hpp"
22 #include "core/misc.hpp"
23 #include "core/moviedata.hpp"
24 #include "core/settings.hpp"
25 #include "core/window.hpp"
26 #include "library/minmax.hpp"
27 #include "library/string.hpp"
28 #include "library/zip.hpp"
30 #include <cmath>
31 #include <vector>
32 #include <string>
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_EHRESET,
51 wxID_AUDIO_ENABLED,
52 wxID_AUDIO_DEVICE,
53 wxID_SAVE_STATE,
54 wxID_SAVE_MOVIE,
55 wxID_SAVE_SUBTITLES,
56 wxID_LOAD_STATE,
57 wxID_LOAD_STATE_RO,
58 wxID_LOAD_STATE_RW,
59 wxID_LOAD_STATE_P,
60 wxID_LOAD_MOVIE,
61 wxID_RUN_SCRIPT,
62 wxID_RUN_LUA,
63 wxID_RESET_LUA,
64 wxID_EVAL_LUA,
65 wxID_SAVE_SCREENSHOT,
66 wxID_READONLY_MODE,
67 wxID_EDIT_AUTHORS,
68 wxID_AUTOHOLD,
69 wxID_EDIT_MEMORYWATCH,
70 wxID_SAVE_MEMORYWATCH,
71 wxID_LOAD_MEMORYWATCH,
72 wxID_EDIT_SUBTITLES,
73 wxID_EDIT_VSUBTITLES,
74 wxID_DUMP_FIRST,
75 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
76 wxID_REWIND_MOVIE,
77 wxID_MEMORY_SEARCH,
78 wxID_CANCEL_SAVES,
79 wxID_SHOW_STATUS,
80 wxID_SET_SPEED,
81 wxID_SPEED_5,
82 wxID_SPEED_10,
83 wxID_SPEED_17,
84 wxID_SPEED_20,
85 wxID_SPEED_25,
86 wxID_SPEED_33,
87 wxID_SPEED_50,
88 wxID_SPEED_100,
89 wxID_SPEED_150,
90 wxID_SPEED_200,
91 wxID_SPEED_300,
92 wxID_SPEED_500,
93 wxID_SPEED_1000,
94 wxID_SPEED_TURBO,
95 wxID_LOAD_LIBRARY,
96 wxID_SETTINGS,
97 wxID_SETTINGS_HOTKEYS,
98 wxID_SETTINGS_CONTROLLERS,
99 wxID_RELOAD_ROM_IMAGE,
100 wxID_LOAD_ROM_IMAGE,
101 wxID_NEW_MOVIE,
102 wxID_SHOW_MESSAGES,
103 wxID_DEDICATED_MEMORY_WATCH,
104 wxID_RMOVIE_FIRST,
105 wxID_RMOVIE_LAST = wxID_RMOVIE_FIRST + 16,
106 wxID_RROM_FIRST,
107 wxID_RROM_LAST = wxID_RROM_FIRST + 16,
108 wxID_CONFLICTRESOLUTION,
109 wxID_VUDISPLAY,
110 wxID_MOVIE_EDIT,
111 wxID_TASINPUT
115 double horizontal_scale_factor = 1.0;
116 double vertical_scale_factor = 1.0;
117 int scaling_flags = SWS_POINT;
118 bool hflip_enabled = false;
119 bool vflip_enabled = false;
120 bool rotate_enabled = false;
122 namespace
124 std::string last_volume = "0dB";
125 std::string last_volume_record = "0dB";
126 std::string last_volume_voice = "0dB";
127 unsigned char* screen_buffer;
128 uint32_t* rotate_buffer;
129 uint32_t old_width;
130 uint32_t old_height;
131 int old_flags = SWS_POINT;
132 bool old_hflip = false;
133 bool old_vflip = false;
134 bool old_rotate = false;
135 bool main_window_dirty;
136 thread_class* emulation_thread;
138 double pick_volume(wxWindow* win, const std::string& title, std::string& last)
140 std::string value;
141 regex_results r;
142 double parsed = 1;
143 value = pick_text(win, title, "Enter volume in absolute units, percentage (%) or dB:",
144 last);
145 if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)", value))
146 parsed = strtod(r[1].c_str(), NULL);
147 else if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)%", value))
148 parsed = strtod(r[1].c_str(), NULL) / 100;
149 else if(r = regex("([+-]?([0-9]*.[0-9]+|[0-9]+))dB", value))
150 parsed = pow(10, strtod(r[1].c_str(), NULL) / 20);
151 else {
152 wxMessageBox(wxT("Invalid volume"), _T("Error"), wxICON_EXCLAMATION | wxOK, win);
153 return -1;
155 last = value;
156 return parsed;
159 void recent_rom_selected(const std::string& file)
161 platform::queue("unpause-emulator");
162 platform::queue("reload-rom " + file);
165 void recent_movie_selected(const std::string& file)
167 platform::queue("load-smart " + file);
170 wxString getname()
172 std::string windowname = "lsnes rr" + lsnes_version + " [" + our_rom->rtype->get_core_identifier()
173 + "]";
174 return towxstring(windowname);
177 struct emu_args
179 struct loaded_rom* rom;
180 struct moviefile* initial;
181 bool load_has_to_succeed;
184 void* emulator_main(void* _args)
186 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
187 try {
188 our_rom = args->rom;
189 messages << "Using core: " << our_rom->rtype->get_core_identifier() << std::endl;
190 struct moviefile* movie = args->initial;
191 bool has_to_succeed = args->load_has_to_succeed;
192 platform::flush_command_queue();
193 main_loop(*our_rom, *movie, has_to_succeed);
194 signal_program_exit();
195 } catch(std::bad_alloc& e) {
196 OOM_panic();
197 } catch(std::exception& e) {
198 messages << "FATAL: " << e.what() << std::endl;
199 platform::fatal_error();
201 return NULL;
204 void join_emulator_thread()
206 emulation_thread->join();
209 keyboard_mouse_calibration mouse_cal = {0};
210 keyboard_key_mouse mouse_x(lsnes_kbd, "mouse_x", "mouse", mouse_cal);
211 keyboard_key_mouse mouse_y(lsnes_kbd, "mouse_y", "mouse", mouse_cal);
212 keyboard_key_key mouse_l(lsnes_kbd, "mouse_left", "mouse");
213 keyboard_key_key mouse_m(lsnes_kbd, "mouse_center", "mouse");
214 keyboard_key_key mouse_r(lsnes_kbd, "mouse_right", "mouse");
215 keyboard_key_key mouse_i(lsnes_kbd, "mouse_inwindow", "mouse");
217 void handle_wx_mouse(wxMouseEvent& e)
219 platform::queue(keypress(keyboard_modifier_set(), mouse_x, e.GetX() / horizontal_scale_factor));
220 platform::queue(keypress(keyboard_modifier_set(), mouse_y, e.GetY() / vertical_scale_factor));
221 if(e.Entering())
222 platform::queue(keypress(keyboard_modifier_set(), mouse_i, 1));
223 if(e.Leaving())
224 platform::queue(keypress(keyboard_modifier_set(), mouse_i, 0));
225 if(e.LeftDown())
226 platform::queue(keypress(keyboard_modifier_set(), mouse_l, 1));
227 if(e.LeftUp())
228 platform::queue(keypress(keyboard_modifier_set(), mouse_l, 0));
229 if(e.MiddleDown())
230 platform::queue(keypress(keyboard_modifier_set(), mouse_m, 1));
231 if(e.MiddleUp())
232 platform::queue(keypress(keyboard_modifier_set(), mouse_m, 0));
233 if(e.RightDown())
234 platform::queue(keypress(keyboard_modifier_set(), mouse_r, 1));
235 if(e.RightUp())
236 platform::queue(keypress(keyboard_modifier_set(), mouse_r, 0));
239 bool is_readonly_mode()
241 bool ret;
242 runemufn([&ret]() { ret = movb.get_movie().readonly_mode(); });
243 return ret;
246 std::pair<int, int> UI_controller_index_by_logical(unsigned lid)
248 std::pair<int, int> ret;
249 runemufn([&ret, lid]() { ret = controls.lcid_to_pcid(lid); });
250 return ret;
253 void set_speed(double target)
255 if(target < 0)
256 set_speed_multiplier(std::numeric_limits<double>::infinity());
257 else
258 set_speed_multiplier(target / 100);
261 class broadcast_listener : public information_dispatch
263 public:
264 broadcast_listener(wxwin_mainwindow* win);
265 void on_sound_unmute(bool unmute) throw();
266 void on_mode_change(bool readonly) throw();
267 void on_core_change();
268 void on_new_core();
269 private:
270 wxwin_mainwindow* mainw;
273 broadcast_listener::broadcast_listener(wxwin_mainwindow* win)
274 : information_dispatch("wxwidgets-broadcast-listener")
276 mainw = win;
279 void broadcast_listener::on_core_change()
281 signal_core_change();
284 void broadcast_listener::on_sound_unmute(bool unmute) throw()
286 runuifun([this, unmute]() { this->mainw->menu_check(wxID_AUDIO_ENABLED, unmute); });
289 void broadcast_listener::on_mode_change(bool readonly) throw()
291 runuifun([this, readonly]() { this->mainw->menu_check(wxID_READONLY_MODE, readonly); });
294 void update_preferences()
296 preferred_core.clear();
297 for(auto i : core_type::get_core_types()) {
298 std::string val = i->get_hname() + " / " + i->get_core_identifier();
299 for(auto j : i->get_extensions()) {
300 std::string key = "ext:" + j;
301 if(core_selections.count(key) && core_selections[key] == val)
302 preferred_core[key] = i;
304 std::string key2 = "type:" + i->get_iname();
305 if(core_selections.count(key2) && core_selections[key2] == val)
306 preferred_core[key2] = i;
311 void broadcast_listener::on_new_core()
313 update_preferences();
316 std::string movie_path()
318 return lsnes_vset["moviepath"].str();
321 std::string rom_path()
323 return lsnes_vset["rompath"].str();
326 bool is_lsnes_movie(const std::string& filename)
328 try {
329 zip_reader r(filename);
330 std::istream& s = r["systemid"];
331 std::string s2;
332 std::getline(s, s2);
333 delete &s;
334 istrip_CR(s2);
335 return (s2 == "lsnes-rr1");
336 } catch(...) {
337 return false;
341 class loadfile : public wxFileDropTarget
343 public:
344 loadfile(wxwin_mainwindow* win) : pwin(win) {};
345 bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
347 bool ret = false;
348 if(filenames.Count() == 2) {
349 if(is_lsnes_movie(tostdstring(filenames[0])) &&
350 !is_lsnes_movie(tostdstring(filenames[1]))) {
351 platform::queue("unpause-emulator");
352 platform::queue("reload-rom " + tostdstring(filenames[1]));
353 platform::queue("load-smart " + tostdstring(filenames[0]));
354 ret = true;
356 if(!is_lsnes_movie(tostdstring(filenames[0])) &&
357 is_lsnes_movie(tostdstring(filenames[1]))) {
358 platform::queue("unpause-emulator");
359 platform::queue("reload-rom " + tostdstring(filenames[0]));
360 platform::queue("load-smart " + tostdstring(filenames[1]));
361 ret = true;
364 if(filenames.Count() == 1) {
365 if(is_lsnes_movie(tostdstring(filenames[0]))) {
366 platform::queue("load-smart " + tostdstring(filenames[0]));
367 pwin->recent_movies->add(tostdstring(filenames[0]));
368 ret = true;
369 } else {
370 platform::queue("unpause-emulator");
371 platform::queue("reload-rom " + tostdstring(filenames[0]));
372 pwin->recent_roms->add(tostdstring(filenames[0]));
373 ret = true;
376 return ret;
378 wxwin_mainwindow* pwin;
382 void boot_emulator(loaded_rom& rom, moviefile& movie)
384 update_preferences();
385 try {
386 struct emu_args* a = new emu_args;
387 a->rom = &rom;
388 a->initial = &movie;
389 a->load_has_to_succeed = false;
390 modal_pause_holder hld;
391 emulation_thread = new thread_class(emulator_main, a);
392 main_window = new wxwin_mainwindow();
393 main_window->Show();
394 } catch(std::bad_alloc& e) {
395 OOM_panic();
399 wxwin_mainwindow::panel::panel(wxWindow* win)
400 : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS)
402 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
403 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
404 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
405 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
406 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
407 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
408 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
409 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
410 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
411 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
412 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
413 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
414 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
415 SetMinSize(wxSize(512, 448));
418 void wxwin_mainwindow::menu_start(wxString name)
420 while(!upper.empty())
421 upper.pop();
422 current_menu = new wxMenu();
423 menubar->Append(current_menu, name);
426 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
428 while(!upper.empty())
429 upper.pop();
430 menubar->Append(menu, name);
431 current_menu = NULL;
434 void wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
436 current_menu->AppendSubMenu(menu, name);
439 void wxwin_mainwindow::menu_entry(int id, wxString name)
441 current_menu->Append(id, name);
442 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
443 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
446 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
448 checkitems[id] = current_menu->AppendCheckItem(id, name);
449 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
450 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
453 void wxwin_mainwindow::menu_start_sub(wxString name)
455 wxMenu* old = current_menu;
456 upper.push(current_menu);
457 current_menu = new wxMenu();
458 old->AppendSubMenu(current_menu, name);
461 void wxwin_mainwindow::menu_end_sub()
463 current_menu = upper.top();
464 upper.pop();
467 bool wxwin_mainwindow::menu_ischecked(int id)
469 if(checkitems.count(id))
470 return checkitems[id]->IsChecked();
471 else
472 return false;
475 void wxwin_mainwindow::menu_check(int id, bool newstate)
477 if(checkitems.count(id))
478 return checkitems[id]->Check(newstate);
479 else
480 return;
483 void wxwin_mainwindow::menu_separator()
485 current_menu->AppendSeparator();
488 void wxwin_mainwindow::panel::request_paint()
490 Refresh();
493 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
495 render_framebuffer();
496 static struct SwsContext* ctx;
497 uint8_t* srcp[1];
498 int srcs[1];
499 uint8_t* dstp[1];
500 int dsts[1];
501 wxPaintDC dc(this);
502 uint32_t tw, th;
503 bool aux = hflip_enabled || vflip_enabled || rotate_enabled;
504 if(rotate_enabled) {
505 tw = main_screen.get_height() * horizontal_scale_factor + 0.5;
506 th = main_screen.get_width() * vertical_scale_factor + 0.5;
507 } else {
508 tw = main_screen.get_width() * horizontal_scale_factor + 0.5;
509 th = main_screen.get_height() * vertical_scale_factor + 0.5;
511 if(!tw || !th) {
512 main_window_dirty = false;
513 return;
515 if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags ||
516 hflip_enabled != old_hflip || vflip_enabled != old_vflip || rotate_enabled != old_rotate) {
517 if(screen_buffer)
518 delete[] screen_buffer;
519 if(rotate_buffer)
520 delete[] rotate_buffer;
521 old_height = th;
522 old_width = tw;
523 old_flags = scaling_flags;
524 old_hflip = hflip_enabled;
525 old_vflip = vflip_enabled;
526 old_rotate = rotate_enabled;
527 uint32_t w = main_screen.get_width();
528 uint32_t h = main_screen.get_height();
529 if(w && h)
530 ctx = sws_getCachedContext(ctx, rotate_enabled ? h : w, rotate_enabled ? w : h, PIX_FMT_RGBA,
531 tw, th, PIX_FMT_BGR24, scaling_flags, NULL, NULL, NULL);
532 tw = max(tw, static_cast<uint32_t>(128));
533 th = max(th, static_cast<uint32_t>(112));
534 screen_buffer = new unsigned char[tw * th * 3];
535 if(aux)
536 rotate_buffer = new uint32_t[main_screen.get_width() * main_screen.get_height()];
537 SetMinSize(wxSize(tw, th));
538 signal_resize_needed();
540 if(aux) {
541 //Hflip, Vflip or rotate active.
542 size_t width = main_screen.get_width();
543 size_t height = main_screen.get_height();
544 size_t width1 = width - 1;
545 size_t height1 = height - 1;
546 size_t stride = main_screen.rowptr(1) - main_screen.rowptr(0);
547 uint32_t* pixels = main_screen.rowptr(0);
548 if(rotate_enabled) {
549 for(unsigned y = 0; y < height; y++) {
550 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
551 uint32_t* dpixels = rotate_buffer + (height1 - y);
552 if(hflip_enabled)
553 for(unsigned x = 0; x < width; x++)
554 dpixels[x * height] = pixels2[width1 - x];
555 else
556 for(unsigned x = 0; x < width; x++)
557 dpixels[x * height] = pixels2[x];
559 } else {
560 for(unsigned y = 0; y < height; y++) {
561 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
562 uint32_t* dpixels = rotate_buffer + y * width;
563 if(hflip_enabled)
564 for(unsigned x = 0; x < width; x++)
565 dpixels[x] = pixels2[width1 - x];
566 else
567 for(unsigned x = 0; x < width; x++)
568 dpixels[x] = pixels2[x];
572 srcs[0] = 4 * (rotate_enabled ? main_screen.get_height() : main_screen.get_width());
573 dsts[0] = 3 * tw;
574 srcp[0] = reinterpret_cast<unsigned char*>(aux ? rotate_buffer : main_screen.rowptr(0));
575 dstp[0] = screen_buffer;
576 memset(screen_buffer, 0, tw * th * 3);
577 if(main_screen.get_width() && main_screen.get_height())
578 sws_scale(ctx, srcp, srcs, 0, rotate_enabled ? main_screen.get_width() : main_screen.get_height(),
579 dstp, dsts);
580 wxBitmap bmp(wxImage(tw, th, screen_buffer, true));
581 dc.DrawBitmap(bmp, 0, 0, false);
582 main_window_dirty = false;
585 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
587 //Blank.
590 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
592 handle_wx_keyboard(e, true);
595 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
597 handle_wx_keyboard(e, false);
600 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
602 handle_wx_mouse(e);
605 wxwin_mainwindow::wxwin_mainwindow()
606 : wxFrame(NULL, wxID_ANY, getname(), wxDefaultPosition, wxSize(-1, -1),
607 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX)
609 broadcast_listener* blistener = new broadcast_listener(this);
610 Centre();
611 mwindow = NULL;
612 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
613 toplevel->Add(gpanel = new panel(this), 1, wxGROW);
614 toplevel->Add(spanel = new wxwin_status::panel(this, gpanel, 20), 1, wxGROW);
615 spanel_shown = true;
616 toplevel->SetSizeHints(this);
617 SetSizer(toplevel);
618 Fit();
619 gpanel->SetFocus();
620 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
621 SetMenuBar(menubar = new wxMenuBar);
622 SetStatusBar(statusbar = new wxStatusBar(this));
624 menu_start(wxT("File"));
625 menu_start_sub(wxT("New"));
626 menu_entry(wxID_NEW_MOVIE, wxT("Movie..."));
627 menu_end_sub();
628 menu_start_sub(wxT("Load"));
629 menu_entry(wxID_LOAD_STATE, wxT("State..."));
630 menu_entry(wxID_LOAD_STATE_RO, wxT("State (readonly)..."));
631 menu_entry(wxID_LOAD_STATE_RW, wxT("State (read-write)..."));
632 menu_entry(wxID_LOAD_STATE_P, wxT("State (preserve input)..."));
633 menu_entry(wxID_LOAD_MOVIE, wxT("Movie..."));
634 if(loaded_library::call_library() != "") {
635 menu_separator();
636 menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + loaded_library::call_library()));
638 menu_separator();
639 menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM"));
640 menu_entry(wxID_LOAD_ROM_IMAGE, wxT("ROM..."));
641 menu_separator();
642 menu_special_sub(wxT("Recent ROMs"), recent_roms = new recent_menu(this, wxID_RROM_FIRST, wxID_RROM_LAST,
643 get_config_path() + "/recent-roms.txt", recent_rom_selected));
644 menu_special_sub(wxT("Recent Movies"), recent_movies = new recent_menu(this, wxID_RMOVIE_FIRST,
645 wxID_RMOVIE_LAST, get_config_path() + "/recent-movies.txt", recent_movie_selected));
646 menu_separator();
647 menu_entry(wxID_CONFLICTRESOLUTION, wxT("Conflict resolution"));
648 menu_end_sub();
649 menu_start_sub(wxT("Save"));
650 menu_entry(wxID_SAVE_STATE, wxT("State..."));
651 menu_entry(wxID_SAVE_MOVIE, wxT("Movie..."));
652 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Screenshot..."));
653 menu_entry(wxID_SAVE_SUBTITLES, wxT("Subtitles..."));
654 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pending saves"));
655 menu_end_sub();
656 menu_separator();
657 menu_entry(wxID_EXIT, wxT("Quit"));
659 menu_start(wxT("System"));
660 menu_entry(wxID_PAUSE, wxT("Pause/Unpause"));
661 menu_entry(wxID_FRAMEADVANCE, wxT("Step frame"));
662 menu_entry(wxID_SUBFRAMEADVANCE, wxT("Step subframe"));
663 menu_entry(wxID_NEXTPOLL, wxT("Step poll"));
664 menu_entry(wxID_ERESET, wxT("Reset"));
665 menu_entry(wxID_EHRESET, wxT("Power cycle"));
667 menu_start(wxT("Movie"));
668 menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode"));
669 menu_check(wxID_READONLY_MODE, is_readonly_mode());
670 menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors..."));
671 menu_entry(wxID_EDIT_SUBTITLES, wxT("Edit subtitles..."));
672 #ifdef WITH_OPUS_CODEC
673 menu_entry(wxID_EDIT_VSUBTITLES, wxT("Edit commantary track..."));
674 #endif
675 menu_separator();
676 menu_entry(wxID_REWIND_MOVIE, wxT("Rewind to start"));
678 menu_start(wxT("Speed"));
679 menu_entry(wxID_SPEED_5, wxT("1/20x"));
680 menu_entry(wxID_SPEED_10, wxT("1/10x"));
681 menu_entry(wxID_SPEED_17, wxT("1/6x"));
682 menu_entry(wxID_SPEED_20, wxT("1/5x"));
683 menu_entry(wxID_SPEED_25, wxT("1/4x"));
684 menu_entry(wxID_SPEED_33, wxT("1/3x"));
685 menu_entry(wxID_SPEED_50, wxT("1/2x"));
686 menu_entry(wxID_SPEED_100, wxT("1x"));
687 menu_entry(wxID_SPEED_150, wxT("1.5x"));
688 menu_entry(wxID_SPEED_200, wxT("2x"));
689 menu_entry(wxID_SPEED_300, wxT("3x"));
690 menu_entry(wxID_SPEED_500, wxT("5x"));
691 menu_entry(wxID_SPEED_1000, wxT("10x"));
692 menu_entry(wxID_SPEED_TURBO, wxT("Turbo"));
693 menu_entry(wxID_SET_SPEED, wxT("Set..."));
695 menu_start(wxT("Tools"));
696 menu_entry(wxID_RUN_SCRIPT, wxT("Run batch file..."));
697 menu_separator();
698 menu_entry(wxID_EVAL_LUA, wxT("Evaluate Lua statement..."));
699 menu_entry(wxID_RUN_LUA, wxT("Run Lua script..."));
700 menu_separator();
701 menu_entry(wxID_RESET_LUA, wxT("Reset Lua VM"));
702 menu_separator();
703 menu_entry(wxID_AUTOHOLD, wxT("Autohold/Autofire..."));
704 menu_entry(wxID_TASINPUT, wxT("TAS input plugin..."));
705 menu_separator();
706 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch..."));
707 menu_separator();
708 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch..."));
709 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch..."));
710 menu_separator();
711 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
712 menu_separator();
713 menu_entry(wxID_MOVIE_EDIT, wxT("Edit movie..."));
714 menu_separator();
715 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
716 wxID_DUMP_FIRST, wxID_DUMP_LAST)));
718 menu_start(wxT("Configure"));
719 menu_entry_check(wxID_SHOW_STATUS, wxT("Show status panel"));
720 menu_check(wxID_SHOW_STATUS, true);
721 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH, wxT("Dedicated memory watch"));
722 menu_entry(wxID_SHOW_MESSAGES, wxT("Show messages"));
723 menu_entry(wxID_SETTINGS, wxT("Configure emulator..."));
724 menu_entry(wxID_SETTINGS_HOTKEYS, wxT("Configure hotkeys..."));
725 menu_entry(wxID_SETTINGS_CONTROLLERS, wxT("Configure controllers..."));
726 if(audioapi_driver_initialized()) {
727 menu_separator();
728 menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled"));
729 menu_entry(wxID_VUDISPLAY, wxT("VU display / volume controls"));
730 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
731 menu_entry(wxID_AUDIO_DEVICE, wxT("Set audio device"));
734 menu_start(wxT("Help"));
735 menu_entry(wxID_ABOUT, wxT("About..."));
737 gpanel->SetDropTarget(new loadfile(this));
738 spanel->SetDropTarget(new loadfile(this));
741 void wxwin_mainwindow::request_paint()
743 gpanel->Refresh();
746 void wxwin_mainwindow::on_close(wxCloseEvent& e)
748 //Veto it for now, latter things will delete it.
749 e.Veto();
750 platform::queue("quit-emulator");
753 void wxwin_mainwindow::notify_update() throw()
755 if(!main_window_dirty) {
756 main_window_dirty = true;
757 gpanel->Refresh();
761 void wxwin_mainwindow::notify_resized() throw()
763 toplevel->Layout();
764 toplevel->SetSizeHints(this);
765 Fit();
768 void wxwin_mainwindow::notify_update_status() throw()
770 if(!spanel->dirty) {
771 spanel->dirty = true;
772 spanel->Refresh();
774 if(mwindow)
775 mwindow->notify_update();
778 void wxwin_mainwindow::notify_exit() throw()
780 wxwidgets_exiting = true;
781 join_emulator_thread();
782 Destroy();
785 std::u32string read_variable_map(const std::map<std::string, std::u32string>& vars, const std::string& key)
787 if(!vars.count(key))
788 return U"";
789 return vars.find(key)->second;
792 void wxwin_mainwindow::update_statusbar(const std::map<std::string, std::u32string>& vars)
794 if(vars.empty())
795 return;
796 try {
797 std::basic_ostringstream<char32_t> s;
798 bool recording = (read_variable_map(vars, "!mode") == U"R");
799 if(recording)
800 s << U"Frame: " << read_variable_map(vars, "!frame");
801 else
802 s << U"Frame: " << read_variable_map(vars, "!frame") << "/" <<
803 read_variable_map(vars, "!length");
804 s << U" Lag: " << read_variable_map(vars, "!lag");
805 s << U" Subframe: " << read_variable_map(vars, "!subframe");
806 if(vars.count("!saveslot"))
807 s << U" Slot: " << read_variable_map(vars, "!saveslot");
808 if(vars.count("!saveslotinfo"))
809 s << U" [" << read_variable_map(vars, "!saveslotinfo") << U"]";
810 s << U" Speed: " << read_variable_map(vars, "!speed") << "%";
811 s << " ";
812 if(read_variable_map(vars, "!dumping") != U"")
813 s << U" Dumping";
814 if(read_variable_map(vars, "!mode") == U"C")
815 s << U" Corrupt";
816 else if(read_variable_map(vars, "!mode") == U"R")
817 s << U" Recording";
818 else if(read_variable_map(vars, "!mode") == U"P")
819 s << U" Playback";
820 else if(read_variable_map(vars, "!mode") == U"F")
821 s << U" Finished";
822 else
823 s << U" Unknown";
824 statusbar->SetStatusText(towxstring(s.str()));
825 } catch(std::exception& e) {
829 #define NEW_KEYBINDING "A new binding..."
830 #define NEW_ALIAS "A new alias..."
831 #define NEW_WATCH "A new watch..."
833 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
835 try {
836 handle_menu_click_cancelable(e);
837 } catch(canceled_exception& e) {
838 //Ignore.
839 } catch(std::bad_alloc& e) {
840 OOM_panic();
841 } catch(std::exception& e) {
842 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
846 void wxwin_mainwindow::refresh_title() throw()
848 SetTitle(getname());
851 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
853 std::string filename;
854 bool s;
855 switch(e.GetId()) {
856 case wxID_FRAMEADVANCE:
857 platform::queue("+advance-frame");
858 platform::queue("-advance-frame");
859 return;
860 case wxID_SUBFRAMEADVANCE:
861 platform::queue("+advance-poll");
862 platform::queue("-advance-poll");
863 return;
864 case wxID_NEXTPOLL:
865 platform::queue("advance-skiplag");
866 return;
867 case wxID_PAUSE:
868 platform::queue("pause-emulator");
869 return;
870 case wxID_ERESET:
871 platform::queue("reset");
872 return;
873 case wxID_EHRESET:
874 platform::queue("reset-hard");
875 return;
876 case wxID_EXIT:
877 platform::queue("quit-emulator");
878 return;
879 case wxID_AUDIO_ENABLED:
880 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
881 return;
882 case wxID_AUDIO_DEVICE:
883 wxeditor_sounddev_display(this);
884 return;
885 case wxID_CANCEL_SAVES:
886 platform::queue("cancel-saves");
887 return;
888 case wxID_LOAD_MOVIE:
889 filename = pick_file(this, "Load Movie", movie_path(), false, "lsmv");
890 recent_movies->add(filename);
891 platform::queue("load-movie " + filename);
892 return;
893 case wxID_LOAD_STATE:
894 filename = pick_file(this, "Load State", movie_path(), false, "lsmv");
895 recent_movies->add(filename);
896 platform::queue("load " + filename);
897 return;
898 case wxID_LOAD_STATE_RO:
899 filename = pick_file(this, "Load State (Read-Only)", movie_path(), false, "lsmv");
900 recent_movies->add(filename);
901 platform::queue("load-readonly " + filename);
902 return;
903 case wxID_LOAD_STATE_RW:
904 filename = pick_file(this, "Load State (Read-Write)", movie_path(), false, "lsmv");
905 recent_movies->add(filename);
906 platform::queue("load-state " + filename);
907 return;
908 case wxID_LOAD_STATE_P:
909 filename = pick_file(this, "Load State (Preserve)", movie_path(), false, "lsmv");
910 recent_movies->add(filename);
911 platform::queue("load-preserve " + filename);
912 return;
913 case wxID_REWIND_MOVIE:
914 platform::queue("rewind-movie");
915 return;
916 case wxID_SAVE_MOVIE:
917 filename = pick_file(this, "Save Movie", movie_path(), true, "lsmv");
918 recent_movies->add(filename);
919 platform::queue("save-movie " + filename);
920 return;
921 case wxID_SAVE_SUBTITLES:
922 platform::queue("save-subtitle " + pick_file(this, "Save Subtitle (.sub)", movie_path(), true,
923 "sub"));
924 return;
925 case wxID_SAVE_STATE:
926 filename = pick_file(this, "Save State", movie_path(), true, "lsmv");
927 recent_movies->add(filename);
928 platform::queue("save-state " + filename);
929 return;
930 case wxID_SAVE_SCREENSHOT:
931 platform::queue("take-screenshot " + pick_file(this, "Save Screenshot", movie_path(), true, "png"));
932 return;
933 case wxID_RUN_SCRIPT:
934 platform::queue("run-script " + pick_file_member(this, "Select Script", "."));
935 return;
936 case wxID_RUN_LUA:
937 platform::queue("run-lua " + pick_file(this, "Select Lua Script", ".", false, "lua"));
938 return;
939 case wxID_RESET_LUA:
940 platform::queue("reset-lua");
941 return;
942 case wxID_EVAL_LUA:
943 platform::queue("evaluate-lua " + pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
944 return;
945 case wxID_READONLY_MODE:
946 s = menu_ischecked(wxID_READONLY_MODE);
947 runemufn([s]() {
948 movb.get_movie().readonly_mode(s);
949 if(!s)
950 lua_callback_do_readwrite();
951 update_movie_state();
952 graphics_driver_notify_status();
954 return;
955 case wxID_AUTOHOLD:
956 wxeditor_autohold_display(this);
957 return;
958 case wxID_EDIT_AUTHORS:
959 wxeditor_authors_display(this);
960 return;
961 case wxID_EDIT_SUBTITLES:
962 wxeditor_subtitles_display(this);
963 return;
964 #ifdef WITH_OPUS_CODEC
965 case wxID_EDIT_VSUBTITLES:
966 show_wxeditor_voicesub(this);
967 return;
968 #endif
969 case wxID_EDIT_MEMORYWATCH:
970 wxeditor_memorywatch_display(this);
971 return;
972 case wxID_SAVE_MEMORYWATCH: {
973 modal_pause_holder hld;
974 std::set<std::string> old_watches;
975 runemufn([&old_watches]() { old_watches = get_watches(); });
976 std::string filename = pick_file(this, "Save watches to file", ".", true, "lwch");
977 std::ofstream out(filename.c_str());
978 for(auto i : old_watches) {
979 std::string val;
980 runemufn([i, &val]() { val = get_watchexpr_for(i); });
981 out << i << std::endl << val << std::endl;
983 out.close();
984 return;
986 case wxID_LOAD_MEMORYWATCH: {
987 modal_pause_holder hld;
988 std::set<std::string> old_watches;
989 runemufn([&old_watches]() { old_watches = get_watches(); });
990 std::map<std::string, std::string> new_watches;
991 std::string filename = pick_file_member(this, "Choose memory watch file", ".");
993 try {
994 std::istream& in = open_file_relative(filename, "");
995 while(in) {
996 std::string wname;
997 std::string wexpr;
998 std::getline(in, wname);
999 std::getline(in, wexpr);
1000 new_watches[strip_CR(wname)] = strip_CR(wexpr);
1002 delete &in;
1003 } catch(std::exception& e) {
1004 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
1005 wxICON_EXCLAMATION);
1006 return;
1009 runemufn([&new_watches, &old_watches]() {
1010 for(auto i : new_watches)
1011 set_watchexpr_for(i.first, i.second);
1012 for(auto i : old_watches)
1013 if(!new_watches.count(i))
1014 set_watchexpr_for(i, "");
1016 return;
1018 case wxID_MEMORY_SEARCH:
1019 wxwindow_memorysearch_display();
1020 return;
1021 case wxID_TASINPUT:
1022 wxeditor_tasinput_display(this);
1023 return;
1024 case wxID_ABOUT: {
1025 std::ostringstream str;
1026 str << "Version: lsnes rr" << lsnes_version << std::endl;
1027 str << "Revision: " << lsnes_git_revision << std::endl;
1028 for(auto i : core_core::all_cores())
1029 if(!i->is_hidden())
1030 str << "Core: " << i->get_core_identifier() << std::endl;
1031 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1032 return;
1034 case wxID_SHOW_STATUS: {
1035 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
1036 if(newstate)
1037 spanel->Show();
1038 if(newstate && !spanel_shown)
1039 toplevel->Add(spanel, 1, wxGROW);
1040 else if(!newstate && spanel_shown)
1041 toplevel->Detach(spanel);
1042 if(!newstate)
1043 spanel->Hide();
1044 spanel_shown = newstate;
1045 toplevel->Layout();
1046 toplevel->SetSizeHints(this);
1047 Fit();
1048 return;
1050 case wxID_DEDICATED_MEMORY_WATCH: {
1051 bool newstate = menu_ischecked(wxID_DEDICATED_MEMORY_WATCH);
1052 if(newstate && !mwindow) {
1053 mwindow = new wxwin_status(-1, "Memory Watch");
1054 spanel->set_watch_flag(1);
1055 mwindow->Show();
1056 } else if(!newstate && mwindow) {
1057 mwindow->Destroy();
1058 mwindow = NULL;
1059 spanel->set_watch_flag(0);
1061 return;
1063 case wxID_SET_SPEED: {
1064 std::string value = "infinite";
1065 double val = get_speed_multiplier();
1066 if(!(val == std::numeric_limits<double>::infinity()))
1067 value = (stringfmt() << (100 * val)).str();
1068 value = pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value);
1069 try {
1070 if(value == "infinite")
1071 set_speed_multiplier(std::numeric_limits<double>::infinity());
1072 else {
1073 double v = parse_value<double>(value) / 100;
1074 if(v <= 0.0001)
1075 throw 42;
1076 set_speed_multiplier(v);
1078 } catch(...) {
1079 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1081 return;
1083 case wxID_SPEED_5:
1084 set_speed(5);
1085 break;
1086 case wxID_SPEED_10:
1087 set_speed(10);
1088 break;
1089 case wxID_SPEED_17:
1090 set_speed(16.66666666666);
1091 break;
1092 case wxID_SPEED_20:
1093 set_speed(20);
1094 break;
1095 case wxID_SPEED_25:
1096 set_speed(25);
1097 break;
1098 case wxID_SPEED_33:
1099 set_speed(33.3333333333333);
1100 break;
1101 case wxID_SPEED_50:
1102 set_speed(50);
1103 break;
1104 case wxID_SPEED_100:
1105 set_speed(100);
1106 break;
1107 case wxID_SPEED_150:
1108 set_speed(150);
1109 break;
1110 case wxID_SPEED_200:
1111 set_speed(200);
1112 break;
1113 case wxID_SPEED_300:
1114 set_speed(300);
1115 break;
1116 case wxID_SPEED_500:
1117 set_speed(500);
1118 break;
1119 case wxID_SPEED_1000:
1120 set_speed(1000);
1121 break;
1122 case wxID_SPEED_TURBO:
1123 set_speed(-1);
1124 break;
1125 case wxID_LOAD_LIBRARY: {
1126 std::string name = std::string("load ") + loaded_library::call_library();
1127 new loaded_library(pick_file(this, name, ".", false, loaded_library::call_library_ext()));
1128 handle_post_loadlibrary();
1129 break;
1131 case wxID_SETTINGS:
1132 wxsetingsdialog_display(this, 0);
1133 break;
1134 case wxID_SETTINGS_HOTKEYS:
1135 wxsetingsdialog_display(this, 1);
1136 break;
1137 case wxID_SETTINGS_CONTROLLERS:
1138 wxsetingsdialog_display(this, 2);
1139 break;
1140 case wxID_LOAD_ROM_IMAGE:
1141 filename = pick_file_member(this, "Select new ROM image", rom_path());
1142 recent_roms->add(filename);
1143 platform::queue("unpause-emulator");
1144 platform::queue("reload-rom " + filename);
1145 return;
1146 case wxID_RELOAD_ROM_IMAGE:
1147 platform::queue("unpause-emulator");
1148 platform::queue("reload-rom");
1149 return;
1150 case wxID_NEW_MOVIE:
1151 show_projectwindow(this);
1152 return;
1153 case wxID_SHOW_MESSAGES:
1154 msg_window->reshow();
1155 return;
1156 case wxID_CONFLICTRESOLUTION:
1157 show_conflictwindow(this);
1158 return;
1159 case wxID_VUDISPLAY:
1160 open_vumeter_window(this);
1161 return;
1162 case wxID_MOVIE_EDIT:
1163 wxeditor_movie_display(this);
1164 return;