From 6c995e77803b64f381ceeb5a23ce59614fdeb6c7 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 28 Oct 2011 20:40:32 +0000 Subject: [PATCH] Add support for modifying hotkeys while the program is running Originally committed to SVN as r5793. --- aegisub/libaegisub/common/hotkey.cpp | 58 ++++++------- .../libaegisub/include/libaegisub/hotkey.h | 81 +++++++++---------- aegisub/src/main.cpp | 7 +- aegisub/src/menu.cpp | 26 +++++- aegisub/src/toolbar.cpp | 44 +++++----- aegisub/src/tooltip_manager.cpp | 4 +- 6 files changed, 124 insertions(+), 96 deletions(-) diff --git a/aegisub/libaegisub/common/hotkey.cpp b/aegisub/libaegisub/common/hotkey.cpp index 402f4f051..2ef5c8b92 100644 --- a/aegisub/libaegisub/common/hotkey.cpp +++ b/aegisub/libaegisub/common/hotkey.cpp @@ -43,9 +43,9 @@ namespace agi { namespace hotkey { -Hotkey *hotkey; - std::string Combo::Str() const { + if (key_map.empty()) return ""; + std::string str(key_map[0]); str.reserve(str.size() + (key_map.size() - 1) * 2); for (unsigned int i=1; i < key_map.size(); i++) { @@ -63,10 +63,6 @@ void Hotkey::ComboInsert(Combo const& combo) { cmd_map.insert(std::make_pair(combo.CmdName(), combo)); } -Hotkey::~Hotkey() { - Flush(); -} - Hotkey::Hotkey(const std::string &file, const std::string &default_config) : config_file(file) { @@ -75,26 +71,23 @@ Hotkey::Hotkey(const std::string &file, const std::string &default_config) json::UnknownElement hotkey_root = agi::json_util::file(config_file, default_config); json::Object object = hotkey_root; - for (json::Object::const_iterator index(object.begin()); index != object.end(); index++) { + for (json::Object::const_iterator index(object.begin()); index != object.end(); ++index) BuildHotkey(index->first, index->second); - } } void Hotkey::BuildHotkey(std::string const& context, const json::Object& object) { - for (json::Object::const_iterator index(object.begin()); index != object.end(); index++) { + for (json::Object::const_iterator index(object.begin()); index != object.end(); ++index) { const json::Array& array = index->second; - for (json::Array::const_iterator arr_index(array.begin()); arr_index != array.end(); arr_index++) { - Combo combo(context, index->first); - + for (json::Array::const_iterator arr_index(array.begin()); arr_index != array.end(); ++arr_index) { const json::Array& arr_mod = (*arr_index)["modifiers"]; - for (json::Array::const_iterator arr_mod_index(arr_mod.begin()); arr_mod_index != arr_mod.end(); arr_mod_index++) { - combo.KeyInsert(*arr_mod_index); - } + std::vector keys; + keys.reserve(arr_mod.size() + 1); + copy(arr_mod.begin(), arr_mod.end(), back_inserter(keys)); + keys.push_back((*arr_index)["key"]); - combo.KeyInsert((*arr_index)["key"]); - ComboInsert(combo); + ComboInsert(Combo(context, index->first, keys)); } } } @@ -111,12 +104,10 @@ bool Hotkey::Scan(const std::string &context, const std::string &str, bool alway LOG_D("agi/hotkey/found") << "Found: " << str << " Context (req/found): " << context << "/Always Command: " << cmd; return true; } - if (ctext == "Default") { + if (ctext == "Default") dfault = index->second.CmdName(); - } - else if (ctext == context) { + else if (ctext == context) local = index->second.CmdName(); - } } if (!local.empty()) { @@ -139,9 +130,8 @@ std::vector Hotkey::GetHotkeys(const std::string &context, const st HotkeyMap::const_iterator it, end; for (std::tr1::tie(it, end) = cmd_map.equal_range(command); it != end; ++it) { std::string ctext = it->second.Context(); - if (ctext == "Always" || ctext == "Default" || ctext == context) { + if (ctext == "Always" || ctext == "Default" || ctext == context) ret.push_back(it->second.StrMenu()); - } } sort(ret.begin(), ret.end()); @@ -156,8 +146,10 @@ std::string Hotkey::GetHotkey(const std::string &context, const std::string &com for (std::tr1::tie(it, end) = cmd_map.equal_range(command); it != end; ++it) { std::string ctext = it->second.Context(); if (ctext == context) return it->second.StrMenu(); - if (ctext == "Default") ret = it->second.StrMenu(); - else if (ret.empty() && ctext == "Always") it->second.StrMenu(); + if (ctext == "Default") + ret = it->second.StrMenu(); + else if (ret.empty() && ctext == "Always") + it->second.StrMenu(); } return ret; } @@ -166,12 +158,10 @@ void Hotkey::Flush() { json::Object root; for (HotkeyMap::iterator index = str_map.begin(); index != str_map.end(); ++index) { - Combo::ComboMap combo_map(index->second.Get()); + std::vector const& combo_map(index->second.Get()); json::Array modifiers; - for (int i = 0; i != combo_map.size()-1; i++) { - modifiers.push_back(json::String(combo_map[i])); - } + copy(combo_map.begin(), combo_map.end() - 1, std::back_inserter(modifiers)); json::Object hotkey; hotkey["modifiers"] = modifiers; @@ -185,5 +175,15 @@ void Hotkey::Flush() { json::Writer::Write(root, file.Get()); } +void Hotkey::SetHotkeyMap(HotkeyMap const& new_map) { + cmd_map = new_map; + + str_map.clear(); + for (HotkeyMap::iterator it = cmd_map.begin(); it != cmd_map.end(); ++it) + str_map.insert(make_pair(it->second.Str(), it->second)); + + HotkeysChanged(); +} + } // namespace toolbar } // namespace agi diff --git a/aegisub/libaegisub/include/libaegisub/hotkey.h b/aegisub/libaegisub/include/libaegisub/hotkey.h index 4fccec64e..9afc22d3d 100644 --- a/aegisub/libaegisub/include/libaegisub/hotkey.h +++ b/aegisub/libaegisub/include/libaegisub/hotkey.h @@ -19,13 +19,14 @@ /// @ingroup hotkey menu event window #ifndef LAGI_PRE -#include #include #include #include #include #endif +#include + namespace json { class UnknownElement; typedef std::map Object; @@ -34,27 +35,23 @@ namespace json { namespace agi { namespace hotkey { -class Hotkey; -/// Hotkey instance. -extern Hotkey *hotkey; - /// @class Combo /// A Combo represents a linear sequence of characters set in an std::vector. This makes up /// a single combination, or "Hotkey". class Combo { -friend class Hotkey; - + std::vector key_map; + std::string cmd_name; + std::string context; public: - /// Linear key sequence that forms a combination or "Combo" - typedef std::vector ComboMap; - /// Constructor /// @param ctx Context /// @param cmd Command name - Combo(std::string const& ctx, std::string const& cmd): cmd_name(cmd), context(ctx) {} - - /// Destructor - ~Combo() {} + Combo(std::string const& ctx, std::string const& cmd, std::vector const& keys) + : key_map(keys) + , cmd_name(cmd) + , context(ctx) + { + } /// String representation of the Combo std::string Str() const; @@ -64,7 +61,7 @@ public: /// Get the literal combo map. /// @return ComboMap (std::vector) of linear key sequence. - const ComboMap& Get() const { return key_map; } + const std::vector& Get() const { return key_map; } /// Command name triggered by the combination. /// @return Command name @@ -72,30 +69,40 @@ public: /// Context this Combo is triggered in. const std::string& Context() const { return context; } - -private: - ComboMap key_map; ///< Map. - const std::string cmd_name; ///< Command name. - const std::string context; ///< Context - - /// Insert a key into the ComboMap. - /// @param key Key to insert. - void KeyInsert(std::string key) { key_map.push_back(key); } }; /// @class Hotkey /// Holds the map of Combo instances and handles searching for matching key sequences. class Hotkey { +public: + /// Map to hold Combo instances + typedef std::multimap HotkeyMap; +private: + HotkeyMap str_map; ///< String representation -> Combo + HotkeyMap cmd_map; ///< Command name -> Combo + const std::string config_file; ///< Default user config location. + + /// Build hotkey map. + /// @param context Context being parsed. + /// @param object json::Object holding items for context being parsed. + void BuildHotkey(std::string const& context, const json::Object& object); + + /// Insert Combo into HotkeyMap instance. + /// @param combo Combo to insert. + void ComboInsert(Combo const& combo); + + /// Write active Hotkey configuration to disk. + void Flush(); + + /// Announce that the loaded hotkeys have been changed + agi::signal::Signal<> HotkeysChanged; public: /// Constructor /// @param file Location of user config file. /// @param default_config Default config. Hotkey(const std::string &file, const std::string &default_config); - /// Destructor. - ~Hotkey(); - /// Scan for a matching key. /// @param context Context requested. /// @param str Hyphen separated key sequence. @@ -115,23 +122,13 @@ public: /// @return A hotkey for the given command or "" if there are none std::string GetHotkey(const std::string &context, const std::string &command) const; -private: - typedef std::multimap HotkeyMap; ///< Map to hold Combo instances. - HotkeyMap str_map; ///< String representation -> Combo - HotkeyMap cmd_map; ///< Command name -> Combo - const std::string config_file; ///< Default user config location. + /// Get the raw command name -> combo map for all registered hotkeys + HotkeyMap const& GetHotkeyMap() const { return cmd_map; } - /// Build hotkey map. - /// @param context Context being parsed. - /// @param object json::Object holding items for context being parsed. - void BuildHotkey(std::string const& context, const json::Object& object); + /// Replace the loaded hotkeys with a new set + void SetHotkeyMap(HotkeyMap const& new_map); - /// Insert Combo into HotkeyMap instance. - /// @param combo Combo to insert. - void ComboInsert(Combo const& combo); - - /// Write active Hotkey configuration to disk. - void Flush(); + DEFINE_SIGNAL_ADDERS(HotkeysChanged, AddHotkeyChangeListener) }; } // namespace hotkey diff --git a/aegisub/src/main.cpp b/aegisub/src/main.cpp index a396e8914..3af405b9c 100644 --- a/aegisub/src/main.cpp +++ b/aegisub/src/main.cpp @@ -186,9 +186,8 @@ bool AegisubApp::OnInit() { // Init commands. cmd::init_builtin_commands(); - // Init hotkeys. - const std::string conf_user_hotkey(StandardPaths::DecodePath("?user/hotkey.json")); - agi::hotkey::hotkey = new agi::hotkey::Hotkey(conf_user_hotkey, GET_DEFAULT_CONFIG(default_hotkey)); + // Init hotkeys + hotkey::init(); // Init icons. icon::icon_init(); @@ -327,7 +326,7 @@ int AegisubApp::OnExit() { delete plugins; delete config::opt; delete config::mru; - delete agi::hotkey::hotkey; + hotkey::clear(); delete config::path; cmd::clear(); diff --git a/aegisub/src/menu.cpp b/aegisub/src/menu.cpp index f3a07707a..2c356fa6f 100644 --- a/aegisub/src/menu.cpp +++ b/aegisub/src/menu.cpp @@ -32,6 +32,7 @@ #include "main.h" #include "standard_paths.h" +#include #include #include @@ -133,6 +134,8 @@ struct menu_item_cmp { class CommandManager { /// Menu items which need to do something on menu open std::deque > dynamic_items; + /// Menu items which need to be updated only when hotkeys change + std::deque > static_items; /// window id -> command map std::vector items; /// MRU menus which need to be updated on menu open @@ -141,19 +144,30 @@ class CommandManager { /// Project context agi::Context *context; + /// Connection for hotkey change signal + agi::signal::Connection hotkeys_changed; + /// Update a single dynamic menu item void UpdateItem(std::pair const& item) { int flags = item.first->Type(); if (flags & cmd::COMMAND_DYNAMIC_NAME) - item.second->SetItemLabel(get_menu_text(item.first, context)); + UpdateItemName(item); if (flags & cmd::COMMAND_VALIDATE) item.second->Enable(item.first->Validate(context)); if (flags & cmd::COMMAND_RADIO || flags & cmd::COMMAND_TOGGLE) item.second->Check(item.first->IsActive(context)); } + void UpdateItemName(std::pair const& item) { + item.second->SetItemLabel(get_menu_text(item.first, context)); + } + public: - CommandManager(agi::Context *context) : context(context) { } + CommandManager(agi::Context *context) + : context(context) + , hotkeys_changed(hotkey::inst->AddHotkeyChangeListener(&CommandManager::OnHotkeysChanged, this)) + { + } /// Append a command to a menu and register the needed handlers int AddCommand(cmd::Command *co, wxMenu *parent, std::string const& text) { @@ -177,6 +191,8 @@ public: if (flags != cmd::COMMAND_NORMAL) dynamic_items.push_back(std::make_pair(co, item)); + else + static_items.push_back(std::make_pair(co, item)); return item->GetId(); } @@ -209,6 +225,12 @@ public: if (id < items.size()) (*items[id])(context); } + + /// Update the hotkeys for all menu items + void OnHotkeysChanged() { + for_each(dynamic_items.begin(), dynamic_items.end(), bind(&CommandManager::UpdateItemName, this, _1)); + for_each(static_items.begin(), static_items.end(), bind(&CommandManager::UpdateItemName, this, _1)); + } }; /// Wrapper for wxMenu to add a command manager diff --git a/aegisub/src/toolbar.cpp b/aegisub/src/toolbar.cpp index dcdf68690..896d0362a 100644 --- a/aegisub/src/toolbar.cpp +++ b/aegisub/src/toolbar.cpp @@ -35,6 +35,7 @@ #include #endif +#include #include #include #include @@ -64,6 +65,9 @@ namespace { /// Listener for icon size change signal agi::signal::Connection icon_size_slot; + /// Listener for hotkey change signal + agi::signal::Connection hotkeys_changed_slot; + /// Enable/disable the toolbar buttons void OnIdle(wxIdleEvent &) { for (size_t i = 0; i < commands.size(); ++i) { @@ -81,8 +85,8 @@ namespace { (*commands[evt.GetId() - TOOL_ID_BASE])(context); } - /// Clear the toolbar and recreate it with the new icon size - void OnIconSizeChanged() { + /// Clear the toolbar and recreate it + void RegenerateToolbar() { Unbind(wxEVT_IDLE, &Toolbar::OnIdle, this); ClearTools(); commands.clear(); @@ -130,20 +134,7 @@ namespace { flags & cmd::COMMAND_TOGGLE ? wxITEM_CHECK : wxITEM_NORMAL; - wxString tooltip = command->StrHelp(); - - std::vector hotkeys = hotkey::get_hotkey_strs(ht_context, command->name()); - - for (size_t i = 0; i < hotkeys.size(); ++i) { - if (i == 0) - tooltip += " ("; - else - tooltip += "/"; - tooltip += hotkeys[i]; - } - if (hotkeys.size()) tooltip += ")"; - - AddTool(TOOL_ID_BASE + commands.size(), command->StrDisplay(context), icon, tooltip, kind); + AddTool(TOOL_ID_BASE + commands.size(), command->StrDisplay(context), icon, GetTooltip(command), kind); commands.push_back(command); needs_onidle = needs_onidle || flags != cmd::COMMAND_NORMAL; @@ -157,14 +148,31 @@ namespace { Realize(); } + + wxString GetTooltip(cmd::Command *command) { + wxString ret = command->StrHelp(); + + std::vector hotkeys = hotkey::get_hotkey_strs(ht_context, command->name()); + for (size_t i = 0; i < hotkeys.size(); ++i) { + if (i == 0) + ret += " ("; + else + ret += "/"; + ret += hotkeys[i]; + } + if (hotkeys.size()) ret += ")"; + + return ret; + } + public: Toolbar(wxWindow *parent, std::string const& name, agi::Context *c, std::string const& ht_context) : wxToolBar(parent, -1, wxDefaultPosition, wxDefaultSize, wxTB_FLAT | wxTB_HORIZONTAL) , name(name) , context(c) , ht_context(ht_context) - , icon_size_slot(OPT_SUB("App/Toolbar Icon Size", &Toolbar::OnIconSizeChanged, this)) - /// @todo bind to hotkey changed event when such a thing exists + , icon_size_slot(OPT_SUB("App/Toolbar Icon Size", &Toolbar::RegenerateToolbar, this)) + , hotkeys_changed_slot(hotkey::inst->AddHotkeyChangeListener(&Toolbar::RegenerateToolbar, this)) { Populate(); Bind(wxEVT_COMMAND_TOOL_CLICKED, &Toolbar::OnClick, this); diff --git a/aegisub/src/tooltip_manager.cpp b/aegisub/src/tooltip_manager.cpp index 11821c241..c0ff387a4 100644 --- a/aegisub/src/tooltip_manager.cpp +++ b/aegisub/src/tooltip_manager.cpp @@ -40,6 +40,8 @@ #include "include/aegisub/hotkey.h" +#include + struct ToolTipBinding { wxWindow *window; wxString toolTip; @@ -54,10 +56,10 @@ ToolTipManager::~ToolTipManager() { } void ToolTipManager::Bind(wxWindow *window, wxString tooltip, const char *context, const char *command) { ToolTipBinding tip = { window, tooltip, command, context }; tip.Update(); - /// @todo bind to hotkey changed signal once such a thing exists static ToolTipManager instance; instance.tips.push_back(tip); + hotkey::inst->AddHotkeyChangeListener(&ToolTipBinding::Update, &instance.tips.back()); } void ToolTipBinding::Update() {