4 #include "platform/wxwidgets/menu_dump.hpp"
5 #include "platform/wxwidgets/menu_upload.hpp"
6 #include "platform/wxwidgets/platform.hpp"
7 #include "platform/wxwidgets/loadsave.hpp"
8 #include "platform/wxwidgets/window_mainwindow.hpp"
9 #include "platform/wxwidgets/window_messages.hpp"
10 #include "platform/wxwidgets/window_status.hpp"
11 #include "platform/wxwidgets/window-romload.hpp"
12 #include "platform/wxwidgets/settings-common.hpp"
13 #include "platform/wxwidgets/menu_tracelog.hpp"
14 #include "platform/wxwidgets/menu_branches.hpp"
15 #include "platform/wxwidgets/menu_projects.hpp"
17 #include "cmdhelp/framebuffer.hpp"
18 #include "cmdhelp/loadsave.hpp"
19 #include "cmdhelp/lua.hpp"
20 #include "cmdhelp/subtitles.hpp"
21 #include "core/audioapi.hpp"
22 #include "core/audioapi-driver.hpp"
23 #include "core/command.hpp"
24 #include "core/controller.hpp"
25 #include "core/controllerframe.hpp"
26 #include "core/dispatch.hpp"
27 #include "core/emustatus.hpp"
28 #include "core/framebuffer.hpp"
29 #include "core/framerate.hpp"
30 #include "core/instance.hpp"
31 #include "core/keymapper.hpp"
32 #include "core/ui-services.hpp"
33 #include "interface/romtype.hpp"
34 #include "core/loadlib.hpp"
35 #include "lua/lua.hpp"
36 #include "core/mainloop.hpp"
37 #include "core/memorywatch.hpp"
38 #include "core/messages.hpp"
39 #include "core/misc.hpp"
40 #include "core/moviedata.hpp"
41 #include "core/project.hpp"
42 #include "core/rom.hpp"
43 #include "core/settings.hpp"
44 #include "core/window.hpp"
45 #include "library/directory.hpp"
46 #include "library/minmax.hpp"
47 #include "library/string.hpp"
48 #include "library/zip.hpp"
49 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
61 #define UINT64_C(val) val##ULL
63 #include <libswscale/swscale.h>
68 wxID_PAUSE
= wxID_HIGHEST
+ 1,
86 wxID_EDIT_MEMORYWATCH
,
87 wxID_SAVE_MEMORYWATCH
,
88 wxID_LOAD_MEMORYWATCH
,
92 wxID_DUMP_LAST
= wxID_DUMP_FIRST
+ 1023,
113 wxID_RELOAD_ROM_IMAGE
,
114 wxID_LOAD_ROM_IMAGE_FIRST
,
115 wxID_LOAD_ROM_IMAGE_LAST
= wxID_LOAD_ROM_IMAGE_FIRST
+ 1023,
118 wxID_DEDICATED_MEMORY_WATCH
,
120 wxID_RMOVIE_LAST
= wxID_RMOVIE_FIRST
+ 16,
122 wxID_RROM_LAST
= wxID_RROM_FIRST
+ 16,
123 wxID_CONFLICTRESOLUTION
,
131 wxID_ENTER_FULLSCREEN
,
133 wxID_ACTIONS_LAST
= wxID_ACTIONS_FIRST
+ 256,
135 wxID_SETTINGS_LAST
= wxID_SETTINGS_FIRST
+ 256,
140 wxID_RLUA_LAST
= wxID_RLUA_FIRST
+ 16,
142 wxID_UPLOAD_LAST
= wxID_UPLOAD_FIRST
+ 256,
145 wxID_TRACELOG_LAST
= wxID_TRACELOG_FIRST
+ 256,
148 wxID_BRANCH_LAST
= wxID_BRANCH_FIRST
+ 10240,
150 wxID_PROJECT_LAST
= wxID_PROJECT_FIRST
+ 17,
157 double video_scale_factor
= 1.0;
158 int scaling_flags
= SWS_POINT
;
159 bool arcorrect_enabled
= false;
160 bool hflip_enabled
= false;
161 bool vflip_enabled
= false;
162 bool rotate_enabled
= false;
166 std::string last_volume
= "0dB";
167 std::string last_volume_record
= "0dB";
168 std::string last_volume_voice
= "0dB";
169 unsigned char* screen_buffer
;
170 struct SwsContext
* sws_ctx
;
171 uint32_t* rotate_buffer
;
174 int old_flags
= SWS_POINT
;
175 bool old_hflip
= false;
176 bool old_vflip
= false;
177 bool old_rotate
= false;
178 bool main_window_dirty
;
180 bool hashing_in_progress
= false;
181 uint64_t hashing_left
= 0;
182 uint64_t hashing_total
= 0;
183 int64_t last_update
= 0;
184 threads::thread
* emulation_thread
;
185 bool status_updated
= false;
186 bool becoming_fullscreen
= false;
187 wxSize current_resolution
;
189 settingvar::variable
<settingvar::model_bool
<settingvar::yes_no
>> background_audio(*lsnes_instance
.settings
,
190 "background-audio", "GUIā£Enable background audio", true);
192 class _status_timer
: public wxTimer
202 status_updated
= false;
203 if(main_window
) main_window
->update_statusbar();
208 void cleanup_dead_download_timers();
209 class _focus_timer
: public wxTimer
214 was_focused
= (wxWindow::FindFocus() != NULL
);
215 was_enabled
= platform::is_sound_enabled();
221 bool is_focused
= (wxWindow::FindFocus() != NULL
);
222 if(is_focused
&& !was_focused
) {
224 if(!background_audio
)
225 platform::sound_enable(was_enabled
);
226 } else if(!is_focused
&& was_focused
) {
228 was_enabled
= platform::is_sound_enabled();
229 if(!background_audio
)
230 platform::sound_enable(false);
232 was_focused
= is_focused
;
233 cleanup_dead_download_timers();
240 class download_timer
;
241 std::list
<download_timer
*> download_timer_gc_queue
;
243 class download_timer
: public wxTimer
246 download_timer(wxwin_mainwindow
* main
, emulator_instance
& _inst
)
255 if(!w
->download_in_progress
) {
256 //Received a call with download finish already done. Ignore.
259 if(w
->download_in_progress
->finished
) {
260 w
->update_statusbar();
261 auto old
= w
->download_in_progress
;
262 w
->download_in_progress
= NULL
;
263 if(old
->errormsg
!= "") {
264 show_message_ok(w
, "Error downloading movie", old
->errormsg
,
267 inst
.iqueue
->queue(CLOADSAVE::ldm
.name
, "$MEMORY:wxwidgets_download_tmp");
270 try { download_timer_gc_queue
.push_back(this); } catch(...) { Stop(); }
272 w
->update_statusbar();
276 emulator_instance
& inst
;
280 void cleanup_dead_download_timers()
282 for(auto i
: download_timer_gc_queue
) {
286 download_timer_gc_queue
.clear();
289 void hash_callback(uint64_t left
, uint64_t total
)
291 wxwin_mainwindow
* mwin
= main_window
;
292 if(left
== 0xFFFFFFFFFFFFFFFFULL
) {
293 hashing_in_progress
= false;
294 runuifun([mwin
]() { if(mwin
) mwin
->notify_update_status(); });
295 last_update
= framerate_regulator::get_utime() - 2000000;
298 hashing_in_progress
= true;
300 hashing_total
= total
;
301 int64_t this_update
= framerate_regulator::get_utime();
302 if(this_update
< last_update
- 1000000 || this_update
> last_update
+ 1000000) {
303 runuifun([mwin
]() { if(mwin
) mwin
->notify_update_status(); });
304 last_update
= this_update
;
308 std::pair
<std::string
, std::string
> lsplit(std::string l
)
310 for(unsigned i
= 0; i
< l
.length() - 3; i
++)
311 if((uint8_t)l
[i
] == 0xE2 && (uint8_t)l
[i
+ 1] == 0x80 && (uint8_t)l
[i
+ 2] == 0xA3)
312 return std::make_pair(l
.substr(0, i
), l
.substr(i
+ 3));
313 return std::make_pair("", l
);
316 recentfiles::multirom
loadreq_to_multirom(const romload_request
& req
)
318 recentfiles::multirom r
;
319 r
.packfile
= req
.packfile
;
320 r
.singlefile
= req
.singlefile
;
322 r
.system
= req
.system
;
323 r
.region
= req
.region
;
324 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++)
325 if(req
.files
[i
] != "") {
326 r
.files
.resize(i
+ 1);
327 r
.files
[i
] = req
.files
[i
];
332 class system_menu
: public wxMenu
335 system_menu(wxWindow
* win
, emulator_instance
& _inst
);
337 void on_select(wxCommandEvent
& e
);
338 void update(bool light
);
340 emulator_instance
& inst
;
342 void insert_pass(int id
, const std::string
& label
);
343 void insert_act(unsigned id
, const std::string
& label
, bool dots
, bool check
);
344 wxMenu
* lookup_menu(const std::string
& key
);
346 std::map
<int, unsigned> action_by_id
;
347 std::map
<unsigned, wxMenuItem
*> item_by_action
;
348 std::map
<wxMenuItem
*, wxMenu
*> menu_by_item
;
349 std::map
<std::string
, wxMenu
*> submenu_by_name
;
350 std::map
<std::string
, wxMenuItem
*> submenui_by_name
;
351 std::set
<unsigned> toggles
;
355 wxMenu
* system_menu::lookup_menu(const std::string
& key
)
360 if(submenu_by_name
.count(key
))
361 return submenu_by_name
[key
];
364 sep
= AppendSeparator();
365 auto p
= lsplit(key
);
366 wxMenu
* into
= lookup_menu(p
.first
);
367 submenu_by_name
[key
] = new wxMenu();
368 submenui_by_name
[key
] = into
->AppendSubMenu(submenu_by_name
[key
], towxstring(p
.second
));
369 menu_by_item
[submenui_by_name
[key
]] = into
;
370 return submenu_by_name
[key
];
373 void system_menu::insert_act(unsigned id
, const std::string
& label
, bool dots
, bool check
)
377 sep
= AppendSeparator();
379 auto p
= lsplit(label
);
380 wxMenu
* into
= lookup_menu(p
.first
);
382 action_by_id
[next_id
] = id
;
383 std::string use_label
= p
.second
+ (dots
? "..." : "");
385 item_by_action
[id
] = into
->AppendCheckItem(next_id
, towxstring(use_label
));
388 item_by_action
[id
] = into
->Append(next_id
, towxstring(use_label
));
389 menu_by_item
[item_by_action
[id
]] = into
;
390 pwin
->Connect(next_id
++, wxEVT_COMMAND_MENU_SELECTED
,
391 wxCommandEventHandler(system_menu::on_select
), NULL
, this);
394 void system_menu::insert_pass(int id
, const std::string
& label
)
397 pwin
->Connect(id
, wxEVT_COMMAND_MENU_SELECTED
,
398 wxCommandEventHandler(wxwin_mainwindow::handle_menu_click
), NULL
, pwin
);
399 Append(id
, towxstring(label
));
402 system_menu::system_menu(wxWindow
* win
, emulator_instance
& _inst
)
407 insert_pass(wxID_PAUSE
, "Pause/Unpause");
408 insert_pass(wxID_FRAMEADVANCE
, "Step frame");
409 insert_pass(wxID_SUBFRAMEADVANCE
, "Step subframe");
410 insert_pass(wxID_NEXTPOLL
, "Step poll");
414 system_menu::~system_menu()
418 void system_menu::on_select(wxCommandEvent
& e
)
421 if(!action_by_id
.count(e
.GetId()))
423 unsigned act_id
= action_by_id
[e
.GetId()];
424 const interface_action
* act
= NULL
;
425 for(auto i
: inst
.rom
->get_actions())
426 if(i
->id
== act_id
) {
433 auto p
= prompt_action_params(pwin
, act
->get_title(), act
->params
);
434 inst
.iqueue
->run([this, act_id
,p
]() { this->inst
.rom
->execute_action(act_id
, p
); });
435 } catch(canceled_exception
& e
) {
436 } catch(std::bad_alloc
& e
) {
441 void system_menu::update(bool light
)
445 next_id
= wxID_ACTIONS_FIRST
;
450 for(auto i
= item_by_action
.begin(); i
!= item_by_action
.end(); i
++)
451 menu_by_item
[i
->second
]->Destroy(i
->second
);
452 for(auto i
= submenui_by_name
.rbegin(); i
!= submenui_by_name
.rend(); i
++)
453 menu_by_item
[i
->second
]->Destroy(i
->second
);
454 action_by_id
.clear();
455 item_by_action
.clear();
456 menu_by_item
.clear();
457 submenu_by_name
.clear();
458 submenui_by_name
.clear();
461 for(auto i
: inst
.rom
->get_actions())
462 insert_act(i
->id
, i
->get_title(), !i
->params
.empty(), i
->is_toggle());
464 for(auto i
: item_by_action
)
465 i
.second
->Enable(inst
.rom
->action_flags(i
.first
) & 1);
466 for(auto i
: toggles
)
467 item_by_action
[i
]->Check(inst
.rom
->action_flags(i
) & 2);
470 std::string
munge_name(const std::string
& orig
)
474 if((r
= regex("(.*)\\(([0-9]+)\\)", newname
))) {
477 sequence
= parse_value
<uint64_t>(r
[2]);
478 newname
= (stringfmt() << r
[1] << "(" << sequence
+ 1 << ")").str();
480 newname
= newname
+ "(2)";
483 newname
= newname
+ "(2)";
488 void handle_watch_load(emulator_instance
& inst
, std::map
<std::string
, std::string
>& new_watches
,
489 std::set
<std::string
>& old_watches
)
491 auto proj
= inst
.project
->get();
493 for(auto i
: new_watches
) {
494 std::string name
= i
.first
;
496 if(!old_watches
.count(name
)) {
498 if(name
!= "" && i
.second
!= "")
499 inst
.mwatch
->set(name
, i
.second
);
500 } catch(std::exception
& e
) {
501 messages
<< "Can't set memory watch '" << name
<< "': "
502 << e
.what() << std::endl
;
505 } else if(inst
.mwatch
->get_string(name
) == i
.second
)
508 name
= munge_name(name
);
512 for(auto i
: new_watches
)
514 if(i
.first
!= "" && i
.second
!= "")
515 inst
.mwatch
->set(i
.first
, i
.second
);
516 } catch(std::exception
& e
) {
517 messages
<< "Can't set memory watch '" << i
.first
<< "': "
518 << e
.what() << std::endl
;
520 for(auto i
: old_watches
)
521 if(!new_watches
.count(i
))
523 inst
.mwatch
->clear(i
);
524 } catch(std::exception
& e
) {
525 messages
<< "Can't clear memory watch '" << i
<< "': "
526 << e
.what() << std::endl
;
531 std::string
get_default_screenshot_name(emulator_instance
& inst
)
533 auto p
= inst
.project
->get();
537 auto files
= directory::enumerate(p
->directory
, ".*-[0-9]+\\.png");
538 std::set
<std::string
> numbers
;
539 for(auto i
: files
) {
542 split
= i
.find_last_of("\\/");
544 split
= i
.find_last_of("/");
546 std::string name
= i
;
547 if(split
< name
.length())
548 name
= name
.substr(split
+ 1);
549 regex_results r
= regex("(.*)-([0-9]+)\\.png", name
);
550 if(r
[1] != p
->prefix
)
552 numbers
.insert(r
[2]);
554 for(uint64_t i
= 1;; i
++) {
555 std::string candidate
= (stringfmt() << i
).str();
556 if(!numbers
.count(candidate
))
557 return p
->prefix
+ "-" + candidate
+ ".png";
562 std::string
project_prefixname(emulator_instance
& inst
, const std::string ext
)
564 auto p
= inst
.project
->get();
568 return p
->prefix
+ "." + ext
;
572 void recent_rom_selected(emulator_instance
& inst
, const recentfiles::multirom
& file
)
575 req
.packfile
= file
.packfile
;
576 req
.singlefile
= file
.singlefile
;
577 req
.core
= file
.core
;
578 req
.system
= file
.system
;
579 req
.region
= file
.region
;
580 for(unsigned i
= 0; i
< file
.files
.size() && i
< ROM_SLOT_COUNT
; i
++)
581 req
.files
[i
] = file
.files
[i
];
582 inst
.iqueue
->run_async([req
]() {
583 CORE().command
->invoke("unpause-emulator");
585 }, [](std::exception
& e
) {});
588 void recent_movie_selected(emulator_instance
& inst
, const recentfiles::path
& file
)
590 inst
.iqueue
->queue(CLOADSAVE::ldsm
.name
, file
.get_path());
593 void recent_script_selected(emulator_instance
& inst
, const recentfiles::path
& file
)
595 inst
.iqueue
->queue(CLUA::run
.name
, file
.get_path());
598 wxString
getname(emulator_instance
& inst
)
600 std::string windowname
= "lsnes rr" + lsnes_version
+ " [";
601 auto p
= inst
.project
->get();
603 windowname
= windowname
+ p
->name
;
605 windowname
= windowname
+ inst
.rom
->get_core_identifier();
606 windowname
= windowname
+ "]";
607 return towxstring(windowname
);
612 emulator_instance
* inst
;
613 struct loaded_rom rom
;
614 struct moviefile
* initial
;
615 bool load_has_to_succeed
;
618 void* emulator_main(void* _args
)
620 struct emu_args
* args
= reinterpret_cast<struct emu_args
*>(_args
);
621 auto& inst
= *args
->inst
;
623 *inst
.rom
= args
->rom
;
624 messages
<< "Using core: " << inst
.rom
->get_core_identifier() << std::endl
;
625 struct moviefile
* movie
= args
->initial
;
626 bool has_to_succeed
= args
->load_has_to_succeed
;
627 platform::flush_command_queue();
628 main_loop(*inst
.rom
, *movie
, has_to_succeed
);
629 signal_program_exit();
630 } catch(std::bad_alloc
& e
) {
632 } catch(std::exception
& e
) {
633 messages
<< "FATAL: " << e
.what() << std::endl
;
634 platform::fatal_error();
640 void join_emulator_thread()
642 emulation_thread
->join();
645 bool is_readonly_mode(emulator_instance
& inst
)
648 inst
.iqueue
->run([&ret
]() {
649 ret
= *CORE().mlogic
? CORE().mlogic
->get_movie().readonly_mode() : false;
654 void set_speed(emulator_instance
& inst
, double target
)
657 inst
.framerate
->set_speed_multiplier(std::numeric_limits
<double>::infinity());
659 inst
.framerate
->set_speed_multiplier(target
/ 100);
662 void update_preferences()
664 preferred_core
.clear();
665 for(auto i
: core_type::get_core_types()) {
666 std::string val
= i
->get_hname() + " / " + i
->get_core_identifier();
667 for(auto j
: i
->get_extensions()) {
668 std::string key
= "ext:" + j
;
669 if(core_selections
.count(key
) && core_selections
[key
] == val
)
670 preferred_core
[key
] = i
;
672 std::string key2
= "type:" + i
->get_iname();
673 if(core_selections
.count(key2
) && core_selections
[key2
] == val
)
674 preferred_core
[key2
] = i
;
678 bool is_lsnes_movie(const std::string
& filename
)
680 std::istream
* s
= NULL
;
683 s
= &zip::openrel(filename
, "");
686 if(*s
&& !strcmp(buf
, "lsmv\x1A"))
694 zip::reader
r(filename
);
695 std::istream
& s
= r
["systemid"];
700 return (s2
== "lsnes-rr1");
706 class loadfile
: public wxFileDropTarget
709 loadfile(wxwin_mainwindow
* win
, emulator_instance
& _inst
) : inst(_inst
), pwin(win
) {};
710 bool OnDropFiles(wxCoord x
, wxCoord y
, const wxArrayString
& filenames
)
714 if(filenames
.Count() == 2) {
715 std::string a
= tostdstring(filenames
[0]);
716 std::string b
= tostdstring(filenames
[1]);
717 bool amov
= is_lsnes_movie(a
);
718 bool bmov
= is_lsnes_movie(b
);
721 if(amov
) std::swap(a
, b
);
722 inst
.iqueue
->run_async([a
, b
]() {
723 CORE().command
->invoke("unpause-emulator");
727 CORE().command
->invoke(CLOADSAVE::ldsm
.name
, b
);
728 }, [](std::exception
& e
) {});
731 if(filenames
.Count() == 1) {
732 std::string a
= tostdstring(filenames
[0]);
733 bool amov
= is_lsnes_movie(a
);
735 inst
.iqueue
->queue(CLOADSAVE::ldsm
.name
, a
);
736 pwin
->recent_movies
->add(a
);
741 inst
.iqueue
->run_async([req
]() {
742 CORE().command
->invoke("unpause-emulator");
744 }, [](std::exception
& e
) {});
745 pwin
->recent_roms
->add(loadreq_to_multirom(req
));
751 emulator_instance
& inst
;
752 wxwin_mainwindow
* pwin
;
756 void boot_emulator(emulator_instance
& inst
, loaded_rom
& rom
, moviefile
& movie
, bool fscreen
)
759 update_preferences();
761 struct emu_args
* a
= new emu_args
;
764 a
->load_has_to_succeed
= false;
766 modal_pause_holder hld
;
767 emulation_thread
= new threads::thread(emulator_main
, a
);
768 main_window
= new wxwin_mainwindow(inst
, fscreen
);
770 } catch(std::bad_alloc
& e
) {
775 wxwin_mainwindow::panel::panel(wxWindow
* win
, emulator_instance
& _inst
)
776 : wxPanel(win
, wxID_ANY
, wxDefaultPosition
, wxDefaultSize
, wxWANTS_CHARS
), inst(_inst
)
779 this->Connect(wxEVT_PAINT
, wxPaintEventHandler(panel::on_paint
), NULL
, this);
780 this->Connect(wxEVT_ERASE_BACKGROUND
, wxEraseEventHandler(panel::on_erase
), NULL
, this);
781 this->Connect(wxEVT_KEY_DOWN
, wxKeyEventHandler(panel::on_keyboard_down
), NULL
, this);
782 this->Connect(wxEVT_KEY_UP
, wxKeyEventHandler(panel::on_keyboard_up
), NULL
, this);
783 this->Connect(wxEVT_LEFT_DOWN
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
784 this->Connect(wxEVT_LEFT_UP
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
785 this->Connect(wxEVT_MIDDLE_DOWN
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
786 this->Connect(wxEVT_MIDDLE_UP
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
787 this->Connect(wxEVT_RIGHT_DOWN
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
788 this->Connect(wxEVT_RIGHT_UP
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
789 this->Connect(wxEVT_MOTION
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
790 this->Connect(wxEVT_ENTER_WINDOW
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
791 this->Connect(wxEVT_LEAVE_WINDOW
, wxMouseEventHandler(panel::on_mouse
), NULL
, this);
792 SetMinSize(wxSize(512, 448));
795 void wxwin_mainwindow::menu_start(wxString name
)
798 while(!upper
.empty())
800 current_menu
= new wxMenu();
801 menubar
->Append(current_menu
, name
);
804 void wxwin_mainwindow::menu_special(wxString name
, wxMenu
* menu
)
807 while(!upper
.empty())
809 menubar
->Append(menu
, name
);
813 wxMenuItem
* wxwin_mainwindow::menu_special_sub(wxString name
, wxMenu
* menu
)
816 return current_menu
->AppendSubMenu(menu
, name
);
819 void wxwin_mainwindow::menu_entry(int id
, wxString name
)
822 current_menu
->Append(id
, name
);
823 Connect(id
, wxEVT_COMMAND_MENU_SELECTED
,
824 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click
), NULL
, this);
827 void wxwin_mainwindow::menu_entry_check(int id
, wxString name
)
830 checkitems
[id
] = current_menu
->AppendCheckItem(id
, name
);
831 Connect(id
, wxEVT_COMMAND_MENU_SELECTED
,
832 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click
), NULL
, this);
835 void wxwin_mainwindow::menu_start_sub(wxString name
)
838 wxMenu
* old
= current_menu
;
839 upper
.push(current_menu
);
840 current_menu
= new wxMenu();
841 old
->AppendSubMenu(current_menu
, name
);
844 void wxwin_mainwindow::menu_end_sub()
846 current_menu
= upper
.top();
850 bool wxwin_mainwindow::menu_ischecked(int id
)
853 if(checkitems
.count(id
))
854 return checkitems
[id
]->IsChecked();
859 void wxwin_mainwindow::menu_check(int id
, bool newstate
)
862 if(checkitems
.count(id
))
863 return checkitems
[id
]->Check(newstate
);
868 void wxwin_mainwindow::menu_enable(int id
, bool newstate
)
871 auto item
= menubar
->FindItem(id
);
874 item
->Enable(newstate
);
877 void wxwin_mainwindow::menu_separator()
880 current_menu
->AppendSeparator();
883 void wxwin_mainwindow::panel::request_paint()
889 std::pair
<double, double> calc_scale_factors(double factor
, bool ar
, double par
)
892 return std::make_pair(factor
, factor
);
894 //Too wide, make taller.
895 return std::make_pair(factor
, factor
/ par
);
897 //Too narrow, make wider.
898 return std::make_pair(factor
* par
, factor
);
902 void wxwin_mainwindow::panel::on_paint(wxPaintEvent
& e
)
905 if(wx_escape_count
>= 3 && is_fs
) {
906 //Leave fullscreen mode.
907 main_window
->enter_or_leave_fullscreen(false);
909 inst
.fbuf
->render_framebuffer();
916 bool aux
= hflip_enabled
|| vflip_enabled
|| rotate_enabled
;
917 auto sfactors
= calc_scale_factors(video_scale_factor
, arcorrect_enabled
, inst
.rom
->get_PAR());
919 tw
= inst
.fbuf
->main_screen
.get_height() * sfactors
.second
+ 0.5;
920 th
= inst
.fbuf
->main_screen
.get_width() * sfactors
.first
+ 0.5;
922 tw
= inst
.fbuf
->main_screen
.get_width() * sfactors
.first
+ 0.5;
923 th
= inst
.fbuf
->main_screen
.get_height() * sfactors
.second
+ 0.5;
926 main_window_dirty
= false;
929 //Scale this to fullscreen.
930 unsigned dx
= 0, dy
= 0;
932 wxSize screen
= main_window
->GetSize();
934 double fss
= min(1.0 * screen
.GetWidth() / tw
, 1.0 * screen
.GetHeight() / th
);
937 if((signed)tw
< screen
.GetWidth())
938 dx
= (screen
.GetWidth() - tw
) / 2;
939 if((signed)th
< screen
.GetHeight())
940 dy
= (screen
.GetHeight() - th
) / 2;
941 if(/*becoming_fullscreen && */current_resolution
!= screen
) {
942 //Force panel to fullscreen.
945 current_resolution
= screen
;
946 //becoming_fullscreen = false;
949 signed dx2
= dx
+ tw
;
950 signed dy2
= dy
+ th
;
951 dc
.SetBrush(*wxBLACK_BRUSH
);
952 dc
.SetPen(*wxBLACK_PEN
);
953 //Erase the borders we don't draw.
954 if(dx
> 0) dc
.DrawRectangle(0, 0, dx
, screen
.GetHeight());
955 if(dy
> 0) dc
.DrawRectangle(0, 0, screen
.GetWidth(), dy
);
956 if(dx2
< screen
.GetWidth()) dc
.DrawRectangle(dx2
, 0, screen
.GetWidth() - dx2
, screen
.GetHeight());
957 if(dy2
< screen
.GetHeight()) dc
.DrawRectangle(0, dy2
, screen
.GetWidth(), screen
.GetHeight() - dy2
);
960 if(!screen_buffer
|| tw
!= old_width
|| th
!= old_height
|| scaling_flags
!= old_flags
||
961 hflip_enabled
!= old_hflip
|| vflip_enabled
!= old_vflip
|| rotate_enabled
!= old_rotate
) {
963 delete[] screen_buffer
;
964 screen_buffer
= NULL
;
967 delete[] rotate_buffer
;
968 rotate_buffer
= NULL
;
972 old_flags
= scaling_flags
;
973 old_hflip
= hflip_enabled
;
974 old_vflip
= vflip_enabled
;
975 old_rotate
= rotate_enabled
;
976 uint32_t w
= inst
.fbuf
->main_screen
.get_width();
977 uint32_t h
= inst
.fbuf
->main_screen
.get_height();
979 sws_ctx
= sws_getCachedContext(sws_ctx
, rotate_enabled
? h
: w
, rotate_enabled
? w
: h
,
980 AV_PIX_FMT_RGBA
, tw
, th
, AV_PIX_FMT_BGR24
, scaling_flags
, NULL
, NULL
, NULL
);
981 tw
= max(tw
, static_cast<uint32_t>(128));
982 th
= max(th
, static_cast<uint32_t>(112));
983 screen_buffer
= new unsigned char[tw
* th
* 3 + 64];
985 rotate_buffer
= new uint32_t[inst
.fbuf
->main_screen
.get_width() *
986 inst
.fbuf
->main_screen
.get_height()];
988 //This is not preformed in fullscreen mode.
989 SetMinSize(wxSize(tw
, th
));
990 signal_resize_needed();
994 //Hflip, Vflip or rotate active.
995 size_t width
= inst
.fbuf
->main_screen
.get_width();
996 size_t height
= inst
.fbuf
->main_screen
.get_height();
997 size_t width1
= width
- 1;
998 size_t height1
= height
- 1;
999 size_t stride
= inst
.fbuf
->main_screen
.rowptr(1) - inst
.fbuf
->main_screen
.rowptr(0);
1000 uint32_t* pixels
= inst
.fbuf
->main_screen
.rowptr(0);
1001 if(rotate_enabled
) {
1002 for(unsigned y
= 0; y
< height
; y
++) {
1003 uint32_t* pixels2
= pixels
+ (vflip_enabled
? (height1
- y
) : y
) * stride
;
1004 uint32_t* dpixels
= rotate_buffer
+ (height1
- y
);
1006 for(unsigned x
= 0; x
< width
; x
++)
1007 dpixels
[x
* height
] = pixels2
[width1
- x
];
1009 for(unsigned x
= 0; x
< width
; x
++)
1010 dpixels
[x
* height
] = pixels2
[x
];
1013 for(unsigned y
= 0; y
< height
; y
++) {
1014 uint32_t* pixels2
= pixels
+ (vflip_enabled
? (height1
- y
) : y
) * stride
;
1015 uint32_t* dpixels
= rotate_buffer
+ y
* width
;
1017 for(unsigned x
= 0; x
< width
; x
++)
1018 dpixels
[x
] = pixels2
[width1
- x
];
1020 for(unsigned x
= 0; x
< width
; x
++)
1021 dpixels
[x
] = pixels2
[x
];
1026 srcs
[0] = 4 * (rotate_enabled
? inst
.fbuf
->main_screen
.get_height() :
1027 inst
.fbuf
->main_screen
.get_width());
1029 srcs
[0] = 4 * inst
.fbuf
->main_screen
.get_stride();
1031 srcp
[0] = reinterpret_cast<unsigned char*>(aux
? rotate_buffer
: inst
.fbuf
->main_screen
.rowptr(0));
1032 dstp
[0] = screen_buffer
;
1033 memset(screen_buffer
, 0, tw
* th
* 3);
1034 if(inst
.fbuf
->main_screen
.get_width() && inst
.fbuf
->main_screen
.get_height())
1035 sws_scale(sws_ctx
, srcp
, srcs
, 0, rotate_enabled
? inst
.fbuf
->main_screen
.get_width() :
1036 inst
.fbuf
->main_screen
.get_height(),
1038 wxBitmap
bmp(wxImage(tw
, th
, screen_buffer
, true));
1039 dc
.DrawBitmap(bmp
, dx
, dy
, false);
1040 main_window_dirty
= false;
1041 main_window
->update_statusbar();
1044 void wxwin_mainwindow::panel::on_erase(wxEraseEvent
& e
)
1049 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent
& e
)
1052 handle_wx_keyboard(inst
, e
, true);
1055 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent
& e
)
1058 handle_wx_keyboard(inst
, e
, false);
1059 if(wx_escape_count
>= 3 && is_fs
) {
1060 //Force leave fullscreen mode.
1061 main_window
->enter_or_leave_fullscreen(false);
1065 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent
& e
)
1068 handle_wx_mouse(inst
, e
);
1071 wxwin_mainwindow::wxwin_mainwindow(emulator_instance
& _inst
, bool fscreen
)
1072 : wxFrame(NULL
, wxID_ANY
, getname(_inst
), wxDefaultPosition
, wxSize(-1, -1),
1073 wxMINIMIZE_BOX
| wxSYSTEM_MENU
| wxCAPTION
| wxCLIP_CHILDREN
| wxCLOSE_BOX
), inst(_inst
)
1076 download_in_progress
= NULL
;
1079 toplevel
= new wxFlexGridSizer(1, 2, 0, 0);
1080 toplevel
->Add(gpanel
= new panel(this, inst
), 1, wxGROW
);
1081 toplevel
->Add(spanel
= new wxwin_status::panel(this, inst
, gpanel
, 20), 1, wxGROW
);
1082 spanel_shown
= true;
1083 toplevel
->SetSizeHints(this);
1087 Connect(wxEVT_CLOSE_WINDOW
, wxCloseEventHandler(wxwin_mainwindow::on_close
));
1088 SetMenuBar(menubar
= new wxMenuBar
);
1089 SetStatusBar(statusbar
= new wxStatusBar(this));
1091 menu_start(wxT("File"));
1092 menu_start_sub(wxT("New"));
1093 menu_entry(wxID_NEW_MOVIE
, wxT("Movie..."));
1094 menu_entry(wxID_NEW_PROJECT
, wxT("Project..."));
1096 menu_start_sub(wxT("Load"));
1097 menu_entry(wxID_LOAD_STATE
, wxT("State..."));
1098 menu_entry(wxID_LOAD_MOVIE
, wxT("Movie..."));
1099 menu_entry(wxID_DOWNLOAD
, wxT("Download movie..."));
1100 if(loadlib::library::name() != "") {
1102 menu_entry(wxID_LOAD_LIBRARY
, towxstring(std::string("Load ") + loadlib::library::name()));
1103 menu_entry(wxID_PLUGIN_MANAGER
, towxstring("Plugin manager"));
1106 menu_entry(wxID_RELOAD_ROM_IMAGE
, wxT("Reload ROM"));
1107 menu_entry(wxID_LOAD_ROM_IMAGE_FIRST
, wxT("ROM..."));
1108 menu_special_sub(wxT("Multifile ROM"), loadroms
= new loadrom_menu(this, wxID_LOAD_ROM_IMAGE_FIRST
+ 1,
1109 wxID_LOAD_ROM_IMAGE_LAST
, [this](core_type
* t
) { this->do_load_rom_image(t
); }));
1110 menu_special_sub(wxT("Project"), projects
= new projects_menu(this, inst
, wxID_PROJECT_FIRST
,
1111 wxID_PROJECT_LAST
, get_config_path() + "/recent-projects.txt", [this](const std::string
& id
) {
1112 this->project_selected(id
); }));
1114 menu_special_sub(wxT("Recent ROMs"), recent_roms
= new recent_menu
<recentfiles::multirom
>(this, inst
,
1115 wxID_RROM_FIRST
, wxID_RROM_LAST
, get_config_path() + "/recent-roms.txt", recent_rom_selected
));
1116 menu_special_sub(wxT("Recent Movies"), recent_movies
= new recent_menu
<recentfiles::path
>(this, inst
,
1117 wxID_RMOVIE_FIRST
, wxID_RMOVIE_LAST
, get_config_path() + "/recent-movies.txt",
1118 recent_movie_selected
));
1119 menu_special_sub(wxT("Recent Lua scripts"), recent_scripts
= new recent_menu
<recentfiles::path
>(this, inst
,
1120 wxID_RLUA_FIRST
, wxID_RLUA_LAST
, get_config_path() + "/recent-scripts.txt",
1121 recent_script_selected
));
1123 menu_entry(wxID_CONFLICTRESOLUTION
, wxT("Conflict resolution"));
1125 branches_menu
* brlist
;
1126 auto brlist_item
= menu_special_sub(wxT("Branches"), brlist
= new branches_menu(this, inst
,
1127 wxID_BRANCH_FIRST
, wxID_BRANCH_LAST
));
1128 brlist
->set_disabler([brlist_item
](bool enabled
) { brlist_item
->Enable(enabled
); });
1131 menu_start_sub(wxT("Save"));
1132 menu_entry(wxID_SAVE_STATE
, wxT("State..."));
1133 menu_entry(wxID_SAVE_MOVIE
, wxT("Movie..."));
1134 menu_entry(wxID_SAVE_SCREENSHOT
, wxT("Screenshot..."));
1135 menu_entry(wxID_SAVE_SUBTITLES
, wxT("Subtitles..."));
1136 menu_entry(wxID_CANCEL_SAVES
, wxT("Cancel pending saves"));
1138 menu_entry(wxID_CHDIR
, wxT("Change working directory..."));
1140 menu_special_sub(wxT("Upload"), new upload_menu(this, inst
, wxID_UPLOAD_FIRST
, wxID_UPLOAD_LAST
));
1142 menu_start_sub(wxT("Close"));
1143 menu_entry(wxID_CLOSE_PROJECT
, wxT("Project"));
1144 menu_entry(wxID_CLOSE_ROM
, wxT("ROM"));
1145 menu_enable(wxID_CLOSE_PROJECT
, inst
.project
->get() != NULL
);
1146 menu_enable(wxID_CLOSE_ROM
, inst
.project
->get() == NULL
);
1149 menu_entry(wxID_EXIT
, wxT("Quit"));
1151 menu_special(wxT("System"), reinterpret_cast<wxMenu
*>(sysmenu
= new system_menu(this, inst
)));
1153 menu_start(wxT("Movie"));
1154 menu_entry_check(wxID_READONLY_MODE
, wxT("Readonly mode"));
1155 menu_check(wxID_READONLY_MODE
, is_readonly_mode(inst
));
1156 menu_entry(wxID_EDIT_AUTHORS
, wxT("Edit game name && authors..."));
1157 menu_entry(wxID_EDIT_SUBTITLES
, wxT("Edit subtitles..."));
1158 menu_entry(wxID_EDIT_VSUBTITLES
, wxT("Edit commantary track..."));
1160 menu_entry(wxID_REWIND_MOVIE
, wxT("Rewind to start"));
1162 menu_start(wxT("Speed"));
1163 menu_entry(wxID_SPEED_5
, wxT("1/20x"));
1164 menu_entry(wxID_SPEED_10
, wxT("1/10x"));
1165 menu_entry(wxID_SPEED_17
, wxT("1/6x"));
1166 menu_entry(wxID_SPEED_20
, wxT("1/5x"));
1167 menu_entry(wxID_SPEED_25
, wxT("1/4x"));
1168 menu_entry(wxID_SPEED_33
, wxT("1/3x"));
1169 menu_entry(wxID_SPEED_50
, wxT("1/2x"));
1170 menu_entry(wxID_SPEED_100
, wxT("1x"));
1171 menu_entry(wxID_SPEED_150
, wxT("1.5x"));
1172 menu_entry(wxID_SPEED_200
, wxT("2x"));
1173 menu_entry(wxID_SPEED_300
, wxT("3x"));
1174 menu_entry(wxID_SPEED_500
, wxT("5x"));
1175 menu_entry(wxID_SPEED_1000
, wxT("10x"));
1176 menu_entry(wxID_SPEED_TURBO
, wxT("Turbo"));
1177 menu_entry(wxID_SET_SPEED
, wxT("Set..."));
1179 menu_start(wxT("Tools"));
1180 menu_entry(wxID_RUN_SCRIPT
, wxT("Run batch file..."));
1182 menu_entry(wxID_EVAL_LUA
, wxT("Evaluate Lua statement..."));
1183 menu_entry(wxID_RUN_LUA
, wxT("Run Lua script..."));
1185 menu_entry(wxID_RESET_LUA
, wxT("Reset Lua VM"));
1187 menu_entry(wxID_AUTOHOLD
, wxT("Autohold/Autofire..."));
1188 menu_entry(wxID_TASINPUT
, wxT("TAS input plugin..."));
1189 menu_entry(wxID_MULTITRACK
, wxT("Multitrack..."));
1190 menu_entry(wxID_EDIT_MACROS
, wxT("Edit macros..."));
1192 menu_entry(wxID_EDIT_MEMORYWATCH
, wxT("Edit memory watch..."));
1194 menu_entry(wxID_LOAD_MEMORYWATCH
, wxT("Load memory watch..."));
1195 menu_entry(wxID_SAVE_MEMORYWATCH
, wxT("Save memory watch..."));
1197 menu_entry(wxID_MEMORY_SEARCH
, wxT("Memory Search..."));
1198 menu_entry(wxID_HEXEDITOR
, wxT("Memory editor..."));
1199 tracelog_menu
* trlog
;
1200 auto trlog_item
= menu_special_sub(wxT("Trace log"), trlog
= new tracelog_menu(this, inst
,
1201 wxID_TRACELOG_FIRST
, wxID_TRACELOG_LAST
));
1202 trlog
->set_disabler([trlog_item
](bool enabled
) { trlog_item
->Enable(enabled
); });
1204 menu_entry(wxID_DISASSEMBLER
, wxT("Disassembler..."));
1206 menu_entry(wxID_MOVIE_EDIT
, wxT("Edit movie..."));
1208 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu
*>(dmenu
= new dumper_menu(this,
1209 inst
, wxID_DUMP_FIRST
, wxID_DUMP_LAST
)));
1211 menu_start_sub(wxT("Movie dump"));
1212 menu_entry(wxID_START_R16M
, wxT("Start r16m dump..."));
1213 menu_entry(wxID_END_R16M
, wxT("End r16m dump"));
1216 menu_start(wxT("Configure"));
1217 menu_entry_check(wxID_SHOW_STATUS
, wxT("Show status panel"));
1218 menu_check(wxID_SHOW_STATUS
, true);
1219 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH
, wxT("Dedicated memory watch"));
1220 menu_entry(wxID_SHOW_MESSAGES
, wxT("Show messages"));
1221 menu_special_sub(wxT("Settings"), new settings_menu(this, inst
, wxID_SETTINGS_FIRST
));
1222 if(audioapi_driver_initialized()) {
1224 menu_entry_check(wxID_AUDIO_ENABLED
, wxT("Sounds enabled"));
1225 menu_entry(wxID_VUDISPLAY
, wxT("VU display / sound controls"));
1226 menu_check(wxID_AUDIO_ENABLED
, platform::is_sound_enabled());
1229 menu_entry(wxID_ENTER_FULLSCREEN
, wxT("Enter fullscreen mode"));
1231 menu_start(wxT("Help"));
1232 menu_entry(wxID_ABOUT
, wxT("About..."));
1234 corechange
.set(inst
.dispatch
->core_change
, []() { signal_core_change(); });
1235 titlechange
.set(inst
.dispatch
->title_change
, []() { signal_core_change(); });
1236 newcore
.set(notify_new_core
, []() { update_preferences(); });
1237 unmuted
.set(inst
.dispatch
->sound_unmute
, [this](bool unmute
) {
1238 runuifun([this, unmute
]() { this->menu_check(wxID_AUDIO_ENABLED
, unmute
); });
1240 modechange
.set(inst
.dispatch
->mode_change
, [this](bool readonly
) {
1241 runuifun([this, readonly
]() { this->menu_check(wxID_READONLY_MODE
, readonly
); });
1243 gpanel
->SetDropTarget(new loadfile(this, inst
));
1244 spanel
->SetDropTarget(new loadfile(this, inst
));
1245 set_hasher_callback(hash_callback
);
1246 reinterpret_cast<system_menu
*>(sysmenu
)->update(false);
1247 menubar
->SetMenuLabel(1, towxstring(inst
.rom
->get_systemmenu_name()));
1248 focus_timer
= new _focus_timer
;
1249 status_timer
= new _status_timer
;
1251 wx_escape_count
= 0;
1252 enter_or_leave_fullscreen(true);
1256 wxwin_mainwindow::~wxwin_mainwindow()
1259 if(sws_ctx
) sws_freeContext(sws_ctx
);
1260 if(screen_buffer
) delete[] screen_buffer
;
1261 if(rotate_buffer
) delete[] rotate_buffer
;
1262 focus_timer
->Stop();
1264 status_timer
->Stop();
1265 delete status_timer
;
1268 void wxwin_mainwindow::request_paint()
1274 void wxwin_mainwindow::on_close(wxCloseEvent
& e
)
1277 //Veto it for now, latter things will delete it.
1279 inst
.iqueue
->queue("quit-emulator");
1282 void wxwin_mainwindow::notify_update() throw()
1285 if(!main_window_dirty
) {
1286 main_window_dirty
= true;
1291 void wxwin_mainwindow::notify_resized() throw()
1295 toplevel
->SetSizeHints(this);
1299 void wxwin_mainwindow::notify_update_status() throw()
1302 spanel
->request_paint();
1304 mwindow
->notify_update();
1305 status_updated
= true;
1308 void wxwin_mainwindow::notify_exit() throw()
1311 wxwidgets_exiting
= true;
1312 join_emulator_thread();
1316 std::string
read_variable_map(const std::map
<std::string
, std::u32string
>& vars
, const std::string
& key
)
1318 if(!vars
.count(key
))
1320 return utf8::to8(vars
.find(key
)->second
);
1323 void wxwin_mainwindow::update_statusbar()
1326 if(download_in_progress
) {
1327 statusbar
->SetStatusText(towxstring(download_in_progress
->statusmsg()));
1330 if(hashing_in_progress
) {
1331 //TODO: Display this as a dialog.
1332 std::ostringstream s
;
1333 s
<< "Hashing ROMs, approximately " << ((hashing_left
+ 524288) >> 20) << " of "
1334 << ((hashing_total
+ 524288) >> 20) << "MB left...";
1335 statusbar
->SetStatusText(towxstring(s
.str()));
1338 auto& vars
= inst
.status
->get_read();
1340 inst
.status
->put_read();
1344 std::ostringstream s
;
1345 bool recording
= (vars
.mode
== 'R');
1346 if(vars
.movie_valid
) {
1348 s
<< "Frame: " << vars
.curframe
;
1350 s
<< "Frame: " << vars
.curframe
<< "/" << vars
.length
;
1351 s
<< " Lag: " << vars
.lag
;
1352 if(vars
.subframe
== _lsnes_status::subframe_savepoint
)
1353 s
<< " Subframe: S";
1354 else if(vars
.subframe
== _lsnes_status::subframe_video
)
1355 s
<< " Subframe: V";
1357 s
<< " Subframe: " << vars
.subframe
;
1359 s
<< "Frame: N/A Lag: N/A Subframe: N/A";
1361 if(vars
.saveslot_valid
) {
1363 if(vars
.branch_valid
) s
<< utf8::to8(vars
.branch
) << "ā";
1365 s
<< " [" << utf8::to8(vars
.slotinfo
) << "]";
1367 s
<< " Speed: " << vars
.speed
<< "% ";
1368 if(vars
.pause
== _lsnes_status::pause_break
)
1370 else if(vars
.pause
== _lsnes_status::pause_normal
)
1374 if(vars
.mode
== 'C')
1376 else if(vars
.mode
== 'R')
1378 else if(vars
.mode
== 'P')
1380 else if(vars
.mode
== 'F')
1384 if(vars
.mbranch_valid
)
1385 s
<< " Branch: " << utf8::to8(vars
.mbranch
);
1386 std::string macros
= utf8::to8(vars
.macros
);
1388 s
<< " Macros: " << macros
;
1390 statusbar
->SetStatusText(towxstring(s
.str()));
1391 } catch(std::exception
& e
) {
1393 inst
.status
->put_read();
1396 #define NEW_KEYBINDING "A new binding..."
1397 #define NEW_ALIAS "A new alias..."
1398 #define NEW_WATCH "A new watch..."
1400 void wxwin_mainwindow::handle_menu_click(wxCommandEvent
& e
)
1404 handle_menu_click_cancelable(e
);
1405 } catch(canceled_exception
& e
) {
1407 } catch(std::bad_alloc
& e
) {
1409 } catch(std::exception
& e
) {
1410 show_message_ok(this, "Error in menu handler", e
.what(), wxICON_EXCLAMATION
);
1414 void wxwin_mainwindow::refresh_title() throw()
1417 SetTitle(getname(inst
));
1418 auto p
= inst
.project
->get();
1419 menu_enable(wxID_RELOAD_ROM_IMAGE
, !p
);
1420 for(int i
= wxID_LOAD_ROM_IMAGE_FIRST
; i
<= wxID_LOAD_ROM_IMAGE_LAST
; i
++)
1422 menu_enable(wxID_CLOSE_PROJECT
, p
!= NULL
);
1423 menu_enable(wxID_CLOSE_ROM
, p
== NULL
);
1424 reinterpret_cast<system_menu
*>(sysmenu
)->update(false);
1425 menubar
->SetMenuLabel(1, towxstring(inst
.rom
->get_systemmenu_name()));
1430 struct movie_or_savestate
1433 typedef std::pair
<std::string
,std::string
> returntype
;
1434 movie_or_savestate(emulator_instance
& _inst
, bool is_state
)
1439 filedialog_input_params
input(bool save
) const
1441 filedialog_input_params p
;
1442 std::string ext
= state
? inst
.project
->savestate_ext() : "lsmv";
1443 std::string name
= state
? "Savestates" : "Movies";
1445 p
.types
.push_back(filedialog_type_entry(name
, "*." + ext
, ext
));
1446 p
.types
.push_back(filedialog_type_entry(name
+ " (binary)", "*." + ext
, ext
));
1448 p
.types
.push_back(filedialog_type_entry(name
, "*." + ext
+ ";*." + ext
+ ".backup",
1450 if(!save
&& state
) {
1451 p
.types
.push_back(filedialog_type_entry("Savestates [playback]", "*." + ext
+
1452 ";*." + ext
+ ".backup", ext
));
1453 p
.types
.push_back(filedialog_type_entry("Savestates [recording]", "*." + ext
+
1454 ";*." + ext
+ ".backup", ext
));
1455 p
.types
.push_back(filedialog_type_entry("Savestates [preserve]", "*." + ext
+
1456 ";*." + ext
+ ".backup", ext
));
1457 p
.types
.push_back(filedialog_type_entry("Savestates [all branches]", "*." + ext
+
1458 ";*." + ext
+ ".backup", ext
));
1460 p
.default_type
= save
? (state
? save_dflt_binary(*inst
.settings
) :
1461 movie_dflt_binary(*inst
.settings
)) : 0;
1464 std::pair
<std::string
, std::string
> output(const filedialog_output_params
& p
, bool save
) const
1468 cmdmod
= p
.typechoice
? "-binary" : "-zip";
1470 switch(p
.typechoice
) {
1471 case 0: cmdmod
= ""; break;
1472 case 1: cmdmod
= "-readonly"; break;
1473 case 2: cmdmod
= "-state"; break;
1474 case 3: cmdmod
= "-preserve"; break;
1475 case 4: cmdmod
= "-allbranches"; break;
1477 return std::make_pair(cmdmod
, p
.path
);
1480 emulator_instance
& inst
;
1483 struct movie_or_savestate
filetype_movie(lsnes_instance
, false);
1484 struct movie_or_savestate
filetype_savestate(lsnes_instance
, true);
1487 void wxwin_mainwindow::project_selected(const std::string
& id
)
1489 std::string filename
, displayname
;
1490 bool load_ok
= false;
1491 inst
.iqueue
->run([id
, &filename
, &displayname
, &load_ok
]() -> void {
1493 auto& p
= CORE().project
->load(id
); //Check.
1494 filename
= p
.filename
;
1495 displayname
= p
.name
;
1498 switch_projects(id
);
1499 } catch(std::exception
& e
) {
1500 messages
<< "Failed to change project: " << e
.what() << std::endl
;
1504 recentfiles::namedobj obj
;
1506 obj
._filename
= filename
;
1507 obj
._display
= displayname
;
1512 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent
& e
)
1515 std::string filename
;
1516 std::pair
<std::string
, std::string
> filename2
;
1519 case wxID_FRAMEADVANCE
:
1520 inst
.iqueue
->queue("+advance-frame");
1521 inst
.iqueue
->queue("-advance-frame");
1523 case wxID_SUBFRAMEADVANCE
:
1524 inst
.iqueue
->queue("+advance-poll");
1525 inst
.iqueue
->queue("-advance-poll");
1528 inst
.iqueue
->queue("advance-skiplag");
1531 inst
.iqueue
->queue("pause-emulator");
1534 inst
.iqueue
->queue("quit-emulator");
1536 case wxID_AUDIO_ENABLED
:
1537 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED
));
1539 case wxID_CANCEL_SAVES
:
1540 inst
.iqueue
->queue("cancel-saves");
1542 case wxID_LOAD_MOVIE
:
1543 filename
= choose_file_load(this, "Load Movie", UI_get_project_moviepath(inst
),
1544 filetype_movie
).second
;
1545 recent_movies
->add(filename
);
1546 inst
.iqueue
->queue(CLOADSAVE::ldm
.name
, filename
);
1548 case wxID_LOAD_STATE
:
1549 filename2
= choose_file_load(this, "Load State", UI_get_project_moviepath(inst
),
1550 filetype_savestate
);
1551 recent_movies
->add(filename2
.second
);
1552 inst
.iqueue
->queue("load" + filename2
.first
+ " " + filename2
.second
);
1554 case wxID_REWIND_MOVIE
:
1555 inst
.iqueue
->queue("rewind-movie");
1557 case wxID_SAVE_MOVIE
:
1558 filename2
= choose_file_save(this, "Save Movie", UI_get_project_moviepath(inst
), filetype_movie
,
1559 project_prefixname(inst
, "lsmv"));
1560 recent_movies
->add(filename2
.second
);
1561 inst
.iqueue
->queue("save-movie" + filename2
.first
+ " " + filename2
.second
);
1563 case wxID_SAVE_SUBTITLES
:
1564 inst
.iqueue
->queue(CSUBTITLE::save
.name
, choose_file_save(this, "Save subtitles",
1565 UI_get_project_moviepath(inst
), filetype_sub
, project_prefixname(inst
, "sub")));
1567 case wxID_SAVE_STATE
:
1568 filename2
= choose_file_save(this, "Save State", UI_get_project_moviepath(inst
),
1569 filetype_savestate
);
1570 recent_movies
->add(filename2
.second
);
1571 inst
.iqueue
->queue("save-state" + filename2
.first
+ " " + filename2
.second
);
1573 case wxID_SAVE_SCREENSHOT
:
1574 inst
.iqueue
->queue(CFRAMEBUF::ss
.name
, choose_file_save(this, "Save Screenshot",
1575 UI_get_project_moviepath(inst
), filetype_png
, get_default_screenshot_name(inst
)));
1577 case wxID_RUN_SCRIPT
:
1578 inst
.iqueue
->queue(CLUA::run
.name
, pick_file_member(this, "Select Script",
1579 UI_get_project_otherpath(inst
)));
1581 case wxID_RUN_LUA
: {
1582 std::string f
= choose_file_load(this, "Select Lua Script", UI_get_project_otherpath(inst
),
1583 filetype_lua_script
);
1584 inst
.iqueue
->queue(CLUA::run
.name
, f
);
1585 recent_scripts
->add(f
);
1588 case wxID_RESET_LUA
:
1589 inst
.iqueue
->queue(CLUA::reset
.name
);
1592 inst
.iqueue
->queue(CLUA::eval
.name
, pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
1594 case wxID_READONLY_MODE
:
1595 s
= menu_ischecked(wxID_READONLY_MODE
);
1596 inst
.iqueue
->run([s
]() {
1597 auto& core
= CORE();
1599 core
.lua2
->callback_movie_lost("readwrite");
1600 if(*core
.mlogic
) core
.mlogic
->get_movie().readonly_mode(s
);
1601 core
.dispatch
->mode_change(s
);
1603 core
.lua2
->callback_do_readwrite();
1604 core
.supdater
->update();
1605 core
.dispatch
->status_update();
1609 wxeditor_autohold_display(this, inst
);
1611 case wxID_EDIT_AUTHORS
:
1612 wxeditor_authors_display(this, inst
);
1614 case wxID_EDIT_MACROS
:
1615 wxeditor_macro_display(this, inst
);
1617 case wxID_EDIT_SUBTITLES
:
1618 wxeditor_subtitles_display(this, inst
);
1620 case wxID_EDIT_VSUBTITLES
:
1621 show_wxeditor_voicesub(this, inst
);
1623 case wxID_EDIT_MEMORYWATCH
:
1624 wxeditor_memorywatches_display(this, inst
);
1626 case wxID_SAVE_MEMORYWATCH
: {
1627 modal_pause_holder hld
;
1628 std::set
<std::string
> old_watches
;
1629 inst
.iqueue
->run([&old_watches
]() { old_watches
= CORE().mwatch
->enumerate(); });
1630 std::string filename
= choose_file_save(this, "Save watches to file",
1631 UI_get_project_otherpath(inst
), filetype_watch
);
1632 std::ofstream
out(filename
.c_str());
1633 for(auto i
: old_watches
) {
1635 inst
.iqueue
->run([i
, &val
]() {
1637 val
= CORE().mwatch
->get_string(i
);
1638 } catch(std::exception
& e
) {
1639 messages
<< "Can't get value of watch '" << i
<< "': " << e
.what()
1643 out
<< i
<< std::endl
<< val
<< std::endl
;
1648 case wxID_LOAD_MEMORYWATCH
: {
1649 modal_pause_holder hld
;
1650 std::set
<std::string
> old_watches
;
1651 inst
.iqueue
->run([&old_watches
]() { old_watches
= CORE().mwatch
->enumerate(); });
1652 std::map
<std::string
, std::string
> new_watches
;
1653 std::string filename
= choose_file_load(this, "Choose memory watch file",
1654 UI_get_project_otherpath(inst
), filetype_watch
);
1656 std::istream
& in
= zip::openrel(filename
, "");
1660 std::getline(in
, wname
);
1661 std::getline(in
, wexpr
);
1662 new_watches
[strip_CR(wname
)] = strip_CR(wexpr
);
1665 } catch(std::exception
& e
) {
1666 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e
.what(),
1667 wxICON_EXCLAMATION
);
1671 inst
.iqueue
->run([this, &new_watches
, &old_watches
]() {
1672 handle_watch_load(this->inst
, new_watches
, old_watches
);
1676 case wxID_MEMORY_SEARCH
:
1677 wxwindow_memorysearch_display(inst
);
1680 wxeditor_tasinput_display(this, inst
);
1683 std::ostringstream str
;
1684 str
<< "Version: lsnes rr" << lsnes_version
<< std::endl
;
1685 str
<< "Revision: " << lsnes_git_revision
<< std::endl
;
1686 for(auto i
: core_core::all_cores())
1688 str
<< "Core: " << i
->get_core_identifier() << std::endl
;
1689 wxMessageBox(towxstring(str
.str()), _T("About"), wxICON_INFORMATION
| wxOK
, this);
1692 case wxID_SHOW_STATUS
: {
1693 bool newstate
= menu_ischecked(wxID_SHOW_STATUS
);
1696 if(newstate
&& !spanel_shown
)
1697 toplevel
->Add(spanel
, 1, wxGROW
);
1698 else if(!newstate
&& spanel_shown
)
1699 toplevel
->Detach(spanel
);
1702 spanel_shown
= newstate
;
1704 toplevel
->SetSizeHints(this);
1708 case wxID_DEDICATED_MEMORY_WATCH
: {
1709 bool newstate
= menu_ischecked(wxID_DEDICATED_MEMORY_WATCH
);
1710 if(newstate
&& !mwindow
) {
1711 mwindow
= new wxwin_status(-1, inst
, "Memory Watch");
1712 spanel
->set_watch_flag(1);
1714 } else if(!newstate
&& mwindow
) {
1717 spanel
->set_watch_flag(0);
1721 case wxID_SET_SPEED
: {
1722 std::string value
= "infinite";
1723 double val
= inst
.framerate
->get_speed_multiplier();
1724 if(!(val
== std::numeric_limits
<double>::infinity()))
1725 value
= (stringfmt() << (100 * val
)).str();
1726 value
= pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value
);
1728 if(value
== "infinite")
1729 inst
.framerate
->set_speed_multiplier(
1730 std::numeric_limits
<double>::infinity());
1732 double v
= parse_value
<double>(value
) / 100;
1735 inst
.framerate
->set_speed_multiplier(v
);
1738 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, this);
1746 set_speed(inst
, 10);
1749 set_speed(inst
, 16.66666666666);
1752 set_speed(inst
, 20);
1755 set_speed(inst
, 25);
1758 set_speed(inst
, 33.3333333333333);
1761 set_speed(inst
, 50);
1763 case wxID_SPEED_100
:
1764 set_speed(inst
, 100);
1766 case wxID_SPEED_150
:
1767 set_speed(inst
, 150);
1769 case wxID_SPEED_200
:
1770 set_speed(inst
, 200);
1772 case wxID_SPEED_300
:
1773 set_speed(inst
, 300);
1775 case wxID_SPEED_500
:
1776 set_speed(inst
, 500);
1778 case wxID_SPEED_1000
:
1779 set_speed(inst
, 1000);
1781 case wxID_SPEED_TURBO
:
1782 set_speed(inst
, -1);
1784 case wxID_LOAD_LIBRARY
: {
1785 std::string name
= std::string("load ") + loadlib::library::name();
1786 with_loaded_library(*new loadlib::module(loadlib::library(choose_file_load(this, name
,
1787 UI_get_project_otherpath(inst
), single_type(loadlib::library::extension(),
1788 loadlib::library::name())))));
1789 handle_post_loadlibrary();
1792 case wxID_PLUGIN_MANAGER
:
1793 wxeditor_plugin_manager_display(this);
1795 case wxID_RELOAD_ROM_IMAGE
:
1796 inst
.iqueue
->run([]() {
1797 CORE().command
->invoke("unpause-emulator");
1798 reload_current_rom();
1801 case wxID_NEW_MOVIE
:
1802 show_projectwindow(this, inst
);
1804 case wxID_SHOW_MESSAGES
:
1805 msg_window
->reshow();
1807 case wxID_CONFLICTRESOLUTION
:
1808 show_conflictwindow(this);
1810 case wxID_VUDISPLAY
:
1811 open_vumeter_window(this, inst
);
1813 case wxID_DISASSEMBLER
:
1814 wxeditor_disassembler_display(this, inst
);
1816 case wxID_MOVIE_EDIT
:
1817 wxeditor_movie_display(this, inst
);
1819 case wxID_NEW_PROJECT
:
1820 open_new_project_window(this, inst
);
1822 case wxID_CLOSE_PROJECT
:
1823 inst
.iqueue
->run([this]() -> void { this->inst
.project
->set(NULL
); });
1825 case wxID_CLOSE_ROM
:
1826 inst
.iqueue
->run([]() -> void { close_rom(); });
1828 case wxID_ENTER_FULLSCREEN
:
1829 wx_escape_count
= 0;
1830 enter_or_leave_fullscreen(true);
1832 case wxID_LOAD_ROM_IMAGE_FIRST
:
1833 do_load_rom_image(NULL
);
1835 case wxID_HEXEDITOR
:
1836 wxeditor_hexedit_display(this, inst
);
1838 case wxID_MULTITRACK
:
1839 wxeditor_multitrack_display(this, inst
);
1842 wxDirDialog
* d
= new wxDirDialog(this, wxT("Change working directory"), wxT("."),
1843 wxDD_DIR_MUST_EXIST
);
1844 if(d
->ShowModal() == wxID_CANCEL
) {
1848 std::string path
= tostdstring(d
->GetPath());
1850 chdir(path
.c_str());
1851 messages
<< "Changed working directory to '" << path
<< "'" << std::endl
;
1854 case wxID_DOWNLOAD
: {
1855 if(download_in_progress
) return;
1856 filename
= pick_text(this, "Download movie", "Enter URL to download");
1857 download_in_progress
= new file_download();
1858 download_in_progress
->url
= lsnes_uri_rewrite(filename
);
1859 download_in_progress
->target_slot
= "wxwidgets_download_tmp";
1860 download_in_progress
->do_async(*inst
.rom
);
1861 new download_timer(this, inst
);
1864 case wxID_START_R16M
: {
1865 std::string filename
= choose_file_save(this, "Save r16m dump to file",
1866 UI_get_project_otherpath(inst
), filetype_r16m
);
1867 inst
.iqueue
->run([filename
]() {
1868 CORE().command
->invoke("start-r16m "+filename
);
1872 case wxID_END_R16M
: {
1873 inst
.iqueue
->run([]() {
1874 CORE().command
->invoke("end-r16m");
1881 void wxwin_mainwindow::action_updated()
1883 runuifun([this]() { reinterpret_cast<system_menu
*>(sysmenu
)->update(true); });
1886 void wxwin_mainwindow::enter_or_leave_fullscreen(bool fs
)
1890 //Save current resolution, so we can see the change.
1891 current_resolution
= main_window
->GetSize();
1893 toplevel
->Detach(spanel
);
1896 ShowFullScreen(true);
1897 becoming_fullscreen
= true;
1898 request_paint(); //Finish the resizing by running paint handler.
1899 } else if(!fs
&& is_fs
) {
1900 becoming_fullscreen
= false;
1901 ShowFullScreen(false);
1905 toplevel
->Add(spanel
, 1, wxGROW
);
1910 wx_escape_count
= 0;
1911 request_paint(); //Don't leave graphical corruption.
1917 struct command::stub _exit_fullscreen
= {"exit-fullscreen", "Exit fullscreen",
1918 "Syntax: exit_fullscreen\nExit fullscreen"};
1919 command::fnptr
<> exit_fullscreen(lsnes_cmds
, _exit_fullscreen
,
1923 main_window
->enter_or_leave_fullscreen(false);