Core cleanups
[lsnes.git] / src / platform / wxwidgets / mainwindow.cpp
blob33ce8ff1c1dae7df7c6c337134ecedbefef634c8
1 #include "lsnes.hpp"
3 #include <wx/dnd.h>
4 #include "platform/wxwidgets/menu_dump.hpp"
5 #include "platform/wxwidgets/platform.hpp"
6 #include "platform/wxwidgets/window_mainwindow.hpp"
7 #include "platform/wxwidgets/window_messages.hpp"
8 #include "platform/wxwidgets/window_status.hpp"
9 #include "platform/wxwidgets/window-romload.hpp"
11 #include "core/audioapi.hpp"
12 #include "core/command.hpp"
13 #include "core/controller.hpp"
14 #include "core/controllerframe.hpp"
15 #include "core/dispatch.hpp"
16 #include "core/framebuffer.hpp"
17 #include "core/framerate.hpp"
18 #include "interface/romtype.hpp"
19 #include "core/loadlib.hpp"
20 #include "lua/lua.hpp"
21 #include "core/mainloop.hpp"
22 #include "core/memorywatch.hpp"
23 #include "core/misc.hpp"
24 #include "core/moviedata.hpp"
25 #include "core/project.hpp"
26 #include "core/settings.hpp"
27 #include "core/window.hpp"
28 #include "library/directory.hpp"
29 #include "library/minmax.hpp"
30 #include "library/string.hpp"
31 #include "library/zip.hpp"
32 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
33 #define FUCKED_SYSTEM
34 #endif
36 #include <cmath>
37 #include <vector>
38 #include <string>
41 extern "C"
43 #ifndef UINT64_C
44 #define UINT64_C(val) val##ULL
45 #endif
46 #include <libswscale/swscale.h>
49 enum
51 wxID_PAUSE = wxID_HIGHEST + 1,
52 wxID_FRAMEADVANCE,
53 wxID_SUBFRAMEADVANCE,
54 wxID_NEXTPOLL,
55 wxID_AUDIO_ENABLED,
56 wxID_AUDIO_DEVICE,
57 wxID_SAVE_STATE,
58 wxID_SAVE_MOVIE,
59 wxID_SAVE_SUBTITLES,
60 wxID_LOAD_STATE,
61 wxID_LOAD_STATE_RO,
62 wxID_LOAD_STATE_RW,
63 wxID_LOAD_STATE_P,
64 wxID_LOAD_MOVIE,
65 wxID_RUN_SCRIPT,
66 wxID_RUN_LUA,
67 wxID_RESET_LUA,
68 wxID_EVAL_LUA,
69 wxID_SAVE_SCREENSHOT,
70 wxID_READONLY_MODE,
71 wxID_EDIT_AUTHORS,
72 wxID_AUTOHOLD,
73 wxID_EDIT_MEMORYWATCH,
74 wxID_SAVE_MEMORYWATCH,
75 wxID_LOAD_MEMORYWATCH,
76 wxID_EDIT_SUBTITLES,
77 wxID_EDIT_VSUBTITLES,
78 wxID_DUMP_FIRST,
79 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
80 wxID_REWIND_MOVIE,
81 wxID_MEMORY_SEARCH,
82 wxID_CANCEL_SAVES,
83 wxID_SHOW_STATUS,
84 wxID_SET_SPEED,
85 wxID_SPEED_5,
86 wxID_SPEED_10,
87 wxID_SPEED_17,
88 wxID_SPEED_20,
89 wxID_SPEED_25,
90 wxID_SPEED_33,
91 wxID_SPEED_50,
92 wxID_SPEED_100,
93 wxID_SPEED_150,
94 wxID_SPEED_200,
95 wxID_SPEED_300,
96 wxID_SPEED_500,
97 wxID_SPEED_1000,
98 wxID_SPEED_TURBO,
99 wxID_LOAD_LIBRARY,
100 wxID_SETTINGS,
101 wxID_SETTINGS_HOTKEYS,
102 wxID_SETTINGS_CONTROLLERS,
103 wxID_RELOAD_ROM_IMAGE,
104 wxID_LOAD_ROM_IMAGE,
105 wxID_NEW_MOVIE,
106 wxID_SHOW_MESSAGES,
107 wxID_DEDICATED_MEMORY_WATCH,
108 wxID_RMOVIE_FIRST,
109 wxID_RMOVIE_LAST = wxID_RMOVIE_FIRST + 16,
110 wxID_RROM_FIRST,
111 wxID_RROM_LAST = wxID_RROM_FIRST + 16,
112 wxID_CONFLICTRESOLUTION,
113 wxID_VUDISPLAY,
114 wxID_MOVIE_EDIT,
115 wxID_TASINPUT,
116 wxID_NEW_PROJECT,
117 wxID_LOAD_PROJECT,
118 wxID_CLOSE_PROJECT,
119 wxID_CLOSE_ROM,
120 wxID_EDIT_MACROS,
121 wxID_ACTIONS_FIRST,
122 wxID_ACTIONS_LAST = wxID_ACTIONS_FIRST + 256,
126 double horizontal_scale_factor = 1.0;
127 double vertical_scale_factor = 1.0;
128 int scaling_flags = SWS_POINT;
129 bool hflip_enabled = false;
130 bool vflip_enabled = false;
131 bool rotate_enabled = false;
133 namespace
135 std::string last_volume = "0dB";
136 std::string last_volume_record = "0dB";
137 std::string last_volume_voice = "0dB";
138 unsigned char* screen_buffer;
139 uint32_t* rotate_buffer;
140 uint32_t old_width;
141 uint32_t old_height;
142 int old_flags = SWS_POINT;
143 bool old_hflip = false;
144 bool old_vflip = false;
145 bool old_rotate = false;
146 bool main_window_dirty;
147 thread_class* emulation_thread;
149 std::pair<std::string, std::string> lsplit(std::string l)
151 for(unsigned i = 0; i < l.length() - 3; i++)
152 if((uint8_t)l[i] == 0xE2 && (uint8_t)l[i + 1] == 0x80 && (uint8_t)l[i + 2] == 0xA3)
153 return std::make_pair(l.substr(0, i), l.substr(i + 3));
154 return std::make_pair("", l);
157 class system_menu : public wxMenu
159 public:
160 system_menu(wxWindow* win);
161 ~system_menu();
162 void on_select(wxCommandEvent& e);
163 void update(bool light);
164 private:
165 wxWindow* pwin;
166 void insert_pass(int id, const std::string& label);
167 void insert_act(unsigned id, const std::string& label, bool dots, bool check);
168 wxMenu* lookup_menu(const std::string& key);
169 wxMenuItem* sep;
170 std::map<int, unsigned> action_by_id;
171 std::map<unsigned, wxMenuItem*> item_by_action;
172 std::map<wxMenuItem*, wxMenu*> menu_by_item;
173 std::map<std::string, wxMenu*> submenu_by_name;
174 std::map<std::string, wxMenuItem*> submenui_by_name;
175 std::set<unsigned> toggles;
176 int next_id;
179 wxMenu* system_menu::lookup_menu(const std::string& key)
181 if(key == "")
182 return this;
183 if(submenu_by_name.count(key))
184 return submenu_by_name[key];
185 //Not found, create.
186 if(!sep)
187 sep = AppendSeparator();
188 auto p = lsplit(key);
189 wxMenu* into = lookup_menu(p.first);
190 submenu_by_name[key] = new wxMenu();
191 submenui_by_name[key] = into->AppendSubMenu(submenu_by_name[key], towxstring(p.second));
192 menu_by_item[submenui_by_name[key]] = into;
193 return submenu_by_name[key];
196 void system_menu::insert_act(unsigned id, const std::string& label, bool dots, bool check)
198 if(!sep)
199 sep = AppendSeparator();
201 auto p = lsplit(label);
202 wxMenu* into = lookup_menu(p.first);
204 action_by_id[next_id] = id;
205 std::string use_label = p.second + (dots ? "..." : "");
206 if(check) {
207 item_by_action[id] = into->AppendCheckItem(next_id, towxstring(use_label));
208 toggles.insert(id);
209 } else
210 item_by_action[id] = into->Append(next_id, towxstring(use_label));
211 menu_by_item[item_by_action[id]] = into;
212 pwin->Connect(next_id++, wxEVT_COMMAND_MENU_SELECTED,
213 wxCommandEventHandler(system_menu::on_select), NULL, this);
216 void system_menu::insert_pass(int id, const std::string& label)
218 pwin->Connect(id, wxEVT_COMMAND_MENU_SELECTED,
219 wxCommandEventHandler(wxwin_mainwindow::handle_menu_click), NULL, pwin);
220 Append(id, towxstring(label));
223 system_menu::system_menu(wxWindow* win)
225 pwin = win;
226 insert_pass(wxID_PAUSE, "Pause/Unpause");
227 insert_pass(wxID_FRAMEADVANCE, "Step frame");
228 insert_pass(wxID_SUBFRAMEADVANCE, "Step subframe");
229 insert_pass(wxID_NEXTPOLL, "Step poll");
230 sep = NULL;
233 system_menu::~system_menu()
237 void system_menu::on_select(wxCommandEvent& e)
239 if(!action_by_id.count(e.GetId()))
240 return;
241 unsigned act_id = action_by_id[e.GetId()];
242 const interface_action* act = NULL;
243 for(auto i : our_rom->rtype->get_actions())
244 if(i->id == act_id) {
245 act = i;
246 break;
248 if(!act)
249 return;
250 try {
251 auto p = prompt_action_params(pwin, act->get_title(), act->params);
252 runemufn([act_id,p]() { our_rom->rtype->execute_action(act_id, p); });
253 } catch(canceled_exception& e) {
254 } catch(std::bad_alloc& e) {
255 OOM_panic();
259 void system_menu::update(bool light)
261 if(!light) {
262 next_id = wxID_ACTIONS_FIRST;
263 if(sep) {
264 Destroy(sep);
265 sep = NULL;
267 for(auto i = item_by_action.begin(); i != item_by_action.end(); i++)
268 menu_by_item[i->second]->Destroy(i->second);
269 for(auto i = submenui_by_name.rbegin(); i != submenui_by_name.rend(); i++)
270 menu_by_item[i->second]->Destroy(i->second);
271 action_by_id.clear();
272 item_by_action.clear();
273 menu_by_item.clear();
274 submenu_by_name.clear();
275 submenui_by_name.clear();
276 toggles.clear();
278 for(auto i : our_rom->rtype->get_actions())
279 insert_act(i->id, i->get_title(), !i->params.empty(), i->is_toggle());
281 for(auto i : item_by_action)
282 i.second->Enable(our_rom->rtype->action_flags(i.first) & 1);
283 for(auto i : toggles)
284 item_by_action[i]->Check(our_rom->rtype->action_flags(i) & 2);
287 std::string munge_name(const std::string& orig)
289 std::string newname;
290 regex_results r;
291 if(r = regex("(.*)\\(([0-9]+)\\)", newname)) {
292 uint64_t sequence;
293 try {
294 sequence = parse_value<uint64_t>(r[2]);
295 newname = (stringfmt() << r[1] << "(" << sequence + 1 << ")").str();
296 } catch(...) {
297 newname = newname + "(2)";
299 } else {
300 newname = newname + "(2)";
302 return newname;
305 void handle_watch_load(std::map<std::string, std::string>& new_watches, std::set<std::string>& old_watches)
307 auto proj = project_get();
308 if(proj) {
309 for(auto i : new_watches) {
310 std::string name = i.first;
311 while(true) {
312 if(!old_watches.count(name)) {
313 set_watchexpr_for(name, i.second);
314 break;
315 } else if(get_watchexpr_for(name) == i.second)
316 break;
317 else
318 name = munge_name(name);
321 } else {
322 for(auto i : new_watches)
323 set_watchexpr_for(i.first, i.second);
324 for(auto i : old_watches)
325 if(!new_watches.count(i))
326 set_watchexpr_for(i, "");
330 std::string get_default_screenshot_name()
332 auto p = project_get();
333 if(!p)
334 return "";
335 else {
336 auto files = enumerate_directory(p->directory, ".*-[0-9]+\\.png");
337 std::set<std::string> numbers;
338 for(auto i : files) {
339 size_t split;
340 #ifdef FUCKED_SYSTEM
341 split = i.find_last_of("\\/");
342 #else
343 split = i.find_last_of("/");
344 #endif
345 std::string name = i;
346 if(split < name.length())
347 name = name.substr(split + 1);
348 regex_results r = regex("(.*)-([0-9]+)\\.png", name);
349 if(r[1] != p->prefix)
350 continue;
351 numbers.insert(r[2]);
353 for(uint64_t i = 1;; i++) {
354 std::string candidate = (stringfmt() << i).str();
355 if(!numbers.count(candidate))
356 return p->prefix + "-" + candidate + ".png";
361 std::string project_prefixname(const std::string ext)
363 auto p = project_get();
364 if(!p)
365 return "";
366 else
367 return p->prefix + "." + ext;
371 double pick_volume(wxWindow* win, const std::string& title, std::string& last)
373 std::string value;
374 regex_results r;
375 double parsed = 1;
376 value = pick_text(win, title, "Enter volume in absolute units, percentage (%) or dB:",
377 last);
378 if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)", value))
379 parsed = strtod(r[1].c_str(), NULL);
380 else if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)%", value))
381 parsed = strtod(r[1].c_str(), NULL) / 100;
382 else if(r = regex("([+-]?([0-9]*.[0-9]+|[0-9]+))dB", value))
383 parsed = pow(10, strtod(r[1].c_str(), NULL) / 20);
384 else {
385 wxMessageBox(wxT("Invalid volume"), _T("Error"), wxICON_EXCLAMATION | wxOK, win);
386 return -1;
388 last = value;
389 return parsed;
392 void recent_rom_selected(const std::string& file)
394 platform::queue("unpause-emulator");
395 platform::queue("reload-rom " + file);
398 void recent_movie_selected(const std::string& file)
400 platform::queue("load-smart " + file);
403 wxString getname()
405 std::string windowname = "lsnes rr" + lsnes_version + " [";
406 auto p = project_get();
407 if(p)
408 windowname = windowname + p->name;
409 else
410 windowname = windowname + our_rom->rtype->get_core_identifier();
411 windowname = windowname + "]";
412 return towxstring(windowname);
415 struct emu_args
417 struct loaded_rom* rom;
418 struct moviefile* initial;
419 bool load_has_to_succeed;
422 void* emulator_main(void* _args)
424 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
425 try {
426 our_rom = args->rom;
427 messages << "Using core: " << our_rom->rtype->get_core_identifier() << std::endl;
428 struct moviefile* movie = args->initial;
429 bool has_to_succeed = args->load_has_to_succeed;
430 platform::flush_command_queue();
431 main_loop(*our_rom, *movie, has_to_succeed);
432 signal_program_exit();
433 } catch(std::bad_alloc& e) {
434 OOM_panic();
435 } catch(std::exception& e) {
436 messages << "FATAL: " << e.what() << std::endl;
437 platform::fatal_error();
439 return NULL;
442 void join_emulator_thread()
444 emulation_thread->join();
447 keyboard_mouse_calibration mouse_cal = {0};
448 keyboard_key_mouse mouse_x(lsnes_kbd, "mouse_x", "mouse", mouse_cal);
449 keyboard_key_mouse mouse_y(lsnes_kbd, "mouse_y", "mouse", mouse_cal);
450 keyboard_key_key mouse_l(lsnes_kbd, "mouse_left", "mouse");
451 keyboard_key_key mouse_m(lsnes_kbd, "mouse_center", "mouse");
452 keyboard_key_key mouse_r(lsnes_kbd, "mouse_right", "mouse");
453 keyboard_key_key mouse_i(lsnes_kbd, "mouse_inwindow", "mouse");
455 void handle_wx_mouse(wxMouseEvent& e)
457 platform::queue(keypress(keyboard_modifier_set(), mouse_x, e.GetX() / horizontal_scale_factor));
458 platform::queue(keypress(keyboard_modifier_set(), mouse_y, e.GetY() / vertical_scale_factor));
459 if(e.Entering())
460 platform::queue(keypress(keyboard_modifier_set(), mouse_i, 1));
461 if(e.Leaving())
462 platform::queue(keypress(keyboard_modifier_set(), mouse_i, 0));
463 if(e.LeftDown())
464 platform::queue(keypress(keyboard_modifier_set(), mouse_l, 1));
465 if(e.LeftUp())
466 platform::queue(keypress(keyboard_modifier_set(), mouse_l, 0));
467 if(e.MiddleDown())
468 platform::queue(keypress(keyboard_modifier_set(), mouse_m, 1));
469 if(e.MiddleUp())
470 platform::queue(keypress(keyboard_modifier_set(), mouse_m, 0));
471 if(e.RightDown())
472 platform::queue(keypress(keyboard_modifier_set(), mouse_r, 1));
473 if(e.RightUp())
474 platform::queue(keypress(keyboard_modifier_set(), mouse_r, 0));
477 bool is_readonly_mode()
479 bool ret;
480 runemufn([&ret]() { ret = movb.get_movie().readonly_mode(); });
481 return ret;
484 std::pair<int, int> UI_controller_index_by_logical(unsigned lid)
486 std::pair<int, int> ret;
487 runemufn([&ret, lid]() { ret = controls.lcid_to_pcid(lid); });
488 return ret;
491 void set_speed(double target)
493 if(target < 0)
494 set_speed_multiplier(std::numeric_limits<double>::infinity());
495 else
496 set_speed_multiplier(target / 100);
499 class broadcast_listener : public information_dispatch
501 public:
502 broadcast_listener(wxwin_mainwindow* win);
503 void on_sound_unmute(bool unmute) throw();
504 void on_mode_change(bool readonly) throw();
505 void on_core_change();
506 void on_new_core();
507 private:
508 wxwin_mainwindow* mainw;
511 broadcast_listener::broadcast_listener(wxwin_mainwindow* win)
512 : information_dispatch("wxwidgets-broadcast-listener")
514 mainw = win;
517 void broadcast_listener::on_core_change()
519 signal_core_change();
522 void broadcast_listener::on_sound_unmute(bool unmute) throw()
524 runuifun([this, unmute]() { this->mainw->menu_check(wxID_AUDIO_ENABLED, unmute); });
527 void broadcast_listener::on_mode_change(bool readonly) throw()
529 runuifun([this, readonly]() { this->mainw->menu_check(wxID_READONLY_MODE, readonly); });
532 void update_preferences()
534 preferred_core.clear();
535 for(auto i : core_type::get_core_types()) {
536 std::string val = i->get_hname() + " / " + i->get_core_identifier();
537 for(auto j : i->get_extensions()) {
538 std::string key = "ext:" + j;
539 if(core_selections.count(key) && core_selections[key] == val)
540 preferred_core[key] = i;
542 std::string key2 = "type:" + i->get_iname();
543 if(core_selections.count(key2) && core_selections[key2] == val)
544 preferred_core[key2] = i;
549 void broadcast_listener::on_new_core()
551 update_preferences();
554 std::string movie_path()
556 return lsnes_vset["moviepath"].str();
559 std::string rom_path()
561 return lsnes_vset["rompath"].str();
564 bool is_lsnes_movie(const std::string& filename)
566 try {
567 zip_reader r(filename);
568 std::istream& s = r["systemid"];
569 std::string s2;
570 std::getline(s, s2);
571 delete &s;
572 istrip_CR(s2);
573 return (s2 == "lsnes-rr1");
574 } catch(...) {
575 return false;
579 class loadfile : public wxFileDropTarget
581 public:
582 loadfile(wxwin_mainwindow* win) : pwin(win) {};
583 bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
585 bool ret = false;
586 if(filenames.Count() == 2) {
587 if(is_lsnes_movie(tostdstring(filenames[0])) &&
588 !is_lsnes_movie(tostdstring(filenames[1]))) {
589 platform::queue("unpause-emulator");
590 platform::queue("reload-rom " + tostdstring(filenames[1]));
591 platform::queue("load-smart " + tostdstring(filenames[0]));
592 ret = true;
594 if(!is_lsnes_movie(tostdstring(filenames[0])) &&
595 is_lsnes_movie(tostdstring(filenames[1]))) {
596 platform::queue("unpause-emulator");
597 platform::queue("reload-rom " + tostdstring(filenames[0]));
598 platform::queue("load-smart " + tostdstring(filenames[1]));
599 ret = true;
602 if(filenames.Count() == 1) {
603 if(is_lsnes_movie(tostdstring(filenames[0]))) {
604 platform::queue("load-smart " + tostdstring(filenames[0]));
605 pwin->recent_movies->add(tostdstring(filenames[0]));
606 ret = true;
607 } else {
608 platform::queue("unpause-emulator");
609 platform::queue("reload-rom " + tostdstring(filenames[0]));
610 pwin->recent_roms->add(tostdstring(filenames[0]));
611 ret = true;
614 return ret;
616 wxwin_mainwindow* pwin;
620 void boot_emulator(loaded_rom& rom, moviefile& movie)
622 update_preferences();
623 try {
624 struct emu_args* a = new emu_args;
625 a->rom = &rom;
626 a->initial = &movie;
627 a->load_has_to_succeed = false;
628 modal_pause_holder hld;
629 emulation_thread = new thread_class(emulator_main, a);
630 main_window = new wxwin_mainwindow();
631 main_window->Show();
632 } catch(std::bad_alloc& e) {
633 OOM_panic();
637 wxwin_mainwindow::panel::panel(wxWindow* win)
638 : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS)
640 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
641 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
642 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
643 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
644 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
645 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
646 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
647 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
648 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
649 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
650 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
651 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
652 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
653 SetMinSize(wxSize(512, 448));
656 void wxwin_mainwindow::menu_start(wxString name)
658 while(!upper.empty())
659 upper.pop();
660 current_menu = new wxMenu();
661 menubar->Append(current_menu, name);
664 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
666 while(!upper.empty())
667 upper.pop();
668 menubar->Append(menu, name);
669 current_menu = NULL;
672 void wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
674 current_menu->AppendSubMenu(menu, name);
677 void wxwin_mainwindow::menu_entry(int id, wxString name)
679 current_menu->Append(id, name);
680 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
681 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
684 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
686 checkitems[id] = current_menu->AppendCheckItem(id, name);
687 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
688 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
691 void wxwin_mainwindow::menu_start_sub(wxString name)
693 wxMenu* old = current_menu;
694 upper.push(current_menu);
695 current_menu = new wxMenu();
696 old->AppendSubMenu(current_menu, name);
699 void wxwin_mainwindow::menu_end_sub()
701 current_menu = upper.top();
702 upper.pop();
705 bool wxwin_mainwindow::menu_ischecked(int id)
707 if(checkitems.count(id))
708 return checkitems[id]->IsChecked();
709 else
710 return false;
713 void wxwin_mainwindow::menu_check(int id, bool newstate)
715 if(checkitems.count(id))
716 return checkitems[id]->Check(newstate);
717 else
718 return;
721 void wxwin_mainwindow::menu_enable(int id, bool newstate)
723 auto item = menubar->FindItem(id);
724 if(!item)
725 return;
726 item->Enable(newstate);
729 void wxwin_mainwindow::menu_separator()
731 current_menu->AppendSeparator();
734 void wxwin_mainwindow::panel::request_paint()
736 Refresh();
739 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
741 render_framebuffer();
742 static struct SwsContext* ctx;
743 uint8_t* srcp[1];
744 int srcs[1];
745 uint8_t* dstp[1];
746 int dsts[1];
747 wxPaintDC dc(this);
748 uint32_t tw, th;
749 bool aux = hflip_enabled || vflip_enabled || rotate_enabled;
750 if(rotate_enabled) {
751 tw = main_screen.get_height() * horizontal_scale_factor + 0.5;
752 th = main_screen.get_width() * vertical_scale_factor + 0.5;
753 } else {
754 tw = main_screen.get_width() * horizontal_scale_factor + 0.5;
755 th = main_screen.get_height() * vertical_scale_factor + 0.5;
757 if(!tw || !th) {
758 main_window_dirty = false;
759 return;
761 if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags ||
762 hflip_enabled != old_hflip || vflip_enabled != old_vflip || rotate_enabled != old_rotate) {
763 if(screen_buffer)
764 delete[] screen_buffer;
765 if(rotate_buffer)
766 delete[] rotate_buffer;
767 old_height = th;
768 old_width = tw;
769 old_flags = scaling_flags;
770 old_hflip = hflip_enabled;
771 old_vflip = vflip_enabled;
772 old_rotate = rotate_enabled;
773 uint32_t w = main_screen.get_width();
774 uint32_t h = main_screen.get_height();
775 if(w && h)
776 ctx = sws_getCachedContext(ctx, rotate_enabled ? h : w, rotate_enabled ? w : h, PIX_FMT_RGBA,
777 tw, th, PIX_FMT_BGR24, scaling_flags, NULL, NULL, NULL);
778 tw = max(tw, static_cast<uint32_t>(128));
779 th = max(th, static_cast<uint32_t>(112));
780 screen_buffer = new unsigned char[tw * th * 3];
781 if(aux)
782 rotate_buffer = new uint32_t[main_screen.get_width() * main_screen.get_height()];
783 SetMinSize(wxSize(tw, th));
784 signal_resize_needed();
786 if(aux) {
787 //Hflip, Vflip or rotate active.
788 size_t width = main_screen.get_width();
789 size_t height = main_screen.get_height();
790 size_t width1 = width - 1;
791 size_t height1 = height - 1;
792 size_t stride = main_screen.rowptr(1) - main_screen.rowptr(0);
793 uint32_t* pixels = main_screen.rowptr(0);
794 if(rotate_enabled) {
795 for(unsigned y = 0; y < height; y++) {
796 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
797 uint32_t* dpixels = rotate_buffer + (height1 - y);
798 if(hflip_enabled)
799 for(unsigned x = 0; x < width; x++)
800 dpixels[x * height] = pixels2[width1 - x];
801 else
802 for(unsigned x = 0; x < width; x++)
803 dpixels[x * height] = pixels2[x];
805 } else {
806 for(unsigned y = 0; y < height; y++) {
807 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
808 uint32_t* dpixels = rotate_buffer + y * width;
809 if(hflip_enabled)
810 for(unsigned x = 0; x < width; x++)
811 dpixels[x] = pixels2[width1 - x];
812 else
813 for(unsigned x = 0; x < width; x++)
814 dpixels[x] = pixels2[x];
818 srcs[0] = 4 * (rotate_enabled ? main_screen.get_height() : main_screen.get_width());
819 dsts[0] = 3 * tw;
820 srcp[0] = reinterpret_cast<unsigned char*>(aux ? rotate_buffer : main_screen.rowptr(0));
821 dstp[0] = screen_buffer;
822 memset(screen_buffer, 0, tw * th * 3);
823 if(main_screen.get_width() && main_screen.get_height())
824 sws_scale(ctx, srcp, srcs, 0, rotate_enabled ? main_screen.get_width() : main_screen.get_height(),
825 dstp, dsts);
826 wxBitmap bmp(wxImage(tw, th, screen_buffer, true));
827 dc.DrawBitmap(bmp, 0, 0, false);
828 main_window_dirty = false;
831 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
833 //Blank.
836 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
838 handle_wx_keyboard(e, true);
841 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
843 handle_wx_keyboard(e, false);
846 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
848 handle_wx_mouse(e);
851 wxwin_mainwindow::wxwin_mainwindow()
852 : wxFrame(NULL, wxID_ANY, getname(), wxDefaultPosition, wxSize(-1, -1),
853 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX)
855 broadcast_listener* blistener = new broadcast_listener(this);
856 Centre();
857 mwindow = NULL;
858 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
859 toplevel->Add(gpanel = new panel(this), 1, wxGROW);
860 toplevel->Add(spanel = new wxwin_status::panel(this, gpanel, 20), 1, wxGROW);
861 spanel_shown = true;
862 toplevel->SetSizeHints(this);
863 SetSizer(toplevel);
864 Fit();
865 gpanel->SetFocus();
866 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
867 SetMenuBar(menubar = new wxMenuBar);
868 SetStatusBar(statusbar = new wxStatusBar(this));
870 menu_start(wxT("File"));
871 menu_start_sub(wxT("New"));
872 menu_entry(wxID_NEW_MOVIE, wxT("Movie..."));
873 menu_entry(wxID_NEW_PROJECT, wxT("Project..."));
874 menu_end_sub();
875 menu_start_sub(wxT("Load"));
876 menu_entry(wxID_LOAD_STATE, wxT("State..."));
877 menu_entry(wxID_LOAD_STATE_RO, wxT("State (readonly)..."));
878 menu_entry(wxID_LOAD_STATE_RW, wxT("State (read-write)..."));
879 menu_entry(wxID_LOAD_STATE_P, wxT("State (preserve input)..."));
880 menu_entry(wxID_LOAD_MOVIE, wxT("Movie..."));
881 if(loaded_library::call_library() != "") {
882 menu_separator();
883 menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + loaded_library::call_library()));
885 menu_separator();
886 menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM"));
887 menu_entry(wxID_LOAD_ROM_IMAGE, wxT("ROM..."));
888 menu_entry(wxID_LOAD_PROJECT, wxT("Project..."));
889 menu_separator();
890 menu_special_sub(wxT("Recent ROMs"), recent_roms = new recent_menu(this, wxID_RROM_FIRST, wxID_RROM_LAST,
891 get_config_path() + "/recent-roms.txt", recent_rom_selected));
892 menu_special_sub(wxT("Recent Movies"), recent_movies = new recent_menu(this, wxID_RMOVIE_FIRST,
893 wxID_RMOVIE_LAST, get_config_path() + "/recent-movies.txt", recent_movie_selected));
894 menu_separator();
895 menu_entry(wxID_CONFLICTRESOLUTION, wxT("Conflict resolution"));
896 menu_end_sub();
897 menu_start_sub(wxT("Save"));
898 menu_entry(wxID_SAVE_STATE, wxT("State..."));
899 menu_entry(wxID_SAVE_MOVIE, wxT("Movie..."));
900 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Screenshot..."));
901 menu_entry(wxID_SAVE_SUBTITLES, wxT("Subtitles..."));
902 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pending saves"));
903 menu_end_sub();
904 menu_start_sub(wxT("Close"));
905 menu_entry(wxID_CLOSE_PROJECT, wxT("Project"));
906 menu_entry(wxID_CLOSE_ROM, wxT("ROM"));
907 menu_enable(wxID_CLOSE_PROJECT, project_get() != NULL);
908 menu_enable(wxID_CLOSE_ROM, project_get() == NULL);
909 menu_end_sub();
910 menu_separator();
911 menu_entry(wxID_EXIT, wxT("Quit"));
913 menu_special(wxT("System"), reinterpret_cast<wxMenu*>(sysmenu = new system_menu(this)));
915 menu_start(wxT("Movie"));
916 menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode"));
917 menu_check(wxID_READONLY_MODE, is_readonly_mode());
918 menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors..."));
919 menu_entry(wxID_EDIT_SUBTITLES, wxT("Edit subtitles..."));
920 menu_entry(wxID_EDIT_VSUBTITLES, wxT("Edit commantary track..."));
921 menu_separator();
922 menu_entry(wxID_REWIND_MOVIE, wxT("Rewind to start"));
924 menu_start(wxT("Speed"));
925 menu_entry(wxID_SPEED_5, wxT("1/20x"));
926 menu_entry(wxID_SPEED_10, wxT("1/10x"));
927 menu_entry(wxID_SPEED_17, wxT("1/6x"));
928 menu_entry(wxID_SPEED_20, wxT("1/5x"));
929 menu_entry(wxID_SPEED_25, wxT("1/4x"));
930 menu_entry(wxID_SPEED_33, wxT("1/3x"));
931 menu_entry(wxID_SPEED_50, wxT("1/2x"));
932 menu_entry(wxID_SPEED_100, wxT("1x"));
933 menu_entry(wxID_SPEED_150, wxT("1.5x"));
934 menu_entry(wxID_SPEED_200, wxT("2x"));
935 menu_entry(wxID_SPEED_300, wxT("3x"));
936 menu_entry(wxID_SPEED_500, wxT("5x"));
937 menu_entry(wxID_SPEED_1000, wxT("10x"));
938 menu_entry(wxID_SPEED_TURBO, wxT("Turbo"));
939 menu_entry(wxID_SET_SPEED, wxT("Set..."));
941 menu_start(wxT("Tools"));
942 menu_entry(wxID_RUN_SCRIPT, wxT("Run batch file..."));
943 menu_separator();
944 menu_entry(wxID_EVAL_LUA, wxT("Evaluate Lua statement..."));
945 menu_entry(wxID_RUN_LUA, wxT("Run Lua script..."));
946 menu_separator();
947 menu_entry(wxID_RESET_LUA, wxT("Reset Lua VM"));
948 menu_separator();
949 menu_entry(wxID_AUTOHOLD, wxT("Autohold/Autofire..."));
950 menu_entry(wxID_TASINPUT, wxT("TAS input plugin..."));
951 menu_entry(wxID_EDIT_MACROS, wxT("Edit macros..."));
952 menu_separator();
953 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch..."));
954 menu_separator();
955 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch..."));
956 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch..."));
957 menu_separator();
958 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
959 menu_separator();
960 menu_entry(wxID_MOVIE_EDIT, wxT("Edit movie..."));
961 menu_separator();
962 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
963 wxID_DUMP_FIRST, wxID_DUMP_LAST)));
965 menu_start(wxT("Configure"));
966 menu_entry_check(wxID_SHOW_STATUS, wxT("Show status panel"));
967 menu_check(wxID_SHOW_STATUS, true);
968 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH, wxT("Dedicated memory watch"));
969 menu_entry(wxID_SHOW_MESSAGES, wxT("Show messages"));
970 menu_entry(wxID_SETTINGS, wxT("Configure emulator..."));
971 menu_entry(wxID_SETTINGS_HOTKEYS, wxT("Configure hotkeys..."));
972 menu_entry(wxID_SETTINGS_CONTROLLERS, wxT("Configure controllers..."));
973 if(audioapi_driver_initialized()) {
974 menu_separator();
975 menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled"));
976 menu_entry(wxID_VUDISPLAY, wxT("VU display / volume controls"));
977 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
978 menu_entry(wxID_AUDIO_DEVICE, wxT("Set audio device"));
981 menu_start(wxT("Help"));
982 menu_entry(wxID_ABOUT, wxT("About..."));
984 gpanel->SetDropTarget(new loadfile(this));
985 spanel->SetDropTarget(new loadfile(this));
988 void wxwin_mainwindow::request_paint()
990 gpanel->Refresh();
993 void wxwin_mainwindow::on_close(wxCloseEvent& e)
995 //Veto it for now, latter things will delete it.
996 e.Veto();
997 platform::queue("quit-emulator");
1000 void wxwin_mainwindow::notify_update() throw()
1002 if(!main_window_dirty) {
1003 main_window_dirty = true;
1004 gpanel->Refresh();
1008 void wxwin_mainwindow::notify_resized() throw()
1010 toplevel->Layout();
1011 toplevel->SetSizeHints(this);
1012 Fit();
1015 void wxwin_mainwindow::notify_update_status() throw()
1017 spanel->request_paint();
1018 if(mwindow)
1019 mwindow->notify_update();
1022 void wxwin_mainwindow::notify_exit() throw()
1024 wxwidgets_exiting = true;
1025 join_emulator_thread();
1026 Destroy();
1029 std::u32string read_variable_map(const std::map<std::string, std::u32string>& vars, const std::string& key)
1031 if(!vars.count(key))
1032 return U"";
1033 return vars.find(key)->second;
1036 void wxwin_mainwindow::update_statusbar(const std::map<std::string, std::u32string>& vars)
1038 if(vars.empty())
1039 return;
1040 try {
1041 std::basic_ostringstream<char32_t> s;
1042 bool recording = (read_variable_map(vars, "!mode") == U"R");
1043 if(recording)
1044 s << U"Frame: " << read_variable_map(vars, "!frame");
1045 else
1046 s << U"Frame: " << read_variable_map(vars, "!frame") << U"/" <<
1047 read_variable_map(vars, "!length");
1048 s << U" Lag: " << read_variable_map(vars, "!lag");
1049 s << U" Subframe: " << read_variable_map(vars, "!subframe");
1050 if(vars.count("!saveslot"))
1051 s << U" Slot: " << read_variable_map(vars, "!saveslot");
1052 if(vars.count("!saveslotinfo"))
1053 s << U" [" << read_variable_map(vars, "!saveslotinfo") << U"]";
1054 s << U" Speed: " << read_variable_map(vars, "!speed") << U"%";
1055 s << U" ";
1056 if(read_variable_map(vars, "!dumping") != U"")
1057 s << U" Dumping";
1058 if(read_variable_map(vars, "!mode") == U"C")
1059 s << U" Corrupt";
1060 else if(read_variable_map(vars, "!mode") == U"R")
1061 s << U" Recording";
1062 else if(read_variable_map(vars, "!mode") == U"P")
1063 s << U" Playback";
1064 else if(read_variable_map(vars, "!mode") == U"F")
1065 s << U" Finished";
1066 else
1067 s << U" Unknown";
1068 std::u32string macros = read_variable_map(vars, "!macros");
1069 if(macros.length())
1070 s << U" Macros: " << macros;
1072 statusbar->SetStatusText(towxstring(s.str()));
1073 } catch(std::exception& e) {
1077 #define NEW_KEYBINDING "A new binding..."
1078 #define NEW_ALIAS "A new alias..."
1079 #define NEW_WATCH "A new watch..."
1081 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
1083 try {
1084 handle_menu_click_cancelable(e);
1085 } catch(canceled_exception& e) {
1086 //Ignore.
1087 } catch(std::bad_alloc& e) {
1088 OOM_panic();
1089 } catch(std::exception& e) {
1090 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
1094 void wxwin_mainwindow::refresh_title() throw()
1096 SetTitle(getname());
1097 auto p = project_get();
1098 menu_enable(wxID_RELOAD_ROM_IMAGE, !p);
1099 menu_enable(wxID_LOAD_ROM_IMAGE, !p);
1100 menu_enable(wxID_CLOSE_PROJECT, p != NULL);
1101 menu_enable(wxID_CLOSE_ROM, p == NULL);
1102 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1103 menubar->SetMenuLabel(1, towxstring(our_rom->rtype->get_systemmenu_name()));
1106 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
1108 std::string filename;
1109 bool s;
1110 switch(e.GetId()) {
1111 case wxID_FRAMEADVANCE:
1112 platform::queue("+advance-frame");
1113 platform::queue("-advance-frame");
1114 return;
1115 case wxID_SUBFRAMEADVANCE:
1116 platform::queue("+advance-poll");
1117 platform::queue("-advance-poll");
1118 return;
1119 case wxID_NEXTPOLL:
1120 platform::queue("advance-skiplag");
1121 return;
1122 case wxID_PAUSE:
1123 platform::queue("pause-emulator");
1124 return;
1125 case wxID_EXIT:
1126 platform::queue("quit-emulator");
1127 return;
1128 case wxID_AUDIO_ENABLED:
1129 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
1130 return;
1131 case wxID_AUDIO_DEVICE:
1132 wxeditor_sounddev_display(this);
1133 return;
1134 case wxID_CANCEL_SAVES:
1135 platform::queue("cancel-saves");
1136 return;
1137 case wxID_LOAD_MOVIE:
1138 filename = pick_file(this, "Load Movie", project_moviepath(), false, "lsmv");
1139 recent_movies->add(filename);
1140 platform::queue("load-movie " + filename);
1141 return;
1142 case wxID_LOAD_STATE:
1143 filename = pick_file(this, "Load State", project_moviepath(), false, project_savestate_ext());
1144 recent_movies->add(filename);
1145 platform::queue("load " + filename);
1146 return;
1147 case wxID_LOAD_STATE_RO:
1148 filename = pick_file(this, "Load State (Read-Only)", project_moviepath(), false,
1149 project_savestate_ext());
1150 recent_movies->add(filename);
1151 platform::queue("load-readonly " + filename);
1152 return;
1153 case wxID_LOAD_STATE_RW:
1154 filename = pick_file(this, "Load State (Read-Write)", project_moviepath(), false,
1155 project_savestate_ext());
1156 recent_movies->add(filename);
1157 platform::queue("load-state " + filename);
1158 return;
1159 case wxID_LOAD_STATE_P:
1160 filename = pick_file(this, "Load State (Preserve)", project_moviepath(), false,
1161 project_savestate_ext());
1162 recent_movies->add(filename);
1163 platform::queue("load-preserve " + filename);
1164 return;
1165 case wxID_REWIND_MOVIE:
1166 platform::queue("rewind-movie");
1167 return;
1168 case wxID_SAVE_MOVIE:
1169 filename = pick_file(this, "Save Movie", project_moviepath(), true, "lsmv",
1170 project_prefixname("lsmv"));
1171 recent_movies->add(filename);
1172 platform::queue("save-movie " + filename);
1173 return;
1174 case wxID_SAVE_SUBTITLES:
1175 platform::queue("save-subtitle " + pick_file(this, "Save Subtitle (.sub)", project_moviepath(), true,
1176 "sub", project_prefixname("sub")));
1177 return;
1178 case wxID_SAVE_STATE:
1179 filename = pick_file(this, "Save State", project_moviepath(), true, project_savestate_ext());
1180 recent_movies->add(filename);
1181 platform::queue("save-state " + filename);
1182 return;
1183 case wxID_SAVE_SCREENSHOT:
1184 platform::queue("take-screenshot " + pick_file(this, "Save Screenshot", project_moviepath(), true,
1185 "png", get_default_screenshot_name()));
1186 return;
1187 case wxID_RUN_SCRIPT:
1188 platform::queue("run-script " + pick_file_member(this, "Select Script", project_otherpath()));
1189 return;
1190 case wxID_RUN_LUA:
1191 platform::queue("run-lua " + pick_file(this, "Select Lua Script", project_otherpath(), false, "lua"));
1192 return;
1193 case wxID_RESET_LUA:
1194 platform::queue("reset-lua");
1195 return;
1196 case wxID_EVAL_LUA:
1197 platform::queue("evaluate-lua " + pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
1198 return;
1199 case wxID_READONLY_MODE:
1200 s = menu_ischecked(wxID_READONLY_MODE);
1201 runemufn([s]() {
1202 movb.get_movie().readonly_mode(s);
1203 if(!s)
1204 lua_callback_do_readwrite();
1205 update_movie_state();
1206 graphics_driver_notify_status();
1208 return;
1209 case wxID_AUTOHOLD:
1210 wxeditor_autohold_display(this);
1211 return;
1212 case wxID_EDIT_AUTHORS:
1213 wxeditor_authors_display(this);
1214 return;
1215 case wxID_EDIT_MACROS:
1216 wxeditor_macro_display(this);
1217 return;
1218 case wxID_EDIT_SUBTITLES:
1219 wxeditor_subtitles_display(this);
1220 return;
1221 case wxID_EDIT_VSUBTITLES:
1222 show_wxeditor_voicesub(this);
1223 return;
1224 case wxID_EDIT_MEMORYWATCH:
1225 wxeditor_memorywatch_display(this);
1226 return;
1227 case wxID_SAVE_MEMORYWATCH: {
1228 modal_pause_holder hld;
1229 std::set<std::string> old_watches;
1230 runemufn([&old_watches]() { old_watches = get_watches(); });
1231 std::string filename = pick_file(this, "Save watches to file", project_otherpath(), true, "lwch");
1232 std::ofstream out(filename.c_str());
1233 for(auto i : old_watches) {
1234 std::string val;
1235 runemufn([i, &val]() { val = get_watchexpr_for(i); });
1236 out << i << std::endl << val << std::endl;
1238 out.close();
1239 return;
1241 case wxID_LOAD_MEMORYWATCH: {
1242 modal_pause_holder hld;
1243 std::set<std::string> old_watches;
1244 runemufn([&old_watches]() { old_watches = get_watches(); });
1245 std::map<std::string, std::string> new_watches;
1246 std::string filename = pick_file(this, "Choose memory watch file", project_otherpath(), "lwch");
1247 try {
1248 std::istream& in = open_file_relative(filename, "");
1249 while(in) {
1250 std::string wname;
1251 std::string wexpr;
1252 std::getline(in, wname);
1253 std::getline(in, wexpr);
1254 new_watches[strip_CR(wname)] = strip_CR(wexpr);
1256 delete &in;
1257 } catch(std::exception& e) {
1258 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
1259 wxICON_EXCLAMATION);
1260 return;
1263 runemufn([&new_watches, &old_watches]() {
1264 handle_watch_load(new_watches, old_watches);
1266 return;
1268 case wxID_MEMORY_SEARCH:
1269 wxwindow_memorysearch_display();
1270 return;
1271 case wxID_TASINPUT:
1272 wxeditor_tasinput_display(this);
1273 return;
1274 case wxID_ABOUT: {
1275 std::ostringstream str;
1276 str << "Version: lsnes rr" << lsnes_version << std::endl;
1277 str << "Revision: " << lsnes_git_revision << std::endl;
1278 for(auto i : core_core::all_cores())
1279 if(!i->is_hidden())
1280 str << "Core: " << i->get_core_identifier() << std::endl;
1281 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1282 return;
1284 case wxID_SHOW_STATUS: {
1285 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
1286 if(newstate)
1287 spanel->Show();
1288 if(newstate && !spanel_shown)
1289 toplevel->Add(spanel, 1, wxGROW);
1290 else if(!newstate && spanel_shown)
1291 toplevel->Detach(spanel);
1292 if(!newstate)
1293 spanel->Hide();
1294 spanel_shown = newstate;
1295 toplevel->Layout();
1296 toplevel->SetSizeHints(this);
1297 Fit();
1298 return;
1300 case wxID_DEDICATED_MEMORY_WATCH: {
1301 bool newstate = menu_ischecked(wxID_DEDICATED_MEMORY_WATCH);
1302 if(newstate && !mwindow) {
1303 mwindow = new wxwin_status(-1, "Memory Watch");
1304 spanel->set_watch_flag(1);
1305 mwindow->Show();
1306 } else if(!newstate && mwindow) {
1307 mwindow->Destroy();
1308 mwindow = NULL;
1309 spanel->set_watch_flag(0);
1311 return;
1313 case wxID_SET_SPEED: {
1314 std::string value = "infinite";
1315 double val = get_speed_multiplier();
1316 if(!(val == std::numeric_limits<double>::infinity()))
1317 value = (stringfmt() << (100 * val)).str();
1318 value = pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value);
1319 try {
1320 if(value == "infinite")
1321 set_speed_multiplier(std::numeric_limits<double>::infinity());
1322 else {
1323 double v = parse_value<double>(value) / 100;
1324 if(v <= 0.0001)
1325 throw 42;
1326 set_speed_multiplier(v);
1328 } catch(...) {
1329 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1331 return;
1333 case wxID_SPEED_5:
1334 set_speed(5);
1335 break;
1336 case wxID_SPEED_10:
1337 set_speed(10);
1338 break;
1339 case wxID_SPEED_17:
1340 set_speed(16.66666666666);
1341 break;
1342 case wxID_SPEED_20:
1343 set_speed(20);
1344 break;
1345 case wxID_SPEED_25:
1346 set_speed(25);
1347 break;
1348 case wxID_SPEED_33:
1349 set_speed(33.3333333333333);
1350 break;
1351 case wxID_SPEED_50:
1352 set_speed(50);
1353 break;
1354 case wxID_SPEED_100:
1355 set_speed(100);
1356 break;
1357 case wxID_SPEED_150:
1358 set_speed(150);
1359 break;
1360 case wxID_SPEED_200:
1361 set_speed(200);
1362 break;
1363 case wxID_SPEED_300:
1364 set_speed(300);
1365 break;
1366 case wxID_SPEED_500:
1367 set_speed(500);
1368 break;
1369 case wxID_SPEED_1000:
1370 set_speed(1000);
1371 break;
1372 case wxID_SPEED_TURBO:
1373 set_speed(-1);
1374 break;
1375 case wxID_LOAD_LIBRARY: {
1376 std::string name = std::string("load ") + loaded_library::call_library();
1377 with_loaded_library(new loaded_library(pick_file(this, name, project_otherpath(), false,
1378 loaded_library::call_library_ext())));
1379 handle_post_loadlibrary();
1380 break;
1382 case wxID_SETTINGS:
1383 wxsetingsdialog_display(this, 0);
1384 break;
1385 case wxID_SETTINGS_HOTKEYS:
1386 wxsetingsdialog_display(this, 1);
1387 break;
1388 case wxID_SETTINGS_CONTROLLERS:
1389 wxsetingsdialog_display(this, 2);
1390 break;
1391 case wxID_LOAD_ROM_IMAGE:
1392 do_load_rom_image();
1393 return;
1394 case wxID_RELOAD_ROM_IMAGE:
1395 platform::queue("unpause-emulator");
1396 platform::queue("reload-rom");
1397 return;
1398 case wxID_NEW_MOVIE:
1399 show_projectwindow(this);
1400 return;
1401 case wxID_SHOW_MESSAGES:
1402 msg_window->reshow();
1403 return;
1404 case wxID_CONFLICTRESOLUTION:
1405 show_conflictwindow(this);
1406 return;
1407 case wxID_VUDISPLAY:
1408 open_vumeter_window(this);
1409 return;
1410 case wxID_MOVIE_EDIT:
1411 wxeditor_movie_display(this);
1412 return;
1413 case wxID_NEW_PROJECT:
1414 open_new_project_window(this);
1415 return;
1416 case wxID_LOAD_PROJECT: {
1417 auto projects = project_enumerate();
1418 std::vector<std::string> a;
1419 std::vector<wxString> b;
1420 for(auto i : projects) {
1421 a.push_back(i.first);
1422 b.push_back(towxstring(i.second));
1424 if(a.empty()) {
1425 show_message_ok(this, "Load project", "No projects available", wxICON_EXCLAMATION);
1426 return;
1428 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(this, wxT("Select project to switch to:"),
1429 wxT("Load project"), b.size(), &b[0]);
1430 if(d2->ShowModal() == wxID_CANCEL) {
1431 d2->Destroy();
1432 throw canceled_exception();
1434 std::string id = a[d2->GetSelection()];
1435 runemufn([id]() -> void {
1436 try {
1437 delete &project_load(id); //Check.
1438 switch_projects(id);
1439 } catch(std::exception& e) {
1440 messages << "Failed to change project: " << e.what() << std::endl;
1444 return;
1446 case wxID_CLOSE_PROJECT:
1447 runemufn([]() -> void { project_set(NULL); });
1448 return;
1449 case wxID_CLOSE_ROM:
1450 runemufn([]() -> void { close_rom(); });
1451 return;
1455 void wxwin_mainwindow::do_load_rom_image()
1457 wxwindow_romload r(rom_path());
1458 if(!r.show(this))
1459 return;
1460 std::string file = r.get_filename();
1461 std::string core = r.get_core();
1462 std::string type = r.get_type();
1463 std::string filename;
1464 if(core != "")
1465 filename = file + " <" + mangle_name(core) + "/" + mangle_name(type) + ">";
1466 else
1467 filename = file;
1468 recent_roms->add(filename);
1469 platform::queue("unpause-emulator");
1470 platform::queue("reload-rom " + filename);
1473 void wxwin_mainwindow::action_updated()
1475 reinterpret_cast<system_menu*>(sysmenu)->update(true);