Add support for modifying hotkeys while the program is running

Originally committed to SVN as r5793.
This commit is contained in:
Thomas Goyne 2011-10-28 20:40:32 +00:00
parent cba0b1edb8
commit 6c995e7780
6 changed files with 124 additions and 96 deletions

View File

@ -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<std::string> 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<std::string> 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<std::string> 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

View File

@ -19,13 +19,14 @@
/// @ingroup hotkey menu event window
#ifndef LAGI_PRE
#include <cmath>
#include <map>
#include <memory>
#include <string>
#include <vector>
#endif
#include <libaegisub/signal.h>
namespace json {
class UnknownElement;
typedef std::map<std::string, UnknownElement> 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<std::string> key_map;
std::string cmd_name;
std::string context;
public:
/// Linear key sequence that forms a combination or "Combo"
typedef std::vector<std::string> 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<std::string> 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<std::string>& 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<std::string, Combo> 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<std::string, Combo> 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

View File

@ -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();

View File

@ -32,6 +32,7 @@
#include "main.h"
#include "standard_paths.h"
#include <libaegisub/hotkey.h>
#include <libaegisub/json.h>
#include <libaegisub/log.h>
@ -133,6 +134,8 @@ struct menu_item_cmp {
class CommandManager {
/// Menu items which need to do something on menu open
std::deque<std::pair<cmd::Command*, wxMenuItem*> > dynamic_items;
/// Menu items which need to be updated only when hotkeys change
std::deque<std::pair<cmd::Command*, wxMenuItem*> > static_items;
/// window id -> command map
std::vector<cmd::Command*> 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<cmd::Command*, wxMenuItem*> 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<cmd::Command*, wxMenuItem*> 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

View File

@ -35,6 +35,7 @@
#include <wx/toolbar.h>
#endif
#include <libaegisub/hotkey.h>
#include <libaegisub/json.h>
#include <libaegisub/log.h>
#include <libaegisub/signal.h>
@ -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<std::string> 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<std::string> 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);

View File

@ -40,6 +40,8 @@
#include "include/aegisub/hotkey.h"
#include <libaegisub/hotkey.h>
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() {