diff --git a/include/core/keymapper.hpp b/include/core/keymapper.hpp index f6436960..823de52b 100644 --- a/include/core/keymapper.hpp +++ b/include/core/keymapper.hpp @@ -396,4 +396,73 @@ public: std::runtime_error); }; +class inverse_key +{ +public: +/** + * Create inverse key. + * + * Parameter command: Command this is for. + * Parameter name: Name of inverse key. + */ + inverse_key(const std::string& command, const std::string& name) throw(std::bad_alloc); +/** + * Destructor. + */ + ~inverse_key(); +/** + * Get set of inverse keys. + * + * Returns: The set of all inverses. + */ + static std::set get_ikeys() throw(std::bad_alloc); +/** + * Find by command. + * + * Parameter command: The command. + * Returns: The instance. + */ + static inverse_key* get_for(const std::string& command) throw(std::bad_alloc); +/** + * Get keyspec. + * + * Parameter primary: If true, get the primary key, else secondary key. + * Returns: The keyspec. + */ + std::string get(bool primary) throw(std::bad_alloc); +/** + * Clear key (if primary is cleared, secondary becomes primary). + * + * Parameter primary: If true, clear the primary, else the secondary. + */ + void clear(bool primary) throw(std::bad_alloc); +/** + * Set key. + * + * Parameter keyspec: The new keyspec. + * Parameter primary: If true, set the primary, else the secondary. + */ + void set(std::string keyspec, bool primary) throw(std::bad_alloc); +/** + * Notify updated mapping. + */ + static void notify_update(const std::string& keyspec, const std::string& command); +/** + * Get name for command. + * + * Returns: The name. + */ + std::string getname() throw(std::bad_alloc); +private: + inverse_key(const inverse_key&); + inverse_key& operator=(const inverse_key&); + static std::set& ikeys(); + static std::map& forkey(); + void addkey(const std::string& keyspec); + std::string cmd; + std::string oname; + std::string primary_spec; + std::string secondary_spec; +}; + #endif diff --git a/include/plat-wxwidgets/platform.hpp b/include/plat-wxwidgets/platform.hpp index 2cc91dc9..b536e03c 100644 --- a/include/plat-wxwidgets/platform.hpp +++ b/include/plat-wxwidgets/platform.hpp @@ -30,6 +30,8 @@ void _runuifun_async(void (*fn)(void*), void* arg); void wxeditor_axes_display(wxWindow* parent); void wxeditor_authors_display(wxWindow* parent); void wxeditor_settings_display(wxWindow* parent); +void wxeditor_hotkeys_display(wxWindow* parent); +std::string wxeditor_keyselect(wxWindow* parent, bool clearable); //Auxillary windows. void wxwindow_memorysearch_display(); diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 1fabd0fd..efcdf590 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -191,7 +191,7 @@ namespace for(size_t i = 0; i < MAX_LOGICAL_BUTTONS; ++i) for(int j = 0; j < 3; ++j) for(unsigned k = 0; k < 8; ++k) { - std::string x, y; + std::string x, y, expx; char cstr[2] = {0, 0}; cstr[0] = 49 + k; switch(j) { @@ -207,21 +207,32 @@ namespace }; x = x + cstr + get_logical_button_name(i); y = cstr + get_logical_button_name(i); + expx = std::string("Controller X ") + get_logical_button_name(i); + expx[11] = 49 + k; our_commands.insert(new button_action(x, j, k, y)); + if(j == 0) + our_icommands.insert(new inverse_key(x, expx)); } for(unsigned k = 0; k < 8; ++k) { std::string x = "controllerXanalog"; + std::string expx = "Controller X analog function"; x[10] = 49 + k; + expx[11] = 49 + k; our_commands.insert(new analog_action(x, k)); + our_icommands.insert(new inverse_key(x, expx)); } } ~button_action_helper() { for(auto i : our_commands) delete i; + for(auto i : our_icommands) + delete i; our_commands.clear(); + our_icommands.clear(); } std::set our_commands; + std::set our_icommands; } bah; } diff --git a/src/core/keymapper.cpp b/src/core/keymapper.cpp index 45d1d02d..017afeaf 100644 --- a/src/core/keymapper.cpp +++ b/src/core/keymapper.cpp @@ -73,6 +73,13 @@ namespace []() throw(std::bad_alloc, std::runtime_error) { keymapper::dumpbindings(); }); + + function_ptr_command<> show_inverse("show-inverse", "Show inverse bindings", + "Syntax: show-inverse\nShow inversebindings that are currently active.\n", + []() throw(std::bad_alloc, std::runtime_error) { + for(auto i : inverse_key::get_ikeys()) + messages << i->getname() << ":" << i->get(true) << ":" << i->get(false) << std::endl; + }); } std::string fixup_command_polarity(std::string cmd, bool polarity) throw(std::bad_alloc) @@ -775,7 +782,11 @@ namespace return k; } - std::map keybindings; + std::map& keybindings() + { + static std::map x; + return x; + } } void keymapper::bind(std::string mod, std::string modmask, std::string keyname, std::string command) @@ -787,28 +798,30 @@ void keymapper::bind(std::string mod, std::string modmask, std::string keyname, if(!modifier_set::valid(_mod, _modmask)) throw std::runtime_error("Invalid modifiers"); auto g = keygroup::lookup(keyname); - if(!keybindings.count(k)) { - keybindings[k] = new keybind_data; - keybindings[k]->mod = _mod; - keybindings[k]->modmask = _modmask; - keybindings[k]->group = g.first; - keybindings[k]->subkey = g.second; + if(!keybindings().count(k)) { + keybindings()[k] = new keybind_data; + keybindings()[k]->mod = _mod; + keybindings()[k]->modmask = _modmask; + keybindings()[k]->group = g.first; + keybindings()[k]->subkey = g.second; } - keybindings[k]->command = command; + keybindings()[k]->command = command; + inverse_key::notify_update(mod + "/" + modmask + "|" + keyname, command); } void keymapper::unbind(std::string mod, std::string modmask, std::string keyname) throw(std::bad_alloc, std::runtime_error) { triple k(mod, modmask, keyname); - if(!keybindings.count(k)) + if(!keybindings().count(k)) throw std::runtime_error("Key is not bound"); - delete keybindings[k]; - keybindings.erase(k); + delete keybindings()[k]; + keybindings().erase(k); + inverse_key::notify_update(mod + "/" + modmask + "|" + keyname, ""); } void keymapper::dumpbindings() throw(std::bad_alloc) { - for(auto i : keybindings) { + for(auto i : keybindings()) { messages << "bind-key "; if(i.first.a != "" || i.first.b != "") messages << i.first.a << "/" << i.first.b << " "; @@ -819,7 +832,7 @@ void keymapper::dumpbindings() throw(std::bad_alloc) std::set keymapper::get_bindings() throw(std::bad_alloc) { std::set r; - for(auto i : keybindings) + for(auto i : keybindings()) r.insert(i.first.a + "/" + i.first.b + "|" + i.first.c); return r; } @@ -832,9 +845,9 @@ std::string keymapper::get_command_for(const std::string& keyspec) throw(std::ba } catch(std::exception& e) { return ""; } - if(!keybindings.count(k)) + if(!keybindings().count(k)) return ""; - return keybindings[k]->command; + return keybindings()[k]->command; } void keymapper::bind_for(const std::string& keyspec, const std::string& cmd) throw(std::bad_alloc, std::runtime_error) @@ -846,3 +859,123 @@ void keymapper::bind_for(const std::string& keyspec, const std::string& cmd) thr else unbind(k.a, k.b, k.c); } + + +inverse_key::inverse_key(const std::string& command, const std::string& name) throw(std::bad_alloc) +{ + cmd = command; + oname = name; + ikeys().insert(this); + forkey()[cmd] = this; + //Search the keybindings for matches. + auto b = keymapper::get_bindings(); + for(auto c : b) + if(keymapper::get_command_for(c) == cmd) + addkey(c); +} + +inverse_key::~inverse_key() +{ + ikeys().erase(this); + forkey().erase(cmd); +} + +std::set inverse_key::get_ikeys() throw(std::bad_alloc) +{ + return ikeys(); +} + +std::string inverse_key::getname() throw(std::bad_alloc) +{ + return oname; +} + +inverse_key* inverse_key::get_for(const std::string& command) throw(std::bad_alloc) +{ + return forkey().count(command) ? forkey()[command] : NULL; +} + +std::set& inverse_key::ikeys() +{ + static std::set x; + return x; +} + +std::map& inverse_key::forkey() +{ + static std::map x; + return x; +} + +std::string inverse_key::get(bool primary) throw(std::bad_alloc) +{ + return primary ? primary_spec : secondary_spec; +} + +void inverse_key::clear(bool primary) throw(std::bad_alloc) +{ + if(primary) { + keymapper::bind_for(primary_spec, ""); + primary_spec = secondary_spec; + secondary_spec = ""; + } else { + keymapper::bind_for(secondary_spec, ""); + secondary_spec = ""; + } + //Search the keybindings for matches. + auto b = keymapper::get_bindings(); + for(auto c : b) + if(keymapper::get_command_for(c) == cmd) + addkey(c); +} + +void inverse_key::set(std::string keyspec, bool primary) throw(std::bad_alloc) +{ + if(keyspec == "") { + clear(primary); + return; + } + if(!primary && (primary_spec == "" || primary_spec == keyspec)) + primary = true; + if(primary) { + if(primary_spec != "") + keymapper::bind_for(primary_spec, ""); + primary_spec = keyspec; + keymapper::bind_for(primary_spec, cmd); + } else { + if(secondary_spec != "") + keymapper::bind_for(secondary_spec, ""); + secondary_spec = keyspec; + keymapper::bind_for(secondary_spec, cmd); + } +} + +void inverse_key::addkey(const std::string& keyspec) +{ + if(primary_spec == "" || primary_spec == keyspec) + primary_spec = keyspec; + else if(secondary_spec == "") + secondary_spec = keyspec; +} + +void inverse_key::notify_update(const std::string& keyspec, const std::string& command) +{ + for(auto k : ikeys()) { + bool u = false; + if(k->primary_spec == keyspec || k->secondary_spec == keyspec) { + if(command == "" || command != k->cmd) { + //Unbound. + k->primary_spec = ""; + k->secondary_spec = ""; + u = true; + } + } else if(command == k->cmd) + k->addkey(keyspec); + if(u) { + auto b = keymapper::get_bindings(); + for(auto c : b) + if(keymapper::get_command_for(c) == k->cmd) + k->addkey(c); + } + } +} diff --git a/src/core/mainloop.cpp b/src/core/mainloop.cpp index b41a7e6e..4024c49f 100644 --- a/src/core/mainloop.cpp +++ b/src/core/mainloop.cpp @@ -430,7 +430,7 @@ namespace }); function_ptr_command<> save_jukebox_prev("cycle-jukebox-backward", "Cycle save jukebox backwards", - "Syntax: cycle-jukebox-backwards\nCycle save jukebox backwards\n", + "Syntax: cycle-jukebox-backward\nCycle save jukebox backwards\n", []() throw(std::bad_alloc, std::runtime_error) { if(save_jukebox_pointer == 0) save_jukebox_pointer = save_jukebox.size() - 1; @@ -443,7 +443,7 @@ namespace }); function_ptr_command<> save_jukebox_next("cycle-jukebox-forward", "Cycle save jukebox forwards", - "Syntax: cycle-jukebox-forwards\nCycle save jukebox forwards\n", + "Syntax: cycle-jukebox-forward\nCycle save jukebox forwards\n", []() throw(std::bad_alloc, std::runtime_error) { if(save_jukebox_pointer == save_jukebox.size() - 1) save_jukebox_pointer = 0; @@ -645,6 +645,87 @@ namespace }); + inverse_key ipause_emulator("pause-emulator", "(Un)pause"); + inverse_key ijback("cycle-jukebox-backward", "Cycle slot backwards"); + inverse_key ijforward("cycle-jukebox-forward", "Cycle slot forwards"); + inverse_key iloadj("load-jukebox", "load selected slot"); + inverse_key isavej("save-jukebox", "Save selected slot"); + inverse_key iadvframe("+advance-frame", "Advance frame"); + inverse_key iadvsubframe("+advance-poll", "Advance subframe"); + inverse_key iskiplag("advance-skiplag", "Advance to next poll"); + inverse_key ireset("reset", "System reset"); + inverse_key iset_rwmode("set-rwmode", "Switch to read/write"); + inverse_key itoggle_romode("set-romode", "Switch to read-only"); + inverse_key itoggle_rwmode("toggle-rwmode", "Toggle read-only"); + inverse_key irepaint("repaint", "Repaint screen"); + inverse_key itogglepause("toggle-pause-on-end", "Toggle pause-on-end"); + inverse_key irewind_movie("rewind-movie", "Rewind movie"); + inverse_key icancel_saves("cancel-saves", "Cancel pending saves"); + inverse_key iload1("load ${project}1.lsmv", "Load slot 1"); + inverse_key iload2("load ${project}2.lsmv", "Load slot 2"); + inverse_key iload3("load ${project}3.lsmv", "Load slot 3"); + inverse_key iload4("load ${project}4.lsmv", "Load slot 4"); + inverse_key iload5("load ${project}5.lsmv", "Load slot 5"); + inverse_key iload6("load ${project}6.lsmv", "Load slot 6"); + inverse_key iload7("load ${project}7.lsmv", "Load slot 7"); + inverse_key iload8("load ${project}8.lsmv", "Load slot 8"); + inverse_key iload9("load ${project}9.lsmv", "Load slot 9"); + inverse_key iload10("load ${project}10.lsmv", "Load slot 10"); + inverse_key iload11("load ${project}11.lsmv", "Load slot 11"); + inverse_key iload12("load ${project}12.lsmv", "Load slot 12"); + inverse_key iload13("load ${project}13.lsmv", "Load slot 13"); + inverse_key iload14("load ${project}14.lsmv", "Load slot 14"); + inverse_key iload15("load ${project}15.lsmv", "Load slot 15"); + inverse_key iload16("load ${project}16.lsmv", "Load slot 16"); + inverse_key iload17("load ${project}17.lsmv", "Load slot 17"); + inverse_key iload18("load ${project}18.lsmv", "Load slot 18"); + inverse_key iload19("load ${project}19.lsmv", "Load slot 19"); + inverse_key iload20("load ${project}20.lsmv", "Load slot 20"); + inverse_key iload21("load ${project}21.lsmv", "Load slot 21"); + inverse_key iload22("load ${project}22.lsmv", "Load slot 22"); + inverse_key iload23("load ${project}23.lsmv", "Load slot 23"); + inverse_key iload24("load ${project}24.lsmv", "Load slot 24"); + inverse_key iload25("load ${project}25.lsmv", "Load slot 25"); + inverse_key iload26("load ${project}26.lsmv", "Load slot 26"); + inverse_key iload27("load ${project}27.lsmv", "Load slot 27"); + inverse_key iload28("load ${project}28.lsmv", "Load slot 28"); + inverse_key iload29("load ${project}29.lsmv", "Load slot 29"); + inverse_key iload30("load ${project}30.lsmv", "Load slot 30"); + inverse_key iload31("load ${project}31.lsmv", "Load slot 31"); + inverse_key iload32("load ${project}32.lsmv", "Load slot 32"); + inverse_key isave1("save-state ${project}1.lsmv", "Save slot 1"); + inverse_key isave2("save-state ${project}2.lsmv", "Save slot 2"); + inverse_key isave3("save-state ${project}3.lsmv", "Save slot 3"); + inverse_key isave4("save-state ${project}4.lsmv", "Save slot 4"); + inverse_key isave5("save-state ${project}5.lsmv", "Save slot 5"); + inverse_key isave6("save-state ${project}6.lsmv", "Save slot 6"); + inverse_key isave7("save-state ${project}7.lsmv", "Save slot 7"); + inverse_key isave8("save-state ${project}8.lsmv", "Save slot 8"); + inverse_key isave9("save-state ${project}9.lsmv", "Save slot 9"); + inverse_key isave10("save-state ${project}10.lsmv", "Save slot 10"); + inverse_key isave11("save-state ${project}11.lsmv", "Save slot 11"); + inverse_key isave12("save-state ${project}12.lsmv", "Save slot 12"); + inverse_key isave13("save-state ${project}13.lsmv", "Save slot 13"); + inverse_key isave14("save-state ${project}14.lsmv", "Save slot 14"); + inverse_key isave15("save-state ${project}15.lsmv", "Save slot 15"); + inverse_key isave16("save-state ${project}16.lsmv", "Save slot 16"); + inverse_key isave17("save-state ${project}17.lsmv", "Save slot 17"); + inverse_key isave18("save-state ${project}18.lsmv", "Save slot 18"); + inverse_key isave19("save-state ${project}19.lsmv", "Save slot 19"); + inverse_key isave20("save-state ${project}20.lsmv", "Save slot 20"); + inverse_key isave21("save-state ${project}21.lsmv", "Save slot 21"); + inverse_key isave22("save-state ${project}22.lsmv", "Save slot 22"); + inverse_key isave23("save-state ${project}23.lsmv", "Save slot 23"); + inverse_key isave24("save-state ${project}24.lsmv", "Save slot 24"); + inverse_key isave25("save-state ${project}25.lsmv", "Save slot 25"); + inverse_key isave26("save-state ${project}26.lsmv", "Save slot 26"); + inverse_key isave27("save-state ${project}27.lsmv", "Save slot 27"); + inverse_key isave28("save-state ${project}28.lsmv", "Save slot 28"); + inverse_key isave29("save-state ${project}29.lsmv", "Save slot 29"); + inverse_key isave30("save-state ${project}30.lsmv", "Save slot 30"); + inverse_key isave31("save-state ${project}31.lsmv", "Save slot 31"); + inverse_key isave32("save-state ${project}32.lsmv", "Save slot 32"); + bool on_quit_prompt = false; class mywindowcallbacks : public information_dispatch { diff --git a/src/core/window.cpp b/src/core/window.cpp index 7aea724d..79d20d61 100644 --- a/src/core/window.cpp +++ b/src/core/window.cpp @@ -140,6 +140,9 @@ namespace throw std::runtime_error("Bad sound setting"); }); + inverse_key ienable_sound("enable-sound on", "Enable sound"); + inverse_key idisable_sound("enable-sound off", "Disable sound"); + function_ptr_command set_sound_device("set-sound-device", "Set sound device", "Syntax: set-sound-device \nSet sound device to .\n", [](const std::string& args) throw(std::bad_alloc, std::runtime_error) { diff --git a/src/plat-wxwidgets/editor-keyselect.cpp b/src/plat-wxwidgets/editor-keyselect.cpp new file mode 100644 index 00000000..b00da493 --- /dev/null +++ b/src/plat-wxwidgets/editor-keyselect.cpp @@ -0,0 +1,365 @@ +#include "plat-wxwidgets/platform.hpp" +#include "core/keymapper.hpp" + +#include + +namespace +{ + struct keyentry_mod_data + { + wxCheckBox* pressed; + wxCheckBox* unmasked; + unsigned tmpflags; + }; + + class wxdialog_keyentry : public wxDialog + { + public: + wxdialog_keyentry(wxWindow* parent, const std::string& title, bool clearable); + void on_change_setting(wxCommandEvent& e); + void on_ok(wxCommandEvent& e); + void on_cancel(wxCommandEvent& e); + void on_clear(wxCommandEvent& e); + std::string getkey(); + private: + std::map modifiers; + wxComboBox* mainkey; + wxButton* ok; + wxButton* cancel; + wxButton* clear; + bool cleared; + }; + + class wxdialog_hotkeys; + + class hotkey + { + public: + hotkey(wxdialog_hotkeys* h, wxWindow* win, wxSizer* s, inverse_key* ikey, unsigned o); + void update(const std::string& primary, const std::string& secondary); + inverse_key* get_associated(); + std::string get_title(); + private: + wxButton* primaryb; + wxButton* secondaryb; + inverse_key* associated; + std::string title; + }; + + class wxdialog_hotkeys : public wxDialog + { + public: + wxdialog_hotkeys(wxWindow* parent); + ~wxdialog_hotkeys(); + void on_close(wxCommandEvent& e); + void on_reconfig(wxCommandEvent& e); + private: + void update(); + wxScrolledWindow* scroll; + wxSizer* scroll_sizer; + std::map hotkeys; + wxButton* close; + }; + + wxdialog_keyentry::wxdialog_keyentry(wxWindow* parent, const std::string& title, bool clearable) + : wxDialog(parent, wxID_ANY, towxstring(title), wxDefaultPosition, wxSize(-1, -1)) + { + std::vector keych; + std::set mods, keys; + + cleared = false; + runemufn([&mods, &keys]() { mods = modifier::get_set(); keys = keygroup::get_keys(); }); + Centre(); + wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0); + SetSizer(top_s); + + wxFlexGridSizer* t_s = new wxFlexGridSizer(mods.size() + 1, 3, 0, 0); + for(auto i : mods) { + t_s->Add(new wxStaticText(this, wxID_ANY, towxstring(i)), 0, wxGROW); + keyentry_mod_data m; + t_s->Add(m.pressed = new wxCheckBox(this, wxID_ANY, wxT("Pressed")), 0, wxGROW); + t_s->Add(m.unmasked = new wxCheckBox(this, wxID_ANY, wxT("Unmasked")), 0, wxGROW); + m.pressed->Disable(); + modifiers[i] = m; + m.pressed->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, + wxCommandEventHandler(wxdialog_keyentry::on_change_setting), NULL, this); + m.unmasked->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, + wxCommandEventHandler(wxdialog_keyentry::on_change_setting), NULL, this); + } + for(auto i : keys) + keych.push_back(towxstring(i)); + t_s->Add(new wxStaticText(this, wxID_ANY, wxT("Key")), 0, wxGROW); + t_s->Add(mainkey = new wxComboBox(this, wxID_ANY, keych[0], wxDefaultPosition, wxDefaultSize, + keych.size(), &keych[0], wxCB_READONLY), 1, wxGROW); + mainkey->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, + wxCommandEventHandler(wxdialog_keyentry::on_change_setting), NULL, this); + top_s->Add(t_s); + + wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); + if(clearable) + pbutton_s->Add(clear = new wxButton(this, wxID_OK, wxT("Clear")), 0, wxGROW); + pbutton_s->AddStretchSpacer(); + pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW); + pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW); + ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxdialog_keyentry::on_ok), NULL, this); + cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxdialog_keyentry::on_cancel), NULL, this); + if(clearable) + clear->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxdialog_keyentry::on_clear), NULL, this); + top_s->Add(pbutton_s, 0, wxGROW); + + t_s->SetSizeHints(this); + top_s->SetSizeHints(this); + Fit(); + } + +#define TMPFLAG_UNMASKED 65 +#define TMPFLAG_UNMASKED_LINK_CHILD 2 +#define TMPFLAG_UNMASKED_LINK_PARENT 68 +#define TMPFLAG_PRESSED 8 +#define TMPFLAG_PRESSED_LINK_CHILD 16 +#define TMPFLAG_PRESSED_LINK_PARENT 32 + + void wxdialog_keyentry::on_change_setting(wxCommandEvent& e) + { + for(auto& i : modifiers) + i.second.tmpflags = 0; + for(auto& i : modifiers) { + modifier* m = NULL; + try { + m = &modifier::lookup(i.first); + } catch(...) { + i.second.pressed->Disable(); + i.second.unmasked->Disable(); + continue; + } + std::string j = m->linked_name(); + if(i.second.unmasked->GetValue()) + i.second.tmpflags |= TMPFLAG_UNMASKED; + if(j != "") { + if(modifiers[j].unmasked->GetValue()) + i.second.tmpflags |= TMPFLAG_UNMASKED_LINK_PARENT; + if(i.second.unmasked->GetValue()) + modifiers[j].tmpflags |= TMPFLAG_UNMASKED_LINK_CHILD; + } + if(i.second.pressed->GetValue()) + i.second.tmpflags |= TMPFLAG_PRESSED; + if(j != "") { + if(modifiers[j].pressed->GetValue()) + i.second.tmpflags |= TMPFLAG_PRESSED_LINK_PARENT; + if(i.second.pressed->GetValue()) + modifiers[j].tmpflags |= TMPFLAG_PRESSED_LINK_CHILD; + } + } + for(auto& i : modifiers) { + //Unmasked is to be enabled if neither unmasked link flag is set. + if(i.second.tmpflags & ((TMPFLAG_UNMASKED_LINK_CHILD | TMPFLAG_UNMASKED_LINK_PARENT) & ~64)) { + i.second.unmasked->SetValue(false); + i.second.unmasked->Disable(); + } else + i.second.unmasked->Enable(); + //Pressed is to be enabled if: + //- This modifier is unmasked or parent is unmasked. + //- Parent nor child is not pressed. + if(((i.second.tmpflags & (TMPFLAG_UNMASKED | TMPFLAG_UNMASKED_LINK_PARENT | + TMPFLAG_PRESSED_LINK_CHILD | TMPFLAG_PRESSED_LINK_PARENT)) & 112) == 64) + i.second.pressed->Enable(); + else { + i.second.pressed->SetValue(false); + i.second.pressed->Disable(); + } + } + } + + void wxdialog_keyentry::on_ok(wxCommandEvent& e) + { + EndModal(wxID_OK); + } + + void wxdialog_keyentry::on_clear(wxCommandEvent& e) + { + cleared = true; + EndModal(wxID_OK); + } + + void wxdialog_keyentry::on_cancel(wxCommandEvent& e) + { + EndModal(wxID_CANCEL); + } + + std::string wxdialog_keyentry::getkey() + { + if(cleared) + return ""; + std::string x; + bool f; + f = true; + for(auto i : modifiers) { + if(i.second.pressed->GetValue()) { + if(!f) + x = x + ","; + f = false; + x = x + i.first; + } + } + x = x + "/"; + f = true; + for(auto i : modifiers) { + if(i.second.unmasked->GetValue()) { + if(!f) + x = x + ","; + f = false; + x = x + i.first; + } + } + x = x + "|" + tostdstring(mainkey->GetValue()); + return x; + } + + wxdialog_hotkeys::wxdialog_hotkeys(wxWindow* parent) + : wxDialog(parent, wxID_ANY, wxT("Hotkeys"), wxDefaultPosition, wxSize(-1, -1)) + { + scroll = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(-1, -1)); + scroll->SetMinSize(wxSize(-1, 500)); + + Centre(); + wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0); + SetSizer(top_s); + scroll_sizer = new wxFlexGridSizer(0, 3, 0, 0); + scroll->SetSizer(scroll_sizer); + + //Obtain all the inverses. + std::set inverses; + runemufn([&inverses]() { + auto x = inverse_key::get_ikeys(); + for(auto y : x) + inverses.insert(y); + }); + unsigned y = 0; + for(auto x : inverses) { + hotkeys[y] = new hotkey(this, scroll, scroll_sizer, x, y); + y++; + } + update(); + + top_s->Add(scroll); + scroll->SetScrollRate(0, 20); + wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); + pbutton_s->AddStretchSpacer(); + pbutton_s->Add(close = new wxButton(this, wxID_CANCEL, wxT("Close")), 0, wxGROW); + close->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxdialog_hotkeys::on_close), NULL, this); + top_s->Add(pbutton_s, 0, wxGROW); + + scroll_sizer->SetSizeHints(this); + top_s->SetSizeHints(this); + scroll_sizer->Layout(); + top_s->Layout(); + Fit(); + } + + void wxdialog_hotkeys::on_reconfig(wxCommandEvent& e) + { + int id = e.GetId(); + if(id <= wxID_HIGHEST) + return; + unsigned button = (id - wxID_HIGHEST - 1) / 2; + bool primflag = (((id - wxID_HIGHEST - 1) % 2) == 0); + if(!hotkeys.count(button)) + return; + wxdialog_keyentry* d = new wxdialog_keyentry(this, "Specify key for " + hotkeys[button]-> + get_title(), true); + if(d->ShowModal() == wxID_CANCEL) { + d->Destroy(); + return; + } + std::string key = d->getkey(); + d->Destroy(); + if(key != "") + hotkeys[button]->get_associated()->set(key, primflag); + else + hotkeys[button]->get_associated()->clear(primflag); + update(); + } + + void wxdialog_hotkeys::update() + { + std::map> data; + runemufn([&data]() { + auto x = inverse_key::get_ikeys(); + for(auto y : x) + data[y] = std::make_pair(y->get(true), y->get(false)); + }); + for(auto i : hotkeys) { + inverse_key* j = i.second->get_associated(); + if(!data.count(j)) + continue; + auto y = data[j]; + i.second->update(y.first, y.second); + } + } + + wxdialog_hotkeys::~wxdialog_hotkeys() + { + for(auto i : hotkeys) + delete i.second; + } + + void wxdialog_hotkeys::on_close(wxCommandEvent& e) + { + EndModal(wxID_OK); + } + + hotkey::hotkey(wxdialog_hotkeys* h, wxWindow* win, wxSizer* s, inverse_key* ikey, unsigned o) + { + title = ikey->getname(); + s->Add(new wxStaticText(win, wxID_ANY, towxstring(title)), 0, wxGROW); + s->Add(primaryb = new wxButton(win, wxID_HIGHEST + 1 + 2 * o, wxT("(none)"), wxDefaultPosition, + wxSize(200, -1)), 0, wxGROW); + s->Add(secondaryb = new wxButton(win, wxID_HIGHEST + 2 + 2 * o, wxT("(none)"), wxDefaultPosition, + wxSize(200, -1)), 0, wxGROW); + primaryb->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxdialog_hotkeys::on_reconfig), NULL, h); + secondaryb->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxdialog_hotkeys::on_reconfig), NULL, h); + associated = ikey; + } + + inverse_key* hotkey::get_associated() + { + return associated; + } + + std::string hotkey::get_title() + { + return title; + } + + void hotkey::update(const std::string& primary, const std::string& secondary) + { + primaryb->SetLabel((primary != "") ? towxstring(primary) : towxstring("(none)")); + secondaryb->SetLabel((secondary != "") ? towxstring(secondary) : towxstring("(none)")); + } +} + +std::string wxeditor_keyselect(wxWindow* parent, bool clearable) +{ + wxdialog_keyentry* d = new wxdialog_keyentry(parent, "Specify key", clearable); + if(d->ShowModal() == wxID_CANCEL) { + d->Destroy(); + throw canceled_exception(); + } + std::string key = d->getkey(); + d->Destroy(); + return key; +} + +void wxeditor_hotkeys_display(wxWindow* parent) +{ + modal_pause_holder hld; + wxdialog_hotkeys* d = new wxdialog_hotkeys(parent); + d->ShowModal(); + d->Destroy(); +} diff --git a/src/plat-wxwidgets/mainwindow.cpp b/src/plat-wxwidgets/mainwindow.cpp index 710d7e80..03da2d03 100644 --- a/src/plat-wxwidgets/mainwindow.cpp +++ b/src/plat-wxwidgets/mainwindow.cpp @@ -70,7 +70,8 @@ enum wxID_REWIND_MOVIE, wxID_EDIT_JUKEBOX, wxID_MEMORY_SEARCH, - wxID_CANCEL_SAVES + wxID_CANCEL_SAVES, + wxID_EDIT_HOTKEYS }; @@ -409,171 +410,6 @@ namespace { runuifun([ahmenu]() { ahmenu->reconfigure(); }); } - - struct keyentry_mod_data - { - wxCheckBox* pressed; - wxCheckBox* unmasked; - unsigned tmpflags; - }; - - class wxdialog_keyentry : public wxDialog - { - public: - wxdialog_keyentry(wxWindow* parent); - void on_change_setting(wxCommandEvent& e); - void on_ok(wxCommandEvent& e); - void on_cancel(wxCommandEvent& e); - std::string getkey(); - private: - std::map modifiers; - wxComboBox* mainkey; - wxButton* ok; - wxButton* cancel; - }; - - wxdialog_keyentry::wxdialog_keyentry(wxWindow* parent) - : wxDialog(parent, wxID_ANY, wxT("Specify key"), wxDefaultPosition, wxSize(-1, -1)) - { - std::vector keych; - std::set mods, keys; - - runemufn([&mods, &keys]() { mods = modifier::get_set(); keys = keygroup::get_keys(); }); - Centre(); - wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0); - SetSizer(top_s); - - wxFlexGridSizer* t_s = new wxFlexGridSizer(mods.size() + 1, 3, 0, 0); - for(auto i : mods) { - t_s->Add(new wxStaticText(this, wxID_ANY, towxstring(i)), 0, wxGROW); - keyentry_mod_data m; - t_s->Add(m.pressed = new wxCheckBox(this, wxID_ANY, wxT("Pressed")), 0, wxGROW); - t_s->Add(m.unmasked = new wxCheckBox(this, wxID_ANY, wxT("Unmasked")), 0, wxGROW); - m.pressed->Disable(); - modifiers[i] = m; - m.pressed->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, - wxCommandEventHandler(wxdialog_keyentry::on_change_setting), NULL, this); - m.unmasked->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, - wxCommandEventHandler(wxdialog_keyentry::on_change_setting), NULL, this); - } - for(auto i : keys) - keych.push_back(towxstring(i)); - t_s->Add(new wxStaticText(this, wxID_ANY, wxT("Key")), 0, wxGROW); - t_s->Add(mainkey = new wxComboBox(this, wxID_ANY, keych[0], wxDefaultPosition, wxDefaultSize, - keych.size(), &keych[0], wxCB_READONLY), 1, wxGROW); - mainkey->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, - wxCommandEventHandler(wxdialog_keyentry::on_change_setting), NULL, this); - top_s->Add(t_s); - - wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); - pbutton_s->AddStretchSpacer(); - pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW); - pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW); - ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED, - wxCommandEventHandler(wxdialog_keyentry::on_ok), NULL, this); - cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, - wxCommandEventHandler(wxdialog_keyentry::on_cancel), NULL, this); - top_s->Add(pbutton_s, 0, wxGROW); - - t_s->SetSizeHints(this); - top_s->SetSizeHints(this); - Fit(); - } - -#define TMPFLAG_UNMASKED 65 -#define TMPFLAG_UNMASKED_LINK_CHILD 2 -#define TMPFLAG_UNMASKED_LINK_PARENT 68 -#define TMPFLAG_PRESSED 8 -#define TMPFLAG_PRESSED_LINK_CHILD 16 -#define TMPFLAG_PRESSED_LINK_PARENT 32 - - void wxdialog_keyentry::on_change_setting(wxCommandEvent& e) - { - for(auto& i : modifiers) - i.second.tmpflags = 0; - for(auto& i : modifiers) { - modifier* m = NULL; - try { - m = &modifier::lookup(i.first); - } catch(...) { - i.second.pressed->Disable(); - i.second.unmasked->Disable(); - continue; - } - std::string j = m->linked_name(); - if(i.second.unmasked->GetValue()) - i.second.tmpflags |= TMPFLAG_UNMASKED; - if(j != "") { - if(modifiers[j].unmasked->GetValue()) - i.second.tmpflags |= TMPFLAG_UNMASKED_LINK_PARENT; - if(i.second.unmasked->GetValue()) - modifiers[j].tmpflags |= TMPFLAG_UNMASKED_LINK_CHILD; - } - if(i.second.pressed->GetValue()) - i.second.tmpflags |= TMPFLAG_PRESSED; - if(j != "") { - if(modifiers[j].pressed->GetValue()) - i.second.tmpflags |= TMPFLAG_PRESSED_LINK_PARENT; - if(i.second.pressed->GetValue()) - modifiers[j].tmpflags |= TMPFLAG_PRESSED_LINK_CHILD; - } - } - for(auto& i : modifiers) { - //Unmasked is to be enabled if neither unmasked link flag is set. - if(i.second.tmpflags & ((TMPFLAG_UNMASKED_LINK_CHILD | TMPFLAG_UNMASKED_LINK_PARENT) & ~64)) { - i.second.unmasked->SetValue(false); - i.second.unmasked->Disable(); - } else - i.second.unmasked->Enable(); - //Pressed is to be enabled if: - //- This modifier is unmasked or parent is unmasked. - //- Parent nor child is not pressed. - if(((i.second.tmpflags & (TMPFLAG_UNMASKED | TMPFLAG_UNMASKED_LINK_PARENT | - TMPFLAG_PRESSED_LINK_CHILD | TMPFLAG_PRESSED_LINK_PARENT)) & 112) == 64) - i.second.pressed->Enable(); - else { - i.second.pressed->SetValue(false); - i.second.pressed->Disable(); - } - } - } - - void wxdialog_keyentry::on_ok(wxCommandEvent& e) - { - EndModal(wxID_OK); - } - - void wxdialog_keyentry::on_cancel(wxCommandEvent& e) - { - EndModal(wxID_CANCEL); - } - - std::string wxdialog_keyentry::getkey() - { - std::string x; - bool f; - f = true; - for(auto i : modifiers) { - if(i.second.pressed->GetValue()) { - if(!f) - x = x + ","; - f = false; - x = x + i.first; - } - } - x = x + "/"; - f = true; - for(auto i : modifiers) { - if(i.second.unmasked->GetValue()) { - if(!f) - x = x + ","; - f = false; - x = x + i.first; - } - } - x = x + "|" + tostdstring(mainkey->GetValue()); - return x; - } } void boot_emulator(loaded_rom& rom, moviefile& movie) @@ -823,6 +659,7 @@ wxwin_mainwindow::wxwin_mainwindow() menu_start(wxT("Settings")); menu_entry(wxID_EDIT_AXES, wxT("Configure axes")); menu_entry(wxID_EDIT_SETTINGS, wxT("Configure settings")); + menu_entry(wxID_EDIT_HOTKEYS, wxT("Configure hotkeys")); menu_entry(wxID_EDIT_KEYBINDINGS, wxT("Configure keybindings")); menu_entry(wxID_EDIT_ALIAS, wxT("Configure aliases")); menu_entry(wxID_EDIT_JUKEBOX, wxT("Configure jukebox")); @@ -980,6 +817,9 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) case wxID_EDIT_SETTINGS: wxeditor_settings_display(this); return; + case wxID_EDIT_HOTKEYS: + wxeditor_hotkeys_display(this); + return; case wxID_EDIT_KEYBINDINGS: { modal_pause_holder hld; std::set bind; @@ -989,15 +829,8 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) for(auto i : bind) choices.push_back(i); std::string key = pick_among(this, "Select binding", "Select keybinding to edit", choices); - if(key == NEW_KEYBINDING) { - wxdialog_keyentry* d2 = new wxdialog_keyentry(this); - if(d2->ShowModal() == wxID_CANCEL) { - d2->Destroy(); - throw canceled_exception(); - } - key = d2->getkey(); - d2->Destroy(); - } + if(key == NEW_KEYBINDING) + key = wxeditor_keyselect(this, false); std::string old_command_value; runemufn([&old_command_value, key]() { old_command_value = keymapper::get_command_for(key); }); std::string newcommand = pick_text(this, "Edit binding", "Enter new command for binding:",