// Copyright (c) 2010, Amar Takhar // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "libaegisub/hotkey.h" #include "libaegisub/cajun/writer.h" #include "libaegisub/fs.h" #include "libaegisub/io.h" #include "libaegisub/json.h" #include "libaegisub/log.h" #include #include #include namespace agi { namespace hotkey { namespace { struct combo_cmp { bool operator()(const Combo *a, const Combo *b) { return a->Str() < b->Str(); } bool operator()(const Combo *a, std::string const& b) { return a->Str() < b; } bool operator()(std::string const& a, const Combo *b) { return a < b->Str(); } }; struct hotkey_visitor : json::ConstVisitor { std::string const& context; std::string const& command; Hotkey::HotkeyMap& map; bool needs_backup = false; hotkey_visitor(std::string const& context, std::string const& command, Hotkey::HotkeyMap& map) : context(context), command(command), map(map) { } void Visit(std::string const& string) override { map.insert(make_pair(command, Combo(context, command, string))); } void Visit(json::Object const& hotkey) override { auto mod_it = hotkey.find("modifiers"); if (mod_it == end(hotkey)) { LOG_E("agi/hotkey/load") << "Hotkey for command '" << command << "' is missing modifiers"; return; } auto key_it = hotkey.find("key"); if (key_it == end(hotkey)) { LOG_E("agi/hotkey/load") << "Hotkey for command '" << command << "' is missing the key"; return; } std::string key_str; json::Array const& arr_mod = mod_it->second; for (std::string const& mod : arr_mod) { key_str += mod; key_str += '-'; } key_str += static_cast(key_it->second); map.insert(make_pair(command, Combo(context, command, std::move(key_str)))); needs_backup = true; } void Visit(const json::Array& array) override { } void Visit(int64_t number) override { } void Visit(double number) override { } void Visit(bool boolean) override { } void Visit(const json::Null& null) override { } }; } Hotkey::Hotkey(fs::path const& file, std::pair default_config) : config_file(file) { LOG_D("hotkey/init") << "Generating hotkeys."; auto root = json_util::file(config_file, default_config); for (auto const& hotkey_context : static_cast(root)) BuildHotkey(hotkey_context.first, hotkey_context.second); UpdateStrMap(); } void Hotkey::BuildHotkey(std::string const& context, json::Object const& hotkeys) { for (auto const& command : hotkeys) { json::Array const& command_hotkeys = command.second; hotkey_visitor visitor{context, command.first, cmd_map}; for (auto const& hotkey : command_hotkeys) hotkey.Accept(visitor); backup_config_file |= visitor.needs_backup; } } std::string Hotkey::Scan(std::string const& context, std::string const& str, bool always) const { const std::string *local = nullptr, *dfault = nullptr; std::vector::const_iterator index, end; for (std::tie(index, end) = boost::equal_range(str_map, str, combo_cmp()); index != end; ++index) { std::string const& ctext = (*index)->Context(); if (always && ctext == "Always") { LOG_D("agi/hotkey/found") << "Found: " << str << " Context (req/found): " << context << "/Always Command: " << (*index)->CmdName(); return (*index)->CmdName(); } if (ctext == "Default") dfault = &(*index)->CmdName(); else if (ctext == context) local = &(*index)->CmdName(); } if (local) { LOG_D("agi/hotkey/found") << "Found: " << str << " Context: " << context << " Command: " << *local; return *local; } if (dfault) { LOG_D("agi/hotkey/found") << "Found: " << str << " Context (req/found): " << context << "/Default Command: " << *dfault; return *dfault; } return ""; } bool Hotkey::HasHotkey(std::string const& context, std::string const& str) const { std::vector::const_iterator index, end; for (std::tie(index, end) = boost::equal_range(str_map, str, combo_cmp()); index != end; ++index) { if (context == (*index)->Context()) return true; } return false; } std::vector Hotkey::GetHotkeys(std::string const& context, std::string const& command) const { std::vector ret; HotkeyMap::const_iterator it, end; for (std::tie(it, end) = cmd_map.equal_range(command); it != end; ++it) { std::string const& ctext = it->second.Context(); if (ctext == "Always" || ctext == "Default" || ctext == context) ret.emplace_back(it->second.Str()); } sort(ret.begin(), ret.end()); ret.erase(unique(ret.begin(), ret.end()), ret.end()); return ret; } std::string Hotkey::GetHotkey(std::string const& context, std::string const& command) const { std::string ret; HotkeyMap::const_iterator it, end; for (std::tie(it, end) = cmd_map.equal_range(command); it != end; ++it) { std::string const& ctext = it->second.Context(); if (ctext == context) return it->second.Str(); if (ctext == "Default") ret = it->second.Str(); else if (ret.empty() && ctext == "Always") ret = it->second.Str(); } return ret; } void Hotkey::Flush() { json::Object root; for (auto const& combo : str_map) { auto const& keys = combo->Str(); if (keys.empty()) continue; json::Object& context = root[combo->Context()]; json::Array& combo_array = context[combo->CmdName()]; combo_array.push_back(keys); } if (backup_config_file && fs::FileExists(config_file) && !fs::FileExists(config_file.string() + ".3_1")) fs::Copy(config_file, config_file.string() + ".3_1"); io::Save file(config_file); JsonWriter::Write(root, file.Get()); } void Hotkey::UpdateStrMap() { str_map.clear(); str_map.reserve(cmd_map.size()); for (auto const& combo : cmd_map) str_map.push_back(&combo.second); sort(begin(str_map), end(str_map), combo_cmp()); } void Hotkey::SetHotkeyMap(HotkeyMap new_map) { cmd_map = std::move(new_map); UpdateStrMap(); Flush(); HotkeysChanged(); } } }