Refactor emulator status reporting (and fix the statusbar doesn't update bug)
[lsnes.git] / src / platform / wxwidgets / mainwindow.cpp
blob42e2a8753fac88b42b8e22cd4b4dfa5936998aee
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/keymapper.hpp"
26 #include "interface/romtype.hpp"
27 #include "core/loadlib.hpp"
28 #include "lua/lua.hpp"
29 #include "core/mainloop.hpp"
30 #include "core/memorywatch.hpp"
31 #include "core/misc.hpp"
32 #include "core/moviedata.hpp"
33 #include "core/project.hpp"
34 #include "core/romloader.hpp"
35 #include "core/settings.hpp"
36 #include "core/window.hpp"
37 #include "library/directory.hpp"
38 #include "library/minmax.hpp"
39 #include "library/string.hpp"
40 #include "library/zip.hpp"
41 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
42 #define FUCKED_SYSTEM
43 #endif
45 #include <cmath>
46 #include <vector>
47 #include <string>
50 extern "C"
52 #ifndef UINT64_C
53 #define UINT64_C(val) val##ULL
54 #endif
55 #include <libswscale/swscale.h>
58 enum
60 wxID_PAUSE = wxID_HIGHEST + 1,
61 wxID_FRAMEADVANCE,
62 wxID_SUBFRAMEADVANCE,
63 wxID_NEXTPOLL,
64 wxID_AUDIO_ENABLED,
65 wxID_SAVE_STATE,
66 wxID_SAVE_MOVIE,
67 wxID_SAVE_SUBTITLES,
68 wxID_LOAD_STATE,
69 wxID_LOAD_MOVIE,
70 wxID_RUN_SCRIPT,
71 wxID_RUN_LUA,
72 wxID_RESET_LUA,
73 wxID_EVAL_LUA,
74 wxID_SAVE_SCREENSHOT,
75 wxID_READONLY_MODE,
76 wxID_EDIT_AUTHORS,
77 wxID_AUTOHOLD,
78 wxID_EDIT_MEMORYWATCH,
79 wxID_SAVE_MEMORYWATCH,
80 wxID_LOAD_MEMORYWATCH,
81 wxID_EDIT_SUBTITLES,
82 wxID_EDIT_VSUBTITLES,
83 wxID_DUMP_FIRST,
84 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
85 wxID_REWIND_MOVIE,
86 wxID_MEMORY_SEARCH,
87 wxID_CANCEL_SAVES,
88 wxID_SHOW_STATUS,
89 wxID_SET_SPEED,
90 wxID_SPEED_5,
91 wxID_SPEED_10,
92 wxID_SPEED_17,
93 wxID_SPEED_20,
94 wxID_SPEED_25,
95 wxID_SPEED_33,
96 wxID_SPEED_50,
97 wxID_SPEED_100,
98 wxID_SPEED_150,
99 wxID_SPEED_200,
100 wxID_SPEED_300,
101 wxID_SPEED_500,
102 wxID_SPEED_1000,
103 wxID_SPEED_TURBO,
104 wxID_LOAD_LIBRARY,
105 wxID_RELOAD_ROM_IMAGE,
106 wxID_LOAD_ROM_IMAGE_FIRST,
107 wxID_LOAD_ROM_IMAGE_LAST = wxID_LOAD_ROM_IMAGE_FIRST + 1023,
108 wxID_NEW_MOVIE,
109 wxID_SHOW_MESSAGES,
110 wxID_DEDICATED_MEMORY_WATCH,
111 wxID_RMOVIE_FIRST,
112 wxID_RMOVIE_LAST = wxID_RMOVIE_FIRST + 16,
113 wxID_RROM_FIRST,
114 wxID_RROM_LAST = wxID_RROM_FIRST + 16,
115 wxID_CONFLICTRESOLUTION,
116 wxID_VUDISPLAY,
117 wxID_MOVIE_EDIT,
118 wxID_TASINPUT,
119 wxID_NEW_PROJECT,
120 wxID_CLOSE_PROJECT,
121 wxID_CLOSE_ROM,
122 wxID_EDIT_MACROS,
123 wxID_ENTER_FULLSCREEN,
124 wxID_ACTIONS_FIRST,
125 wxID_ACTIONS_LAST = wxID_ACTIONS_FIRST + 256,
126 wxID_SETTINGS_FIRST,
127 wxID_SETTINGS_LAST = wxID_SETTINGS_FIRST + 256,
128 wxID_HEXEDITOR,
129 wxID_MULTITRACK,
130 wxID_CHDIR,
131 wxID_RLUA_FIRST,
132 wxID_RLUA_LAST = wxID_RLUA_FIRST + 16,
133 wxID_UPLOAD_FIRST,
134 wxID_UPLOAD_LAST = wxID_UPLOAD_FIRST + 256,
135 wxID_DOWNLOAD,
136 wxID_TRACELOG_FIRST,
137 wxID_TRACELOG_LAST = wxID_TRACELOG_FIRST + 256,
138 wxID_PLUGIN_MANAGER,
139 wxID_BRANCH_FIRST,
140 wxID_BRANCH_LAST = wxID_BRANCH_FIRST + 10240,
141 wxID_PROJECT_FIRST,
142 wxID_PROJECT_LAST = wxID_PROJECT_FIRST + 17,
143 wxID_DISASSEMBLER,
147 double video_scale_factor = 1.0;
148 int scaling_flags = SWS_POINT;
149 bool arcorrect_enabled = false;
150 bool hflip_enabled = false;
151 bool vflip_enabled = false;
152 bool rotate_enabled = false;
154 namespace
156 std::string last_volume = "0dB";
157 std::string last_volume_record = "0dB";
158 std::string last_volume_voice = "0dB";
159 unsigned char* screen_buffer;
160 struct SwsContext* sws_ctx;
161 uint32_t* rotate_buffer;
162 uint32_t old_width;
163 uint32_t old_height;
164 int old_flags = SWS_POINT;
165 bool old_hflip = false;
166 bool old_vflip = false;
167 bool old_rotate = false;
168 bool main_window_dirty;
169 bool is_fs = false;
170 bool hashing_in_progress = false;
171 uint64_t hashing_left = 0;
172 uint64_t hashing_total = 0;
173 int64_t last_update = 0;
174 threads::thread* emulation_thread;
176 settingvar::variable<settingvar::model_bool<settingvar::yes_no>> background_audio(lsnes_vset,
177 "background-audio", "GUIā€£Enable background audio", true);
179 class _focus_timer : public wxTimer
181 public:
182 _focus_timer()
184 was_focused = (wxWindow::FindFocus() != NULL);
185 was_enabled = platform::is_sound_enabled();
186 Start(500);
188 void Notify()
190 bool is_focused = (wxWindow::FindFocus() != NULL);
191 if(is_focused && !was_focused) {
192 //Gained focus.
193 if(!background_audio)
194 platform::sound_enable(was_enabled);
195 } else if(!is_focused && was_focused) {
196 //Lost focus.
197 was_enabled = platform::is_sound_enabled();
198 if(!background_audio)
199 platform::sound_enable(false);
201 was_focused = is_focused;
203 private:
204 bool was_focused;
205 bool was_enabled;
208 class download_timer : public wxTimer
210 public:
211 download_timer(wxwin_mainwindow* main)
213 w = main;
214 Start(50);
216 void Notify()
218 if(w->download_in_progress->finished) {
219 w->update_statusbar();
220 auto old = w->download_in_progress;
221 w->download_in_progress = NULL;
222 if(old->errormsg != "") {
223 show_message_ok(w, "Error downloading movie", old->errormsg,
224 wxICON_EXCLAMATION);
225 } else {
226 platform::queue("load-movie $MEMORY:wxwidgets_download_tmp");
228 delete old;
229 Stop();
230 delete this;
231 } else {
232 w->update_statusbar();
235 private:
236 wxwin_mainwindow* w;
239 void hash_callback(uint64_t left, uint64_t total)
241 wxwin_mainwindow* mwin = main_window;
242 if(left == 0xFFFFFFFFFFFFFFFFULL) {
243 hashing_in_progress = false;
244 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
245 last_update = get_utime() - 2000000;
246 return;
248 hashing_in_progress = true;
249 hashing_left = left;
250 hashing_total = total;
251 int64_t this_update = get_utime();
252 if(this_update < last_update - 1000000 || this_update > last_update + 1000000) {
253 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
254 last_update = this_update;
258 std::pair<std::string, std::string> lsplit(std::string l)
260 for(unsigned i = 0; i < l.length() - 3; i++)
261 if((uint8_t)l[i] == 0xE2 && (uint8_t)l[i + 1] == 0x80 && (uint8_t)l[i + 2] == 0xA3)
262 return std::make_pair(l.substr(0, i), l.substr(i + 3));
263 return std::make_pair("", l);
266 recentfiles::multirom loadreq_to_multirom(const romload_request& req)
268 recentfiles::multirom r;
269 r.packfile = req.packfile;
270 r.singlefile = req.singlefile;
271 r.core = req.core;
272 r.system = req.system;
273 r.region = req.region;
274 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++)
275 if(req.files[i] != "") {
276 r.files.resize(i + 1);
277 r.files[i] = req.files[i];
279 return r;
282 class system_menu : public wxMenu
284 public:
285 system_menu(wxWindow* win);
286 ~system_menu();
287 void on_select(wxCommandEvent& e);
288 void update(bool light);
289 private:
290 wxWindow* pwin;
291 void insert_pass(int id, const std::string& label);
292 void insert_act(unsigned id, const std::string& label, bool dots, bool check);
293 wxMenu* lookup_menu(const std::string& key);
294 wxMenuItem* sep;
295 std::map<int, unsigned> action_by_id;
296 std::map<unsigned, wxMenuItem*> item_by_action;
297 std::map<wxMenuItem*, wxMenu*> menu_by_item;
298 std::map<std::string, wxMenu*> submenu_by_name;
299 std::map<std::string, wxMenuItem*> submenui_by_name;
300 std::set<unsigned> toggles;
301 int next_id;
304 wxMenu* system_menu::lookup_menu(const std::string& key)
306 if(key == "")
307 return this;
308 if(submenu_by_name.count(key))
309 return submenu_by_name[key];
310 //Not found, create.
311 if(!sep)
312 sep = AppendSeparator();
313 auto p = lsplit(key);
314 wxMenu* into = lookup_menu(p.first);
315 submenu_by_name[key] = new wxMenu();
316 submenui_by_name[key] = into->AppendSubMenu(submenu_by_name[key], towxstring(p.second));
317 menu_by_item[submenui_by_name[key]] = into;
318 return submenu_by_name[key];
321 void system_menu::insert_act(unsigned id, const std::string& label, bool dots, bool check)
323 if(!sep)
324 sep = AppendSeparator();
326 auto p = lsplit(label);
327 wxMenu* into = lookup_menu(p.first);
329 action_by_id[next_id] = id;
330 std::string use_label = p.second + (dots ? "..." : "");
331 if(check) {
332 item_by_action[id] = into->AppendCheckItem(next_id, towxstring(use_label));
333 toggles.insert(id);
334 } else
335 item_by_action[id] = into->Append(next_id, towxstring(use_label));
336 menu_by_item[item_by_action[id]] = into;
337 pwin->Connect(next_id++, wxEVT_COMMAND_MENU_SELECTED,
338 wxCommandEventHandler(system_menu::on_select), NULL, this);
341 void system_menu::insert_pass(int id, const std::string& label)
343 pwin->Connect(id, wxEVT_COMMAND_MENU_SELECTED,
344 wxCommandEventHandler(wxwin_mainwindow::handle_menu_click), NULL, pwin);
345 Append(id, towxstring(label));
348 system_menu::system_menu(wxWindow* win)
350 pwin = win;
351 insert_pass(wxID_PAUSE, "Pause/Unpause");
352 insert_pass(wxID_FRAMEADVANCE, "Step frame");
353 insert_pass(wxID_SUBFRAMEADVANCE, "Step subframe");
354 insert_pass(wxID_NEXTPOLL, "Step poll");
355 sep = NULL;
358 system_menu::~system_menu()
362 void system_menu::on_select(wxCommandEvent& e)
364 if(!action_by_id.count(e.GetId()))
365 return;
366 unsigned act_id = action_by_id[e.GetId()];
367 const interface_action* act = NULL;
368 for(auto i : our_rom.rtype->get_actions())
369 if(i->id == act_id) {
370 act = i;
371 break;
373 if(!act)
374 return;
375 try {
376 auto p = prompt_action_params(pwin, act->get_title(), act->params);
377 runemufn([act_id,p]() { our_rom.rtype->execute_action(act_id, p); });
378 } catch(canceled_exception& e) {
379 } catch(std::bad_alloc& e) {
380 OOM_panic();
384 void system_menu::update(bool light)
386 if(!light) {
387 next_id = wxID_ACTIONS_FIRST;
388 if(sep) {
389 Destroy(sep);
390 sep = NULL;
392 for(auto i = item_by_action.begin(); i != item_by_action.end(); i++)
393 menu_by_item[i->second]->Destroy(i->second);
394 for(auto i = submenui_by_name.rbegin(); i != submenui_by_name.rend(); i++)
395 menu_by_item[i->second]->Destroy(i->second);
396 action_by_id.clear();
397 item_by_action.clear();
398 menu_by_item.clear();
399 submenu_by_name.clear();
400 submenui_by_name.clear();
401 toggles.clear();
403 for(auto i : our_rom.rtype->get_actions())
404 insert_act(i->id, i->get_title(), !i->params.empty(), i->is_toggle());
406 for(auto i : item_by_action)
407 i.second->Enable(our_rom.rtype->action_flags(i.first) & 1);
408 for(auto i : toggles)
409 item_by_action[i]->Check(our_rom.rtype->action_flags(i) & 2);
412 std::string munge_name(const std::string& orig)
414 std::string newname;
415 regex_results r;
416 if(r = regex("(.*)\\(([0-9]+)\\)", newname)) {
417 uint64_t sequence;
418 try {
419 sequence = parse_value<uint64_t>(r[2]);
420 newname = (stringfmt() << r[1] << "(" << sequence + 1 << ")").str();
421 } catch(...) {
422 newname = newname + "(2)";
424 } else {
425 newname = newname + "(2)";
427 return newname;
430 void handle_watch_load(std::map<std::string, std::string>& new_watches, std::set<std::string>& old_watches)
432 auto proj = project_get();
433 if(proj) {
434 for(auto i : new_watches) {
435 std::string name = i.first;
436 while(true) {
437 if(!old_watches.count(name)) {
438 try {
439 if(name != "" && i.second != "")
440 lsnes_memorywatch.set(name, i.second);
441 } catch(std::exception& e) {
442 messages << "Can't set memory watch '" << name << "': "
443 << e.what() << std::endl;
445 break;
446 } else if(lsnes_memorywatch.get_string(name) == i.second)
447 break;
448 else
449 name = munge_name(name);
452 } else {
453 for(auto i : new_watches)
454 try {
455 if(i.first != "" && i.second != "")
456 lsnes_memorywatch.set(i.first, i.second);
457 } catch(std::exception& e) {
458 messages << "Can't set memory watch '" << i.first << "': "
459 << e.what() << std::endl;
461 for(auto i : old_watches)
462 if(!new_watches.count(i))
463 try {
464 lsnes_memorywatch.clear(i);
465 } catch(std::exception& e) {
466 messages << "Can't clear memory watch '" << i << "': "
467 << e.what() << std::endl;
472 std::string get_default_screenshot_name()
474 auto p = project_get();
475 if(!p)
476 return "";
477 else {
478 auto files = enumerate_directory(p->directory, ".*-[0-9]+\\.png");
479 std::set<std::string> numbers;
480 for(auto i : files) {
481 size_t split;
482 #ifdef FUCKED_SYSTEM
483 split = i.find_last_of("\\/");
484 #else
485 split = i.find_last_of("/");
486 #endif
487 std::string name = i;
488 if(split < name.length())
489 name = name.substr(split + 1);
490 regex_results r = regex("(.*)-([0-9]+)\\.png", name);
491 if(r[1] != p->prefix)
492 continue;
493 numbers.insert(r[2]);
495 for(uint64_t i = 1;; i++) {
496 std::string candidate = (stringfmt() << i).str();
497 if(!numbers.count(candidate))
498 return p->prefix + "-" + candidate + ".png";
503 std::string project_prefixname(const std::string ext)
505 auto p = project_get();
506 if(!p)
507 return "";
508 else
509 return p->prefix + "." + ext;
513 double pick_volume(wxWindow* win, const std::string& title, std::string& last)
515 std::string value;
516 regex_results r;
517 double parsed = 1;
518 value = pick_text(win, title, "Enter volume in absolute units, percentage (%) or dB:",
519 last);
520 if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)", value))
521 parsed = strtod(r[1].c_str(), NULL);
522 else if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)%", value))
523 parsed = strtod(r[1].c_str(), NULL) / 100;
524 else if(r = regex("([+-]?([0-9]*.[0-9]+|[0-9]+))dB", value))
525 parsed = pow(10, strtod(r[1].c_str(), NULL) / 20);
526 else {
527 wxMessageBox(wxT("Invalid volume"), _T("Error"), wxICON_EXCLAMATION | wxOK, win);
528 return -1;
530 last = value;
531 return parsed;
534 void recent_rom_selected(const recentfiles::multirom& file)
536 romload_request req;
537 req.packfile = file.packfile;
538 req.singlefile = file.singlefile;
539 req.core = file.core;
540 req.system = file.system;
541 req.region = file.region;
542 for(unsigned i = 0; i < file.files.size() && i < ROM_SLOT_COUNT; i++)
543 req.files[i] = file.files[i];
544 runemufn_async([req]() {
545 lsnes_cmd.invoke("unpause-emulator");
546 load_new_rom(req);
550 void recent_movie_selected(const recentfiles::path& file)
552 platform::queue("load-smart " + file.get_path());
555 void recent_script_selected(const recentfiles::path& file)
557 platform::queue("run-lua " + file.get_path());
560 wxString getname()
562 std::string windowname = "lsnes rr" + lsnes_version + " [";
563 auto p = project_get();
564 if(p)
565 windowname = windowname + p->name;
566 else
567 windowname = windowname + our_rom.rtype->get_core_identifier();
568 windowname = windowname + "]";
569 return towxstring(windowname);
572 struct emu_args
574 struct loaded_rom rom;
575 struct moviefile* initial;
576 bool load_has_to_succeed;
579 void* emulator_main(void* _args)
581 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
582 try {
583 our_rom = args->rom;
584 messages << "Using core: " << our_rom.rtype->get_core_identifier() << std::endl;
585 struct moviefile* movie = args->initial;
586 bool has_to_succeed = args->load_has_to_succeed;
587 platform::flush_command_queue();
588 main_loop(our_rom, *movie, has_to_succeed);
589 signal_program_exit();
590 } catch(std::bad_alloc& e) {
591 OOM_panic();
592 } catch(std::exception& e) {
593 messages << "FATAL: " << e.what() << std::endl;
594 platform::fatal_error();
596 delete args;
597 return NULL;
600 void join_emulator_thread()
602 emulation_thread->join();
605 keyboard::mouse_calibration mouse_cal = {0};
606 keyboard::key_mouse mouse_x(lsnes_kbd, "mouse_x", "mouse", mouse_cal);
607 keyboard::key_mouse mouse_y(lsnes_kbd, "mouse_y", "mouse", mouse_cal);
608 keyboard::key_key mouse_l(lsnes_kbd, "mouse_left", "mouse");
609 keyboard::key_key mouse_m(lsnes_kbd, "mouse_center", "mouse");
610 keyboard::key_key mouse_r(lsnes_kbd, "mouse_right", "mouse");
611 keyboard::key_key mouse_i(lsnes_kbd, "mouse_inwindow", "mouse");
613 std::pair<double, double> calc_scale_factors(double factor, bool ar,
614 double par)
616 if(!ar)
617 return std::make_pair(factor, factor);
618 else if(par < 1) {
619 //Too wide, make taller.
620 return std::make_pair(factor, factor / par);
621 } else {
622 //Too narrow, make wider.
623 return std::make_pair(factor * par, factor);
627 void handle_wx_mouse(wxMouseEvent& e)
629 auto sfactors = calc_scale_factors(video_scale_factor, arcorrect_enabled,
630 (our_rom.rtype) ? our_rom.rtype->get_PAR() : 1.0);
631 platform::queue(keypress(keyboard::modifier_set(), mouse_x, e.GetX() / sfactors.first));
632 platform::queue(keypress(keyboard::modifier_set(), mouse_y, e.GetY() / sfactors.second));
633 if(e.Entering())
634 platform::queue(keypress(keyboard::modifier_set(), mouse_i, 1));
635 if(e.Leaving())
636 platform::queue(keypress(keyboard::modifier_set(), mouse_i, 0));
637 if(e.LeftDown())
638 platform::queue(keypress(keyboard::modifier_set(), mouse_l, 1));
639 if(e.LeftUp())
640 platform::queue(keypress(keyboard::modifier_set(), mouse_l, 0));
641 if(e.MiddleDown())
642 platform::queue(keypress(keyboard::modifier_set(), mouse_m, 1));
643 if(e.MiddleUp())
644 platform::queue(keypress(keyboard::modifier_set(), mouse_m, 0));
645 if(e.RightDown())
646 platform::queue(keypress(keyboard::modifier_set(), mouse_r, 1));
647 if(e.RightUp())
648 platform::queue(keypress(keyboard::modifier_set(), mouse_r, 0));
651 bool is_readonly_mode()
653 bool ret;
654 runemufn([&ret]() { ret = movb ? movb.get_movie().readonly_mode() : false; });
655 return ret;
658 std::pair<int, int> UI_controller_index_by_logical(unsigned lid)
660 std::pair<int, int> ret;
661 runemufn([&ret, lid]() { ret = controls.lcid_to_pcid(lid); });
662 return ret;
665 void set_speed(double target)
667 if(target < 0)
668 set_speed_multiplier(std::numeric_limits<double>::infinity());
669 else
670 set_speed_multiplier(target / 100);
673 void update_preferences()
675 preferred_core.clear();
676 for(auto i : core_type::get_core_types()) {
677 std::string val = i->get_hname() + " / " + i->get_core_identifier();
678 for(auto j : i->get_extensions()) {
679 std::string key = "ext:" + j;
680 if(core_selections.count(key) && core_selections[key] == val)
681 preferred_core[key] = i;
683 std::string key2 = "type:" + i->get_iname();
684 if(core_selections.count(key2) && core_selections[key2] == val)
685 preferred_core[key2] = i;
689 std::string movie_path()
691 return lsnes_vset["moviepath"].str();
694 bool is_lsnes_movie(const std::string& filename)
696 std::istream* s = NULL;
697 try {
698 bool ans = false;
699 s = &zip::openrel(filename, "");
700 char buf[6] = {0};
701 s->read(buf, 5);
702 if(*s && !strcmp(buf, "lsmv\x1A"))
703 ans = true;
704 delete s;
705 if(ans) return true;
706 } catch(...) {
707 delete s;
709 try {
710 zip::reader r(filename);
711 std::istream& s = r["systemid"];
712 std::string s2;
713 std::getline(s, s2);
714 delete &s;
715 istrip_CR(s2);
716 return (s2 == "lsnes-rr1");
717 } catch(...) {
718 return false;
722 class loadfile : public wxFileDropTarget
724 public:
725 loadfile(wxwin_mainwindow* win) : pwin(win) {};
726 bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
728 bool ret = false;
729 if(filenames.Count() == 2) {
730 std::string a = tostdstring(filenames[0]);
731 std::string b = tostdstring(filenames[1]);
732 bool amov = is_lsnes_movie(a);
733 bool bmov = is_lsnes_movie(b);
734 if(amov == bmov)
735 return false;
736 if(amov) std::swap(a, b);
737 runemufn_async([a, b]() {
738 lsnes_cmd.invoke("unpause-emulator");
739 romload_request req;
740 req.packfile = a;
741 load_new_rom(req);
742 lsnes_cmd.invoke("load-smart " + b);
744 ret = true;
746 if(filenames.Count() == 1) {
747 std::string a = tostdstring(filenames[0]);
748 bool amov = is_lsnes_movie(a);
749 if(amov) {
750 platform::queue("load-smart " + a);
751 pwin->recent_movies->add(a);
752 ret = true;
753 } else {
754 romload_request req;
755 req.packfile = a;
756 runemufn_async([req]() {
757 lsnes_cmd.invoke("unpause-emulator");
758 load_new_rom(req);
760 pwin->recent_roms->add(loadreq_to_multirom(req));
761 ret = true;
764 return ret;
766 wxwin_mainwindow* pwin;
770 void boot_emulator(loaded_rom& rom, moviefile& movie, bool fscreen)
772 update_preferences();
773 try {
774 struct emu_args* a = new emu_args;
775 a->rom = rom;
776 a->initial = &movie;
777 a->load_has_to_succeed = false;
778 modal_pause_holder hld;
779 emulation_thread = new threads::thread(emulator_main, a);
780 main_window = new wxwin_mainwindow(fscreen);
781 main_window->Show();
782 } catch(std::bad_alloc& e) {
783 OOM_panic();
787 wxwin_mainwindow::panel::panel(wxWindow* win)
788 : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS)
790 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
791 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
792 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
793 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
794 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
795 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
796 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
797 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
798 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
799 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
800 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
801 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
802 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
803 SetMinSize(wxSize(512, 448));
806 void wxwin_mainwindow::menu_start(wxString name)
808 while(!upper.empty())
809 upper.pop();
810 current_menu = new wxMenu();
811 menubar->Append(current_menu, name);
814 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
816 while(!upper.empty())
817 upper.pop();
818 menubar->Append(menu, name);
819 current_menu = NULL;
822 wxMenuItem* wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
824 return current_menu->AppendSubMenu(menu, name);
827 void wxwin_mainwindow::menu_entry(int id, wxString name)
829 current_menu->Append(id, name);
830 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
831 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
834 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
836 checkitems[id] = current_menu->AppendCheckItem(id, name);
837 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
838 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
841 void wxwin_mainwindow::menu_start_sub(wxString name)
843 wxMenu* old = current_menu;
844 upper.push(current_menu);
845 current_menu = new wxMenu();
846 old->AppendSubMenu(current_menu, name);
849 void wxwin_mainwindow::menu_end_sub()
851 current_menu = upper.top();
852 upper.pop();
855 bool wxwin_mainwindow::menu_ischecked(int id)
857 if(checkitems.count(id))
858 return checkitems[id]->IsChecked();
859 else
860 return false;
863 void wxwin_mainwindow::menu_check(int id, bool newstate)
865 if(checkitems.count(id))
866 return checkitems[id]->Check(newstate);
867 else
868 return;
871 void wxwin_mainwindow::menu_enable(int id, bool newstate)
873 auto item = menubar->FindItem(id);
874 if(!item)
875 return;
876 item->Enable(newstate);
879 void wxwin_mainwindow::menu_separator()
881 current_menu->AppendSeparator();
884 void wxwin_mainwindow::panel::request_paint()
886 Refresh();
889 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
891 if(wx_escape_count >= 3 && is_fs) {
892 //Leave fullscreen mode.
893 main_window->enter_or_leave_fullscreen(false);
895 render_framebuffer();
896 static
897 uint8_t* srcp[1];
898 int srcs[1];
899 uint8_t* dstp[1];
900 int dsts[1];
901 wxPaintDC dc(this);
902 uint32_t tw, th;
903 bool aux = hflip_enabled || vflip_enabled || rotate_enabled;
904 auto sfactors = calc_scale_factors(video_scale_factor, arcorrect_enabled, our_rom.rtype ?
905 our_rom.rtype->get_PAR() : 1.0);
906 if(rotate_enabled) {
907 tw = main_screen.get_height() * sfactors.second + 0.5;
908 th = main_screen.get_width() * sfactors.first + 0.5;
909 } else {
910 tw = main_screen.get_width() * sfactors.first + 0.5;
911 th = main_screen.get_height() * sfactors.second + 0.5;
913 if(!tw || !th) {
914 main_window_dirty = false;
915 return;
917 //Scale this to fullscreen.
918 if(is_fs) {
919 wxSize screen = main_window->GetSize();
920 double fss = min(1.0 * screen.GetWidth() / tw, 1.0 * screen.GetHeight() / th);
921 tw *= fss;
922 th *= fss;
925 if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags ||
926 hflip_enabled != old_hflip || vflip_enabled != old_vflip || rotate_enabled != old_rotate) {
927 if(screen_buffer) {
928 delete[] screen_buffer;
929 screen_buffer = NULL;
931 if(rotate_buffer) {
932 delete[] rotate_buffer;
933 rotate_buffer = NULL;
935 old_height = th;
936 old_width = tw;
937 old_flags = scaling_flags;
938 old_hflip = hflip_enabled;
939 old_vflip = vflip_enabled;
940 old_rotate = rotate_enabled;
941 uint32_t w = main_screen.get_width();
942 uint32_t h = main_screen.get_height();
943 if(w && h)
944 sws_ctx = sws_getCachedContext(sws_ctx, rotate_enabled ? h : w, rotate_enabled ? w : h,
945 PIX_FMT_RGBA, tw, th, PIX_FMT_BGR24, scaling_flags, NULL, NULL, NULL);
946 tw = max(tw, static_cast<uint32_t>(128));
947 th = max(th, static_cast<uint32_t>(112));
948 screen_buffer = new unsigned char[tw * th * 3];
949 if(aux)
950 rotate_buffer = new uint32_t[main_screen.get_width() * main_screen.get_height()];
951 SetMinSize(wxSize(tw, th));
952 signal_resize_needed();
954 if(aux) {
955 //Hflip, Vflip or rotate active.
956 size_t width = main_screen.get_width();
957 size_t height = main_screen.get_height();
958 size_t width1 = width - 1;
959 size_t height1 = height - 1;
960 size_t stride = main_screen.rowptr(1) - main_screen.rowptr(0);
961 uint32_t* pixels = main_screen.rowptr(0);
962 if(rotate_enabled) {
963 for(unsigned y = 0; y < height; y++) {
964 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
965 uint32_t* dpixels = rotate_buffer + (height1 - y);
966 if(hflip_enabled)
967 for(unsigned x = 0; x < width; x++)
968 dpixels[x * height] = pixels2[width1 - x];
969 else
970 for(unsigned x = 0; x < width; x++)
971 dpixels[x * height] = pixels2[x];
973 } else {
974 for(unsigned y = 0; y < height; y++) {
975 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
976 uint32_t* dpixels = rotate_buffer + y * width;
977 if(hflip_enabled)
978 for(unsigned x = 0; x < width; x++)
979 dpixels[x] = pixels2[width1 - x];
980 else
981 for(unsigned x = 0; x < width; x++)
982 dpixels[x] = pixels2[x];
986 if(aux)
987 srcs[0] = 4 * (rotate_enabled ? main_screen.get_height() : main_screen.get_width());
988 else
989 srcs[0] = 4 * main_screen.get_stride();
990 dsts[0] = 3 * tw;
991 srcp[0] = reinterpret_cast<unsigned char*>(aux ? rotate_buffer : main_screen.rowptr(0));
992 dstp[0] = screen_buffer;
993 memset(screen_buffer, 0, tw * th * 3);
994 if(main_screen.get_width() && main_screen.get_height())
995 sws_scale(sws_ctx, srcp, srcs, 0, rotate_enabled ? main_screen.get_width() : main_screen.get_height(),
996 dstp, dsts);
997 wxBitmap bmp(wxImage(tw, th, screen_buffer, true));
998 dc.DrawBitmap(bmp, 0, 0, false);
999 main_window_dirty = false;
1002 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
1004 //Blank.
1007 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
1009 handle_wx_keyboard(e, true);
1012 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
1014 handle_wx_keyboard(e, false);
1017 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
1019 handle_wx_mouse(e);
1022 wxwin_mainwindow::wxwin_mainwindow(bool fscreen)
1023 : wxFrame(NULL, wxID_ANY, getname(), wxDefaultPosition, wxSize(-1, -1),
1024 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX)
1026 download_in_progress = NULL;
1027 Centre();
1028 mwindow = NULL;
1029 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
1030 toplevel->Add(gpanel = new panel(this), 1, wxGROW);
1031 toplevel->Add(spanel = new wxwin_status::panel(this, gpanel, 20), 1, wxGROW);
1032 spanel_shown = true;
1033 toplevel->SetSizeHints(this);
1034 SetSizer(toplevel);
1035 Fit();
1036 gpanel->SetFocus();
1037 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
1038 SetMenuBar(menubar = new wxMenuBar);
1039 SetStatusBar(statusbar = new wxStatusBar(this));
1041 menu_start(wxT("File"));
1042 menu_start_sub(wxT("New"));
1043 menu_entry(wxID_NEW_MOVIE, wxT("Movie..."));
1044 menu_entry(wxID_NEW_PROJECT, wxT("Project..."));
1045 menu_end_sub();
1046 menu_start_sub(wxT("Load"));
1047 menu_entry(wxID_LOAD_STATE, wxT("State..."));
1048 menu_entry(wxID_LOAD_MOVIE, wxT("Movie..."));
1049 menu_entry(wxID_DOWNLOAD, wxT("Download movie..."));
1050 if(loadlib::library::name() != "") {
1051 menu_separator();
1052 menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + loadlib::library::name()));
1053 menu_entry(wxID_PLUGIN_MANAGER, towxstring("Plugin manager"));
1055 menu_separator();
1056 menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM"));
1057 menu_entry(wxID_LOAD_ROM_IMAGE_FIRST, wxT("ROM..."));
1058 menu_special_sub(wxT("Multifile ROM"), loadroms = new loadrom_menu(this, wxID_LOAD_ROM_IMAGE_FIRST + 1,
1059 wxID_LOAD_ROM_IMAGE_LAST, [this](core_type* t) { this->do_load_rom_image(t); }));
1060 menu_special_sub(wxT("Project"), projects = new projects_menu(this, wxID_PROJECT_FIRST, wxID_PROJECT_LAST,
1061 get_config_path() + "/recent-projects.txt", [this](const std::string& id) {
1062 this->project_selected(id); }));
1063 menu_separator();
1064 menu_special_sub(wxT("Recent ROMs"), recent_roms = new recent_menu<recentfiles::multirom>(this,
1065 wxID_RROM_FIRST, wxID_RROM_LAST, get_config_path() + "/recent-roms.txt", recent_rom_selected));
1066 menu_special_sub(wxT("Recent Movies"), recent_movies = new recent_menu<recentfiles::path>(this,
1067 wxID_RMOVIE_FIRST, wxID_RMOVIE_LAST, get_config_path() + "/recent-movies.txt",
1068 recent_movie_selected));
1069 menu_special_sub(wxT("Recent Lua scripts"), recent_scripts = new recent_menu<recentfiles::path>(this,
1070 wxID_RLUA_FIRST, wxID_RLUA_LAST, get_config_path() + "/recent-scripts.txt",
1071 recent_script_selected));
1072 menu_separator();
1073 menu_entry(wxID_CONFLICTRESOLUTION, wxT("Conflict resolution"));
1074 menu_separator();
1075 branches_menu* brlist;
1076 auto brlist_item = menu_special_sub(wxT("Branches"), brlist = new branches_menu(this, wxID_BRANCH_FIRST,
1077 wxID_BRANCH_LAST));
1078 brlist->set_disabler([brlist_item](bool enabled) { brlist_item->Enable(enabled); });
1079 brlist->update();
1080 menu_end_sub();
1081 menu_start_sub(wxT("Save"));
1082 menu_entry(wxID_SAVE_STATE, wxT("State..."));
1083 menu_entry(wxID_SAVE_MOVIE, wxT("Movie..."));
1084 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Screenshot..."));
1085 menu_entry(wxID_SAVE_SUBTITLES, wxT("Subtitles..."));
1086 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pending saves"));
1087 menu_separator();
1088 menu_entry(wxID_CHDIR, wxT("Change working directory..."));
1089 menu_separator();
1090 menu_special_sub(wxT("Upload"), new upload_menu(this, wxID_UPLOAD_FIRST, wxID_UPLOAD_LAST));
1091 menu_end_sub();
1092 menu_start_sub(wxT("Close"));
1093 menu_entry(wxID_CLOSE_PROJECT, wxT("Project"));
1094 menu_entry(wxID_CLOSE_ROM, wxT("ROM"));
1095 menu_enable(wxID_CLOSE_PROJECT, project_get() != NULL);
1096 menu_enable(wxID_CLOSE_ROM, project_get() == NULL);
1097 menu_end_sub();
1098 menu_separator();
1099 menu_entry(wxID_EXIT, wxT("Quit"));
1101 menu_special(wxT("System"), reinterpret_cast<wxMenu*>(sysmenu = new system_menu(this)));
1103 menu_start(wxT("Movie"));
1104 menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode"));
1105 menu_check(wxID_READONLY_MODE, is_readonly_mode());
1106 menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors..."));
1107 menu_entry(wxID_EDIT_SUBTITLES, wxT("Edit subtitles..."));
1108 menu_entry(wxID_EDIT_VSUBTITLES, wxT("Edit commantary track..."));
1109 menu_separator();
1110 menu_entry(wxID_REWIND_MOVIE, wxT("Rewind to start"));
1112 menu_start(wxT("Speed"));
1113 menu_entry(wxID_SPEED_5, wxT("1/20x"));
1114 menu_entry(wxID_SPEED_10, wxT("1/10x"));
1115 menu_entry(wxID_SPEED_17, wxT("1/6x"));
1116 menu_entry(wxID_SPEED_20, wxT("1/5x"));
1117 menu_entry(wxID_SPEED_25, wxT("1/4x"));
1118 menu_entry(wxID_SPEED_33, wxT("1/3x"));
1119 menu_entry(wxID_SPEED_50, wxT("1/2x"));
1120 menu_entry(wxID_SPEED_100, wxT("1x"));
1121 menu_entry(wxID_SPEED_150, wxT("1.5x"));
1122 menu_entry(wxID_SPEED_200, wxT("2x"));
1123 menu_entry(wxID_SPEED_300, wxT("3x"));
1124 menu_entry(wxID_SPEED_500, wxT("5x"));
1125 menu_entry(wxID_SPEED_1000, wxT("10x"));
1126 menu_entry(wxID_SPEED_TURBO, wxT("Turbo"));
1127 menu_entry(wxID_SET_SPEED, wxT("Set..."));
1129 menu_start(wxT("Tools"));
1130 menu_entry(wxID_RUN_SCRIPT, wxT("Run batch file..."));
1131 menu_separator();
1132 menu_entry(wxID_EVAL_LUA, wxT("Evaluate Lua statement..."));
1133 menu_entry(wxID_RUN_LUA, wxT("Run Lua script..."));
1134 menu_separator();
1135 menu_entry(wxID_RESET_LUA, wxT("Reset Lua VM"));
1136 menu_separator();
1137 menu_entry(wxID_AUTOHOLD, wxT("Autohold/Autofire..."));
1138 menu_entry(wxID_TASINPUT, wxT("TAS input plugin..."));
1139 menu_entry(wxID_MULTITRACK, wxT("Multitrack..."));
1140 menu_entry(wxID_EDIT_MACROS, wxT("Edit macros..."));
1141 menu_separator();
1142 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch..."));
1143 menu_separator();
1144 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch..."));
1145 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch..."));
1146 menu_separator();
1147 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
1148 menu_entry(wxID_HEXEDITOR, wxT("Memory editor..."));
1149 tracelog_menu* trlog;
1150 auto trlog_item = menu_special_sub(wxT("Trace log"), trlog = new tracelog_menu(this, wxID_TRACELOG_FIRST,
1151 wxID_TRACELOG_LAST));
1152 trlog->set_disabler([trlog_item](bool enabled) { trlog_item->Enable(enabled); });
1153 trlog->update();
1154 menu_entry(wxID_DISASSEMBLER, wxT("Disassembler..."));
1155 menu_separator();
1156 menu_entry(wxID_MOVIE_EDIT, wxT("Edit movie..."));
1157 menu_separator();
1158 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
1159 wxID_DUMP_FIRST, wxID_DUMP_LAST)));
1161 menu_start(wxT("Configure"));
1162 menu_entry_check(wxID_SHOW_STATUS, wxT("Show status panel"));
1163 menu_check(wxID_SHOW_STATUS, true);
1164 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH, wxT("Dedicated memory watch"));
1165 menu_entry(wxID_SHOW_MESSAGES, wxT("Show messages"));
1166 menu_special_sub(wxT("Settings"), new settings_menu(this, wxID_SETTINGS_FIRST));
1167 if(audioapi_driver_initialized()) {
1168 menu_separator();
1169 menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled"));
1170 menu_entry(wxID_VUDISPLAY, wxT("VU display / sound controls"));
1171 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
1173 menu_separator();
1174 menu_entry(wxID_ENTER_FULLSCREEN, wxT("Enter fullscreen mode"));
1176 menu_start(wxT("Help"));
1177 menu_entry(wxID_ABOUT, wxT("About..."));
1179 corechange.set(notify_core_change, []() { signal_core_change(); });
1180 titlechange.set(notify_title_change, []() { signal_core_change(); });
1181 newcore.set(notify_new_core, []() { update_preferences(); });
1182 unmuted.set(notify_sound_unmute, [this](bool unmute) {
1183 runuifun([this, unmute]() { this->menu_check(wxID_AUDIO_ENABLED, unmute); });
1185 modechange.set(notify_mode_change, [this](bool readonly) {
1186 runuifun([this, readonly]() { this->menu_check(wxID_READONLY_MODE, readonly); });
1188 gpanel->SetDropTarget(new loadfile(this));
1189 spanel->SetDropTarget(new loadfile(this));
1190 set_hasher_callback(hash_callback);
1191 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1192 menubar->SetMenuLabel(1, towxstring(our_rom.rtype->get_systemmenu_name()));
1193 focus_timer = new _focus_timer;
1194 if(fscreen) {
1195 wx_escape_count = 0;
1196 enter_or_leave_fullscreen(true);
1200 wxwin_mainwindow::~wxwin_mainwindow()
1202 if(sws_ctx) sws_freeContext(sws_ctx);
1203 if(screen_buffer) delete[] screen_buffer;
1204 if(rotate_buffer) delete[] rotate_buffer;
1207 void wxwin_mainwindow::request_paint()
1209 gpanel->Refresh();
1212 void wxwin_mainwindow::on_close(wxCloseEvent& e)
1214 //Veto it for now, latter things will delete it.
1215 e.Veto();
1216 platform::queue("quit-emulator");
1219 void wxwin_mainwindow::notify_update() throw()
1221 if(!main_window_dirty) {
1222 main_window_dirty = true;
1223 gpanel->Refresh();
1227 void wxwin_mainwindow::notify_resized() throw()
1229 toplevel->Layout();
1230 toplevel->SetSizeHints(this);
1231 Fit();
1234 void wxwin_mainwindow::notify_update_status() throw()
1236 spanel->request_paint();
1237 if(mwindow)
1238 mwindow->notify_update();
1239 update_statusbar();
1242 void wxwin_mainwindow::notify_exit() throw()
1244 wxwidgets_exiting = true;
1245 join_emulator_thread();
1246 Destroy();
1249 std::string read_variable_map(const std::map<std::string, std::u32string>& vars, const std::string& key)
1251 if(!vars.count(key))
1252 return "";
1253 return utf8::to8(vars.find(key)->second);
1256 void wxwin_mainwindow::update_statusbar()
1258 if(download_in_progress) {
1259 statusbar->SetStatusText(towxstring(download_in_progress->statusmsg()));
1260 return;
1262 if(hashing_in_progress) {
1263 //TODO: Display this as a dialog.
1264 std::ostringstream s;
1265 s << "Hashing ROMs, approximately " << ((hashing_left + 524288) >> 20) << " of "
1266 << ((hashing_total + 524288) >> 20) << "MB left...";
1267 statusbar->SetStatusText(towxstring(s.str()));
1268 return;
1270 auto& vars = lsnes_status.get_read();
1271 if(!vars.valid) {
1272 lsnes_status.put_read();
1273 return;
1275 try {
1276 std::ostringstream s;
1277 bool recording = (vars.mode == 'R');
1278 if(vars.movie_valid) {
1279 if(recording)
1280 s << "Frame: " << vars.curframe;
1281 else
1282 s << "Frame: " << vars.curframe << "/" << vars.length;
1283 s << " Lag: " << vars.lag;
1284 if(vars.subframe == _lsnes_status::subframe_savepoint)
1285 s << " Subframe: S";
1286 else if(vars.subframe == _lsnes_status::subframe_video)
1287 s << " Subframe: V";
1288 else
1289 s << " Subframe: " << vars.subframe;
1290 } else {
1291 s << "Frame: N/A Lag: N/A Subframe: N/A";
1293 if(vars.saveslot_valid) {
1294 s << " Slot: ";
1295 if(vars.branch_valid) s << utf8::to8(vars.branch) << "ā†’";
1296 s << vars.saveslot;
1297 s << " [" << utf8::to8(vars.slotinfo) << "]";
1299 s << " Speed: " << vars.speed << "% ";
1300 if(vars.pause == _lsnes_status::pause_break)
1301 s << " Breakpoint";
1302 else if(vars.pause == _lsnes_status::pause_normal)
1303 s << " Paused";
1304 if(vars.dumping)
1305 s << " Dumping";
1306 if(vars.mode == 'C')
1307 s << " Corrupt";
1308 else if(vars.mode == 'R')
1309 s << " Recording";
1310 else if(vars.mode == 'P')
1311 s << " Playback";
1312 else if(vars.mode == 'F')
1313 s << " Finished";
1314 else
1315 s << " Unknown";
1316 if(vars.mbranch_valid)
1317 s << " Branch: " << utf8::to8(vars.mbranch);
1318 std::string macros = utf8::to8(vars.macros);
1319 if(macros.length())
1320 s << " Macros: " << macros;
1322 statusbar->SetStatusText(towxstring(s.str()));
1323 } catch(std::exception& e) {
1325 lsnes_status.put_read();
1328 #define NEW_KEYBINDING "A new binding..."
1329 #define NEW_ALIAS "A new alias..."
1330 #define NEW_WATCH "A new watch..."
1332 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
1334 try {
1335 handle_menu_click_cancelable(e);
1336 } catch(canceled_exception& e) {
1337 //Ignore.
1338 } catch(std::bad_alloc& e) {
1339 OOM_panic();
1340 } catch(std::exception& e) {
1341 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
1345 void wxwin_mainwindow::refresh_title() throw()
1347 SetTitle(getname());
1348 auto p = project_get();
1349 menu_enable(wxID_RELOAD_ROM_IMAGE, !p);
1350 for(int i = wxID_LOAD_ROM_IMAGE_FIRST; i <= wxID_LOAD_ROM_IMAGE_LAST; i++)
1351 menu_enable(i, !p);
1352 menu_enable(wxID_CLOSE_PROJECT, p != NULL);
1353 menu_enable(wxID_CLOSE_ROM, p == NULL);
1354 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1355 menubar->SetMenuLabel(1, towxstring(our_rom.rtype->get_systemmenu_name()));
1358 namespace
1360 struct movie_or_savestate
1362 public:
1363 typedef std::pair<std::string,std::string> returntype;
1364 movie_or_savestate(bool is_state)
1366 state = is_state;
1368 filedialog_input_params input(bool save) const
1370 filedialog_input_params p;
1371 std::string ext = state ? project_savestate_ext() : "lsmv";
1372 std::string name = state ? "Savestates" : "Movies";
1373 if(save) {
1374 p.types.push_back(filedialog_type_entry(name, "*." + ext, ext));
1375 p.types.push_back(filedialog_type_entry(name + " (binary)", "*." + ext, ext));
1376 } else
1377 p.types.push_back(filedialog_type_entry(name, "*." + ext + ";*." + ext + ".backup",
1378 ext));
1379 if(!save && state) {
1380 p.types.push_back(filedialog_type_entry("Savestates [read only]", "*." + ext +
1381 ";*." + ext + ".backup", ext));
1382 p.types.push_back(filedialog_type_entry("Savestates [read-write]", "*." + ext +
1383 ";*." + ext + ".backup", ext));
1384 p.types.push_back(filedialog_type_entry("Savestates [preserve]", "*." + ext +
1385 ";*." + ext + ".backup", ext));
1386 p.types.push_back(filedialog_type_entry("Savestates [all branches]", "*." + ext +
1387 ";*." + ext + ".backup", ext));
1389 p.default_type = save ? (state ? save_dflt_binary : movie_dflt_binary) : 0;
1390 return p;
1392 std::pair<std::string, std::string> output(const filedialog_output_params& p, bool save) const
1394 std::string cmdmod;
1395 if(save)
1396 cmdmod = p.typechoice ? "-binary" : "-zip";
1397 else if(state)
1398 switch(p.typechoice) {
1399 case 0: cmdmod = ""; break;
1400 case 1: cmdmod = "-readonly"; break;
1401 case 2: cmdmod = "-state"; break;
1402 case 3: cmdmod = "-preserve"; break;
1403 case 4: cmdmod = "-allbranches"; break;
1405 return std::make_pair(cmdmod, p.path);
1407 private:
1408 bool state;
1410 struct movie_or_savestate filetype_movie(false);
1411 struct movie_or_savestate filetype_savestate(true);
1414 void wxwin_mainwindow::project_selected(const std::string& id)
1416 std::string filename, displayname;
1417 bool load_ok = false;
1418 runemufn([id, &filename, &displayname, &load_ok]() -> void {
1419 try {
1420 auto& p = project_load(id); //Check.
1421 filename = p.filename;
1422 displayname = p.name;
1423 load_ok = true;
1424 delete &p;
1425 switch_projects(id);
1426 } catch(std::exception& e) {
1427 messages << "Failed to change project: " << e.what() << std::endl;
1430 if(load_ok) {
1431 recentfiles::namedobj obj;
1432 obj._id = id;
1433 obj._filename = filename;
1434 obj._display = displayname;
1435 projects->add(obj);
1439 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
1441 std::string filename;
1442 std::pair<std::string, std::string> filename2;
1443 bool s;
1444 switch(e.GetId()) {
1445 case wxID_FRAMEADVANCE:
1446 platform::queue("+advance-frame");
1447 platform::queue("-advance-frame");
1448 return;
1449 case wxID_SUBFRAMEADVANCE:
1450 platform::queue("+advance-poll");
1451 platform::queue("-advance-poll");
1452 return;
1453 case wxID_NEXTPOLL:
1454 platform::queue("advance-skiplag");
1455 return;
1456 case wxID_PAUSE:
1457 platform::queue("pause-emulator");
1458 return;
1459 case wxID_EXIT:
1460 platform::queue("quit-emulator");
1461 return;
1462 case wxID_AUDIO_ENABLED:
1463 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
1464 return;
1465 case wxID_CANCEL_SAVES:
1466 platform::queue("cancel-saves");
1467 return;
1468 case wxID_LOAD_MOVIE:
1469 filename = choose_file_load(this, "Load Movie", project_moviepath(), filetype_movie).second;
1470 recent_movies->add(filename);
1471 platform::queue("load-movie " + filename);
1472 return;
1473 case wxID_LOAD_STATE:
1474 filename2 = choose_file_load(this, "Load State", project_moviepath(), filetype_savestate);
1475 recent_movies->add(filename2.second);
1476 platform::queue("load" + filename2.first + " " + filename2.second);
1477 return;
1478 case wxID_REWIND_MOVIE:
1479 platform::queue("rewind-movie");
1480 return;
1481 case wxID_SAVE_MOVIE:
1482 filename2 = choose_file_save(this, "Save Movie", project_moviepath(), filetype_movie,
1483 project_prefixname("lsmv"));
1484 recent_movies->add(filename2.second);
1485 platform::queue("save-movie" + filename2.first + " " + filename2.second);
1486 return;
1487 case wxID_SAVE_SUBTITLES:
1488 platform::queue("save-subtitle " + choose_file_save(this, "Save subtitles", project_moviepath(),
1489 filetype_sub, project_prefixname("sub")));
1490 return;
1491 case wxID_SAVE_STATE:
1492 filename2 = choose_file_save(this, "Save State", project_moviepath(), filetype_savestate);
1493 recent_movies->add(filename2.second);
1494 platform::queue("save-state" + filename2.first + " " + filename2.second);
1495 return;
1496 case wxID_SAVE_SCREENSHOT:
1497 platform::queue("take-screenshot " + choose_file_save(this, "Save Screenshot", project_moviepath(),
1498 filetype_png, get_default_screenshot_name()));
1499 return;
1500 case wxID_RUN_SCRIPT:
1501 platform::queue("run-script " + pick_file_member(this, "Select Script", project_otherpath()));
1502 return;
1503 case wxID_RUN_LUA: {
1504 std::string f = choose_file_load(this, "Select Lua Script", project_otherpath(),
1505 filetype_lua_script);
1506 platform::queue("run-lua " + f);
1507 recent_scripts->add(f);
1508 return;
1510 case wxID_RESET_LUA:
1511 platform::queue("reset-lua");
1512 return;
1513 case wxID_EVAL_LUA:
1514 platform::queue("evaluate-lua " + pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
1515 return;
1516 case wxID_READONLY_MODE:
1517 s = menu_ischecked(wxID_READONLY_MODE);
1518 runemufn([s]() {
1519 if(!s)
1520 lua_callback_movie_lost("readwrite");
1521 if(movb) movb.get_movie().readonly_mode(s);
1522 notify_mode_change(s);
1523 if(!s)
1524 lua_callback_do_readwrite();
1525 update_movie_state();
1526 graphics_driver_notify_status();
1528 return;
1529 case wxID_AUTOHOLD:
1530 wxeditor_autohold_display(this);
1531 return;
1532 case wxID_EDIT_AUTHORS:
1533 wxeditor_authors_display(this);
1534 return;
1535 case wxID_EDIT_MACROS:
1536 wxeditor_macro_display(this);
1537 return;
1538 case wxID_EDIT_SUBTITLES:
1539 wxeditor_subtitles_display(this);
1540 return;
1541 case wxID_EDIT_VSUBTITLES:
1542 show_wxeditor_voicesub(this);
1543 return;
1544 case wxID_EDIT_MEMORYWATCH:
1545 wxeditor_memorywatches_display(this);
1546 return;
1547 case wxID_SAVE_MEMORYWATCH: {
1548 modal_pause_holder hld;
1549 std::set<std::string> old_watches;
1550 runemufn([&old_watches]() { old_watches = lsnes_memorywatch.enumerate(); });
1551 std::string filename = choose_file_save(this, "Save watches to file", project_otherpath(),
1552 filetype_watch);
1553 std::ofstream out(filename.c_str());
1554 for(auto i : old_watches) {
1555 std::string val;
1556 runemufn([i, &val]() {
1557 try {
1558 val = lsnes_memorywatch.get_string(i);
1559 } catch(std::exception& e) {
1560 messages << "Can't get value of watch '" << i << "': " << e.what()
1561 << std::endl;
1564 out << i << std::endl << val << std::endl;
1566 out.close();
1567 return;
1569 case wxID_LOAD_MEMORYWATCH: {
1570 modal_pause_holder hld;
1571 std::set<std::string> old_watches;
1572 runemufn([&old_watches]() { old_watches = lsnes_memorywatch.enumerate(); });
1573 std::map<std::string, std::string> new_watches;
1574 std::string filename = choose_file_load(this, "Choose memory watch file", project_otherpath(),
1575 filetype_watch);
1576 try {
1577 std::istream& in = zip::openrel(filename, "");
1578 while(in) {
1579 std::string wname;
1580 std::string wexpr;
1581 std::getline(in, wname);
1582 std::getline(in, wexpr);
1583 new_watches[strip_CR(wname)] = strip_CR(wexpr);
1585 delete &in;
1586 } catch(std::exception& e) {
1587 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
1588 wxICON_EXCLAMATION);
1589 return;
1592 runemufn([&new_watches, &old_watches]() {
1593 handle_watch_load(new_watches, old_watches);
1595 return;
1597 case wxID_MEMORY_SEARCH:
1598 wxwindow_memorysearch_display();
1599 return;
1600 case wxID_TASINPUT:
1601 wxeditor_tasinput_display(this);
1602 return;
1603 case wxID_ABOUT: {
1604 std::ostringstream str;
1605 str << "Version: lsnes rr" << lsnes_version << std::endl;
1606 str << "Revision: " << lsnes_git_revision << std::endl;
1607 for(auto i : core_core::all_cores())
1608 if(!i->is_hidden())
1609 str << "Core: " << i->get_core_identifier() << std::endl;
1610 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1611 return;
1613 case wxID_SHOW_STATUS: {
1614 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
1615 if(newstate)
1616 spanel->Show();
1617 if(newstate && !spanel_shown)
1618 toplevel->Add(spanel, 1, wxGROW);
1619 else if(!newstate && spanel_shown)
1620 toplevel->Detach(spanel);
1621 if(!newstate)
1622 spanel->Hide();
1623 spanel_shown = newstate;
1624 toplevel->Layout();
1625 toplevel->SetSizeHints(this);
1626 Fit();
1627 return;
1629 case wxID_DEDICATED_MEMORY_WATCH: {
1630 bool newstate = menu_ischecked(wxID_DEDICATED_MEMORY_WATCH);
1631 if(newstate && !mwindow) {
1632 mwindow = new wxwin_status(-1, "Memory Watch");
1633 spanel->set_watch_flag(1);
1634 mwindow->Show();
1635 } else if(!newstate && mwindow) {
1636 mwindow->Destroy();
1637 mwindow = NULL;
1638 spanel->set_watch_flag(0);
1640 return;
1642 case wxID_SET_SPEED: {
1643 std::string value = "infinite";
1644 double val = get_speed_multiplier();
1645 if(!(val == std::numeric_limits<double>::infinity()))
1646 value = (stringfmt() << (100 * val)).str();
1647 value = pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value);
1648 try {
1649 if(value == "infinite")
1650 set_speed_multiplier(std::numeric_limits<double>::infinity());
1651 else {
1652 double v = parse_value<double>(value) / 100;
1653 if(v <= 0.0001)
1654 throw 42;
1655 set_speed_multiplier(v);
1657 } catch(...) {
1658 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1660 return;
1662 case wxID_SPEED_5:
1663 set_speed(5);
1664 break;
1665 case wxID_SPEED_10:
1666 set_speed(10);
1667 break;
1668 case wxID_SPEED_17:
1669 set_speed(16.66666666666);
1670 break;
1671 case wxID_SPEED_20:
1672 set_speed(20);
1673 break;
1674 case wxID_SPEED_25:
1675 set_speed(25);
1676 break;
1677 case wxID_SPEED_33:
1678 set_speed(33.3333333333333);
1679 break;
1680 case wxID_SPEED_50:
1681 set_speed(50);
1682 break;
1683 case wxID_SPEED_100:
1684 set_speed(100);
1685 break;
1686 case wxID_SPEED_150:
1687 set_speed(150);
1688 break;
1689 case wxID_SPEED_200:
1690 set_speed(200);
1691 break;
1692 case wxID_SPEED_300:
1693 set_speed(300);
1694 break;
1695 case wxID_SPEED_500:
1696 set_speed(500);
1697 break;
1698 case wxID_SPEED_1000:
1699 set_speed(1000);
1700 break;
1701 case wxID_SPEED_TURBO:
1702 set_speed(-1);
1703 break;
1704 case wxID_LOAD_LIBRARY: {
1705 std::string name = std::string("load ") + loadlib::library::name();
1706 with_loaded_library(*new loadlib::module(loadlib::library(choose_file_load(this, name,
1707 project_otherpath(), single_type(loadlib::library::extension(),
1708 loadlib::library::name())))));
1709 handle_post_loadlibrary();
1710 break;
1712 case wxID_PLUGIN_MANAGER:
1713 wxeditor_plugin_manager_display(this);
1714 return;
1715 case wxID_RELOAD_ROM_IMAGE:
1716 runemufn([]() {
1717 lsnes_cmd.invoke("unpause-emulator");
1718 reload_current_rom();
1720 return;
1721 case wxID_NEW_MOVIE:
1722 show_projectwindow(this);
1723 return;
1724 case wxID_SHOW_MESSAGES:
1725 msg_window->reshow();
1726 return;
1727 case wxID_CONFLICTRESOLUTION:
1728 show_conflictwindow(this);
1729 return;
1730 case wxID_VUDISPLAY:
1731 open_vumeter_window(this);
1732 return;
1733 case wxID_DISASSEMBLER:
1734 wxeditor_disassembler_display(this);
1735 return;
1736 case wxID_MOVIE_EDIT:
1737 wxeditor_movie_display(this);
1738 return;
1739 case wxID_NEW_PROJECT:
1740 open_new_project_window(this);
1741 return;
1742 case wxID_CLOSE_PROJECT:
1743 runemufn([]() -> void { project_set(NULL); });
1744 return;
1745 case wxID_CLOSE_ROM:
1746 runemufn([]() -> void { close_rom(); });
1747 return;
1748 case wxID_ENTER_FULLSCREEN:
1749 wx_escape_count = 0;
1750 enter_or_leave_fullscreen(true);
1751 return;
1752 case wxID_LOAD_ROM_IMAGE_FIRST:
1753 do_load_rom_image(NULL);
1754 return;
1755 case wxID_HEXEDITOR:
1756 wxeditor_hexedit_display(this);
1757 return;
1758 case wxID_MULTITRACK:
1759 wxeditor_multitrack_display(this);
1760 return;
1761 case wxID_CHDIR: {
1762 wxDirDialog* d = new wxDirDialog(this, wxT("Change working directory"), wxT("."), wxDD_DIR_MUST_EXIST);
1763 if(d->ShowModal() == wxID_CANCEL) {
1764 d->Destroy();
1765 return;
1767 std::string path = tostdstring(d->GetPath());
1768 d->Destroy();
1769 chdir(path.c_str());
1770 messages << "Changed working directory to '" << path << "'" << std::endl;
1771 return;
1773 case wxID_DOWNLOAD: {
1774 if(download_in_progress) return;
1775 filename = pick_text(this, "Download movie", "Enter URL to download");
1776 download_in_progress = new file_download();
1777 download_in_progress->url = filename;
1778 download_in_progress->target_slot = "wxwidgets_download_tmp";
1779 download_in_progress->do_async();
1780 new download_timer(this);
1781 return;
1786 void wxwin_mainwindow::action_updated()
1788 reinterpret_cast<system_menu*>(sysmenu)->update(true);
1791 void wxwin_mainwindow::enter_or_leave_fullscreen(bool fs)
1793 if(fs && !is_fs) {
1794 if(spanel_shown)
1795 toplevel->Detach(spanel);
1796 spanel->Hide();
1797 is_fs = fs;
1798 ShowFullScreen(true);
1799 Fit();
1800 } else if(!fs && is_fs) {
1801 ShowFullScreen(false);
1802 if(spanel_shown) {
1803 spanel->Show();
1804 toplevel->Add(spanel, 1, wxGROW);
1806 Fit();
1807 is_fs = fs;