forked from mia/Aegisub
Add support for modifying hotkeys while the program is running
Originally committed to SVN as r5793.
This commit is contained in:
parent
cba0b1edb8
commit
6c995e7780
6 changed files with 124 additions and 96 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue