Refactor keygroup into library/
[lsnes.git] / src / platform / wxwidgets / mainwindow.cpp
blob3c156d50964868cbfe49e94e6defb1c5cbead75c
1 #include "lsnes.hpp"
3 #include <wx/dnd.h>
4 #include "platform/wxwidgets/menu_dump.hpp"
5 #include "platform/wxwidgets/platform.hpp"
6 #include "platform/wxwidgets/window_mainwindow.hpp"
7 #include "platform/wxwidgets/window_messages.hpp"
8 #include "platform/wxwidgets/window_status.hpp"
10 #include "core/emucore.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 "library/loadlib.hpp"
19 #include "lua/lua.hpp"
20 #include "core/mainloop.hpp"
21 #include "core/memorywatch.hpp"
22 #include "core/misc.hpp"
23 #include "core/moviedata.hpp"
24 #include "core/settings.hpp"
25 #include "core/window.hpp"
26 #include "library/minmax.hpp"
27 #include "library/string.hpp"
28 #include "library/zip.hpp"
30 #include <cmath>
31 #include <vector>
32 #include <string>
35 extern "C"
37 #ifndef UINT64_C
38 #define UINT64_C(val) val##ULL
39 #endif
40 #include <libswscale/swscale.h>
43 enum
45 wxID_PAUSE = wxID_HIGHEST + 1,
46 wxID_FRAMEADVANCE,
47 wxID_SUBFRAMEADVANCE,
48 wxID_NEXTPOLL,
49 wxID_ERESET,
50 wxID_AUDIO_ENABLED,
51 wxID_SHOW_AUDIO_STATUS,
52 wxID_AUDIODEV_FIRST,
53 wxID_AUDIODEV_LAST = wxID_AUDIODEV_FIRST + 255,
54 wxID_SAVE_STATE,
55 wxID_SAVE_MOVIE,
56 wxID_SAVE_SUBTITLES,
57 wxID_LOAD_STATE,
58 wxID_LOAD_STATE_RO,
59 wxID_LOAD_STATE_RW,
60 wxID_LOAD_STATE_P,
61 wxID_LOAD_MOVIE,
62 wxID_RUN_SCRIPT,
63 wxID_RUN_LUA,
64 wxID_RESET_LUA,
65 wxID_EVAL_LUA,
66 wxID_SAVE_SCREENSHOT,
67 wxID_READONLY_MODE,
68 wxID_EDIT_AUTHORS,
69 wxID_AUTOHOLD_FIRST,
70 wxID_AUTOHOLD_LAST = wxID_AUTOHOLD_FIRST + 1023,
71 wxID_EDIT_MEMORYWATCH,
72 wxID_SAVE_MEMORYWATCH,
73 wxID_LOAD_MEMORYWATCH,
74 wxID_EDIT_SUBTITLES,
75 wxID_EDIT_VSUBTITLES,
76 wxID_DUMP_FIRST,
77 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
78 wxID_REWIND_MOVIE,
79 wxID_MEMORY_SEARCH,
80 wxID_CANCEL_SAVES,
81 wxID_SHOW_STATUS,
82 wxID_SET_SPEED,
83 wxID_SET_VOLUME,
84 wxID_SPEED_5,
85 wxID_SPEED_10,
86 wxID_SPEED_17,
87 wxID_SPEED_20,
88 wxID_SPEED_25,
89 wxID_SPEED_33,
90 wxID_SPEED_50,
91 wxID_SPEED_100,
92 wxID_SPEED_150,
93 wxID_SPEED_200,
94 wxID_SPEED_300,
95 wxID_SPEED_500,
96 wxID_SPEED_1000,
97 wxID_SPEED_TURBO,
98 wxID_LOAD_LIBRARY,
99 wxID_SETTINGS,
100 wxID_SETTINGS_HOTKEYS,
101 wxID_RELOAD_ROM_IMAGE,
102 wxID_LOAD_ROM_IMAGE,
103 wxID_NEW_MOVIE,
104 wxID_SHOW_MESSAGES,
105 wxID_DEDICATED_MEMORY_WATCH,
106 wxID_RMOVIE_FIRST,
107 wxID_RMOVIE_LAST = wxID_RMOVIE_FIRST + 16,
108 wxID_RROM_FIRST,
109 wxID_RROM_LAST = wxID_RROM_FIRST + 16,
113 double horizontal_scale_factor = 1.0;
114 double vertical_scale_factor = 1.0;
115 int scaling_flags = SWS_POINT;
117 namespace
119 std::string last_volume = "0dB";
120 unsigned char* screen_buffer;
121 uint32_t old_width;
122 uint32_t old_height;
123 int old_flags = SWS_POINT;
124 bool main_window_dirty;
125 struct thread* emulation_thread;
127 void recent_rom_selected(const std::string& file)
129 platform::queue("unpause-emulator");
130 platform::queue("reload-rom " + file);
133 void recent_movie_selected(const std::string& file)
135 platform::queue("load-smart " + file);
138 wxString getname()
140 std::string windowname = "lsnes rr" + lsnes_version + "[" + bsnes_core_version + "]";
141 return towxstring(windowname);
144 struct emu_args
146 struct loaded_rom* rom;
147 struct moviefile* initial;
148 bool load_has_to_succeed;
151 void* emulator_main(void* _args)
153 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
154 try {
155 our_rom = args->rom;
156 struct moviefile* movie = args->initial;
157 bool has_to_succeed = args->load_has_to_succeed;
158 platform::flush_command_queue();
159 main_loop(*our_rom, *movie, has_to_succeed);
160 signal_program_exit();
161 } catch(std::bad_alloc& e) {
162 OOM_panic();
163 } catch(std::exception& e) {
164 messages << "FATAL: " << e.what() << std::endl;
165 platform::fatal_error();
167 return NULL;
170 void join_emulator_thread()
172 emulation_thread->join();
175 keyboard_mouse_calibration mouse_cal = {0};
176 keyboard_key_mouse mouse_x(lsnes_kbd, "mouse_x", "mouse", mouse_cal);
177 keyboard_key_mouse mouse_y(lsnes_kbd, "mouse_y", "mouse", mouse_cal);
178 keyboard_key_key mouse_l(lsnes_kbd, "mouse_left", "mouse");
179 keyboard_key_key mouse_m(lsnes_kbd, "mouse_center", "mouse");
180 keyboard_key_key mouse_r(lsnes_kbd, "mouse_right", "mouse");
181 keyboard_key_key mouse_i(lsnes_kbd, "mouse_inwindow", "mouse");
183 void handle_wx_mouse(wxMouseEvent& e)
185 platform::queue(keypress(keyboard_modifier_set(), mouse_x, e.GetX() / horizontal_scale_factor));
186 platform::queue(keypress(keyboard_modifier_set(), mouse_y, e.GetY() / vertical_scale_factor));
187 if(e.Entering())
188 platform::queue(keypress(keyboard_modifier_set(), mouse_i, 1));
189 if(e.Leaving())
190 platform::queue(keypress(keyboard_modifier_set(), mouse_i, 0));
191 if(e.LeftDown())
192 platform::queue(keypress(keyboard_modifier_set(), mouse_l, 1));
193 if(e.LeftUp())
194 platform::queue(keypress(keyboard_modifier_set(), mouse_l, 0));
195 if(e.MiddleDown())
196 platform::queue(keypress(keyboard_modifier_set(), mouse_m, 1));
197 if(e.MiddleUp())
198 platform::queue(keypress(keyboard_modifier_set(), mouse_m, 0));
199 if(e.RightDown())
200 platform::queue(keypress(keyboard_modifier_set(), mouse_r, 1));
201 if(e.RightUp())
202 platform::queue(keypress(keyboard_modifier_set(), mouse_r, 0));
205 bool is_readonly_mode()
207 bool ret;
208 runemufn([&ret]() { ret = movb.get_movie().readonly_mode(); });
209 return ret;
212 bool UI_get_autohold(unsigned port, unsigned controller, unsigned idx)
214 bool ret;
215 runemufn([&ret, port, controller, idx]() { ret = controls.autohold2(port, controller, idx); });
216 return ret;
219 void UI_change_autohold(unsigned port, unsigned controller, unsigned idx, bool newstate)
221 runemufn([port, controller, idx, newstate]() { controls.autohold2(port, controller, idx,
222 newstate); });
225 std::pair<int, int> UI_controller_index_by_logical(unsigned lid)
227 std::pair<int, int> ret;
228 runemufn([&ret, lid]() { ret = controls.lcid_to_pcid(lid); });
229 return ret;
232 int UI_button_id(unsigned port, unsigned controller, unsigned lidx)
234 int ret;
235 runemufn([&ret, port, controller, lidx]() { ret = controls.button_id(port, controller, lidx); });
236 return ret;
239 void set_speed(double target)
241 std::string v = (stringfmt() << target).str();
242 if(target < 0)
243 lsnes_set.set("targetfps", "infinite");
244 else
245 lsnes_set.set("targetfps", v);
248 class controller_autohold_menu : public wxMenu
250 public:
251 controller_autohold_menu(unsigned lid);
252 void change_type();
253 bool is_dummy();
254 void on_select(wxCommandEvent& e);
255 void update(unsigned port, unsigned controller, unsigned ctrlnum, bool newstate);
256 private:
257 unsigned our_lid;
258 std::pair<int, int> our_pid;
259 std::vector<wxMenuItem*> entries;
260 unsigned enabled_entries;
261 std::map<unsigned, int> pidxs;
262 std::vector<bool> autoholds;
265 class autohold_menu : public wxMenu
267 public:
268 autohold_menu(wxwin_mainwindow* win);
269 void reconfigure();
270 void on_select(wxCommandEvent& e);
271 void update(unsigned port, unsigned controller, unsigned ctrlnum, bool newstate);
272 private:
273 std::vector<controller_autohold_menu*> menus;
274 std::vector<wxMenuItem*> entries;
277 class sound_select_menu : public wxMenu
279 public:
280 sound_select_menu(wxwin_mainwindow* win);
281 void update(const std::string& dev);
282 void on_select(wxCommandEvent& e);
283 private:
284 std::map<std::string, wxMenuItem*> items;
285 std::map<int, std::string> devices;
288 class sound_select_menu;
290 class broadcast_listener : public information_dispatch
292 public:
293 broadcast_listener(wxwin_mainwindow* win);
294 void set_sound_select(sound_select_menu* sdev);
295 void set_autohold_menu(autohold_menu* ah);
296 void on_sound_unmute(bool unmute) throw();
297 void on_sound_change(const std::string& dev) throw();
298 void on_mode_change(bool readonly) throw();
299 void on_autohold_update(unsigned port, unsigned controller, unsigned ctrlnum, bool newstate);
300 void on_autohold_reconfigure();
301 private:
302 wxwin_mainwindow* mainw;
303 sound_select_menu* sounddev;
304 autohold_menu* ahmenu;
307 controller_autohold_menu::controller_autohold_menu(unsigned lid)
309 auto limits = get_core_logical_controller_limits();
310 entries.resize(limits.second);
311 modal_pause_holder hld;
312 our_lid = lid;
313 for(unsigned i = 0; i < limits.second; i++) {
314 int id = wxID_AUTOHOLD_FIRST + limits.second * lid + i;
315 entries[i] = AppendCheckItem(id, towxstring(get_logical_button_name(i)));
317 change_type();
320 void controller_autohold_menu::change_type()
322 enabled_entries = 0;
323 our_pid = controls.lcid_to_pcid(our_lid);
324 //We have modal lock.
325 auto limits = get_core_logical_controller_limits();
326 this->autoholds.resize(limits.second);
327 for(unsigned i = 0; i < limits.second; i++) {
328 this->pidxs[i] = -1;
329 if(this->our_pid.first >= 0)
330 this->pidxs[i] = controls.button_id(this->our_pid.first, this->our_pid.second,
332 if(this->pidxs[i] >= 0)
333 this->autoholds[i] = controls.autohold2(this->our_pid.first, this->our_pid.second,
334 this->pidxs[i]);
335 else
336 this->autoholds[i] = false;
338 for(auto i : pidxs) {
339 if(i.second >= 0) {
340 entries[i.first]->Check(autoholds[i.first]);
341 entries[i.first]->Enable();
342 enabled_entries++;
343 } else {
344 entries[i.first]->Check(false);
345 entries[i.first]->Enable(false);
350 bool controller_autohold_menu::is_dummy()
352 return !enabled_entries;
355 void controller_autohold_menu::on_select(wxCommandEvent& e)
357 auto limits = get_core_logical_controller_limits();
358 int x = e.GetId();
359 if(x < wxID_AUTOHOLD_FIRST + our_lid * limits.second || x >= wxID_AUTOHOLD_FIRST *
360 (our_lid + 1) * limits.second) {
361 return;
363 unsigned lidx = (x - wxID_AUTOHOLD_FIRST) % limits.second;
364 modal_pause_holder hld;
365 std::pair<int, int> pid = controls.lcid_to_pcid(our_lid);
366 if(pid.first < 0 || !entries[lidx])
367 return;
368 int pidx = controls.button_id(pid.first, pid.second, lidx);
369 if(pidx < 0)
370 return;
371 //Autohold change on pid=pid, ctrlindx=idx, state
372 bool newstate = entries[lidx]->IsChecked();
373 UI_change_autohold(pid.first, pid.second, pidx, newstate);
376 void controller_autohold_menu::update(unsigned port, unsigned controller, unsigned ctrlnum, bool newstate)
378 modal_pause_holder hld;
379 if(our_pid.first < 0 || port != our_pid.first || controller != our_pid.second)
380 return;
381 auto limits = get_core_logical_controller_limits();
382 for(unsigned i = 0; i < limits.second; i++) {
383 if(pidxs[i] < 0 || static_cast<unsigned>(pidxs[i]) != ctrlnum)
384 continue;
385 entries[i]->Check(newstate);
390 autohold_menu::autohold_menu(wxwin_mainwindow* win)
392 auto limits = get_core_logical_controller_limits();
393 entries.resize(limits.first);
394 menus.resize(limits.first);
395 for(unsigned i = 0; i < limits.first; i++) {
396 std::ostringstream str;
397 str << "Controller #&" << (i + 1);
398 menus[i] = new controller_autohold_menu(i);
399 entries[i] = AppendSubMenu(menus[i], towxstring(str.str()));
400 entries[i]->Enable(!menus[i]->is_dummy());
402 win->Connect(wxID_AUTOHOLD_FIRST, wxID_AUTOHOLD_LAST, wxEVT_COMMAND_MENU_SELECTED,
403 wxCommandEventHandler(autohold_menu::on_select), NULL, this);
404 reconfigure();
407 void autohold_menu::reconfigure()
409 modal_pause_holder hld;
410 auto limits = get_core_logical_controller_limits();
411 for(unsigned i = 0; i < limits.first; i++) {
412 menus[i]->change_type();
413 entries[i]->Enable(!menus[i]->is_dummy());
417 void autohold_menu::on_select(wxCommandEvent& e)
419 auto limits = get_core_logical_controller_limits();
420 for(unsigned i = 0; i < limits.first; i++)
421 menus[i]->on_select(e);
424 void autohold_menu::update(unsigned port, unsigned controller, unsigned ctrlnum, bool newstate)
426 auto limits = get_core_logical_controller_limits();
427 for(unsigned i = 0; i < limits.first; i++)
428 menus[i]->update(port, controller, ctrlnum, newstate);
431 sound_select_menu::sound_select_menu(wxwin_mainwindow* win)
433 std::string curdev = audioapi_driver_get_device();
434 int j = wxID_AUDIODEV_FIRST;
435 for(auto i : audioapi_driver_get_devices()) {
436 items[i.first] = AppendRadioItem(j, towxstring(i.first + "(" + i.second + ")"));
437 devices[j] = i.first;
438 if(i.first == curdev)
439 items[i.first]->Check();
440 win->Connect(j, wxEVT_COMMAND_MENU_SELECTED,
441 wxCommandEventHandler(sound_select_menu::on_select), NULL, this);
442 j++;
446 void sound_select_menu::update(const std::string& dev)
448 items[dev]->Check();
451 void sound_select_menu::on_select(wxCommandEvent& e)
453 std::string devname = devices[e.GetId()];
454 if(devname != "")
455 runemufn([devname]() { platform::set_sound_device(devname); });
458 broadcast_listener::broadcast_listener(wxwin_mainwindow* win)
459 : information_dispatch("wxwidgets-broadcast-listener")
461 mainw = win;
464 void broadcast_listener::set_sound_select(sound_select_menu* sdev)
466 sounddev = sdev;
469 void broadcast_listener::set_autohold_menu(autohold_menu* ah)
471 ahmenu = ah;
474 void broadcast_listener::on_sound_unmute(bool unmute) throw()
476 runuifun([this, unmute]() { this->mainw->menu_check(wxID_AUDIO_ENABLED, unmute); });
479 void broadcast_listener::on_sound_change(const std::string& dev) throw()
481 runuifun([this, dev]() { if(this->sounddev) this->sounddev->update(dev); });
484 void broadcast_listener::on_mode_change(bool readonly) throw()
486 runuifun([this, readonly]() { this->mainw->menu_check(wxID_READONLY_MODE, readonly); });
489 void broadcast_listener::on_autohold_update(unsigned port, unsigned controller, unsigned ctrlnum,
490 bool newstate)
492 runuifun([this, port, controller, ctrlnum, newstate]() { this->ahmenu->update(port, controller,
493 ctrlnum, newstate); });
496 void broadcast_listener::on_autohold_reconfigure()
498 runuifun([this]() { this->ahmenu->reconfigure(); });
501 path_setting moviepath_setting(lsnes_set, "moviepath");
502 path_setting rompath_setting(lsnes_set, "rompath");
504 std::string movie_path()
506 return lsnes_set.get("moviepath");
509 std::string rom_path()
511 return lsnes_set.get("rompath");
514 bool is_lsnes_movie(const std::string& filename)
516 try {
517 zip_reader r(filename);
518 std::istream& s = r["systemid"];
519 std::string s2;
520 std::getline(s, s2);
521 delete &s;
522 istrip_CR(s2);
523 return (s2 == "lsnes-rr1");
524 } catch(...) {
525 return false;
529 class loadfile : public wxFileDropTarget
531 public:
532 loadfile(wxwin_mainwindow* win) : pwin(win) {};
533 bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
535 bool ret = false;
536 if(filenames.Count() == 2) {
537 if(is_lsnes_movie(tostdstring(filenames[0])) &&
538 !is_lsnes_movie(tostdstring(filenames[1]))) {
539 platform::queue("unpause-emulator");
540 platform::queue("reload-rom " + tostdstring(filenames[1]));
541 platform::queue("load-smart " + tostdstring(filenames[0]));
542 ret = true;
544 if(!is_lsnes_movie(tostdstring(filenames[0])) &&
545 is_lsnes_movie(tostdstring(filenames[1]))) {
546 platform::queue("unpause-emulator");
547 platform::queue("reload-rom " + tostdstring(filenames[0]));
548 platform::queue("load-smart " + tostdstring(filenames[1]));
549 ret = true;
552 if(filenames.Count() == 1) {
553 if(is_lsnes_movie(tostdstring(filenames[0]))) {
554 platform::queue("load-smart " + tostdstring(filenames[0]));
555 pwin->recent_movies->add(tostdstring(filenames[0]));
556 ret = true;
557 } else {
558 platform::queue("unpause-emulator");
559 platform::queue("reload-rom " + tostdstring(filenames[0]));
560 pwin->recent_roms->add(tostdstring(filenames[0]));
561 ret = true;
564 return ret;
566 wxwin_mainwindow* pwin;
570 void boot_emulator(loaded_rom& rom, moviefile& movie)
572 try {
573 struct emu_args* a = new emu_args;
574 a->rom = &rom;
575 a->initial = &movie;
576 a->load_has_to_succeed = false;
577 modal_pause_holder hld;
578 emulation_thread = &thread::create(emulator_main, a);
579 main_window = new wxwin_mainwindow();
580 main_window->Show();
581 } catch(std::bad_alloc& e) {
582 OOM_panic();
586 wxwin_mainwindow::panel::panel(wxWindow* win)
587 : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS)
589 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
590 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
591 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
592 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
593 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
594 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
595 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
596 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
597 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
598 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
599 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
600 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
601 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
602 SetMinSize(wxSize(512, 448));
605 void wxwin_mainwindow::menu_start(wxString name)
607 while(!upper.empty())
608 upper.pop();
609 current_menu = new wxMenu();
610 menubar->Append(current_menu, name);
613 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
615 while(!upper.empty())
616 upper.pop();
617 menubar->Append(menu, name);
618 current_menu = NULL;
621 void wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
623 current_menu->AppendSubMenu(menu, name);
626 void wxwin_mainwindow::menu_entry(int id, wxString name)
628 current_menu->Append(id, name);
629 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
630 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
633 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
635 checkitems[id] = current_menu->AppendCheckItem(id, name);
636 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
637 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
640 void wxwin_mainwindow::menu_start_sub(wxString name)
642 wxMenu* old = current_menu;
643 upper.push(current_menu);
644 current_menu = new wxMenu();
645 old->AppendSubMenu(current_menu, name);
648 void wxwin_mainwindow::menu_end_sub()
650 current_menu = upper.top();
651 upper.pop();
654 bool wxwin_mainwindow::menu_ischecked(int id)
656 if(checkitems.count(id))
657 return checkitems[id]->IsChecked();
658 else
659 return false;
662 void wxwin_mainwindow::menu_check(int id, bool newstate)
664 if(checkitems.count(id))
665 return checkitems[id]->Check(newstate);
666 else
667 return;
670 void wxwin_mainwindow::menu_separator()
672 current_menu->AppendSeparator();
675 void wxwin_mainwindow::panel::request_paint()
677 Refresh();
680 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
682 render_framebuffer();
683 static struct SwsContext* ctx;
684 uint8_t* srcp[1];
685 int srcs[1];
686 uint8_t* dstp[1];
687 int dsts[1];
688 wxPaintDC dc(this);
689 uint32_t tw = main_screen.get_width() * horizontal_scale_factor + 0.5;
690 uint32_t th = main_screen.get_height() * vertical_scale_factor + 0.5;
691 if(!tw || !th) {
692 main_window_dirty = false;
693 return;
695 if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags) {
696 if(screen_buffer)
697 delete[] screen_buffer;
698 old_height = th;
699 old_width = tw;
700 old_flags = scaling_flags;
701 uint32_t w = main_screen.get_width();
702 uint32_t h = main_screen.get_height();
703 if(w && h)
704 ctx = sws_getCachedContext(ctx, w, h, PIX_FMT_RGBA, tw, th, PIX_FMT_BGR24, scaling_flags,
705 NULL, NULL, NULL);
706 tw = max(tw, static_cast<uint32_t>(128));
707 th = max(th, static_cast<uint32_t>(112));
708 screen_buffer = new unsigned char[tw * th * 3];
709 SetMinSize(wxSize(tw, th));
710 signal_resize_needed();
712 srcs[0] = 4 * main_screen.get_width();
713 dsts[0] = 3 * tw;
714 srcp[0] = reinterpret_cast<unsigned char*>(main_screen.rowptr(0));
715 dstp[0] = screen_buffer;
716 memset(screen_buffer, 0, tw * th * 3);
717 if(main_screen.get_width() && main_screen.get_height())
718 sws_scale(ctx, srcp, srcs, 0, main_screen.get_height(), dstp, dsts);
719 wxBitmap bmp(wxImage(tw, th, screen_buffer, true));
720 dc.DrawBitmap(bmp, 0, 0, false);
721 main_window_dirty = false;
724 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
726 //Blank.
729 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
731 handle_wx_keyboard(e, true);
734 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
736 handle_wx_keyboard(e, false);
739 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
741 handle_wx_mouse(e);
744 wxwin_mainwindow::wxwin_mainwindow()
745 : wxFrame(NULL, wxID_ANY, getname(), wxDefaultPosition, wxSize(-1, -1),
746 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX)
748 broadcast_listener* blistener = new broadcast_listener(this);
749 Centre();
750 mwindow = NULL;
751 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
752 toplevel->Add(gpanel = new panel(this), 1, wxGROW);
753 toplevel->Add(spanel = new wxwin_status::panel(this, gpanel, 20), 1, wxGROW);
754 spanel_shown = true;
755 toplevel->SetSizeHints(this);
756 SetSizer(toplevel);
757 Fit();
758 gpanel->SetFocus();
759 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
760 menubar = new wxMenuBar;
761 SetMenuBar(menubar);
763 menu_start(wxT("File"));
764 menu_start_sub(wxT("New"));
765 menu_entry(wxID_NEW_MOVIE, wxT("Movie..."));
766 menu_end_sub();
767 menu_start_sub(wxT("Load"));
768 menu_entry(wxID_LOAD_STATE, wxT("State..."));
769 menu_entry(wxID_LOAD_STATE_RO, wxT("State (readonly)..."));
770 menu_entry(wxID_LOAD_STATE_RW, wxT("State (read-write)..."));
771 menu_entry(wxID_LOAD_STATE_P, wxT("State (preserve input)..."));
772 menu_entry(wxID_LOAD_MOVIE, wxT("Movie..."));
773 if(loaded_library::call_library() != "") {
774 menu_separator();
775 menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + loaded_library::call_library()));
777 menu_separator();
778 menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM"));
779 menu_entry(wxID_LOAD_ROM_IMAGE, wxT("ROM..."));
780 menu_separator();
781 menu_special_sub(wxT("Recent ROMs"), recent_roms = new recent_menu(this, wxID_RROM_FIRST, wxID_RROM_LAST,
782 get_config_path() + "/recent-roms.txt", recent_rom_selected));
783 menu_special_sub(wxT("Recent Movies"), recent_movies = new recent_menu(this, wxID_RMOVIE_FIRST,
784 wxID_RMOVIE_LAST, get_config_path() + "/recent-movies.txt", recent_movie_selected));
785 menu_end_sub();
786 menu_start_sub(wxT("Save"));
787 menu_entry(wxID_SAVE_STATE, wxT("State..."));
788 menu_entry(wxID_SAVE_MOVIE, wxT("Movie..."));
789 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Screenshot..."));
790 menu_entry(wxID_SAVE_SUBTITLES, wxT("Subtitles..."));
791 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pending saves"));
792 menu_end_sub();
793 menu_separator();
794 menu_entry(wxID_EXIT, wxT("Quit"));
796 menu_start(wxT("System"));
797 menu_entry(wxID_PAUSE, wxT("Pause/Unpause"));
798 menu_entry(wxID_FRAMEADVANCE, wxT("Step frame"));
799 menu_entry(wxID_SUBFRAMEADVANCE, wxT("Step subframe"));
800 menu_entry(wxID_NEXTPOLL, wxT("Step poll"));
801 menu_entry(wxID_ERESET, wxT("Reset"));
803 menu_start(wxT("Movie"));
804 menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode"));
805 menu_check(wxID_READONLY_MODE, is_readonly_mode());
806 menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors..."));
807 menu_entry(wxID_EDIT_SUBTITLES, wxT("Edit subtitles..."));
808 #ifdef WITH_OPUS_CODEC
809 menu_entry(wxID_EDIT_VSUBTITLES, wxT("Edit commantary track..."));
810 #endif
811 menu_separator();
812 menu_entry(wxID_REWIND_MOVIE, wxT("Rewind to start"));
814 //Autohold menu: (ACOS)
815 menu_special(wxT("Autohold"), reinterpret_cast<autohold_menu*>(ahmenu = new autohold_menu(this)));
816 blistener->set_autohold_menu(reinterpret_cast<autohold_menu*>(ahmenu));
818 menu_start(wxT("Speed"));
819 menu_entry(wxID_SPEED_5, wxT("1/20x"));
820 menu_entry(wxID_SPEED_10, wxT("1/10x"));
821 menu_entry(wxID_SPEED_17, wxT("1/6x"));
822 menu_entry(wxID_SPEED_20, wxT("1/5x"));
823 menu_entry(wxID_SPEED_25, wxT("1/4x"));
824 menu_entry(wxID_SPEED_33, wxT("1/3x"));
825 menu_entry(wxID_SPEED_50, wxT("1/2x"));
826 menu_entry(wxID_SPEED_100, wxT("1x"));
827 menu_entry(wxID_SPEED_150, wxT("1.5x"));
828 menu_entry(wxID_SPEED_200, wxT("2x"));
829 menu_entry(wxID_SPEED_300, wxT("3x"));
830 menu_entry(wxID_SPEED_500, wxT("5x"));
831 menu_entry(wxID_SPEED_1000, wxT("10x"));
832 menu_entry(wxID_SPEED_TURBO, wxT("Turbo"));
833 menu_entry(wxID_SET_SPEED, wxT("Set..."));
835 menu_start(wxT("Tools"));
836 menu_entry(wxID_RUN_SCRIPT, wxT("Run batch file..."));
837 menu_separator();
838 menu_entry(wxID_EVAL_LUA, wxT("Evaluate Lua statement..."));
839 menu_entry(wxID_RUN_LUA, wxT("Run Lua script..."));
840 menu_separator();
841 menu_entry(wxID_RESET_LUA, wxT("Reset Lua VM"));
842 menu_separator();
843 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch..."));
844 menu_separator();
845 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch..."));
846 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch..."));
847 menu_separator();
848 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
849 menu_separator();
850 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
851 wxID_DUMP_FIRST, wxID_DUMP_LAST)));
853 menu_start(wxT("Configure"));
854 menu_entry_check(wxID_SHOW_STATUS, wxT("Show status panel"));
855 menu_check(wxID_SHOW_STATUS, true);
856 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH, wxT("Dedicated memory watch"));
857 menu_entry(wxID_SHOW_MESSAGES, wxT("Show messages"));
858 menu_entry(wxID_SETTINGS, wxT("Configure emulator..."));
859 menu_entry(wxID_SETTINGS_HOTKEYS, wxT("Configure hotkeys..."));
860 if(audioapi_driver_initialized()) {
861 menu_separator();
862 menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled"));
863 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
864 menu_entry(wxID_SET_VOLUME, wxT("Set Sound volume"));
865 menu_entry(wxID_SHOW_AUDIO_STATUS, wxT("Show audio status"));
866 menu_special_sub(wxT("Select sound device"), reinterpret_cast<sound_select_menu*>(sounddev =
867 new sound_select_menu(this)));
868 blistener->set_sound_select(reinterpret_cast<sound_select_menu*>(sounddev));
871 menu_start(wxT("Help"));
872 menu_entry(wxID_ABOUT, wxT("About..."));
874 gpanel->SetDropTarget(new loadfile(this));
875 spanel->SetDropTarget(new loadfile(this));
878 void wxwin_mainwindow::request_paint()
880 gpanel->Refresh();
883 void wxwin_mainwindow::on_close(wxCloseEvent& e)
885 //Veto it for now, latter things will delete it.
886 e.Veto();
887 platform::queue("quit-emulator");
890 void wxwin_mainwindow::notify_update() throw()
892 if(!main_window_dirty) {
893 main_window_dirty = true;
894 gpanel->Refresh();
898 void wxwin_mainwindow::notify_resized() throw()
900 toplevel->Layout();
901 toplevel->SetSizeHints(this);
902 Fit();
905 void wxwin_mainwindow::notify_update_status() throw()
907 if(!spanel->dirty) {
908 spanel->dirty = true;
909 spanel->Refresh();
911 if(mwindow)
912 mwindow->notify_update();
915 void wxwin_mainwindow::notify_exit() throw()
917 wxwidgets_exiting = true;
918 join_emulator_thread();
919 Destroy();
922 #define NEW_KEYBINDING "A new binding..."
923 #define NEW_ALIAS "A new alias..."
924 #define NEW_WATCH "A new watch..."
926 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
928 try {
929 handle_menu_click_cancelable(e);
930 } catch(canceled_exception& e) {
931 //Ignore.
932 } catch(std::bad_alloc& e) {
933 OOM_panic();
934 } catch(std::exception& e) {
935 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
939 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
941 std::string filename;
942 bool s;
943 switch(e.GetId()) {
944 case wxID_FRAMEADVANCE:
945 platform::queue("+advance-frame");
946 platform::queue("-advance-frame");
947 return;
948 case wxID_SUBFRAMEADVANCE:
949 platform::queue("+advance-poll");
950 platform::queue("-advance-poll");
951 return;
952 case wxID_NEXTPOLL:
953 platform::queue("advance-skiplag");
954 return;
955 case wxID_PAUSE:
956 platform::queue("pause-emulator");
957 return;
958 case wxID_ERESET:
959 platform::queue("reset");
960 return;
961 case wxID_EXIT:
962 platform::queue("quit-emulator");
963 return;
964 case wxID_AUDIO_ENABLED:
965 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
966 return;
967 case wxID_SHOW_AUDIO_STATUS:
968 platform::queue("show-sound-status");
969 return;
970 case wxID_CANCEL_SAVES:
971 platform::queue("cancel-saves");
972 return;
973 case wxID_LOAD_MOVIE:
974 filename = pick_file(this, "Load Movie", movie_path(), false);
975 recent_movies->add(filename);
976 platform::queue("load-movie " + filename);
977 return;
978 case wxID_LOAD_STATE:
979 filename = pick_file(this, "Load State", movie_path(), false);
980 recent_movies->add(filename);
981 platform::queue("load " + filename);
982 return;
983 case wxID_LOAD_STATE_RO:
984 filename = pick_file(this, "Load State (Read-Only)", movie_path(), false);
985 recent_movies->add(filename);
986 platform::queue("load-readonly " + filename);
987 return;
988 case wxID_LOAD_STATE_RW:
989 filename = pick_file(this, "Load State (Read-Write)", movie_path(), false);
990 recent_movies->add(filename);
991 platform::queue("load-state " + filename);
992 return;
993 case wxID_LOAD_STATE_P:
994 filename = pick_file(this, "Load State (Preserve)", movie_path(), false);
995 recent_movies->add(filename);
996 platform::queue("load-preserve " + filename);
997 return;
998 case wxID_REWIND_MOVIE:
999 platform::queue("rewind-movie");
1000 return;
1001 case wxID_SAVE_MOVIE:
1002 filename = pick_file(this, "Save Movie", movie_path(), true);
1003 recent_movies->add(filename);
1004 platform::queue("save-movie " + filename);
1005 return;
1006 case wxID_SAVE_SUBTITLES:
1007 platform::queue("save-subtitle " + pick_file(this, "Save Subtitle (.sub)", movie_path(), true));
1008 return;
1009 case wxID_SAVE_STATE:
1010 filename = pick_file(this, "Save State", movie_path(), true);
1011 recent_movies->add(filename);
1012 platform::queue("save-state " + filename);
1013 return;
1014 case wxID_SAVE_SCREENSHOT:
1015 platform::queue("take-screenshot " + pick_file(this, "Save Screenshot", movie_path(), true));
1016 return;
1017 case wxID_RUN_SCRIPT:
1018 platform::queue("run-script " + pick_file_member(this, "Select Script", "."));
1019 return;
1020 case wxID_RUN_LUA:
1021 platform::queue("run-lua " + pick_file(this, "Select Lua Script", ".", false));
1022 return;
1023 case wxID_RESET_LUA:
1024 platform::queue("reset-lua");
1025 return;
1026 case wxID_EVAL_LUA:
1027 platform::queue("evaluate-lua " + pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
1028 return;
1029 case wxID_READONLY_MODE:
1030 s = menu_ischecked(wxID_READONLY_MODE);
1031 runemufn([s]() {
1032 movb.get_movie().readonly_mode(s);
1033 if(!s)
1034 lua_callback_do_readwrite();
1035 update_movie_state();
1037 return;
1038 case wxID_EDIT_AUTHORS:
1039 wxeditor_authors_display(this);
1040 return;
1041 case wxID_EDIT_SUBTITLES:
1042 wxeditor_subtitles_display(this);
1043 return;
1044 #ifdef WITH_OPUS_CODEC
1045 case wxID_EDIT_VSUBTITLES:
1046 show_wxeditor_voicesub(this);
1047 return;
1048 #endif
1049 case wxID_EDIT_MEMORYWATCH:
1050 wxeditor_memorywatch_display(this);
1051 return;
1052 case wxID_SAVE_MEMORYWATCH: {
1053 modal_pause_holder hld;
1054 std::set<std::string> old_watches;
1055 runemufn([&old_watches]() { old_watches = get_watches(); });
1056 std::string filename = pick_file(this, "Save watches to file", ".", true);
1057 std::ofstream out(filename.c_str());
1058 for(auto i : old_watches) {
1059 std::string val;
1060 runemufn([i, &val]() { val = get_watchexpr_for(i); });
1061 out << i << std::endl << val << std::endl;
1063 out.close();
1064 return;
1066 case wxID_LOAD_MEMORYWATCH: {
1067 modal_pause_holder hld;
1068 std::set<std::string> old_watches;
1069 runemufn([&old_watches]() { old_watches = get_watches(); });
1070 std::map<std::string, std::string> new_watches;
1071 std::string filename = pick_file_member(this, "Choose memory watch file", ".");
1073 try {
1074 std::istream& in = open_file_relative(filename, "");
1075 while(in) {
1076 std::string wname;
1077 std::string wexpr;
1078 std::getline(in, wname);
1079 std::getline(in, wexpr);
1080 new_watches[strip_CR(wname)] = strip_CR(wexpr);
1082 delete &in;
1083 } catch(std::exception& e) {
1084 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
1085 wxICON_EXCLAMATION);
1086 return;
1089 runemufn([&new_watches, &old_watches]() {
1090 for(auto i : new_watches)
1091 set_watchexpr_for(i.first, i.second);
1092 for(auto i : old_watches)
1093 if(!new_watches.count(i))
1094 set_watchexpr_for(i, "");
1096 return;
1098 case wxID_MEMORY_SEARCH:
1099 wxwindow_memorysearch_display();
1100 return;
1101 case wxID_ABOUT: {
1102 std::ostringstream str;
1103 str << "Version: lsnes rr" << lsnes_version << std::endl;
1104 str << "Revision: " << lsnes_git_revision << std::endl;
1105 str << "Core: " << bsnes_core_version << std::endl;
1106 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1107 return;
1109 case wxID_SHOW_STATUS: {
1110 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
1111 if(newstate)
1112 spanel->Show();
1113 if(newstate && !spanel_shown)
1114 toplevel->Add(spanel, 1, wxGROW);
1115 else if(!newstate && spanel_shown)
1116 toplevel->Detach(spanel);
1117 if(!newstate)
1118 spanel->Hide();
1119 spanel_shown = newstate;
1120 toplevel->Layout();
1121 toplevel->SetSizeHints(this);
1122 Fit();
1123 return;
1125 case wxID_DEDICATED_MEMORY_WATCH: {
1126 bool newstate = menu_ischecked(wxID_DEDICATED_MEMORY_WATCH);
1127 if(newstate && !mwindow) {
1128 mwindow = new wxwin_status(-1, "Memory Watch");
1129 spanel->set_watch_flag(1);
1130 mwindow->Show();
1131 } else if(!newstate && mwindow) {
1132 mwindow->Destroy();
1133 mwindow = NULL;
1134 spanel->set_watch_flag(0);
1136 return;
1138 case wxID_SET_SPEED: {
1139 bool bad = false;
1140 std::string value = lsnes_set.is_set("targetfps") ? lsnes_set.get("targetfps") : "";
1141 value = pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value);
1142 try {
1143 lsnes_set.set("targetfps", value);
1144 } catch(...) {
1145 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1147 return;
1149 case wxID_SET_VOLUME: {
1150 std::string value;
1151 regex_results r;
1152 double parsed = 1;
1153 value = pick_text(this, "Set volume", "Enter volume in absolute units, percentage (%) or dB:",
1154 last_volume);
1155 if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)", value))
1156 parsed = strtod(r[1].c_str(), NULL);
1157 else if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)%", value))
1158 parsed = strtod(r[1].c_str(), NULL) / 100;
1159 else if(r = regex("([+-]?([0-9]*.[0-9]+|[0-9]+))dB", value))
1160 parsed = pow(10, strtod(r[1].c_str(), NULL) / 20);
1161 else {
1162 wxMessageBox(wxT("Invalid volume"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1163 return;
1165 last_volume = value;
1166 runemufn([parsed]() { platform::global_volume = parsed; });
1167 return;
1169 case wxID_SPEED_5:
1170 set_speed(5);
1171 break;
1172 case wxID_SPEED_10:
1173 set_speed(10);
1174 break;
1175 case wxID_SPEED_17:
1176 set_speed(16.66666666666);
1177 break;
1178 case wxID_SPEED_20:
1179 set_speed(20);
1180 break;
1181 case wxID_SPEED_25:
1182 set_speed(25);
1183 break;
1184 case wxID_SPEED_33:
1185 set_speed(33.3333333333333);
1186 break;
1187 case wxID_SPEED_50:
1188 set_speed(50);
1189 break;
1190 case wxID_SPEED_100:
1191 set_speed(100);
1192 break;
1193 case wxID_SPEED_150:
1194 set_speed(150);
1195 break;
1196 case wxID_SPEED_200:
1197 set_speed(200);
1198 break;
1199 case wxID_SPEED_300:
1200 set_speed(300);
1201 break;
1202 case wxID_SPEED_500:
1203 set_speed(500);
1204 break;
1205 case wxID_SPEED_1000:
1206 set_speed(1000);
1207 break;
1208 case wxID_SPEED_TURBO:
1209 set_speed(-1);
1210 break;
1211 case wxID_LOAD_LIBRARY: {
1212 std::string name = std::string("load ") + loaded_library::call_library();
1213 new loaded_library(pick_file(this, name, ".", false));
1214 break;
1216 case wxID_SETTINGS:
1217 wxsetingsdialog_display(this, false);
1218 break;
1219 case wxID_SETTINGS_HOTKEYS:
1220 wxsetingsdialog_display(this, true);
1221 break;
1222 case wxID_LOAD_ROM_IMAGE:
1223 filename = pick_file_member(this, "Select new ROM image", rom_path());
1224 recent_roms->add(filename);
1225 platform::queue("unpause-emulator");
1226 platform::queue("reload-rom " + filename);
1227 return;
1228 case wxID_RELOAD_ROM_IMAGE:
1229 platform::queue("unpause-emulator");
1230 platform::queue("reload-rom");
1231 return;
1232 case wxID_NEW_MOVIE:
1233 show_projectwindow(this);
1234 return;
1235 case wxID_SHOW_MESSAGES:
1236 msg_window->reshow();
1237 return;