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/audioapi.hpp"
11 #include "core/command.hpp"
12 #include "core/controller.hpp"
13 #include "core/controllerframe.hpp"
14 #include "core/dispatch.hpp"
15 #include "core/framebuffer.hpp"
16 #include "core/framerate.hpp"
17 #include "interface/romtype.hpp"
18 #include "core/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,
69 wxID_EDIT_MEMORYWATCH
,
70 wxID_SAVE_MEMORYWATCH
,
71 wxID_LOAD_MEMORYWATCH
,
75 wxID_DUMP_LAST
= wxID_DUMP_FIRST
+ 1023,
97 wxID_SETTINGS_HOTKEYS
,
98 wxID_SETTINGS_CONTROLLERS
,
99 wxID_RELOAD_ROM_IMAGE
,
103 wxID_DEDICATED_MEMORY_WATCH
,
105 wxID_RMOVIE_LAST
= wxID_RMOVIE_FIRST
+ 16,
107 wxID_RROM_LAST
= wxID_RROM_FIRST
+ 16,
108 wxID_CONFLICTRESOLUTION
,
115 double horizontal_scale_factor
= 1.0;
116 double vertical_scale_factor
= 1.0;
117 int scaling_flags
= SWS_POINT
;
118 bool hflip_enabled
= false;
119 bool vflip_enabled
= false;
120 bool rotate_enabled
= false;
124 std::string last_volume
= "0dB";
125 std::string last_volume_record
= "0dB";
126 std::string last_volume_voice
= "0dB";
127 unsigned char* screen_buffer
;
128 uint32_t* rotate_buffer
;
131 int old_flags
= SWS_POINT
;
132 bool old_hflip
= false;
133 bool old_vflip
= false;
134 bool old_rotate
= false;
135 bool main_window_dirty
;
136 thread_class
* emulation_thread
;
138 double pick_volume(wxWindow
* win
, const std::string
& title
, std::string
& last
)
143 value
= pick_text(win
, title
, "Enter volume in absolute units, percentage (%) or dB:",
145 if(r
= regex("([0-9]*\\.[0-9]+|[0-9]+)", value
))
146 parsed
= strtod(r
[1].c_str(), NULL
);
147 else if(r
= regex("([0-9]*\\.[0-9]+|[0-9]+)%", value
))
148 parsed
= strtod(r
[1].c_str(), NULL
) / 100;
149 else if(r
= regex("([+-]?([0-9]*.[0-9]+|[0-9]+))dB", value
))
150 parsed
= pow(10, strtod(r
[1].c_str(), NULL
) / 20);
152 wxMessageBox(wxT("Invalid volume"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, win
);
159 void recent_rom_selected(const std::string
& file
)
161 platform::queue("unpause-emulator");
162 platform::queue("reload-rom " + file
);
165 void recent_movie_selected(const std::string
& file
)
167 platform::queue("load-smart " + file
);
172 std::string windowname
= "lsnes rr" + lsnes_version
+ " [" + our_rom
->rtype
->get_core_identifier()
174 return towxstring(windowname
);
179 struct loaded_rom
* rom
;
180 struct moviefile
* initial
;
181 bool load_has_to_succeed
;
184 void* emulator_main(void* _args
)
186 struct emu_args
* args
= reinterpret_cast<struct emu_args
*>(_args
);
189 messages
<< "Using core: " << our_rom
->rtype
->get_core_identifier() << std::endl
;
190 struct moviefile
* movie
= args
->initial
;
191 bool has_to_succeed
= args
->load_has_to_succeed
;
192 platform::flush_command_queue();
193 main_loop(*our_rom
, *movie
, has_to_succeed
);
194 signal_program_exit();
195 } catch(std::bad_alloc
& e
) {
197 } catch(std::exception
& e
) {
198 messages
<< "FATAL: " << e
.what() << std::endl
;
199 platform::fatal_error();
204 void join_emulator_thread()
206 emulation_thread
->join();
209 keyboard_mouse_calibration mouse_cal
= {0};
210 keyboard_key_mouse
mouse_x(lsnes_kbd
, "mouse_x", "mouse", mouse_cal
);
211 keyboard_key_mouse
mouse_y(lsnes_kbd
, "mouse_y", "mouse", mouse_cal
);
212 keyboard_key_key
mouse_l(lsnes_kbd
, "mouse_left", "mouse");
213 keyboard_key_key
mouse_m(lsnes_kbd
, "mouse_center", "mouse");
214 keyboard_key_key
mouse_r(lsnes_kbd
, "mouse_right", "mouse");
215 keyboard_key_key
mouse_i(lsnes_kbd
, "mouse_inwindow", "mouse");
217 void handle_wx_mouse(wxMouseEvent
& e
)
219 platform::queue(keypress(keyboard_modifier_set(), mouse_x
, e
.GetX() / horizontal_scale_factor
));
220 platform::queue(keypress(keyboard_modifier_set(), mouse_y
, e
.GetY() / vertical_scale_factor
));
222 platform::queue(keypress(keyboard_modifier_set(), mouse_i
, 1));
224 platform::queue(keypress(keyboard_modifier_set(), mouse_i
, 0));
226 platform::queue(keypress(keyboard_modifier_set(), mouse_l
, 1));
228 platform::queue(keypress(keyboard_modifier_set(), mouse_l
, 0));
230 platform::queue(keypress(keyboard_modifier_set(), mouse_m
, 1));
232 platform::queue(keypress(keyboard_modifier_set(), mouse_m
, 0));
234 platform::queue(keypress(keyboard_modifier_set(), mouse_r
, 1));
236 platform::queue(keypress(keyboard_modifier_set(), mouse_r
, 0));
239 bool is_readonly_mode()
242 runemufn([&ret
]() { ret
= movb
.get_movie().readonly_mode(); });
246 std::pair
<int, int> UI_controller_index_by_logical(unsigned lid
)
248 std::pair
<int, int> ret
;
249 runemufn([&ret
, lid
]() { ret
= controls
.lcid_to_pcid(lid
); });
253 void set_speed(double target
)
256 set_speed_multiplier(std::numeric_limits
<double>::infinity());
258 set_speed_multiplier(target
/ 100);
261 class broadcast_listener
: public information_dispatch
264 broadcast_listener(wxwin_mainwindow
* win
);
265 void on_sound_unmute(bool unmute
) throw();
266 void on_mode_change(bool readonly
) throw();
267 void on_core_change();
270 wxwin_mainwindow
* mainw
;
273 broadcast_listener::broadcast_listener(wxwin_mainwindow
* win
)
274 : information_dispatch("wxwidgets-broadcast-listener")
279 void broadcast_listener::on_core_change()
281 signal_core_change();
284 void broadcast_listener::on_sound_unmute(bool unmute
) throw()
286 runuifun([this, unmute
]() { this->mainw
->menu_check(wxID_AUDIO_ENABLED
, unmute
); });
289 void broadcast_listener::on_mode_change(bool readonly
) throw()
291 runuifun([this, readonly
]() { this->mainw
->menu_check(wxID_READONLY_MODE
, readonly
); });
294 void update_preferences()
296 preferred_core
.clear();
297 for(auto i
: core_type::get_core_types()) {
298 std::string val
= i
->get_hname() + " / " + i
->get_core_identifier();
299 for(auto j
: i
->get_extensions()) {
300 std::string key
= "ext:" + j
;
301 if(core_selections
.count(key
) && core_selections
[key
] == val
)
302 preferred_core
[key
] = i
;
304 std::string key2
= "type:" + i
->get_iname();
305 if(core_selections
.count(key2
) && core_selections
[key2
] == val
)
306 preferred_core
[key2
] = i
;
311 void broadcast_listener::on_new_core()
313 update_preferences();
316 std::string
movie_path()
318 return lsnes_vset
["moviepath"].str();
321 std::string
rom_path()
323 return lsnes_vset
["rompath"].str();
326 bool is_lsnes_movie(const std::string
& filename
)
329 zip_reader
r(filename
);
330 std::istream
& s
= r
["systemid"];
335 return (s2
== "lsnes-rr1");
341 class loadfile
: public wxFileDropTarget
344 loadfile(wxwin_mainwindow
* win
) : pwin(win
) {};
345 bool OnDropFiles(wxCoord x
, wxCoord y
, const wxArrayString
& filenames
)
348 if(filenames
.Count() == 2) {
349 if(is_lsnes_movie(tostdstring(filenames
[0])) &&
350 !is_lsnes_movie(tostdstring(filenames
[1]))) {
351 platform::queue("unpause-emulator");
352 platform::queue("reload-rom " + tostdstring(filenames
[1]));
353 platform::queue("load-smart " + tostdstring(filenames
[0]));
356 if(!is_lsnes_movie(tostdstring(filenames
[0])) &&
357 is_lsnes_movie(tostdstring(filenames
[1]))) {
358 platform::queue("unpause-emulator");
359 platform::queue("reload-rom " + tostdstring(filenames
[0]));
360 platform::queue("load-smart " + tostdstring(filenames
[1]));
364 if(filenames
.Count() == 1) {
365 if(is_lsnes_movie(tostdstring(filenames
[0]))) {
366 platform::queue("load-smart " + tostdstring(filenames
[0]));
367 pwin
->recent_movies
->add(tostdstring(filenames
[0]));
370 platform::queue("unpause-emulator");
371 platform::queue("reload-rom " + tostdstring(filenames
[0]));
372 pwin
->recent_roms
->add(tostdstring(filenames
[0]));
378 wxwin_mainwindow
* pwin
;
382 void boot_emulator(loaded_rom
& rom
, moviefile
& movie
)
384 update_preferences();
386 struct emu_args
* a
= new emu_args
;
389 a
->load_has_to_succeed
= false;
390 modal_pause_holder hld
;
391 emulation_thread
= new thread_class(emulator_main
, a
);
392 main_window
= new wxwin_mainwindow();
394 } catch(std::bad_alloc
& e
) {
399 wxwin_mainwindow::panel::panel(wxWindow
* win
)
400 : wxPanel(win
, wxID_ANY
, wxDefaultPosition
, wxDefaultSize
, wxWANTS_CHARS
)
402 this->Connect(wxEVT_PAINT
, wxPaintEventHandler(panel::on_paint
), NULL
, this);
403 this->Connect(wxEVT_ERASE_BACKGROUND
, wxEraseEventHandler(panel::on_erase
), NULL
, this);
404 this->Connect(wxEVT_KEY_DOWN
, wxKeyEventHandler(panel::on_keyboard_down
), NULL
, this);
405 this->Connect(wxEVT_KEY_UP
, wxKeyEventHandler(panel::on_keyboard_up
), NULL
, this);
406 this->Connect(wxEVT_LEFT_DOWN
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
407 this->Connect(wxEVT_LEFT_UP
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
408 this->Connect(wxEVT_MIDDLE_DOWN
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
409 this->Connect(wxEVT_MIDDLE_UP
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
410 this->Connect(wxEVT_RIGHT_DOWN
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
411 this->Connect(wxEVT_RIGHT_UP
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
412 this->Connect(wxEVT_MOTION
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
413 this->Connect(wxEVT_ENTER_WINDOW
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
414 this->Connect(wxEVT_LEAVE_WINDOW
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
415 SetMinSize(wxSize(512, 448));
418 void wxwin_mainwindow::menu_start(wxString name
)
420 while(!upper
.empty())
422 current_menu
= new wxMenu();
423 menubar
->Append(current_menu
, name
);
426 void wxwin_mainwindow::menu_special(wxString name
, wxMenu
* menu
)
428 while(!upper
.empty())
430 menubar
->Append(menu
, name
);
434 void wxwin_mainwindow::menu_special_sub(wxString name
, wxMenu
* menu
)
436 current_menu
->AppendSubMenu(menu
, name
);
439 void wxwin_mainwindow::menu_entry(int id
, wxString name
)
441 current_menu
->Append(id
, name
);
442 Connect(id
, wxEVT_COMMAND_MENU_SELECTED
,
443 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click
), NULL
, this);
446 void wxwin_mainwindow::menu_entry_check(int id
, wxString name
)
448 checkitems
[id
] = current_menu
->AppendCheckItem(id
, name
);
449 Connect(id
, wxEVT_COMMAND_MENU_SELECTED
,
450 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click
), NULL
, this);
453 void wxwin_mainwindow::menu_start_sub(wxString name
)
455 wxMenu
* old
= current_menu
;
456 upper
.push(current_menu
);
457 current_menu
= new wxMenu();
458 old
->AppendSubMenu(current_menu
, name
);
461 void wxwin_mainwindow::menu_end_sub()
463 current_menu
= upper
.top();
467 bool wxwin_mainwindow::menu_ischecked(int id
)
469 if(checkitems
.count(id
))
470 return checkitems
[id
]->IsChecked();
475 void wxwin_mainwindow::menu_check(int id
, bool newstate
)
477 if(checkitems
.count(id
))
478 return checkitems
[id
]->Check(newstate
);
483 void wxwin_mainwindow::menu_separator()
485 current_menu
->AppendSeparator();
488 void wxwin_mainwindow::panel::request_paint()
493 void wxwin_mainwindow::panel::on_paint(wxPaintEvent
& e
)
495 render_framebuffer();
496 static struct SwsContext
* ctx
;
503 bool aux
= hflip_enabled
|| vflip_enabled
|| rotate_enabled
;
505 tw
= main_screen
.get_height() * horizontal_scale_factor
+ 0.5;
506 th
= main_screen
.get_width() * vertical_scale_factor
+ 0.5;
508 tw
= main_screen
.get_width() * horizontal_scale_factor
+ 0.5;
509 th
= main_screen
.get_height() * vertical_scale_factor
+ 0.5;
512 main_window_dirty
= false;
515 if(!screen_buffer
|| tw
!= old_width
|| th
!= old_height
|| scaling_flags
!= old_flags
||
516 hflip_enabled
!= old_hflip
|| vflip_enabled
!= old_vflip
|| rotate_enabled
!= old_rotate
) {
518 delete[] screen_buffer
;
520 delete[] rotate_buffer
;
523 old_flags
= scaling_flags
;
524 old_hflip
= hflip_enabled
;
525 old_vflip
= vflip_enabled
;
526 old_rotate
= rotate_enabled
;
527 uint32_t w
= main_screen
.get_width();
528 uint32_t h
= main_screen
.get_height();
530 ctx
= sws_getCachedContext(ctx
, rotate_enabled
? h
: w
, rotate_enabled
? w
: h
, PIX_FMT_RGBA
,
531 tw
, th
, PIX_FMT_BGR24
, scaling_flags
, NULL
, NULL
, NULL
);
532 tw
= max(tw
, static_cast<uint32_t>(128));
533 th
= max(th
, static_cast<uint32_t>(112));
534 screen_buffer
= new unsigned char[tw
* th
* 3];
536 rotate_buffer
= new uint32_t[main_screen
.get_width() * main_screen
.get_height()];
537 SetMinSize(wxSize(tw
, th
));
538 signal_resize_needed();
541 //Hflip, Vflip or rotate active.
542 size_t width
= main_screen
.get_width();
543 size_t height
= main_screen
.get_height();
544 size_t width1
= width
- 1;
545 size_t height1
= height
- 1;
546 size_t stride
= main_screen
.rowptr(1) - main_screen
.rowptr(0);
547 uint32_t* pixels
= main_screen
.rowptr(0);
549 for(unsigned y
= 0; y
< height
; y
++) {
550 uint32_t* pixels2
= pixels
+ (vflip_enabled
? (height1
- y
) : y
) * stride
;
551 uint32_t* dpixels
= rotate_buffer
+ (height1
- y
);
553 for(unsigned x
= 0; x
< width
; x
++)
554 dpixels
[x
* height
] = pixels2
[width1
- x
];
556 for(unsigned x
= 0; x
< width
; x
++)
557 dpixels
[x
* height
] = pixels2
[x
];
560 for(unsigned y
= 0; y
< height
; y
++) {
561 uint32_t* pixels2
= pixels
+ (vflip_enabled
? (height1
- y
) : y
) * stride
;
562 uint32_t* dpixels
= rotate_buffer
+ y
* width
;
564 for(unsigned x
= 0; x
< width
; x
++)
565 dpixels
[x
] = pixels2
[width1
- x
];
567 for(unsigned x
= 0; x
< width
; x
++)
568 dpixels
[x
] = pixels2
[x
];
572 srcs
[0] = 4 * (rotate_enabled
? main_screen
.get_height() : main_screen
.get_width());
574 srcp
[0] = reinterpret_cast<unsigned char*>(aux
? rotate_buffer
: main_screen
.rowptr(0));
575 dstp
[0] = screen_buffer
;
576 memset(screen_buffer
, 0, tw
* th
* 3);
577 if(main_screen
.get_width() && main_screen
.get_height())
578 sws_scale(ctx
, srcp
, srcs
, 0, rotate_enabled
? main_screen
.get_width() : main_screen
.get_height(),
580 wxBitmap
bmp(wxImage(tw
, th
, screen_buffer
, true));
581 dc
.DrawBitmap(bmp
, 0, 0, false);
582 main_window_dirty
= false;
585 void wxwin_mainwindow::panel::on_erase(wxEraseEvent
& e
)
590 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent
& e
)
592 handle_wx_keyboard(e
, true);
595 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent
& e
)
597 handle_wx_keyboard(e
, false);
600 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent
& e
)
605 wxwin_mainwindow::wxwin_mainwindow()
606 : wxFrame(NULL
, wxID_ANY
, getname(), wxDefaultPosition
, wxSize(-1, -1),
607 wxMINIMIZE_BOX
| wxSYSTEM_MENU
| wxCAPTION
| wxCLIP_CHILDREN
| wxCLOSE_BOX
)
609 broadcast_listener
* blistener
= new broadcast_listener(this);
612 toplevel
= new wxFlexGridSizer(1, 2, 0, 0);
613 toplevel
->Add(gpanel
= new panel(this), 1, wxGROW
);
614 toplevel
->Add(spanel
= new wxwin_status::panel(this, gpanel
, 20), 1, wxGROW
);
616 toplevel
->SetSizeHints(this);
620 Connect(wxEVT_CLOSE_WINDOW
, wxCloseEventHandler(wxwin_mainwindow::on_close
));
621 SetMenuBar(menubar
= new wxMenuBar
);
622 SetStatusBar(statusbar
= new wxStatusBar(this));
624 menu_start(wxT("File"));
625 menu_start_sub(wxT("New"));
626 menu_entry(wxID_NEW_MOVIE
, wxT("Movie..."));
628 menu_start_sub(wxT("Load"));
629 menu_entry(wxID_LOAD_STATE
, wxT("State..."));
630 menu_entry(wxID_LOAD_STATE_RO
, wxT("State (readonly)..."));
631 menu_entry(wxID_LOAD_STATE_RW
, wxT("State (read-write)..."));
632 menu_entry(wxID_LOAD_STATE_P
, wxT("State (preserve input)..."));
633 menu_entry(wxID_LOAD_MOVIE
, wxT("Movie..."));
634 if(loaded_library::call_library() != "") {
636 menu_entry(wxID_LOAD_LIBRARY
, towxstring(std::string("Load ") + loaded_library::call_library()));
639 menu_entry(wxID_RELOAD_ROM_IMAGE
, wxT("Reload ROM"));
640 menu_entry(wxID_LOAD_ROM_IMAGE
, wxT("ROM..."));
642 menu_special_sub(wxT("Recent ROMs"), recent_roms
= new recent_menu(this, wxID_RROM_FIRST
, wxID_RROM_LAST
,
643 get_config_path() + "/recent-roms.txt", recent_rom_selected
));
644 menu_special_sub(wxT("Recent Movies"), recent_movies
= new recent_menu(this, wxID_RMOVIE_FIRST
,
645 wxID_RMOVIE_LAST
, get_config_path() + "/recent-movies.txt", recent_movie_selected
));
647 menu_entry(wxID_CONFLICTRESOLUTION
, wxT("Conflict resolution"));
649 menu_start_sub(wxT("Save"));
650 menu_entry(wxID_SAVE_STATE
, wxT("State..."));
651 menu_entry(wxID_SAVE_MOVIE
, wxT("Movie..."));
652 menu_entry(wxID_SAVE_SCREENSHOT
, wxT("Screenshot..."));
653 menu_entry(wxID_SAVE_SUBTITLES
, wxT("Subtitles..."));
654 menu_entry(wxID_CANCEL_SAVES
, wxT("Cancel pending saves"));
657 menu_entry(wxID_EXIT
, wxT("Quit"));
659 menu_start(wxT("System"));
660 menu_entry(wxID_PAUSE
, wxT("Pause/Unpause"));
661 menu_entry(wxID_FRAMEADVANCE
, wxT("Step frame"));
662 menu_entry(wxID_SUBFRAMEADVANCE
, wxT("Step subframe"));
663 menu_entry(wxID_NEXTPOLL
, wxT("Step poll"));
664 menu_entry(wxID_ERESET
, wxT("Reset"));
665 menu_entry(wxID_EHRESET
, wxT("Power cycle"));
667 menu_start(wxT("Movie"));
668 menu_entry_check(wxID_READONLY_MODE
, wxT("Readonly mode"));
669 menu_check(wxID_READONLY_MODE
, is_readonly_mode());
670 menu_entry(wxID_EDIT_AUTHORS
, wxT("Edit game name && authors..."));
671 menu_entry(wxID_EDIT_SUBTITLES
, wxT("Edit subtitles..."));
672 #ifdef WITH_OPUS_CODEC
673 menu_entry(wxID_EDIT_VSUBTITLES
, wxT("Edit commantary track..."));
676 menu_entry(wxID_REWIND_MOVIE
, wxT("Rewind to start"));
678 menu_start(wxT("Speed"));
679 menu_entry(wxID_SPEED_5
, wxT("1/20x"));
680 menu_entry(wxID_SPEED_10
, wxT("1/10x"));
681 menu_entry(wxID_SPEED_17
, wxT("1/6x"));
682 menu_entry(wxID_SPEED_20
, wxT("1/5x"));
683 menu_entry(wxID_SPEED_25
, wxT("1/4x"));
684 menu_entry(wxID_SPEED_33
, wxT("1/3x"));
685 menu_entry(wxID_SPEED_50
, wxT("1/2x"));
686 menu_entry(wxID_SPEED_100
, wxT("1x"));
687 menu_entry(wxID_SPEED_150
, wxT("1.5x"));
688 menu_entry(wxID_SPEED_200
, wxT("2x"));
689 menu_entry(wxID_SPEED_300
, wxT("3x"));
690 menu_entry(wxID_SPEED_500
, wxT("5x"));
691 menu_entry(wxID_SPEED_1000
, wxT("10x"));
692 menu_entry(wxID_SPEED_TURBO
, wxT("Turbo"));
693 menu_entry(wxID_SET_SPEED
, wxT("Set..."));
695 menu_start(wxT("Tools"));
696 menu_entry(wxID_RUN_SCRIPT
, wxT("Run batch file..."));
698 menu_entry(wxID_EVAL_LUA
, wxT("Evaluate Lua statement..."));
699 menu_entry(wxID_RUN_LUA
, wxT("Run Lua script..."));
701 menu_entry(wxID_RESET_LUA
, wxT("Reset Lua VM"));
703 menu_entry(wxID_AUTOHOLD
, wxT("Autohold/Autofire..."));
704 menu_entry(wxID_TASINPUT
, wxT("TAS input plugin..."));
706 menu_entry(wxID_EDIT_MEMORYWATCH
, wxT("Edit memory watch..."));
708 menu_entry(wxID_LOAD_MEMORYWATCH
, wxT("Load memory watch..."));
709 menu_entry(wxID_SAVE_MEMORYWATCH
, wxT("Save memory watch..."));
711 menu_entry(wxID_MEMORY_SEARCH
, wxT("Memory Search..."));
713 menu_entry(wxID_MOVIE_EDIT
, wxT("Edit movie..."));
715 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu
*>(dmenu
= new dumper_menu(this,
716 wxID_DUMP_FIRST
, wxID_DUMP_LAST
)));
718 menu_start(wxT("Configure"));
719 menu_entry_check(wxID_SHOW_STATUS
, wxT("Show status panel"));
720 menu_check(wxID_SHOW_STATUS
, true);
721 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH
, wxT("Dedicated memory watch"));
722 menu_entry(wxID_SHOW_MESSAGES
, wxT("Show messages"));
723 menu_entry(wxID_SETTINGS
, wxT("Configure emulator..."));
724 menu_entry(wxID_SETTINGS_HOTKEYS
, wxT("Configure hotkeys..."));
725 menu_entry(wxID_SETTINGS_CONTROLLERS
, wxT("Configure controllers..."));
726 if(audioapi_driver_initialized()) {
728 menu_entry_check(wxID_AUDIO_ENABLED
, wxT("Sounds enabled"));
729 menu_entry(wxID_VUDISPLAY
, wxT("VU display / volume controls"));
730 menu_check(wxID_AUDIO_ENABLED
, platform::is_sound_enabled());
731 menu_entry(wxID_AUDIO_DEVICE
, wxT("Set audio device"));
734 menu_start(wxT("Help"));
735 menu_entry(wxID_ABOUT
, wxT("About..."));
737 gpanel
->SetDropTarget(new loadfile(this));
738 spanel
->SetDropTarget(new loadfile(this));
741 void wxwin_mainwindow::request_paint()
746 void wxwin_mainwindow::on_close(wxCloseEvent
& e
)
748 //Veto it for now, latter things will delete it.
750 platform::queue("quit-emulator");
753 void wxwin_mainwindow::notify_update() throw()
755 if(!main_window_dirty
) {
756 main_window_dirty
= true;
761 void wxwin_mainwindow::notify_resized() throw()
764 toplevel
->SetSizeHints(this);
768 void wxwin_mainwindow::notify_update_status() throw()
771 spanel
->dirty
= true;
775 mwindow
->notify_update();
778 void wxwin_mainwindow::notify_exit() throw()
780 wxwidgets_exiting
= true;
781 join_emulator_thread();
785 std::u32string
read_variable_map(const std::map
<std::string
, std::u32string
>& vars
, const std::string
& key
)
789 return vars
.find(key
)->second
;
792 void wxwin_mainwindow::update_statusbar(const std::map
<std::string
, std::u32string
>& vars
)
797 std::basic_ostringstream
<char32_t
> s
;
798 bool recording
= (read_variable_map(vars
, "!mode") == U
"R");
800 s
<< U
"Frame: " << read_variable_map(vars
, "!frame");
802 s
<< U
"Frame: " << read_variable_map(vars
, "!frame") << "/" <<
803 read_variable_map(vars
, "!length");
804 s
<< U
" Lag: " << read_variable_map(vars
, "!lag");
805 s
<< U
" Subframe: " << read_variable_map(vars
, "!subframe");
806 if(vars
.count("!saveslot"))
807 s
<< U
" Slot: " << read_variable_map(vars
, "!saveslot");
808 if(vars
.count("!saveslotinfo"))
809 s
<< U
" [" << read_variable_map(vars
, "!saveslotinfo") << U
"]";
810 s
<< U
" Speed: " << read_variable_map(vars
, "!speed") << "%";
812 if(read_variable_map(vars
, "!dumping") != U
"")
814 if(read_variable_map(vars
, "!mode") == U
"C")
816 else if(read_variable_map(vars
, "!mode") == U
"R")
818 else if(read_variable_map(vars
, "!mode") == U
"P")
820 else if(read_variable_map(vars
, "!mode") == U
"F")
824 statusbar
->SetStatusText(towxstring(s
.str()));
825 } catch(std::exception
& e
) {
829 #define NEW_KEYBINDING "A new binding..."
830 #define NEW_ALIAS "A new alias..."
831 #define NEW_WATCH "A new watch..."
833 void wxwin_mainwindow::handle_menu_click(wxCommandEvent
& e
)
836 handle_menu_click_cancelable(e
);
837 } catch(canceled_exception
& e
) {
839 } catch(std::bad_alloc
& e
) {
841 } catch(std::exception
& e
) {
842 show_message_ok(this, "Error in menu handler", e
.what(), wxICON_EXCLAMATION
);
846 void wxwin_mainwindow::refresh_title() throw()
851 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent
& e
)
853 std::string filename
;
856 case wxID_FRAMEADVANCE
:
857 platform::queue("+advance-frame");
858 platform::queue("-advance-frame");
860 case wxID_SUBFRAMEADVANCE
:
861 platform::queue("+advance-poll");
862 platform::queue("-advance-poll");
865 platform::queue("advance-skiplag");
868 platform::queue("pause-emulator");
871 platform::queue("reset");
874 platform::queue("reset-hard");
877 platform::queue("quit-emulator");
879 case wxID_AUDIO_ENABLED
:
880 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED
));
882 case wxID_AUDIO_DEVICE
:
883 wxeditor_sounddev_display(this);
885 case wxID_CANCEL_SAVES
:
886 platform::queue("cancel-saves");
888 case wxID_LOAD_MOVIE
:
889 filename
= pick_file(this, "Load Movie", movie_path(), false, "lsmv");
890 recent_movies
->add(filename
);
891 platform::queue("load-movie " + filename
);
893 case wxID_LOAD_STATE
:
894 filename
= pick_file(this, "Load State", movie_path(), false, "lsmv");
895 recent_movies
->add(filename
);
896 platform::queue("load " + filename
);
898 case wxID_LOAD_STATE_RO
:
899 filename
= pick_file(this, "Load State (Read-Only)", movie_path(), false, "lsmv");
900 recent_movies
->add(filename
);
901 platform::queue("load-readonly " + filename
);
903 case wxID_LOAD_STATE_RW
:
904 filename
= pick_file(this, "Load State (Read-Write)", movie_path(), false, "lsmv");
905 recent_movies
->add(filename
);
906 platform::queue("load-state " + filename
);
908 case wxID_LOAD_STATE_P
:
909 filename
= pick_file(this, "Load State (Preserve)", movie_path(), false, "lsmv");
910 recent_movies
->add(filename
);
911 platform::queue("load-preserve " + filename
);
913 case wxID_REWIND_MOVIE
:
914 platform::queue("rewind-movie");
916 case wxID_SAVE_MOVIE
:
917 filename
= pick_file(this, "Save Movie", movie_path(), true, "lsmv");
918 recent_movies
->add(filename
);
919 platform::queue("save-movie " + filename
);
921 case wxID_SAVE_SUBTITLES
:
922 platform::queue("save-subtitle " + pick_file(this, "Save Subtitle (.sub)", movie_path(), true,
925 case wxID_SAVE_STATE
:
926 filename
= pick_file(this, "Save State", movie_path(), true, "lsmv");
927 recent_movies
->add(filename
);
928 platform::queue("save-state " + filename
);
930 case wxID_SAVE_SCREENSHOT
:
931 platform::queue("take-screenshot " + pick_file(this, "Save Screenshot", movie_path(), true, "png"));
933 case wxID_RUN_SCRIPT
:
934 platform::queue("run-script " + pick_file_member(this, "Select Script", "."));
937 platform::queue("run-lua " + pick_file(this, "Select Lua Script", ".", false, "lua"));
940 platform::queue("reset-lua");
943 platform::queue("evaluate-lua " + pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
945 case wxID_READONLY_MODE
:
946 s
= menu_ischecked(wxID_READONLY_MODE
);
948 movb
.get_movie().readonly_mode(s
);
950 lua_callback_do_readwrite();
951 update_movie_state();
952 graphics_driver_notify_status();
956 wxeditor_autohold_display(this);
958 case wxID_EDIT_AUTHORS
:
959 wxeditor_authors_display(this);
961 case wxID_EDIT_SUBTITLES
:
962 wxeditor_subtitles_display(this);
964 #ifdef WITH_OPUS_CODEC
965 case wxID_EDIT_VSUBTITLES
:
966 show_wxeditor_voicesub(this);
969 case wxID_EDIT_MEMORYWATCH
:
970 wxeditor_memorywatch_display(this);
972 case wxID_SAVE_MEMORYWATCH
: {
973 modal_pause_holder hld
;
974 std::set
<std::string
> old_watches
;
975 runemufn([&old_watches
]() { old_watches
= get_watches(); });
976 std::string filename
= pick_file(this, "Save watches to file", ".", true, "lwch");
977 std::ofstream
out(filename
.c_str());
978 for(auto i
: old_watches
) {
980 runemufn([i
, &val
]() { val
= get_watchexpr_for(i
); });
981 out
<< i
<< std::endl
<< val
<< std::endl
;
986 case wxID_LOAD_MEMORYWATCH
: {
987 modal_pause_holder hld
;
988 std::set
<std::string
> old_watches
;
989 runemufn([&old_watches
]() { old_watches
= get_watches(); });
990 std::map
<std::string
, std::string
> new_watches
;
991 std::string filename
= pick_file_member(this, "Choose memory watch file", ".");
994 std::istream
& in
= open_file_relative(filename
, "");
998 std::getline(in
, wname
);
999 std::getline(in
, wexpr
);
1000 new_watches
[strip_CR(wname
)] = strip_CR(wexpr
);
1003 } catch(std::exception
& e
) {
1004 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e
.what(),
1005 wxICON_EXCLAMATION
);
1009 runemufn([&new_watches
, &old_watches
]() {
1010 for(auto i
: new_watches
)
1011 set_watchexpr_for(i
.first
, i
.second
);
1012 for(auto i
: old_watches
)
1013 if(!new_watches
.count(i
))
1014 set_watchexpr_for(i
, "");
1018 case wxID_MEMORY_SEARCH
:
1019 wxwindow_memorysearch_display();
1022 wxeditor_tasinput_display(this);
1025 std::ostringstream str
;
1026 str
<< "Version: lsnes rr" << lsnes_version
<< std::endl
;
1027 str
<< "Revision: " << lsnes_git_revision
<< std::endl
;
1028 for(auto i
: core_core::all_cores())
1030 str
<< "Core: " << i
->get_core_identifier() << std::endl
;
1031 wxMessageBox(towxstring(str
.str()), _T("About"), wxICON_INFORMATION
| wxOK
, this);
1034 case wxID_SHOW_STATUS
: {
1035 bool newstate
= menu_ischecked(wxID_SHOW_STATUS
);
1038 if(newstate
&& !spanel_shown
)
1039 toplevel
->Add(spanel
, 1, wxGROW
);
1040 else if(!newstate
&& spanel_shown
)
1041 toplevel
->Detach(spanel
);
1044 spanel_shown
= newstate
;
1046 toplevel
->SetSizeHints(this);
1050 case wxID_DEDICATED_MEMORY_WATCH
: {
1051 bool newstate
= menu_ischecked(wxID_DEDICATED_MEMORY_WATCH
);
1052 if(newstate
&& !mwindow
) {
1053 mwindow
= new wxwin_status(-1, "Memory Watch");
1054 spanel
->set_watch_flag(1);
1056 } else if(!newstate
&& mwindow
) {
1059 spanel
->set_watch_flag(0);
1063 case wxID_SET_SPEED
: {
1064 std::string value
= "infinite";
1065 double val
= get_speed_multiplier();
1066 if(!(val
== std::numeric_limits
<double>::infinity()))
1067 value
= (stringfmt() << (100 * val
)).str();
1068 value
= pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value
);
1070 if(value
== "infinite")
1071 set_speed_multiplier(std::numeric_limits
<double>::infinity());
1073 double v
= parse_value
<double>(value
) / 100;
1076 set_speed_multiplier(v
);
1079 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, this);
1090 set_speed(16.66666666666);
1099 set_speed(33.3333333333333);
1104 case wxID_SPEED_100
:
1107 case wxID_SPEED_150
:
1110 case wxID_SPEED_200
:
1113 case wxID_SPEED_300
:
1116 case wxID_SPEED_500
:
1119 case wxID_SPEED_1000
:
1122 case wxID_SPEED_TURBO
:
1125 case wxID_LOAD_LIBRARY
: {
1126 std::string name
= std::string("load ") + loaded_library::call_library();
1127 new loaded_library(pick_file(this, name
, ".", false, loaded_library::call_library_ext()));
1128 handle_post_loadlibrary();
1132 wxsetingsdialog_display(this, 0);
1134 case wxID_SETTINGS_HOTKEYS
:
1135 wxsetingsdialog_display(this, 1);
1137 case wxID_SETTINGS_CONTROLLERS
:
1138 wxsetingsdialog_display(this, 2);
1140 case wxID_LOAD_ROM_IMAGE
:
1141 filename
= pick_file_member(this, "Select new ROM image", rom_path());
1142 recent_roms
->add(filename
);
1143 platform::queue("unpause-emulator");
1144 platform::queue("reload-rom " + filename
);
1146 case wxID_RELOAD_ROM_IMAGE
:
1147 platform::queue("unpause-emulator");
1148 platform::queue("reload-rom");
1150 case wxID_NEW_MOVIE
:
1151 show_projectwindow(this);
1153 case wxID_SHOW_MESSAGES
:
1154 msg_window
->reshow();
1156 case wxID_CONFLICTRESOLUTION
:
1157 show_conflictwindow(this);
1159 case wxID_VUDISPLAY
:
1160 open_vumeter_window(this);
1162 case wxID_MOVIE_EDIT
:
1163 wxeditor_movie_display(this);