Fix build on GCC 4.9
[lsnes.git] / src / platform / wxwidgets / mainwindow.cpp
blob977a80cbadfc331d87cc411f78047dd9e82cc175
1 #include "lsnes.hpp"
3 #include <wx/dnd.h>
4 #include "platform/wxwidgets/menu_dump.hpp"
5 #include "platform/wxwidgets/menu_upload.hpp"
6 #include "platform/wxwidgets/platform.hpp"
7 #include "platform/wxwidgets/loadsave.hpp"
8 #include "platform/wxwidgets/window_mainwindow.hpp"
9 #include "platform/wxwidgets/window_messages.hpp"
10 #include "platform/wxwidgets/window_status.hpp"
11 #include "platform/wxwidgets/window-romload.hpp"
12 #include "platform/wxwidgets/settings-common.hpp"
13 #include "platform/wxwidgets/menu_tracelog.hpp"
14 #include "platform/wxwidgets/menu_branches.hpp"
15 #include "platform/wxwidgets/menu_projects.hpp"
17 #include "core/audioapi.hpp"
18 #include "core/command.hpp"
19 #include "core/controller.hpp"
20 #include "core/controllerframe.hpp"
21 #include "core/dispatch.hpp"
22 #include "core/emustatus.hpp"
23 #include "core/framebuffer.hpp"
24 #include "core/framerate.hpp"
25 #include "core/instance.hpp"
26 #include "core/keymapper.hpp"
27 #include "core/ui-services.hpp"
28 #include "interface/romtype.hpp"
29 #include "core/loadlib.hpp"
30 #include "lua/lua.hpp"
31 #include "core/mainloop.hpp"
32 #include "core/memorywatch.hpp"
33 #include "core/messages.hpp"
34 #include "core/misc.hpp"
35 #include "core/moviedata.hpp"
36 #include "core/project.hpp"
37 #include "core/rom.hpp"
38 #include "core/settings.hpp"
39 #include "core/window.hpp"
40 #include "library/directory.hpp"
41 #include "library/minmax.hpp"
42 #include "library/string.hpp"
43 #include "library/zip.hpp"
44 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
45 #define FUCKED_SYSTEM
46 #endif
48 #include <cmath>
49 #include <vector>
50 #include <string>
53 extern "C"
55 #ifndef UINT64_C
56 #define UINT64_C(val) val##ULL
57 #endif
58 #include <libswscale/swscale.h>
61 enum
63 wxID_PAUSE = wxID_HIGHEST + 1,
64 wxID_FRAMEADVANCE,
65 wxID_SUBFRAMEADVANCE,
66 wxID_NEXTPOLL,
67 wxID_AUDIO_ENABLED,
68 wxID_SAVE_STATE,
69 wxID_SAVE_MOVIE,
70 wxID_SAVE_SUBTITLES,
71 wxID_LOAD_STATE,
72 wxID_LOAD_MOVIE,
73 wxID_RUN_SCRIPT,
74 wxID_RUN_LUA,
75 wxID_RESET_LUA,
76 wxID_EVAL_LUA,
77 wxID_SAVE_SCREENSHOT,
78 wxID_READONLY_MODE,
79 wxID_EDIT_AUTHORS,
80 wxID_AUTOHOLD,
81 wxID_EDIT_MEMORYWATCH,
82 wxID_SAVE_MEMORYWATCH,
83 wxID_LOAD_MEMORYWATCH,
84 wxID_EDIT_SUBTITLES,
85 wxID_EDIT_VSUBTITLES,
86 wxID_DUMP_FIRST,
87 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
88 wxID_REWIND_MOVIE,
89 wxID_MEMORY_SEARCH,
90 wxID_CANCEL_SAVES,
91 wxID_SHOW_STATUS,
92 wxID_SET_SPEED,
93 wxID_SPEED_5,
94 wxID_SPEED_10,
95 wxID_SPEED_17,
96 wxID_SPEED_20,
97 wxID_SPEED_25,
98 wxID_SPEED_33,
99 wxID_SPEED_50,
100 wxID_SPEED_100,
101 wxID_SPEED_150,
102 wxID_SPEED_200,
103 wxID_SPEED_300,
104 wxID_SPEED_500,
105 wxID_SPEED_1000,
106 wxID_SPEED_TURBO,
107 wxID_LOAD_LIBRARY,
108 wxID_RELOAD_ROM_IMAGE,
109 wxID_LOAD_ROM_IMAGE_FIRST,
110 wxID_LOAD_ROM_IMAGE_LAST = wxID_LOAD_ROM_IMAGE_FIRST + 1023,
111 wxID_NEW_MOVIE,
112 wxID_SHOW_MESSAGES,
113 wxID_DEDICATED_MEMORY_WATCH,
114 wxID_RMOVIE_FIRST,
115 wxID_RMOVIE_LAST = wxID_RMOVIE_FIRST + 16,
116 wxID_RROM_FIRST,
117 wxID_RROM_LAST = wxID_RROM_FIRST + 16,
118 wxID_CONFLICTRESOLUTION,
119 wxID_VUDISPLAY,
120 wxID_MOVIE_EDIT,
121 wxID_TASINPUT,
122 wxID_NEW_PROJECT,
123 wxID_CLOSE_PROJECT,
124 wxID_CLOSE_ROM,
125 wxID_EDIT_MACROS,
126 wxID_ENTER_FULLSCREEN,
127 wxID_ACTIONS_FIRST,
128 wxID_ACTIONS_LAST = wxID_ACTIONS_FIRST + 256,
129 wxID_SETTINGS_FIRST,
130 wxID_SETTINGS_LAST = wxID_SETTINGS_FIRST + 256,
131 wxID_HEXEDITOR,
132 wxID_MULTITRACK,
133 wxID_CHDIR,
134 wxID_RLUA_FIRST,
135 wxID_RLUA_LAST = wxID_RLUA_FIRST + 16,
136 wxID_UPLOAD_FIRST,
137 wxID_UPLOAD_LAST = wxID_UPLOAD_FIRST + 256,
138 wxID_DOWNLOAD,
139 wxID_TRACELOG_FIRST,
140 wxID_TRACELOG_LAST = wxID_TRACELOG_FIRST + 256,
141 wxID_PLUGIN_MANAGER,
142 wxID_BRANCH_FIRST,
143 wxID_BRANCH_LAST = wxID_BRANCH_FIRST + 10240,
144 wxID_PROJECT_FIRST,
145 wxID_PROJECT_LAST = wxID_PROJECT_FIRST + 17,
146 wxID_DISASSEMBLER,
150 double video_scale_factor = 1.0;
151 int scaling_flags = SWS_POINT;
152 bool arcorrect_enabled = false;
153 bool hflip_enabled = false;
154 bool vflip_enabled = false;
155 bool rotate_enabled = false;
157 namespace
159 std::string last_volume = "0dB";
160 std::string last_volume_record = "0dB";
161 std::string last_volume_voice = "0dB";
162 unsigned char* screen_buffer;
163 struct SwsContext* sws_ctx;
164 uint32_t* rotate_buffer;
165 uint32_t old_width;
166 uint32_t old_height;
167 int old_flags = SWS_POINT;
168 bool old_hflip = false;
169 bool old_vflip = false;
170 bool old_rotate = false;
171 bool main_window_dirty;
172 bool is_fs = false;
173 bool hashing_in_progress = false;
174 uint64_t hashing_left = 0;
175 uint64_t hashing_total = 0;
176 int64_t last_update = 0;
177 threads::thread* emulation_thread;
178 bool status_updated = false;
180 settingvar::variable<settingvar::model_bool<settingvar::yes_no>> background_audio(*lsnes_instance.settings,
181 "background-audio", "GUIā€£Enable background audio", true);
183 class _status_timer : public wxTimer
185 public:
186 _status_timer()
188 Start(50);
190 void Notify()
192 if(status_updated) {
193 status_updated = false;
194 if(main_window) main_window->update_statusbar();
199 class _focus_timer : public wxTimer
201 public:
202 _focus_timer()
204 was_focused = (wxWindow::FindFocus() != NULL);
205 was_enabled = platform::is_sound_enabled();
206 Start(500);
208 void Notify()
210 bool is_focused = (wxWindow::FindFocus() != NULL);
211 if(is_focused && !was_focused) {
212 //Gained focus.
213 if(!background_audio)
214 platform::sound_enable(was_enabled);
215 } else if(!is_focused && was_focused) {
216 //Lost focus.
217 was_enabled = platform::is_sound_enabled();
218 if(!background_audio)
219 platform::sound_enable(false);
221 was_focused = is_focused;
223 private:
224 bool was_focused;
225 bool was_enabled;
228 class download_timer : public wxTimer
230 public:
231 download_timer(wxwin_mainwindow* main, emulator_instance& _inst)
232 : inst(_inst)
234 w = main;
235 Start(50);
237 void Notify()
239 if(w->download_in_progress->finished) {
240 w->update_statusbar();
241 auto old = w->download_in_progress;
242 w->download_in_progress = NULL;
243 if(old->errormsg != "") {
244 show_message_ok(w, "Error downloading movie", old->errormsg,
245 wxICON_EXCLAMATION);
246 } else {
247 inst.iqueue->queue("load-movie $MEMORY:wxwidgets_download_tmp");
249 delete old;
250 Stop();
251 delete this;
252 } else {
253 w->update_statusbar();
256 private:
257 emulator_instance& inst;
258 wxwin_mainwindow* w;
261 void hash_callback(uint64_t left, uint64_t total)
263 wxwin_mainwindow* mwin = main_window;
264 if(left == 0xFFFFFFFFFFFFFFFFULL) {
265 hashing_in_progress = false;
266 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
267 last_update = framerate_regulator::get_utime() - 2000000;
268 return;
270 hashing_in_progress = true;
271 hashing_left = left;
272 hashing_total = total;
273 int64_t this_update = framerate_regulator::get_utime();
274 if(this_update < last_update - 1000000 || this_update > last_update + 1000000) {
275 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
276 last_update = this_update;
280 std::pair<std::string, std::string> lsplit(std::string l)
282 for(unsigned i = 0; i < l.length() - 3; i++)
283 if((uint8_t)l[i] == 0xE2 && (uint8_t)l[i + 1] == 0x80 && (uint8_t)l[i + 2] == 0xA3)
284 return std::make_pair(l.substr(0, i), l.substr(i + 3));
285 return std::make_pair("", l);
288 recentfiles::multirom loadreq_to_multirom(const romload_request& req)
290 recentfiles::multirom r;
291 r.packfile = req.packfile;
292 r.singlefile = req.singlefile;
293 r.core = req.core;
294 r.system = req.system;
295 r.region = req.region;
296 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++)
297 if(req.files[i] != "") {
298 r.files.resize(i + 1);
299 r.files[i] = req.files[i];
301 return r;
304 class system_menu : public wxMenu
306 public:
307 system_menu(wxWindow* win, emulator_instance& _inst);
308 ~system_menu();
309 void on_select(wxCommandEvent& e);
310 void update(bool light);
311 private:
312 emulator_instance& inst;
313 wxWindow* pwin;
314 void insert_pass(int id, const std::string& label);
315 void insert_act(unsigned id, const std::string& label, bool dots, bool check);
316 wxMenu* lookup_menu(const std::string& key);
317 wxMenuItem* sep;
318 std::map<int, unsigned> action_by_id;
319 std::map<unsigned, wxMenuItem*> item_by_action;
320 std::map<wxMenuItem*, wxMenu*> menu_by_item;
321 std::map<std::string, wxMenu*> submenu_by_name;
322 std::map<std::string, wxMenuItem*> submenui_by_name;
323 std::set<unsigned> toggles;
324 int next_id;
327 wxMenu* system_menu::lookup_menu(const std::string& key)
329 if(key == "")
330 return this;
331 if(submenu_by_name.count(key))
332 return submenu_by_name[key];
333 //Not found, create.
334 if(!sep)
335 sep = AppendSeparator();
336 auto p = lsplit(key);
337 wxMenu* into = lookup_menu(p.first);
338 submenu_by_name[key] = new wxMenu();
339 submenui_by_name[key] = into->AppendSubMenu(submenu_by_name[key], towxstring(p.second));
340 menu_by_item[submenui_by_name[key]] = into;
341 return submenu_by_name[key];
344 void system_menu::insert_act(unsigned id, const std::string& label, bool dots, bool check)
346 if(!sep)
347 sep = AppendSeparator();
349 auto p = lsplit(label);
350 wxMenu* into = lookup_menu(p.first);
352 action_by_id[next_id] = id;
353 std::string use_label = p.second + (dots ? "..." : "");
354 if(check) {
355 item_by_action[id] = into->AppendCheckItem(next_id, towxstring(use_label));
356 toggles.insert(id);
357 } else
358 item_by_action[id] = into->Append(next_id, towxstring(use_label));
359 menu_by_item[item_by_action[id]] = into;
360 pwin->Connect(next_id++, wxEVT_COMMAND_MENU_SELECTED,
361 wxCommandEventHandler(system_menu::on_select), NULL, this);
364 void system_menu::insert_pass(int id, const std::string& label)
366 pwin->Connect(id, wxEVT_COMMAND_MENU_SELECTED,
367 wxCommandEventHandler(wxwin_mainwindow::handle_menu_click), NULL, pwin);
368 Append(id, towxstring(label));
371 system_menu::system_menu(wxWindow* win, emulator_instance& _inst)
372 : inst(_inst)
374 pwin = win;
375 insert_pass(wxID_PAUSE, "Pause/Unpause");
376 insert_pass(wxID_FRAMEADVANCE, "Step frame");
377 insert_pass(wxID_SUBFRAMEADVANCE, "Step subframe");
378 insert_pass(wxID_NEXTPOLL, "Step poll");
379 sep = NULL;
382 system_menu::~system_menu()
386 void system_menu::on_select(wxCommandEvent& e)
388 if(!action_by_id.count(e.GetId()))
389 return;
390 unsigned act_id = action_by_id[e.GetId()];
391 const interface_action* act = NULL;
392 for(auto i : inst.rom->get_actions())
393 if(i->id == act_id) {
394 act = i;
395 break;
397 if(!act)
398 return;
399 try {
400 auto p = prompt_action_params(pwin, act->get_title(), act->params);
401 inst.iqueue->run([this, act_id,p]() { this->inst.rom->execute_action(act_id, p); });
402 } catch(canceled_exception& e) {
403 } catch(std::bad_alloc& e) {
404 OOM_panic();
408 void system_menu::update(bool light)
410 if(!light) {
411 next_id = wxID_ACTIONS_FIRST;
412 if(sep) {
413 Destroy(sep);
414 sep = NULL;
416 for(auto i = item_by_action.begin(); i != item_by_action.end(); i++)
417 menu_by_item[i->second]->Destroy(i->second);
418 for(auto i = submenui_by_name.rbegin(); i != submenui_by_name.rend(); i++)
419 menu_by_item[i->second]->Destroy(i->second);
420 action_by_id.clear();
421 item_by_action.clear();
422 menu_by_item.clear();
423 submenu_by_name.clear();
424 submenui_by_name.clear();
425 toggles.clear();
427 for(auto i : inst.rom->get_actions())
428 insert_act(i->id, i->get_title(), !i->params.empty(), i->is_toggle());
430 for(auto i : item_by_action)
431 i.second->Enable(inst.rom->action_flags(i.first) & 1);
432 for(auto i : toggles)
433 item_by_action[i]->Check(inst.rom->action_flags(i) & 2);
436 std::string munge_name(const std::string& orig)
438 std::string newname;
439 regex_results r;
440 if(r = regex("(.*)\\(([0-9]+)\\)", newname)) {
441 uint64_t sequence;
442 try {
443 sequence = parse_value<uint64_t>(r[2]);
444 newname = (stringfmt() << r[1] << "(" << sequence + 1 << ")").str();
445 } catch(...) {
446 newname = newname + "(2)";
448 } else {
449 newname = newname + "(2)";
451 return newname;
454 void handle_watch_load(emulator_instance& inst, std::map<std::string, std::string>& new_watches,
455 std::set<std::string>& old_watches)
457 auto proj = inst.project->get();
458 if(proj) {
459 for(auto i : new_watches) {
460 std::string name = i.first;
461 while(true) {
462 if(!old_watches.count(name)) {
463 try {
464 if(name != "" && i.second != "")
465 inst.mwatch->set(name, i.second);
466 } catch(std::exception& e) {
467 messages << "Can't set memory watch '" << name << "': "
468 << e.what() << std::endl;
470 break;
471 } else if(inst.mwatch->get_string(name) == i.second)
472 break;
473 else
474 name = munge_name(name);
477 } else {
478 for(auto i : new_watches)
479 try {
480 if(i.first != "" && i.second != "")
481 inst.mwatch->set(i.first, i.second);
482 } catch(std::exception& e) {
483 messages << "Can't set memory watch '" << i.first << "': "
484 << e.what() << std::endl;
486 for(auto i : old_watches)
487 if(!new_watches.count(i))
488 try {
489 inst.mwatch->clear(i);
490 } catch(std::exception& e) {
491 messages << "Can't clear memory watch '" << i << "': "
492 << e.what() << std::endl;
497 std::string get_default_screenshot_name(emulator_instance& inst)
499 auto p = inst.project->get();
500 if(!p)
501 return "";
502 else {
503 auto files = directory::enumerate(p->directory, ".*-[0-9]+\\.png");
504 std::set<std::string> numbers;
505 for(auto i : files) {
506 size_t split;
507 #ifdef FUCKED_SYSTEM
508 split = i.find_last_of("\\/");
509 #else
510 split = i.find_last_of("/");
511 #endif
512 std::string name = i;
513 if(split < name.length())
514 name = name.substr(split + 1);
515 regex_results r = regex("(.*)-([0-9]+)\\.png", name);
516 if(r[1] != p->prefix)
517 continue;
518 numbers.insert(r[2]);
520 for(uint64_t i = 1;; i++) {
521 std::string candidate = (stringfmt() << i).str();
522 if(!numbers.count(candidate))
523 return p->prefix + "-" + candidate + ".png";
528 std::string project_prefixname(emulator_instance& inst, const std::string ext)
530 auto p = inst.project->get();
531 if(!p)
532 return "";
533 else
534 return p->prefix + "." + ext;
538 void recent_rom_selected(emulator_instance& inst, const recentfiles::multirom& file)
540 romload_request req;
541 req.packfile = file.packfile;
542 req.singlefile = file.singlefile;
543 req.core = file.core;
544 req.system = file.system;
545 req.region = file.region;
546 for(unsigned i = 0; i < file.files.size() && i < ROM_SLOT_COUNT; i++)
547 req.files[i] = file.files[i];
548 inst.iqueue->run_async([req]() {
549 CORE().command->invoke("unpause-emulator");
550 load_new_rom(req);
551 }, [](std::exception& e) {});
554 void recent_movie_selected(emulator_instance& inst, const recentfiles::path& file)
556 inst.iqueue->queue("load-smart " + file.get_path());
559 void recent_script_selected(emulator_instance& inst, const recentfiles::path& file)
561 inst.iqueue->queue("run-lua " + file.get_path());
564 wxString getname(emulator_instance& inst)
566 std::string windowname = "lsnes rr" + lsnes_version + " [";
567 auto p = inst.project->get();
568 if(p)
569 windowname = windowname + p->name;
570 else
571 windowname = windowname + inst.rom->get_core_identifier();
572 windowname = windowname + "]";
573 return towxstring(windowname);
576 struct emu_args
578 emulator_instance* inst;
579 struct loaded_rom rom;
580 struct moviefile* initial;
581 bool load_has_to_succeed;
584 void* emulator_main(void* _args)
586 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
587 auto& inst = *args->inst;
588 try {
589 *inst.rom = args->rom;
590 messages << "Using core: " << inst.rom->get_core_identifier() << std::endl;
591 struct moviefile* movie = args->initial;
592 bool has_to_succeed = args->load_has_to_succeed;
593 platform::flush_command_queue();
594 main_loop(*inst.rom, *movie, has_to_succeed);
595 signal_program_exit();
596 } catch(std::bad_alloc& e) {
597 OOM_panic();
598 } catch(std::exception& e) {
599 messages << "FATAL: " << e.what() << std::endl;
600 platform::fatal_error();
602 delete args;
603 return NULL;
606 void join_emulator_thread()
608 emulation_thread->join();
611 bool is_readonly_mode(emulator_instance& inst)
613 bool ret;
614 inst.iqueue->run([&ret]() {
615 ret = *CORE().mlogic ? CORE().mlogic->get_movie().readonly_mode() : false;
617 return ret;
620 void set_speed(emulator_instance& inst, double target)
622 if(target < 0)
623 inst.framerate->set_speed_multiplier(std::numeric_limits<double>::infinity());
624 else
625 inst.framerate->set_speed_multiplier(target / 100);
628 void update_preferences()
630 preferred_core.clear();
631 for(auto i : core_type::get_core_types()) {
632 std::string val = i->get_hname() + " / " + i->get_core_identifier();
633 for(auto j : i->get_extensions()) {
634 std::string key = "ext:" + j;
635 if(core_selections.count(key) && core_selections[key] == val)
636 preferred_core[key] = i;
638 std::string key2 = "type:" + i->get_iname();
639 if(core_selections.count(key2) && core_selections[key2] == val)
640 preferred_core[key2] = i;
644 bool is_lsnes_movie(const std::string& filename)
646 std::istream* s = NULL;
647 try {
648 bool ans = false;
649 s = &zip::openrel(filename, "");
650 char buf[6] = {0};
651 s->read(buf, 5);
652 if(*s && !strcmp(buf, "lsmv\x1A"))
653 ans = true;
654 delete s;
655 if(ans) return true;
656 } catch(...) {
657 delete s;
659 try {
660 zip::reader r(filename);
661 std::istream& s = r["systemid"];
662 std::string s2;
663 std::getline(s, s2);
664 delete &s;
665 istrip_CR(s2);
666 return (s2 == "lsnes-rr1");
667 } catch(...) {
668 return false;
672 class loadfile : public wxFileDropTarget
674 public:
675 loadfile(wxwin_mainwindow* win, emulator_instance& _inst) : inst(_inst), pwin(win) {};
676 bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
678 bool ret = false;
679 if(filenames.Count() == 2) {
680 std::string a = tostdstring(filenames[0]);
681 std::string b = tostdstring(filenames[1]);
682 bool amov = is_lsnes_movie(a);
683 bool bmov = is_lsnes_movie(b);
684 if(amov == bmov)
685 return false;
686 if(amov) std::swap(a, b);
687 inst.iqueue->run_async([a, b]() {
688 CORE().command->invoke("unpause-emulator");
689 romload_request req;
690 req.packfile = a;
691 load_new_rom(req);
692 CORE().command->invoke("load-smart " + b);
693 }, [](std::exception& e) {});
694 ret = true;
696 if(filenames.Count() == 1) {
697 std::string a = tostdstring(filenames[0]);
698 bool amov = is_lsnes_movie(a);
699 if(amov) {
700 inst.iqueue->queue("load-smart " + a);
701 pwin->recent_movies->add(a);
702 ret = true;
703 } else {
704 romload_request req;
705 req.packfile = a;
706 inst.iqueue->run_async([req]() {
707 CORE().command->invoke("unpause-emulator");
708 load_new_rom(req);
709 }, [](std::exception& e) {});
710 pwin->recent_roms->add(loadreq_to_multirom(req));
711 ret = true;
714 return ret;
716 emulator_instance& inst;
717 wxwin_mainwindow* pwin;
721 void boot_emulator(emulator_instance& inst, loaded_rom& rom, moviefile& movie, bool fscreen)
723 update_preferences();
724 try {
725 struct emu_args* a = new emu_args;
726 a->rom = rom;
727 a->initial = &movie;
728 a->load_has_to_succeed = false;
729 a->inst = &inst;
730 modal_pause_holder hld;
731 emulation_thread = new threads::thread(emulator_main, a);
732 main_window = new wxwin_mainwindow(inst, fscreen);
733 main_window->Show();
734 } catch(std::bad_alloc& e) {
735 OOM_panic();
739 wxwin_mainwindow::panel::panel(wxWindow* win, emulator_instance& _inst)
740 : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS), inst(_inst)
742 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
743 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
744 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
745 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
746 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
747 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
748 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
749 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
750 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
751 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
752 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
753 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
754 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
755 SetMinSize(wxSize(512, 448));
758 void wxwin_mainwindow::menu_start(wxString name)
760 while(!upper.empty())
761 upper.pop();
762 current_menu = new wxMenu();
763 menubar->Append(current_menu, name);
766 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
768 while(!upper.empty())
769 upper.pop();
770 menubar->Append(menu, name);
771 current_menu = NULL;
774 wxMenuItem* wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
776 return current_menu->AppendSubMenu(menu, name);
779 void wxwin_mainwindow::menu_entry(int id, wxString name)
781 current_menu->Append(id, name);
782 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
783 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
786 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
788 checkitems[id] = current_menu->AppendCheckItem(id, name);
789 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
790 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
793 void wxwin_mainwindow::menu_start_sub(wxString name)
795 wxMenu* old = current_menu;
796 upper.push(current_menu);
797 current_menu = new wxMenu();
798 old->AppendSubMenu(current_menu, name);
801 void wxwin_mainwindow::menu_end_sub()
803 current_menu = upper.top();
804 upper.pop();
807 bool wxwin_mainwindow::menu_ischecked(int id)
809 if(checkitems.count(id))
810 return checkitems[id]->IsChecked();
811 else
812 return false;
815 void wxwin_mainwindow::menu_check(int id, bool newstate)
817 if(checkitems.count(id))
818 return checkitems[id]->Check(newstate);
819 else
820 return;
823 void wxwin_mainwindow::menu_enable(int id, bool newstate)
825 auto item = menubar->FindItem(id);
826 if(!item)
827 return;
828 item->Enable(newstate);
831 void wxwin_mainwindow::menu_separator()
833 current_menu->AppendSeparator();
836 void wxwin_mainwindow::panel::request_paint()
838 Refresh();
841 std::pair<double, double> calc_scale_factors(double factor, bool ar, double par)
843 if(!ar)
844 return std::make_pair(factor, factor);
845 else if(par < 1) {
846 //Too wide, make taller.
847 return std::make_pair(factor, factor / par);
848 } else {
849 //Too narrow, make wider.
850 return std::make_pair(factor * par, factor);
854 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
856 if(wx_escape_count >= 3 && is_fs) {
857 //Leave fullscreen mode.
858 main_window->enter_or_leave_fullscreen(false);
860 inst.fbuf->render_framebuffer();
861 static
862 uint8_t* srcp[1];
863 int srcs[1];
864 uint8_t* dstp[1];
865 int dsts[1];
866 wxPaintDC dc(this);
867 uint32_t tw, th;
868 bool aux = hflip_enabled || vflip_enabled || rotate_enabled;
869 auto sfactors = calc_scale_factors(video_scale_factor, arcorrect_enabled, inst.rom->get_PAR());
870 if(rotate_enabled) {
871 tw = inst.fbuf->main_screen.get_height() * sfactors.second + 0.5;
872 th = inst.fbuf->main_screen.get_width() * sfactors.first + 0.5;
873 } else {
874 tw = inst.fbuf->main_screen.get_width() * sfactors.first + 0.5;
875 th = inst.fbuf->main_screen.get_height() * sfactors.second + 0.5;
877 if(!tw || !th) {
878 main_window_dirty = false;
879 return;
881 //Scale this to fullscreen.
882 if(is_fs) {
883 wxSize screen = main_window->GetSize();
884 double fss = min(1.0 * screen.GetWidth() / tw, 1.0 * screen.GetHeight() / th);
885 tw *= fss;
886 th *= fss;
889 if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags ||
890 hflip_enabled != old_hflip || vflip_enabled != old_vflip || rotate_enabled != old_rotate) {
891 if(screen_buffer) {
892 delete[] screen_buffer;
893 screen_buffer = NULL;
895 if(rotate_buffer) {
896 delete[] rotate_buffer;
897 rotate_buffer = NULL;
899 old_height = th;
900 old_width = tw;
901 old_flags = scaling_flags;
902 old_hflip = hflip_enabled;
903 old_vflip = vflip_enabled;
904 old_rotate = rotate_enabled;
905 uint32_t w = inst.fbuf->main_screen.get_width();
906 uint32_t h = inst.fbuf->main_screen.get_height();
907 if(w && h)
908 sws_ctx = sws_getCachedContext(sws_ctx, rotate_enabled ? h : w, rotate_enabled ? w : h,
909 PIX_FMT_RGBA, tw, th, PIX_FMT_BGR24, scaling_flags, NULL, NULL, NULL);
910 tw = max(tw, static_cast<uint32_t>(128));
911 th = max(th, static_cast<uint32_t>(112));
912 screen_buffer = new unsigned char[tw * th * 3];
913 if(aux)
914 rotate_buffer = new uint32_t[inst.fbuf->main_screen.get_width() *
915 inst.fbuf->main_screen.get_height()];
916 SetMinSize(wxSize(tw, th));
917 signal_resize_needed();
919 if(aux) {
920 //Hflip, Vflip or rotate active.
921 size_t width = inst.fbuf->main_screen.get_width();
922 size_t height = inst.fbuf->main_screen.get_height();
923 size_t width1 = width - 1;
924 size_t height1 = height - 1;
925 size_t stride = inst.fbuf->main_screen.rowptr(1) - inst.fbuf->main_screen.rowptr(0);
926 uint32_t* pixels = inst.fbuf->main_screen.rowptr(0);
927 if(rotate_enabled) {
928 for(unsigned y = 0; y < height; y++) {
929 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
930 uint32_t* dpixels = rotate_buffer + (height1 - y);
931 if(hflip_enabled)
932 for(unsigned x = 0; x < width; x++)
933 dpixels[x * height] = pixels2[width1 - x];
934 else
935 for(unsigned x = 0; x < width; x++)
936 dpixels[x * height] = pixels2[x];
938 } else {
939 for(unsigned y = 0; y < height; y++) {
940 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
941 uint32_t* dpixels = rotate_buffer + y * width;
942 if(hflip_enabled)
943 for(unsigned x = 0; x < width; x++)
944 dpixels[x] = pixels2[width1 - x];
945 else
946 for(unsigned x = 0; x < width; x++)
947 dpixels[x] = pixels2[x];
951 if(aux)
952 srcs[0] = 4 * (rotate_enabled ? inst.fbuf->main_screen.get_height() :
953 inst.fbuf->main_screen.get_width());
954 else
955 srcs[0] = 4 * inst.fbuf->main_screen.get_stride();
956 dsts[0] = 3 * tw;
957 srcp[0] = reinterpret_cast<unsigned char*>(aux ? rotate_buffer : inst.fbuf->main_screen.rowptr(0));
958 dstp[0] = screen_buffer;
959 memset(screen_buffer, 0, tw * th * 3);
960 if(inst.fbuf->main_screen.get_width() && inst.fbuf->main_screen.get_height())
961 sws_scale(sws_ctx, srcp, srcs, 0, rotate_enabled ? inst.fbuf->main_screen.get_width() :
962 inst.fbuf->main_screen.get_height(),
963 dstp, dsts);
964 wxBitmap bmp(wxImage(tw, th, screen_buffer, true));
965 dc.DrawBitmap(bmp, 0, 0, false);
966 main_window_dirty = false;
967 main_window->update_statusbar();
970 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
972 //Blank.
975 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
977 handle_wx_keyboard(inst, e, true);
980 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
982 handle_wx_keyboard(inst, e, false);
985 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
987 handle_wx_mouse(inst, e);
990 wxwin_mainwindow::wxwin_mainwindow(emulator_instance& _inst, bool fscreen)
991 : wxFrame(NULL, wxID_ANY, getname(_inst), wxDefaultPosition, wxSize(-1, -1),
992 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX), inst(_inst)
994 download_in_progress = NULL;
995 Centre();
996 mwindow = NULL;
997 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
998 toplevel->Add(gpanel = new panel(this, inst), 1, wxGROW);
999 toplevel->Add(spanel = new wxwin_status::panel(this, inst, gpanel, 20), 1, wxGROW);
1000 spanel_shown = true;
1001 toplevel->SetSizeHints(this);
1002 SetSizer(toplevel);
1003 Fit();
1004 gpanel->SetFocus();
1005 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
1006 SetMenuBar(menubar = new wxMenuBar);
1007 SetStatusBar(statusbar = new wxStatusBar(this));
1009 menu_start(wxT("File"));
1010 menu_start_sub(wxT("New"));
1011 menu_entry(wxID_NEW_MOVIE, wxT("Movie..."));
1012 menu_entry(wxID_NEW_PROJECT, wxT("Project..."));
1013 menu_end_sub();
1014 menu_start_sub(wxT("Load"));
1015 menu_entry(wxID_LOAD_STATE, wxT("State..."));
1016 menu_entry(wxID_LOAD_MOVIE, wxT("Movie..."));
1017 menu_entry(wxID_DOWNLOAD, wxT("Download movie..."));
1018 if(loadlib::library::name() != "") {
1019 menu_separator();
1020 menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + loadlib::library::name()));
1021 menu_entry(wxID_PLUGIN_MANAGER, towxstring("Plugin manager"));
1023 menu_separator();
1024 menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM"));
1025 menu_entry(wxID_LOAD_ROM_IMAGE_FIRST, wxT("ROM..."));
1026 menu_special_sub(wxT("Multifile ROM"), loadroms = new loadrom_menu(this, wxID_LOAD_ROM_IMAGE_FIRST + 1,
1027 wxID_LOAD_ROM_IMAGE_LAST, [this](core_type* t) { this->do_load_rom_image(t); }));
1028 menu_special_sub(wxT("Project"), projects = new projects_menu(this, inst, wxID_PROJECT_FIRST,
1029 wxID_PROJECT_LAST, get_config_path() + "/recent-projects.txt", [this](const std::string& id) {
1030 this->project_selected(id); }));
1031 menu_separator();
1032 menu_special_sub(wxT("Recent ROMs"), recent_roms = new recent_menu<recentfiles::multirom>(this, inst,
1033 wxID_RROM_FIRST, wxID_RROM_LAST, get_config_path() + "/recent-roms.txt", recent_rom_selected));
1034 menu_special_sub(wxT("Recent Movies"), recent_movies = new recent_menu<recentfiles::path>(this, inst,
1035 wxID_RMOVIE_FIRST, wxID_RMOVIE_LAST, get_config_path() + "/recent-movies.txt",
1036 recent_movie_selected));
1037 menu_special_sub(wxT("Recent Lua scripts"), recent_scripts = new recent_menu<recentfiles::path>(this, inst,
1038 wxID_RLUA_FIRST, wxID_RLUA_LAST, get_config_path() + "/recent-scripts.txt",
1039 recent_script_selected));
1040 menu_separator();
1041 menu_entry(wxID_CONFLICTRESOLUTION, wxT("Conflict resolution"));
1042 menu_separator();
1043 branches_menu* brlist;
1044 auto brlist_item = menu_special_sub(wxT("Branches"), brlist = new branches_menu(this, inst,
1045 wxID_BRANCH_FIRST, wxID_BRANCH_LAST));
1046 brlist->set_disabler([brlist_item](bool enabled) { brlist_item->Enable(enabled); });
1047 brlist->update();
1048 menu_end_sub();
1049 menu_start_sub(wxT("Save"));
1050 menu_entry(wxID_SAVE_STATE, wxT("State..."));
1051 menu_entry(wxID_SAVE_MOVIE, wxT("Movie..."));
1052 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Screenshot..."));
1053 menu_entry(wxID_SAVE_SUBTITLES, wxT("Subtitles..."));
1054 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pending saves"));
1055 menu_separator();
1056 menu_entry(wxID_CHDIR, wxT("Change working directory..."));
1057 menu_separator();
1058 menu_special_sub(wxT("Upload"), new upload_menu(this, inst, wxID_UPLOAD_FIRST, wxID_UPLOAD_LAST));
1059 menu_end_sub();
1060 menu_start_sub(wxT("Close"));
1061 menu_entry(wxID_CLOSE_PROJECT, wxT("Project"));
1062 menu_entry(wxID_CLOSE_ROM, wxT("ROM"));
1063 menu_enable(wxID_CLOSE_PROJECT, inst.project->get() != NULL);
1064 menu_enable(wxID_CLOSE_ROM, inst.project->get() == NULL);
1065 menu_end_sub();
1066 menu_separator();
1067 menu_entry(wxID_EXIT, wxT("Quit"));
1069 menu_special(wxT("System"), reinterpret_cast<wxMenu*>(sysmenu = new system_menu(this, inst)));
1071 menu_start(wxT("Movie"));
1072 menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode"));
1073 menu_check(wxID_READONLY_MODE, is_readonly_mode(inst));
1074 menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors..."));
1075 menu_entry(wxID_EDIT_SUBTITLES, wxT("Edit subtitles..."));
1076 menu_entry(wxID_EDIT_VSUBTITLES, wxT("Edit commantary track..."));
1077 menu_separator();
1078 menu_entry(wxID_REWIND_MOVIE, wxT("Rewind to start"));
1080 menu_start(wxT("Speed"));
1081 menu_entry(wxID_SPEED_5, wxT("1/20x"));
1082 menu_entry(wxID_SPEED_10, wxT("1/10x"));
1083 menu_entry(wxID_SPEED_17, wxT("1/6x"));
1084 menu_entry(wxID_SPEED_20, wxT("1/5x"));
1085 menu_entry(wxID_SPEED_25, wxT("1/4x"));
1086 menu_entry(wxID_SPEED_33, wxT("1/3x"));
1087 menu_entry(wxID_SPEED_50, wxT("1/2x"));
1088 menu_entry(wxID_SPEED_100, wxT("1x"));
1089 menu_entry(wxID_SPEED_150, wxT("1.5x"));
1090 menu_entry(wxID_SPEED_200, wxT("2x"));
1091 menu_entry(wxID_SPEED_300, wxT("3x"));
1092 menu_entry(wxID_SPEED_500, wxT("5x"));
1093 menu_entry(wxID_SPEED_1000, wxT("10x"));
1094 menu_entry(wxID_SPEED_TURBO, wxT("Turbo"));
1095 menu_entry(wxID_SET_SPEED, wxT("Set..."));
1097 menu_start(wxT("Tools"));
1098 menu_entry(wxID_RUN_SCRIPT, wxT("Run batch file..."));
1099 menu_separator();
1100 menu_entry(wxID_EVAL_LUA, wxT("Evaluate Lua statement..."));
1101 menu_entry(wxID_RUN_LUA, wxT("Run Lua script..."));
1102 menu_separator();
1103 menu_entry(wxID_RESET_LUA, wxT("Reset Lua VM"));
1104 menu_separator();
1105 menu_entry(wxID_AUTOHOLD, wxT("Autohold/Autofire..."));
1106 menu_entry(wxID_TASINPUT, wxT("TAS input plugin..."));
1107 menu_entry(wxID_MULTITRACK, wxT("Multitrack..."));
1108 menu_entry(wxID_EDIT_MACROS, wxT("Edit macros..."));
1109 menu_separator();
1110 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch..."));
1111 menu_separator();
1112 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch..."));
1113 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch..."));
1114 menu_separator();
1115 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
1116 menu_entry(wxID_HEXEDITOR, wxT("Memory editor..."));
1117 tracelog_menu* trlog;
1118 auto trlog_item = menu_special_sub(wxT("Trace log"), trlog = new tracelog_menu(this, inst,
1119 wxID_TRACELOG_FIRST, wxID_TRACELOG_LAST));
1120 trlog->set_disabler([trlog_item](bool enabled) { trlog_item->Enable(enabled); });
1121 trlog->update();
1122 menu_entry(wxID_DISASSEMBLER, wxT("Disassembler..."));
1123 menu_separator();
1124 menu_entry(wxID_MOVIE_EDIT, wxT("Edit movie..."));
1125 menu_separator();
1126 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
1127 inst, wxID_DUMP_FIRST, wxID_DUMP_LAST)));
1129 menu_start(wxT("Configure"));
1130 menu_entry_check(wxID_SHOW_STATUS, wxT("Show status panel"));
1131 menu_check(wxID_SHOW_STATUS, true);
1132 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH, wxT("Dedicated memory watch"));
1133 menu_entry(wxID_SHOW_MESSAGES, wxT("Show messages"));
1134 menu_special_sub(wxT("Settings"), new settings_menu(this, inst, wxID_SETTINGS_FIRST));
1135 if(audioapi_driver_initialized()) {
1136 menu_separator();
1137 menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled"));
1138 menu_entry(wxID_VUDISPLAY, wxT("VU display / sound controls"));
1139 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
1141 menu_separator();
1142 menu_entry(wxID_ENTER_FULLSCREEN, wxT("Enter fullscreen mode"));
1144 menu_start(wxT("Help"));
1145 menu_entry(wxID_ABOUT, wxT("About..."));
1147 corechange.set(inst.dispatch->core_change, []() { signal_core_change(); });
1148 titlechange.set(inst.dispatch->title_change, []() { signal_core_change(); });
1149 newcore.set(notify_new_core, []() { update_preferences(); });
1150 unmuted.set(inst.dispatch->sound_unmute, [this](bool unmute) {
1151 runuifun([this, unmute]() { this->menu_check(wxID_AUDIO_ENABLED, unmute); });
1153 modechange.set(inst.dispatch->mode_change, [this](bool readonly) {
1154 runuifun([this, readonly]() { this->menu_check(wxID_READONLY_MODE, readonly); });
1156 gpanel->SetDropTarget(new loadfile(this, inst));
1157 spanel->SetDropTarget(new loadfile(this, inst));
1158 set_hasher_callback(hash_callback);
1159 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1160 menubar->SetMenuLabel(1, towxstring(inst.rom->get_systemmenu_name()));
1161 focus_timer = new _focus_timer;
1162 status_timer = new _status_timer;
1163 if(fscreen) {
1164 wx_escape_count = 0;
1165 enter_or_leave_fullscreen(true);
1169 wxwin_mainwindow::~wxwin_mainwindow()
1171 if(sws_ctx) sws_freeContext(sws_ctx);
1172 if(screen_buffer) delete[] screen_buffer;
1173 if(rotate_buffer) delete[] rotate_buffer;
1174 focus_timer->Stop();
1175 delete focus_timer;
1176 status_timer->Stop();
1177 delete status_timer;
1180 void wxwin_mainwindow::request_paint()
1182 gpanel->Refresh();
1185 void wxwin_mainwindow::on_close(wxCloseEvent& e)
1187 //Veto it for now, latter things will delete it.
1188 e.Veto();
1189 inst.iqueue->queue("quit-emulator");
1192 void wxwin_mainwindow::notify_update() throw()
1194 if(!main_window_dirty) {
1195 main_window_dirty = true;
1196 gpanel->Refresh();
1200 void wxwin_mainwindow::notify_resized() throw()
1202 toplevel->Layout();
1203 toplevel->SetSizeHints(this);
1204 Fit();
1207 void wxwin_mainwindow::notify_update_status() throw()
1209 spanel->request_paint();
1210 if(mwindow)
1211 mwindow->notify_update();
1212 status_updated = true;
1215 void wxwin_mainwindow::notify_exit() throw()
1217 wxwidgets_exiting = true;
1218 join_emulator_thread();
1219 Destroy();
1222 std::string read_variable_map(const std::map<std::string, std::u32string>& vars, const std::string& key)
1224 if(!vars.count(key))
1225 return "";
1226 return utf8::to8(vars.find(key)->second);
1229 void wxwin_mainwindow::update_statusbar()
1231 if(download_in_progress) {
1232 statusbar->SetStatusText(towxstring(download_in_progress->statusmsg()));
1233 return;
1235 if(hashing_in_progress) {
1236 //TODO: Display this as a dialog.
1237 std::ostringstream s;
1238 s << "Hashing ROMs, approximately " << ((hashing_left + 524288) >> 20) << " of "
1239 << ((hashing_total + 524288) >> 20) << "MB left...";
1240 statusbar->SetStatusText(towxstring(s.str()));
1241 return;
1243 auto& vars = inst.status->get_read();
1244 if(!vars.valid) {
1245 inst.status->put_read();
1246 return;
1248 try {
1249 std::ostringstream s;
1250 bool recording = (vars.mode == 'R');
1251 if(vars.movie_valid) {
1252 if(recording)
1253 s << "Frame: " << vars.curframe;
1254 else
1255 s << "Frame: " << vars.curframe << "/" << vars.length;
1256 s << " Lag: " << vars.lag;
1257 if(vars.subframe == _lsnes_status::subframe_savepoint)
1258 s << " Subframe: S";
1259 else if(vars.subframe == _lsnes_status::subframe_video)
1260 s << " Subframe: V";
1261 else
1262 s << " Subframe: " << vars.subframe;
1263 } else {
1264 s << "Frame: N/A Lag: N/A Subframe: N/A";
1266 if(vars.saveslot_valid) {
1267 s << " Slot: ";
1268 if(vars.branch_valid) s << utf8::to8(vars.branch) << "ā†’";
1269 s << vars.saveslot;
1270 s << " [" << utf8::to8(vars.slotinfo) << "]";
1272 s << " Speed: " << vars.speed << "% ";
1273 if(vars.pause == _lsnes_status::pause_break)
1274 s << " Breakpoint";
1275 else if(vars.pause == _lsnes_status::pause_normal)
1276 s << " Paused";
1277 if(vars.dumping)
1278 s << " Dumping";
1279 if(vars.mode == 'C')
1280 s << " Corrupt";
1281 else if(vars.mode == 'R')
1282 s << " Recording";
1283 else if(vars.mode == 'P')
1284 s << " Playback";
1285 else if(vars.mode == 'F')
1286 s << " Finished";
1287 else
1288 s << " Unknown";
1289 if(vars.mbranch_valid)
1290 s << " Branch: " << utf8::to8(vars.mbranch);
1291 std::string macros = utf8::to8(vars.macros);
1292 if(macros.length())
1293 s << " Macros: " << macros;
1295 statusbar->SetStatusText(towxstring(s.str()));
1296 } catch(std::exception& e) {
1298 inst.status->put_read();
1301 #define NEW_KEYBINDING "A new binding..."
1302 #define NEW_ALIAS "A new alias..."
1303 #define NEW_WATCH "A new watch..."
1305 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
1307 try {
1308 handle_menu_click_cancelable(e);
1309 } catch(canceled_exception& e) {
1310 //Ignore.
1311 } catch(std::bad_alloc& e) {
1312 OOM_panic();
1313 } catch(std::exception& e) {
1314 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
1318 void wxwin_mainwindow::refresh_title() throw()
1320 SetTitle(getname(inst));
1321 auto p = inst.project->get();
1322 menu_enable(wxID_RELOAD_ROM_IMAGE, !p);
1323 for(int i = wxID_LOAD_ROM_IMAGE_FIRST; i <= wxID_LOAD_ROM_IMAGE_LAST; i++)
1324 menu_enable(i, !p);
1325 menu_enable(wxID_CLOSE_PROJECT, p != NULL);
1326 menu_enable(wxID_CLOSE_ROM, p == NULL);
1327 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1328 menubar->SetMenuLabel(1, towxstring(inst.rom->get_systemmenu_name()));
1331 namespace
1333 struct movie_or_savestate
1335 public:
1336 typedef std::pair<std::string,std::string> returntype;
1337 movie_or_savestate(emulator_instance& _inst, bool is_state)
1338 : inst(_inst)
1340 state = is_state;
1342 filedialog_input_params input(bool save) const
1344 filedialog_input_params p;
1345 std::string ext = state ? inst.project->savestate_ext() : "lsmv";
1346 std::string name = state ? "Savestates" : "Movies";
1347 if(save) {
1348 p.types.push_back(filedialog_type_entry(name, "*." + ext, ext));
1349 p.types.push_back(filedialog_type_entry(name + " (binary)", "*." + ext, ext));
1350 } else
1351 p.types.push_back(filedialog_type_entry(name, "*." + ext + ";*." + ext + ".backup",
1352 ext));
1353 if(!save && state) {
1354 p.types.push_back(filedialog_type_entry("Savestates [playback]", "*." + ext +
1355 ";*." + ext + ".backup", ext));
1356 p.types.push_back(filedialog_type_entry("Savestates [recording]", "*." + ext +
1357 ";*." + ext + ".backup", ext));
1358 p.types.push_back(filedialog_type_entry("Savestates [preserve]", "*." + ext +
1359 ";*." + ext + ".backup", ext));
1360 p.types.push_back(filedialog_type_entry("Savestates [all branches]", "*." + ext +
1361 ";*." + ext + ".backup", ext));
1363 p.default_type = save ? (state ? save_dflt_binary(*inst.settings) :
1364 movie_dflt_binary(*inst.settings)) : 0;
1365 return p;
1367 std::pair<std::string, std::string> output(const filedialog_output_params& p, bool save) const
1369 std::string cmdmod;
1370 if(save)
1371 cmdmod = p.typechoice ? "-binary" : "-zip";
1372 else if(state)
1373 switch(p.typechoice) {
1374 case 0: cmdmod = ""; break;
1375 case 1: cmdmod = "-readonly"; break;
1376 case 2: cmdmod = "-state"; break;
1377 case 3: cmdmod = "-preserve"; break;
1378 case 4: cmdmod = "-allbranches"; break;
1380 return std::make_pair(cmdmod, p.path);
1382 private:
1383 emulator_instance& inst;
1384 bool state;
1386 struct movie_or_savestate filetype_movie(lsnes_instance, false);
1387 struct movie_or_savestate filetype_savestate(lsnes_instance, true);
1390 void wxwin_mainwindow::project_selected(const std::string& id)
1392 std::string filename, displayname;
1393 bool load_ok = false;
1394 inst.iqueue->run([id, &filename, &displayname, &load_ok]() -> void {
1395 try {
1396 auto& p = CORE().project->load(id); //Check.
1397 filename = p.filename;
1398 displayname = p.name;
1399 load_ok = true;
1400 delete &p;
1401 switch_projects(id);
1402 } catch(std::exception& e) {
1403 messages << "Failed to change project: " << e.what() << std::endl;
1406 if(load_ok) {
1407 recentfiles::namedobj obj;
1408 obj._id = id;
1409 obj._filename = filename;
1410 obj._display = displayname;
1411 projects->add(obj);
1415 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
1417 std::string filename;
1418 std::pair<std::string, std::string> filename2;
1419 bool s;
1420 switch(e.GetId()) {
1421 case wxID_FRAMEADVANCE:
1422 inst.iqueue->queue("+advance-frame");
1423 inst.iqueue->queue("-advance-frame");
1424 return;
1425 case wxID_SUBFRAMEADVANCE:
1426 inst.iqueue->queue("+advance-poll");
1427 inst.iqueue->queue("-advance-poll");
1428 return;
1429 case wxID_NEXTPOLL:
1430 inst.iqueue->queue("advance-skiplag");
1431 return;
1432 case wxID_PAUSE:
1433 inst.iqueue->queue("pause-emulator");
1434 return;
1435 case wxID_EXIT:
1436 inst.iqueue->queue("quit-emulator");
1437 return;
1438 case wxID_AUDIO_ENABLED:
1439 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
1440 return;
1441 case wxID_CANCEL_SAVES:
1442 inst.iqueue->queue("cancel-saves");
1443 return;
1444 case wxID_LOAD_MOVIE:
1445 filename = choose_file_load(this, "Load Movie", UI_get_project_moviepath(inst),
1446 filetype_movie).second;
1447 recent_movies->add(filename);
1448 inst.iqueue->queue("load-movie " + filename);
1449 return;
1450 case wxID_LOAD_STATE:
1451 filename2 = choose_file_load(this, "Load State", UI_get_project_moviepath(inst),
1452 filetype_savestate);
1453 recent_movies->add(filename2.second);
1454 inst.iqueue->queue("load" + filename2.first + " " + filename2.second);
1455 return;
1456 case wxID_REWIND_MOVIE:
1457 inst.iqueue->queue("rewind-movie");
1458 return;
1459 case wxID_SAVE_MOVIE:
1460 filename2 = choose_file_save(this, "Save Movie", UI_get_project_moviepath(inst), filetype_movie,
1461 project_prefixname(inst, "lsmv"));
1462 recent_movies->add(filename2.second);
1463 inst.iqueue->queue("save-movie" + filename2.first + " " + filename2.second);
1464 return;
1465 case wxID_SAVE_SUBTITLES:
1466 inst.iqueue->queue("save-subtitle " + choose_file_save(this, "Save subtitles",
1467 UI_get_project_moviepath(inst), filetype_sub, project_prefixname(inst, "sub")));
1468 return;
1469 case wxID_SAVE_STATE:
1470 filename2 = choose_file_save(this, "Save State", UI_get_project_moviepath(inst),
1471 filetype_savestate);
1472 recent_movies->add(filename2.second);
1473 inst.iqueue->queue("save-state" + filename2.first + " " + filename2.second);
1474 return;
1475 case wxID_SAVE_SCREENSHOT:
1476 inst.iqueue->queue("take-screenshot " + choose_file_save(this, "Save Screenshot",
1477 UI_get_project_moviepath(inst), filetype_png, get_default_screenshot_name(inst)));
1478 return;
1479 case wxID_RUN_SCRIPT:
1480 inst.iqueue->queue("run-script " + pick_file_member(this, "Select Script",
1481 UI_get_project_otherpath(inst)));
1482 return;
1483 case wxID_RUN_LUA: {
1484 std::string f = choose_file_load(this, "Select Lua Script", UI_get_project_otherpath(inst),
1485 filetype_lua_script);
1486 inst.iqueue->queue("run-lua " + f);
1487 recent_scripts->add(f);
1488 return;
1490 case wxID_RESET_LUA:
1491 inst.iqueue->queue("reset-lua");
1492 return;
1493 case wxID_EVAL_LUA:
1494 inst.iqueue->queue("evaluate-lua " + pick_text(this, "Evaluate Lua",
1495 "Enter Lua Statement:"));
1496 return;
1497 case wxID_READONLY_MODE:
1498 s = menu_ischecked(wxID_READONLY_MODE);
1499 inst.iqueue->run([s]() {
1500 auto& core = CORE();
1501 if(!s)
1502 core.lua2->callback_movie_lost("readwrite");
1503 if(*core.mlogic) core.mlogic->get_movie().readonly_mode(s);
1504 core.dispatch->mode_change(s);
1505 if(!s)
1506 core.lua2->callback_do_readwrite();
1507 core.supdater->update();
1508 core.dispatch->status_update();
1510 return;
1511 case wxID_AUTOHOLD:
1512 wxeditor_autohold_display(this, inst);
1513 return;
1514 case wxID_EDIT_AUTHORS:
1515 wxeditor_authors_display(this, inst);
1516 return;
1517 case wxID_EDIT_MACROS:
1518 wxeditor_macro_display(this, inst);
1519 return;
1520 case wxID_EDIT_SUBTITLES:
1521 wxeditor_subtitles_display(this, inst);
1522 return;
1523 case wxID_EDIT_VSUBTITLES:
1524 show_wxeditor_voicesub(this, inst);
1525 return;
1526 case wxID_EDIT_MEMORYWATCH:
1527 wxeditor_memorywatches_display(this, inst);
1528 return;
1529 case wxID_SAVE_MEMORYWATCH: {
1530 modal_pause_holder hld;
1531 std::set<std::string> old_watches;
1532 inst.iqueue->run([&old_watches]() { old_watches = CORE().mwatch->enumerate(); });
1533 std::string filename = choose_file_save(this, "Save watches to file",
1534 UI_get_project_otherpath(inst), filetype_watch);
1535 std::ofstream out(filename.c_str());
1536 for(auto i : old_watches) {
1537 std::string val;
1538 inst.iqueue->run([i, &val]() {
1539 try {
1540 val = CORE().mwatch->get_string(i);
1541 } catch(std::exception& e) {
1542 messages << "Can't get value of watch '" << i << "': " << e.what()
1543 << std::endl;
1546 out << i << std::endl << val << std::endl;
1548 out.close();
1549 return;
1551 case wxID_LOAD_MEMORYWATCH: {
1552 modal_pause_holder hld;
1553 std::set<std::string> old_watches;
1554 inst.iqueue->run([&old_watches]() { old_watches = CORE().mwatch->enumerate(); });
1555 std::map<std::string, std::string> new_watches;
1556 std::string filename = choose_file_load(this, "Choose memory watch file",
1557 UI_get_project_otherpath(inst), filetype_watch);
1558 try {
1559 std::istream& in = zip::openrel(filename, "");
1560 while(in) {
1561 std::string wname;
1562 std::string wexpr;
1563 std::getline(in, wname);
1564 std::getline(in, wexpr);
1565 new_watches[strip_CR(wname)] = strip_CR(wexpr);
1567 delete &in;
1568 } catch(std::exception& e) {
1569 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
1570 wxICON_EXCLAMATION);
1571 return;
1574 inst.iqueue->run([this, &new_watches, &old_watches]() {
1575 handle_watch_load(this->inst, new_watches, old_watches);
1577 return;
1579 case wxID_MEMORY_SEARCH:
1580 wxwindow_memorysearch_display(inst);
1581 return;
1582 case wxID_TASINPUT:
1583 wxeditor_tasinput_display(this, inst);
1584 return;
1585 case wxID_ABOUT: {
1586 std::ostringstream str;
1587 str << "Version: lsnes rr" << lsnes_version << std::endl;
1588 str << "Revision: " << lsnes_git_revision << std::endl;
1589 for(auto i : core_core::all_cores())
1590 if(!i->is_hidden())
1591 str << "Core: " << i->get_core_identifier() << std::endl;
1592 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1593 return;
1595 case wxID_SHOW_STATUS: {
1596 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
1597 if(newstate)
1598 spanel->Show();
1599 if(newstate && !spanel_shown)
1600 toplevel->Add(spanel, 1, wxGROW);
1601 else if(!newstate && spanel_shown)
1602 toplevel->Detach(spanel);
1603 if(!newstate)
1604 spanel->Hide();
1605 spanel_shown = newstate;
1606 toplevel->Layout();
1607 toplevel->SetSizeHints(this);
1608 Fit();
1609 return;
1611 case wxID_DEDICATED_MEMORY_WATCH: {
1612 bool newstate = menu_ischecked(wxID_DEDICATED_MEMORY_WATCH);
1613 if(newstate && !mwindow) {
1614 mwindow = new wxwin_status(-1, inst, "Memory Watch");
1615 spanel->set_watch_flag(1);
1616 mwindow->Show();
1617 } else if(!newstate && mwindow) {
1618 mwindow->Destroy();
1619 mwindow = NULL;
1620 spanel->set_watch_flag(0);
1622 return;
1624 case wxID_SET_SPEED: {
1625 std::string value = "infinite";
1626 double val = inst.framerate->get_speed_multiplier();
1627 if(!(val == std::numeric_limits<double>::infinity()))
1628 value = (stringfmt() << (100 * val)).str();
1629 value = pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value);
1630 try {
1631 if(value == "infinite")
1632 inst.framerate->set_speed_multiplier(
1633 std::numeric_limits<double>::infinity());
1634 else {
1635 double v = parse_value<double>(value) / 100;
1636 if(v <= 0.0001)
1637 throw 42;
1638 inst.framerate->set_speed_multiplier(v);
1640 } catch(...) {
1641 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1643 return;
1645 case wxID_SPEED_5:
1646 set_speed(inst, 5);
1647 break;
1648 case wxID_SPEED_10:
1649 set_speed(inst, 10);
1650 break;
1651 case wxID_SPEED_17:
1652 set_speed(inst, 16.66666666666);
1653 break;
1654 case wxID_SPEED_20:
1655 set_speed(inst, 20);
1656 break;
1657 case wxID_SPEED_25:
1658 set_speed(inst, 25);
1659 break;
1660 case wxID_SPEED_33:
1661 set_speed(inst, 33.3333333333333);
1662 break;
1663 case wxID_SPEED_50:
1664 set_speed(inst, 50);
1665 break;
1666 case wxID_SPEED_100:
1667 set_speed(inst, 100);
1668 break;
1669 case wxID_SPEED_150:
1670 set_speed(inst, 150);
1671 break;
1672 case wxID_SPEED_200:
1673 set_speed(inst, 200);
1674 break;
1675 case wxID_SPEED_300:
1676 set_speed(inst, 300);
1677 break;
1678 case wxID_SPEED_500:
1679 set_speed(inst, 500);
1680 break;
1681 case wxID_SPEED_1000:
1682 set_speed(inst, 1000);
1683 break;
1684 case wxID_SPEED_TURBO:
1685 set_speed(inst, -1);
1686 break;
1687 case wxID_LOAD_LIBRARY: {
1688 std::string name = std::string("load ") + loadlib::library::name();
1689 with_loaded_library(*new loadlib::module(loadlib::library(choose_file_load(this, name,
1690 UI_get_project_otherpath(inst), single_type(loadlib::library::extension(),
1691 loadlib::library::name())))));
1692 handle_post_loadlibrary();
1693 break;
1695 case wxID_PLUGIN_MANAGER:
1696 wxeditor_plugin_manager_display(this);
1697 return;
1698 case wxID_RELOAD_ROM_IMAGE:
1699 inst.iqueue->run([]() {
1700 CORE().command->invoke("unpause-emulator");
1701 reload_current_rom();
1703 return;
1704 case wxID_NEW_MOVIE:
1705 show_projectwindow(this, inst);
1706 return;
1707 case wxID_SHOW_MESSAGES:
1708 msg_window->reshow();
1709 return;
1710 case wxID_CONFLICTRESOLUTION:
1711 show_conflictwindow(this);
1712 return;
1713 case wxID_VUDISPLAY:
1714 open_vumeter_window(this, inst);
1715 return;
1716 case wxID_DISASSEMBLER:
1717 wxeditor_disassembler_display(this, inst);
1718 return;
1719 case wxID_MOVIE_EDIT:
1720 wxeditor_movie_display(this, inst);
1721 return;
1722 case wxID_NEW_PROJECT:
1723 open_new_project_window(this, inst);
1724 return;
1725 case wxID_CLOSE_PROJECT:
1726 inst.iqueue->run([this]() -> void { this->inst.project->set(NULL); });
1727 return;
1728 case wxID_CLOSE_ROM:
1729 inst.iqueue->run([]() -> void { close_rom(); });
1730 return;
1731 case wxID_ENTER_FULLSCREEN:
1732 wx_escape_count = 0;
1733 enter_or_leave_fullscreen(true);
1734 return;
1735 case wxID_LOAD_ROM_IMAGE_FIRST:
1736 do_load_rom_image(NULL);
1737 return;
1738 case wxID_HEXEDITOR:
1739 wxeditor_hexedit_display(this, inst);
1740 return;
1741 case wxID_MULTITRACK:
1742 wxeditor_multitrack_display(this, inst);
1743 return;
1744 case wxID_CHDIR: {
1745 wxDirDialog* d = new wxDirDialog(this, wxT("Change working directory"), wxT("."),
1746 wxDD_DIR_MUST_EXIST);
1747 if(d->ShowModal() == wxID_CANCEL) {
1748 d->Destroy();
1749 return;
1751 std::string path = tostdstring(d->GetPath());
1752 d->Destroy();
1753 chdir(path.c_str());
1754 messages << "Changed working directory to '" << path << "'" << std::endl;
1755 return;
1757 case wxID_DOWNLOAD: {
1758 if(download_in_progress) return;
1759 filename = pick_text(this, "Download movie", "Enter URL to download");
1760 download_in_progress = new file_download();
1761 download_in_progress->url = lsnes_uri_rewrite(filename);
1762 download_in_progress->target_slot = "wxwidgets_download_tmp";
1763 download_in_progress->do_async(*inst.rom);
1764 new download_timer(this, inst);
1765 return;
1770 void wxwin_mainwindow::action_updated()
1772 reinterpret_cast<system_menu*>(sysmenu)->update(true);
1775 void wxwin_mainwindow::enter_or_leave_fullscreen(bool fs)
1777 if(fs && !is_fs) {
1778 if(spanel_shown)
1779 toplevel->Detach(spanel);
1780 spanel->Hide();
1781 is_fs = fs;
1782 ShowFullScreen(true);
1783 Fit();
1784 } else if(!fs && is_fs) {
1785 ShowFullScreen(false);
1786 if(spanel_shown) {
1787 spanel->Show();
1788 toplevel->Add(spanel, 1, wxGROW);
1790 Fit();
1791 is_fs = fs;