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"
38 #define UINT64_C(val) val##ULL
40 #include <libswscale/swscale.h>
45 wxID_PAUSE
= wxID_HIGHEST
+ 1,
51 wxID_SHOW_AUDIO_STATUS
,
53 wxID_AUDIODEV_LAST
= wxID_AUDIODEV_FIRST
+ 255,
70 wxID_AUTOHOLD_LAST
= wxID_AUTOHOLD_FIRST
+ 1023,
71 wxID_EDIT_MEMORYWATCH
,
72 wxID_SAVE_MEMORYWATCH
,
73 wxID_LOAD_MEMORYWATCH
,
77 wxID_DUMP_LAST
= wxID_DUMP_FIRST
+ 1023,
100 wxID_SETTINGS_HOTKEYS
,
101 wxID_RELOAD_ROM_IMAGE
,
105 wxID_DEDICATED_MEMORY_WATCH
,
107 wxID_RMOVIE_LAST
= wxID_RMOVIE_FIRST
+ 16,
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
;
119 std::string last_volume
= "0dB";
120 unsigned char* screen_buffer
;
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
);
140 std::string windowname
= "lsnes rr" + lsnes_version
+ "[" + bsnes_core_version
+ "]";
141 return towxstring(windowname
);
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
);
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
) {
163 } catch(std::exception
& e
) {
164 messages
<< "FATAL: " << e
.what() << std::endl
;
165 platform::fatal_error();
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
));
188 platform::queue(keypress(keyboard_modifier_set(), mouse_i
, 1));
190 platform::queue(keypress(keyboard_modifier_set(), mouse_i
, 0));
192 platform::queue(keypress(keyboard_modifier_set(), mouse_l
, 1));
194 platform::queue(keypress(keyboard_modifier_set(), mouse_l
, 0));
196 platform::queue(keypress(keyboard_modifier_set(), mouse_m
, 1));
198 platform::queue(keypress(keyboard_modifier_set(), mouse_m
, 0));
200 platform::queue(keypress(keyboard_modifier_set(), mouse_r
, 1));
202 platform::queue(keypress(keyboard_modifier_set(), mouse_r
, 0));
205 bool is_readonly_mode()
208 runemufn([&ret
]() { ret
= movb
.get_movie().readonly_mode(); });
212 bool UI_get_autohold(unsigned port
, unsigned controller
, unsigned idx
)
215 runemufn([&ret
, port
, controller
, idx
]() { ret
= controls
.autohold2(port
, controller
, idx
); });
219 void UI_change_autohold(unsigned port
, unsigned controller
, unsigned idx
, bool newstate
)
221 runemufn([port
, controller
, idx
, newstate
]() { controls
.autohold2(port
, controller
, idx
,
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
); });
232 int UI_button_id(unsigned port
, unsigned controller
, unsigned lidx
)
235 runemufn([&ret
, port
, controller
, lidx
]() { ret
= controls
.button_id(port
, controller
, lidx
); });
239 void set_speed(double target
)
241 std::string v
= (stringfmt() << target
).str();
243 lsnes_set
.set("targetfps", "infinite");
245 lsnes_set
.set("targetfps", v
);
248 class controller_autohold_menu
: public wxMenu
251 controller_autohold_menu(unsigned lid
);
254 void on_select(wxCommandEvent
& e
);
255 void update(unsigned port
, unsigned controller
, unsigned ctrlnum
, bool newstate
);
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
268 autohold_menu(wxwin_mainwindow
* win
);
270 void on_select(wxCommandEvent
& e
);
271 void update(unsigned port
, unsigned controller
, unsigned ctrlnum
, bool newstate
);
273 std::vector
<controller_autohold_menu
*> menus
;
274 std::vector
<wxMenuItem
*> entries
;
277 class sound_select_menu
: public wxMenu
280 sound_select_menu(wxwin_mainwindow
* win
);
281 void update(const std::string
& dev
);
282 void on_select(wxCommandEvent
& e
);
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
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();
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
;
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
)));
320 void controller_autohold_menu::change_type()
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
++) {
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
,
336 this->autoholds
[i
] = false;
338 for(auto i
: pidxs
) {
340 entries
[i
.first
]->Check(autoholds
[i
.first
]);
341 entries
[i
.first
]->Enable();
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();
359 if(x
< wxID_AUTOHOLD_FIRST
+ our_lid
* limits
.second
|| x
>= wxID_AUTOHOLD_FIRST
*
360 (our_lid
+ 1) * limits
.second
) {
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
])
368 int pidx
= controls
.button_id(pid
.first
, pid
.second
, lidx
);
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
)
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
)
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);
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);
446 void sound_select_menu::update(const std::string
& dev
)
451 void sound_select_menu::on_select(wxCommandEvent
& e
)
453 std::string devname
= devices
[e
.GetId()];
455 runemufn([devname
]() { platform::set_sound_device(devname
); });
458 broadcast_listener::broadcast_listener(wxwin_mainwindow
* win
)
459 : information_dispatch("wxwidgets-broadcast-listener")
464 void broadcast_listener::set_sound_select(sound_select_menu
* sdev
)
469 void broadcast_listener::set_autohold_menu(autohold_menu
* 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
,
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
)
517 zip_reader
r(filename
);
518 std::istream
& s
= r
["systemid"];
523 return (s2
== "lsnes-rr1");
529 class loadfile
: public wxFileDropTarget
532 loadfile(wxwin_mainwindow
* win
) : pwin(win
) {};
533 bool OnDropFiles(wxCoord x
, wxCoord y
, const wxArrayString
& filenames
)
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]));
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]));
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]));
558 platform::queue("unpause-emulator");
559 platform::queue("reload-rom " + tostdstring(filenames
[0]));
560 pwin
->recent_roms
->add(tostdstring(filenames
[0]));
566 wxwin_mainwindow
* pwin
;
570 void boot_emulator(loaded_rom
& rom
, moviefile
& movie
)
573 struct emu_args
* a
= new emu_args
;
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();
581 } catch(std::bad_alloc
& e
) {
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())
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())
617 menubar
->Append(menu
, name
);
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();
654 bool wxwin_mainwindow::menu_ischecked(int id
)
656 if(checkitems
.count(id
))
657 return checkitems
[id
]->IsChecked();
662 void wxwin_mainwindow::menu_check(int id
, bool newstate
)
664 if(checkitems
.count(id
))
665 return checkitems
[id
]->Check(newstate
);
670 void wxwin_mainwindow::menu_separator()
672 current_menu
->AppendSeparator();
675 void wxwin_mainwindow::panel::request_paint()
680 void wxwin_mainwindow::panel::on_paint(wxPaintEvent
& e
)
682 render_framebuffer();
683 static struct SwsContext
* ctx
;
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;
692 main_window_dirty
= false;
695 if(!screen_buffer
|| tw
!= old_width
|| th
!= old_height
|| scaling_flags
!= old_flags
) {
697 delete[] screen_buffer
;
700 old_flags
= scaling_flags
;
701 uint32_t w
= main_screen
.get_width();
702 uint32_t h
= main_screen
.get_height();
704 ctx
= sws_getCachedContext(ctx
, w
, h
, PIX_FMT_RGBA
, tw
, th
, PIX_FMT_BGR24
, scaling_flags
,
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();
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
)
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
)
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);
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
);
755 toplevel
->SetSizeHints(this);
759 Connect(wxEVT_CLOSE_WINDOW
, wxCloseEventHandler(wxwin_mainwindow::on_close
));
760 menubar
= new wxMenuBar
;
763 menu_start(wxT("File"));
764 menu_start_sub(wxT("New"));
765 menu_entry(wxID_NEW_MOVIE
, wxT("Movie..."));
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() != "") {
775 menu_entry(wxID_LOAD_LIBRARY
, towxstring(std::string("Load ") + loaded_library::call_library()));
778 menu_entry(wxID_RELOAD_ROM_IMAGE
, wxT("Reload ROM"));
779 menu_entry(wxID_LOAD_ROM_IMAGE
, wxT("ROM..."));
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
));
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"));
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..."));
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..."));
838 menu_entry(wxID_EVAL_LUA
, wxT("Evaluate Lua statement..."));
839 menu_entry(wxID_RUN_LUA
, wxT("Run Lua script..."));
841 menu_entry(wxID_RESET_LUA
, wxT("Reset Lua VM"));
843 menu_entry(wxID_EDIT_MEMORYWATCH
, wxT("Edit memory watch..."));
845 menu_entry(wxID_LOAD_MEMORYWATCH
, wxT("Load memory watch..."));
846 menu_entry(wxID_SAVE_MEMORYWATCH
, wxT("Save memory watch..."));
848 menu_entry(wxID_MEMORY_SEARCH
, wxT("Memory Search..."));
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()) {
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()
883 void wxwin_mainwindow::on_close(wxCloseEvent
& e
)
885 //Veto it for now, latter things will delete it.
887 platform::queue("quit-emulator");
890 void wxwin_mainwindow::notify_update() throw()
892 if(!main_window_dirty
) {
893 main_window_dirty
= true;
898 void wxwin_mainwindow::notify_resized() throw()
901 toplevel
->SetSizeHints(this);
905 void wxwin_mainwindow::notify_update_status() throw()
908 spanel
->dirty
= true;
912 mwindow
->notify_update();
915 void wxwin_mainwindow::notify_exit() throw()
917 wxwidgets_exiting
= true;
918 join_emulator_thread();
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
)
929 handle_menu_click_cancelable(e
);
930 } catch(canceled_exception
& e
) {
932 } catch(std::bad_alloc
& e
) {
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
;
944 case wxID_FRAMEADVANCE
:
945 platform::queue("+advance-frame");
946 platform::queue("-advance-frame");
948 case wxID_SUBFRAMEADVANCE
:
949 platform::queue("+advance-poll");
950 platform::queue("-advance-poll");
953 platform::queue("advance-skiplag");
956 platform::queue("pause-emulator");
959 platform::queue("reset");
962 platform::queue("quit-emulator");
964 case wxID_AUDIO_ENABLED
:
965 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED
));
967 case wxID_SHOW_AUDIO_STATUS
:
968 platform::queue("show-sound-status");
970 case wxID_CANCEL_SAVES
:
971 platform::queue("cancel-saves");
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
);
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
);
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
);
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
);
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
);
998 case wxID_REWIND_MOVIE
:
999 platform::queue("rewind-movie");
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
);
1006 case wxID_SAVE_SUBTITLES
:
1007 platform::queue("save-subtitle " + pick_file(this, "Save Subtitle (.sub)", movie_path(), true));
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
);
1014 case wxID_SAVE_SCREENSHOT
:
1015 platform::queue("take-screenshot " + pick_file(this, "Save Screenshot", movie_path(), true));
1017 case wxID_RUN_SCRIPT
:
1018 platform::queue("run-script " + pick_file_member(this, "Select Script", "."));
1021 platform::queue("run-lua " + pick_file(this, "Select Lua Script", ".", false));
1023 case wxID_RESET_LUA
:
1024 platform::queue("reset-lua");
1027 platform::queue("evaluate-lua " + pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
1029 case wxID_READONLY_MODE
:
1030 s
= menu_ischecked(wxID_READONLY_MODE
);
1032 movb
.get_movie().readonly_mode(s
);
1034 lua_callback_do_readwrite();
1035 update_movie_state();
1038 case wxID_EDIT_AUTHORS
:
1039 wxeditor_authors_display(this);
1041 case wxID_EDIT_SUBTITLES
:
1042 wxeditor_subtitles_display(this);
1044 #ifdef WITH_OPUS_CODEC
1045 case wxID_EDIT_VSUBTITLES
:
1046 show_wxeditor_voicesub(this);
1049 case wxID_EDIT_MEMORYWATCH
:
1050 wxeditor_memorywatch_display(this);
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
) {
1060 runemufn([i
, &val
]() { val
= get_watchexpr_for(i
); });
1061 out
<< i
<< std::endl
<< val
<< std::endl
;
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", ".");
1074 std::istream
& in
= open_file_relative(filename
, "");
1078 std::getline(in
, wname
);
1079 std::getline(in
, wexpr
);
1080 new_watches
[strip_CR(wname
)] = strip_CR(wexpr
);
1083 } catch(std::exception
& e
) {
1084 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e
.what(),
1085 wxICON_EXCLAMATION
);
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
, "");
1098 case wxID_MEMORY_SEARCH
:
1099 wxwindow_memorysearch_display();
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);
1109 case wxID_SHOW_STATUS
: {
1110 bool newstate
= menu_ischecked(wxID_SHOW_STATUS
);
1113 if(newstate
&& !spanel_shown
)
1114 toplevel
->Add(spanel
, 1, wxGROW
);
1115 else if(!newstate
&& spanel_shown
)
1116 toplevel
->Detach(spanel
);
1119 spanel_shown
= newstate
;
1121 toplevel
->SetSizeHints(this);
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);
1131 } else if(!newstate
&& mwindow
) {
1134 spanel
->set_watch_flag(0);
1138 case wxID_SET_SPEED
: {
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
);
1143 lsnes_set
.set("targetfps", value
);
1145 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, this);
1149 case wxID_SET_VOLUME
: {
1153 value
= pick_text(this, "Set volume", "Enter volume in absolute units, percentage (%) or dB:",
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);
1162 wxMessageBox(wxT("Invalid volume"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, this);
1165 last_volume
= value
;
1166 runemufn([parsed
]() { platform::global_volume
= parsed
; });
1176 set_speed(16.66666666666);
1185 set_speed(33.3333333333333);
1190 case wxID_SPEED_100
:
1193 case wxID_SPEED_150
:
1196 case wxID_SPEED_200
:
1199 case wxID_SPEED_300
:
1202 case wxID_SPEED_500
:
1205 case wxID_SPEED_1000
:
1208 case wxID_SPEED_TURBO
:
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));
1217 wxsetingsdialog_display(this, false);
1219 case wxID_SETTINGS_HOTKEYS
:
1220 wxsetingsdialog_display(this, true);
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
);
1228 case wxID_RELOAD_ROM_IMAGE
:
1229 platform::queue("unpause-emulator");
1230 platform::queue("reload-rom");
1232 case wxID_NEW_MOVIE
:
1233 show_projectwindow(this);
1235 case wxID_SHOW_MESSAGES
:
1236 msg_window
->reshow();