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