/* * File: macro.cc * Summary: Crude macro-capability * Written by: Juho Snellman */ /* * The macro-implementation works like this: * - For generic game code, #define getch() getchm(). * - getchm() works by reading characters from an internal * buffer. If none are available, new characters are read into * the buffer with _getch_mul(). * - _getch_mul() reads at least one character, but will read more * if available (determined using kbhit(), which should be defined * in the platform specific libraries). * - Before adding the characters read into the buffer, any macros * in the sequence are replaced (see macro_add_buf_long for the * details). * * (When the above text mentions characters, it actually means int). */ #include "AppHdr.h" #define MACRO_CC #include "macro.h" #include #include #include #include #include #include #include #include // for snprintf #include // for tolower #include #include "cio.h" #include "externs.h" #include "options.h" #include "message.h" #include "state.h" #include "stuff.h" // for trim_string: #include "initfile.h" typedef std::deque keybuf; typedef std::map macromap; static macromap Keymaps[KMC_CONTEXT_COUNT]; static macromap Macros; static macromap *all_maps[] = { &Keymaps[KMC_DEFAULT], &Keymaps[KMC_LEVELMAP], &Keymaps[KMC_TARGETTING], &Keymaps[KMC_CONFIRM], &Macros, }; static keybuf Buffer; #define USERFUNCBASE -10000 static std::vector userfunctions; static std::vector recorders; typedef std::map name_to_cmd_map; typedef std::map cmd_to_name_map; struct command_name { command_type cmd; const char* name; }; static command_name _command_name_list[] = { #include "cmd-name.h" }; static name_to_cmd_map _names_to_cmds; static cmd_to_name_map _cmds_to_names; struct default_binding { int key; command_type cmd; }; static default_binding _default_binding_list[] = { #include "cmd-keys.h" }; typedef std::map key_to_cmd_map; typedef std::map cmd_to_key_map; static key_to_cmd_map _keys_to_cmds[KMC_CONTEXT_COUNT]; static cmd_to_key_map _cmds_to_keys[KMC_CONTEXT_COUNT]; inline int userfunc_index(int key) { int index = (key <= USERFUNCBASE? USERFUNCBASE - key : -1); return (index < 0 || index >= (int) userfunctions.size()? -1 : index); } static int userfunc_index(const keyseq &seq) { if (seq.empty()) return (-1); return userfunc_index(seq.front()); } bool is_userfunction(int key) { return (userfunc_index(key) != -1); } static bool is_userfunction(const keyseq &seq) { return (userfunc_index(seq) != -1); } std::string get_userfunction(int key) { int index = userfunc_index(key); return (index == -1 ? NULL : userfunctions[index]); } static std::string get_userfunction(const keyseq &seq) { int index = userfunc_index(seq); return (index == -1 ? NULL : userfunctions[index]); } static bool userfunc_referenced(int index, const macromap &mm) { for (macromap::const_iterator i = mm.begin(); i != mm.end(); i++) { if (userfunc_index(i->second) == index) return (true); } return (false); } static bool userfunc_referenced(int index) { for (unsigned i = 0; i < sizeof(all_maps) / sizeof(*all_maps); ++i) { macromap *m = all_maps[i]; if (userfunc_referenced(index, *m)) return (true); } return (false); } // Expensive function to discard unused function names static void userfunc_collectgarbage(void) { for (int i = userfunctions.size() - 1; i >= 0; --i) { if (!userfunctions.empty() && !userfunc_referenced(i)) userfunctions[i].clear(); } } static int userfunc_getindex(const std::string &fname) { if (fname.length() == 0) return (-1); userfunc_collectgarbage(); // Pass 1 to see if we have this string already for (int i = 0, count = userfunctions.size(); i < count; ++i) { if (userfunctions[i] == fname) return (i); } // Pass 2 to hunt for gaps. for (int i = 0, count = userfunctions.size(); i < count; ++i) { if (userfunctions[i].empty()) { userfunctions[i] = fname; return (i); } } userfunctions.push_back(fname); return (userfunctions.size() - 1); } // Returns the name of the file that contains macros. static std::string get_macro_file() { std::string dir = !Options.macro_dir.empty() ? Options.macro_dir : !SysEnv.crawl_dir.empty() ? SysEnv.crawl_dir : ""; if (!dir.empty()) { #ifndef DGL_MACRO_ABSOLUTE_PATH if (dir[dir.length() - 1] != FILE_SEPARATOR) dir += FILE_SEPARATOR; #endif } #if defined(DGL_MACRO_ABSOLUTE_PATH) return (dir.empty()? "macro.txt" : dir); #elif defined(DGL_NAMED_MACRO_FILE) return (dir + strip_filename_unsafe_chars(you.your_name) + "-macro.txt"); #else return (dir + "macro.txt"); #endif } static void buf2keyseq(const char *buff, keyseq &k) { // Sanity check if (!buff) return; // convert c_str to keyseq // Check for user functions if (*buff == '=' && buff[1] == '=' && buff[2] == '=' && buff[3]) { int index = userfunc_getindex(buff + 3); if (index != -1) k.push_back( USERFUNCBASE - index ); } else { const int len = strlen( buff ); for (int i = 0; i < len; i++) k.push_back( buff[i] ); } } static int read_key_code(std::string s) { if (s.empty()) return (0); int base = 10; if (s[0] == 'x') { s = s.substr(1); base = 16; } else if (s[0] == '^') { // ^A = 1, etc. return (1 + toupper(s[1]) - 'A'); } char *tail; return strtol(s.c_str(), &tail, base); } /* * Takes as argument a string, and returns a sequence of keys described * by the string. Most characters produce their own ASCII code. These * are the cases: * \\ produces the ASCII code of a single \ * \{123} produces 123 (decimal) * \{^A} produces 1 (Ctrl-A) * \{x40} produces 64 (hexadecimal code) * \{!more} or \{!m} disables -more- prompt until the end of the macro. */ static keyseq parse_keyseq( std::string s ) { int state = 0; keyseq v; int num; if (s.find("===") == 0) { buf2keyseq(s.c_str(), v); return (v); } bool more_reset = false; for (int i = 0, size = s.length(); i < size; ++i) { char c = s[i]; switch (state) { case 0: // Normal state if (c == '\\') state = 1; else v.push_back(c); break; case 1: // Last char is a '\' if (c == '\\') { state = 0; v.push_back(c); } else if (c == '{') { state = 2; num = 0; } // XXX Error handling break; case 2: // Inside \{} { const std::string::size_type clb = s.find('}', i); if (clb == std::string::npos) break; const std::string arg = s.substr(i, clb - i); if (!more_reset && (arg == "!more" || arg == "!m")) { more_reset = true; v.push_back(KEY_MACRO_MORE_PROTECT); } else { const int key = read_key_code(arg); v.push_back(key); } state = 0; i = clb; break; } } } return (v); } /* * Serialises a key sequence into a string of the format described * above. */ static std::string vtostr( const keyseq &seq ) { std::ostringstream s; const keyseq *v = &seq; keyseq dummy; if (is_userfunction(seq)) { // Laboriously reconstruct the original user input std::string newseq = std::string("==") + get_userfunction(seq); buf2keyseq(newseq.c_str(), dummy); dummy.push_front('='); v = &dummy; } for (keyseq::const_iterator i = v->begin(); i != v->end(); i++) { if (*i <= 32 || *i > 127) { if (*i == KEY_MACRO_MORE_PROTECT) s << "\\{!more}"; else { char buff[20]; snprintf( buff, sizeof(buff), "\\{%d}", *i ); s << buff; } } else if (*i == '\\') s << "\\\\"; else s << static_cast(*i); } return (s.str()); } /* * Add a macro (suprise, suprise). */ static void macro_add( macromap &mapref, keyseq key, keyseq action ) { mapref[key] = action; } static void macro_add( macromap &mapref, keyseq key, const char *buff ) { keyseq act; buf2keyseq(buff, act); if (!act.empty()) macro_add( mapref, key, act ); } /* * Remove a macro. */ static void macro_del( macromap &mapref, keyseq key ) { mapref.erase( key ); } /* * Adds keypresses from a sequence into the internal keybuffer. Ignores * macros. */ void macro_buf_add( const keyseq &actions, bool reverse) { keyseq act; bool need_more_reset = false; for (keyseq::const_iterator i = actions.begin(); i != actions.end(); ++i) { int key = *i; if (key == KEY_MACRO_MORE_PROTECT) { key = KEY_MACRO_DISABLE_MORE; need_more_reset = true; } act.push_back(key); } if (need_more_reset) act.push_back(KEY_MACRO_ENABLE_MORE); Buffer.insert( reverse? Buffer.begin() : Buffer.end(), act.begin(), act.end() ); } /* * Adds a single keypress into the internal keybuffer. */ void macro_buf_add( int key, bool reverse ) { if (reverse) Buffer.push_front( key ); else Buffer.push_back( key ); } /* * Add a command into the internal keybuffer. */ void macro_buf_add_cmd(command_type cmd, bool reverse) { ASSERT(cmd > CMD_NO_CMD && cmd < CMD_MIN_SYNTHETIC); // There should be plenty of room between the synthetic keys // (KEY_MACRO_MORE_PROTECT == -10) and USERFUNCBASE (-10000) for // command_type to fit (currently 1000 through 2069). macro_buf_add( -((int) cmd), reverse); } /* * Adds keypresses from a sequence into the internal keybuffer. Does some * O(N^2) analysis to the sequence to replace macros. */ static void macro_buf_add_long( keyseq actions, macromap &keymap = Keymaps[KMC_DEFAULT] ) { keyseq tmp; // debug << "Adding: " << vtostr(actions) << endl; // debug.flush(); // Check whether any subsequences of the sequence are macros. // The matching starts from as early as possible, and is // as long as possible given the first constraint. I.e from // the sequence "abcdef" and macros "ab", "bcde" and "de" // "ab" and "de" are recognised as macros. while (actions.size() > 0) { tmp = actions; while (tmp.size() > 0) { keyseq result = keymap[tmp]; // Found a macro. Add the expansion (action) of the // macro into the buffer. if (result.size() > 0) { macro_buf_add( result ); break; } // Didn't find a macro. Remove a key from the end // of the sequence, and try again. tmp.pop_back(); } if (tmp.size() == 0) { // Didn't find a macro. Add the first keypress of the sequence // into the buffer, remove it from the sequence, and try again. macro_buf_add( actions.front() ); actions.pop_front(); } else { // Found a macro, which has already been added above. Now just // remove the macroed keys from the sequence. for (unsigned int i = 0; i < tmp.size(); i++) actions.pop_front(); } } } static int macro_keys_left = -1; bool is_processing_macro() { return (macro_keys_left >= 0); } /* * Command macros are only applied from the immediate front of the * buffer, and only when the game is expecting a command. */ static void macro_buf_apply_command_macro( void ) { keyseq tmp = Buffer; // find the longest match from the start of the buffer and replace it while (tmp.size() > 0) { const keyseq &result = Macros[tmp]; if (result.size() > 0) { // Found macro, remove match from front: for (unsigned int i = 0; i < tmp.size(); i++) { Buffer.pop_front(); if (macro_keys_left >= 0) macro_keys_left--; } if (macro_keys_left == -1) macro_keys_left = 0; macro_keys_left += result.size(); macro_buf_add(result, true); break; } tmp.pop_back(); } } /* * Removes the earliest keypress from the keybuffer, and returns its * value. If buffer was empty, returns -1; */ static int macro_buf_get( void ) { if (Buffer.size() == 0) { // If we're trying to fetch a new keystroke, then the processing // of the previous keystroke is complete. if (macro_keys_left == 0) macro_keys_left = -1; return (-1); } int key = Buffer.front(); Buffer.pop_front(); if (macro_keys_left >= 0) macro_keys_left--; for (int i = 0, size_i = recorders.size(); i < size_i; i++) recorders[i]->add_key(key); return (key); } static void write_map(std::ofstream &f, const macromap &mp, const char *key) { for (macromap::const_iterator i = mp.begin(); i != mp.end(); i++) { // Need this check, since empty values are added into the // macro struct for all used keyboard commands. if (i->second.size()) { f << key << vtostr((*i).first) << std::endl << "A:" << vtostr((*i).second) << std::endl << std::endl; } } } /* * Saves macros into the macrofile, overwriting the old one. */ void macro_save() { std::ofstream f; const std::string macrofile = get_macro_file(); f.open(macrofile.c_str()); if (!f) { mprf(MSGCH_ERROR, "Couldn't open %s for writing!", macrofile.c_str()); return; } f << "# WARNING: This file is entirely auto-generated." << std::endl << std::endl << "# Key Mappings:" << std::endl; for (int mc = KMC_DEFAULT; mc < KMC_CONTEXT_COUNT; ++mc) { char keybuf[30] = "K:"; if (mc) snprintf(keybuf, sizeof keybuf, "K%d:", mc); write_map(f, Keymaps[mc], keybuf); } f << "# Command Macros:" << std::endl; write_map(f, Macros, "M:"); f.close(); } /* * Reads as many keypresses as are available (waiting for at least one), * and returns them as a single keyseq. */ static keyseq _getch_mul( int (*rgetch)() = NULL ) { keyseq keys; int a; // Something's gone wrong with replaying keys if crawl needs to // get new keys from the user. if (crawl_state.is_replaying_keys()) { mpr("(Key replay ran out of keys)", MSGCH_ERROR); crawl_state.cancel_cmd_repeat(); crawl_state.cancel_cmd_again(); } if (!rgetch) rgetch = m_getch; keys.push_back( a = rgetch() ); // The a == 0 test is legacy code that I don't dare to remove. I // have a vague recollection of it being a kludge for conio support. while (kbhit() || a == 0) keys.push_back( a = rgetch() ); return (keys); } /* * Replacement for getch(). Returns keys from the key buffer if available. * If not, adds some content to the buffer, and returns some of it. */ int getchm( int (*rgetch)() ) { flush_prev_message(); return getchm( KMC_DEFAULT, rgetch ); } int getchm(KeymapContext mc, int (*rgetch)()) { int a; // Got data from buffer. if ((a = macro_buf_get()) != -1) return (a); // Read some keys... keyseq keys = _getch_mul(rgetch); if (mc == KMC_NONE) macro_buf_add(keys); else macro_buf_add_long(keys, Keymaps[mc]); return (macro_buf_get()); } /* * Replacement for getch(). Returns keys from the key buffer if available. * If not, adds some content to the buffer, and returns some of it. */ int getch_with_command_macros( void ) { if (Buffer.size() == 0) { // Read some keys... keyseq keys = _getch_mul(); // ... and add them into the buffer (apply keymaps) macro_buf_add_long( keys ); // Apply longest matching macro at front of buffer: macro_buf_apply_command_macro(); } return (macro_buf_get()); } /* * Flush the buffer. Later we'll probably want to give the player options * as to when this happens (ex. always before command input, casting failed). */ void flush_input_buffer( int reason ) { ASSERT(reason != FLUSH_KEY_REPLAY_CANCEL || crawl_state.is_replaying_keys() || crawl_state.cmd_repeat_start); ASSERT(reason != FLUSH_ABORT_MACRO || is_processing_macro()); // Any attempt to flush means that the processing of the previously // fetched keystroke is complete. if (macro_keys_left == 0) macro_keys_left = -1; if (crawl_state.is_replaying_keys() && reason != FLUSH_ABORT_MACRO && reason != FLUSH_KEY_REPLAY_CANCEL && reason != FLUSH_REPLAY_SETUP_FAILURE) { return; } if (Options.flush_input[ reason ] || reason == FLUSH_ABORT_MACRO || reason == FLUSH_KEY_REPLAY_CANCEL || reason == FLUSH_REPLAY_SETUP_FAILURE) { while (!Buffer.empty()) { const int key = Buffer.front(); Buffer.pop_front(); if (key == KEY_MACRO_ENABLE_MORE) Options.show_more_prompt = true; } macro_keys_left = -1; } } void macro_add_query( void ) { int input; bool keymap = false; KeymapContext keymc = KMC_DEFAULT; mesclr(); mpr("(m)acro, keymap " "[(k) default, (x) level-map, (t)argeting, (c)onfirm, m(e)nu], " "(s)ave? ", MSGCH_PROMPT); input = m_getch(); input = tolower( input ); if (input == 'k') { keymap = true; keymc = KMC_DEFAULT; } else if (input == 'x') { keymap = true; keymc = KMC_LEVELMAP; } else if (input == 't') { keymap = true; keymc = KMC_TARGETTING; } else if (input == 'c') { keymap = true; keymc = KMC_CONFIRM; } else if (input == 'e') { keymap = true; keymc = KMC_MENU; } else if (input == 'm') keymap = false; else if (input == 's') { mpr("Saving macros."); macro_save(); return; } else { mpr( "Aborting." ); return; } // reference to the appropriate mapping macromap &mapref = (keymap ? Keymaps[keymc] : Macros); mprf(MSGCH_PROMPT, "Input %s%s trigger key: ", keymap ? (keymc == KMC_DEFAULT ? "default " : keymc == KMC_LEVELMAP ? "level-map " : keymc == KMC_TARGETTING ? "targetting " : keymc == KMC_CONFIRM ? "confirm " : keymc == KMC_MENU ? "menu " : "buggy") : "", (keymap ? "keymap" : "macro") ); keyseq key; mouse_control mc(MOUSE_MODE_MACRO); key = _getch_mul(); cprintf( "%s" EOL, (vtostr( key )).c_str() ); // echo key to screen if (mapref[key].size() > 0) { mprf(MSGCH_WARN, "Current Action: %s", vtostr(mapref[key]).c_str()); mpr("Do you wish to (r)edefine, (c)lear, or (a)bort? ", MSGCH_PROMPT); input = m_getch(); input = tolower( input ); if (input == 'a' || input == ESCAPE) { mpr( "Aborting." ); return; } else if (input == 'c') { mpr( "Cleared." ); macro_del( mapref, key ); return; } } mpr( "Input Macro Action: ", MSGCH_PROMPT ); // Using _getch_mul() here isn't very useful... We'd like the // flexibility to define multicharacter macros without having // to resort to editing files and restarting the game. -- bwr // keyseq act = _getch_mul(); char buff[4096]; get_input_line(buff, sizeof buff); if (Options.macro_meta_entry) macro_add( mapref, key, parse_keyseq(buff) ); else macro_add( mapref, key, buff ); redraw_screen(); } /* * Initialises the macros. */ static void _read_macros_from(const char* filename) { std::string s; std::ifstream f; keyseq key, action; bool keymap = false; KeymapContext keymc = KMC_DEFAULT; f.open( filename ); while (f >> s) { trim_string(s); // remove white space from ends if (s[0] == '#') continue; // skip comments else if (s.substr(0, 2) == "K:") { key = parse_keyseq(s.substr(2)); keymap = true; keymc = KMC_DEFAULT; } else if (s.length() >= 3 && s[0] == 'K' && s[2] == ':') { keymc = KeymapContext( KMC_DEFAULT + s[1] - '0' ); if (keymc >= KMC_DEFAULT && keymc < KMC_CONTEXT_COUNT) { key = parse_keyseq(s.substr(3)); keymap = true; } } else if (s.substr(0, 2) == "M:") { key = parse_keyseq(s.substr(2)); keymap = false; } else if (s.substr(0, 2) == "A:") { action = parse_keyseq(s.substr(2)); macro_add( (keymap ? Keymaps[keymc] : Macros), key, action ); } } } void macro_init() { const std::vector& files = Options.additional_macro_files; for (std::vector::const_iterator it = files.begin(); it != files.end(); ++it) { _read_macros_from(it->c_str()); } _read_macros_from(get_macro_file().c_str()); } void macro_userfn(const char *keys, const char *regname) { // TODO: Implement. // Converting 'keys' to a key sequence is the difficulty. Doing it portably // requires a mapping of key names to whatever getch() spits back, unlikely // to happen in a hurry. } bool is_synthetic_key(int key) { switch (key) { case KEY_MACRO_ENABLE_MORE: case KEY_MACRO_DISABLE_MORE: case KEY_MACRO_MORE_PROTECT: case KEY_REPEAT_KEYS: return (true); default: return (false); } } key_recorder::key_recorder(key_recorder_callback cb, void* cb_data) : paused(false), call_back(cb), call_back_data(cb_data) { keys.clear(); macro_trigger_keys.clear(); } void key_recorder::add_key(int key, bool reverse) { if (paused) return; if (call_back) { // Don't record key if true if ((*call_back)(this, key, reverse)) return; } if (reverse) keys.push_front(key); else keys.push_back(key); } void key_recorder::remove_trigger_keys(int num_keys) { ASSERT(num_keys >= 1); if (paused) return; for (int i = 0; i < num_keys; i++) { ASSERT(keys.size() >= 1); int key = keys[keys.size() - 1]; if (call_back) { // Key wasn't recorded in the first place, so no need to remove // it if ((*call_back)(this, key, true)) continue; } macro_trigger_keys.push_front(key); keys.pop_back(); } } void key_recorder::clear() { keys.clear(); macro_trigger_keys.clear(); } pause_all_key_recorders::pause_all_key_recorders() { for (unsigned int i = 0; i < recorders.size(); i++) { prev_pause_status.push_back(recorders[i]->paused); recorders[i]->paused = true; } } pause_all_key_recorders::~pause_all_key_recorders() { for (unsigned int i = 0; i < recorders.size(); i++) recorders[i]->paused = prev_pause_status[i]; } void add_key_recorder(key_recorder* recorder) { for (int i = 0, size = recorders.size(); i < size; i++) ASSERT(recorders[i] != recorder); recorders.push_back(recorder); } void remove_key_recorder(key_recorder* recorder) { std::vector::iterator i; for (i = recorders.begin(); i != recorders.end(); i++) if (*i == recorder) { recorders.erase(i); return; } end(1, true, "remove_key_recorder(): recorder not found\n"); } // Add macro trigger keys to beginning of the buffer, then expand // them. void insert_macro_into_buff(const keyseq& keys) { for (int i = (int) keys.size() - 1; i >= 0; i--) macro_buf_add(keys[i], true); macro_buf_apply_command_macro(); } int get_macro_buf_size() { return (Buffer.size()); } /////////////////////////////////////////////////////////////// // Keybinding stuff #define VALID_BIND_COMMAND(cmd) (cmd > CMD_NO_CMD && cmd < CMD_MIN_SYNTHETIC) void init_keybindings() { int i; for (i = 0; _command_name_list[i].cmd != CMD_NO_CMD && _command_name_list[i].name != NULL; i++) { command_name &data = _command_name_list[i]; ASSERT(VALID_BIND_COMMAND(data.cmd)); ASSERT(_names_to_cmds.find(data.name) == _names_to_cmds.end()); ASSERT(_cmds_to_names.find(data.cmd) == _cmds_to_names.end()); _names_to_cmds[data.name] = data.cmd; _cmds_to_names[data.cmd] = data.name; } ASSERT(i >= 130); for (i = 0; _default_binding_list[i].cmd != CMD_NO_CMD && _default_binding_list[i].key != '\0'; i++) { default_binding &data = _default_binding_list[i]; ASSERT(VALID_BIND_COMMAND(data.cmd)); KeymapContext context = context_for_command(data.cmd); ASSERT(context < KMC_CONTEXT_COUNT); key_to_cmd_map &key_map = _keys_to_cmds[context]; cmd_to_key_map &cmd_map = _cmds_to_keys[context]; // Only one command per key, but it's okay to have several // keys map to the same command. ASSERT(key_map.find(data.key) == key_map.end()); key_map[data.key] = data.cmd; cmd_map[data.cmd] = data.key; } ASSERT(i >= 130); } command_type name_to_command(std::string name) { name_to_cmd_map::iterator it = _names_to_cmds.find(name); if (it == _names_to_cmds.end()) return (CMD_NO_CMD); return static_cast(it->second); } std::string command_to_name(command_type cmd) { cmd_to_name_map::iterator it = _cmds_to_names.find(cmd); if (it == _cmds_to_names.end()) return ("CMD_NO_CMD"); return (it->second); } command_type key_to_command(int key, KeymapContext context) { if (-key > CMD_NO_CMD && -key < CMD_MIN_SYNTHETIC) { command_type cmd = (command_type) -key; KeymapContext cmd_context = context_for_command(cmd); if (cmd == CMD_NO_CMD) return (CMD_NO_CMD); if (cmd_context != context) { mprf(MSGCH_ERROR, "key_to_command(): command '%s' (%d:%d) wrong for desired " "context", command_to_name(cmd).c_str(), -key - CMD_NO_CMD, CMD_MAX_CMD + key); if (is_processing_macro()) flush_input_buffer(FLUSH_ABORT_MACRO); if (crawl_state.is_replaying_keys() || crawl_state.cmd_repeat_start) { flush_input_buffer(FLUSH_KEY_REPLAY_CANCEL); } flush_input_buffer(FLUSH_BEFORE_COMMAND); return (CMD_NO_CMD); } return (cmd); } key_to_cmd_map &key_map = _keys_to_cmds[context]; key_to_cmd_map::iterator it = key_map.find(key); if (it == key_map.end()) return CMD_NO_CMD; command_type cmd = static_cast(it->second); ASSERT(context_for_command(cmd) == context); return cmd; } int command_to_key(command_type cmd) { KeymapContext context = context_for_command(cmd); if (context == KMC_NONE) return ('\0'); cmd_to_key_map &cmd_map = _cmds_to_keys[context]; cmd_to_key_map::iterator it = cmd_map.find(cmd); if (it == cmd_map.end()) return ('\0'); return (it->second); } KeymapContext context_for_command(command_type cmd) { if (cmd > CMD_NO_CMD && cmd <= CMD_MAX_NORMAL) return KMC_DEFAULT; if (cmd >= CMD_MIN_OVERMAP && cmd <= CMD_MAX_OVERMAP) return KMC_LEVELMAP; if (cmd >= CMD_MIN_TARGET && cmd <= CMD_MAX_TARGET) return KMC_TARGETTING; #ifdef USE_TILE if (cmd >= CMD_MIN_DOLL && cmd <= CMD_MAX_DOLL) return KMC_DOLL; #endif return KMC_NONE; } void bind_command_to_key(command_type cmd, int key) { KeymapContext context = context_for_command(cmd); std::string command_name = command_to_name(cmd); if (context == KMC_NONE || command_name == "CMD_NO_CMD" || !VALID_BIND_COMMAND(cmd)) { if (command_name == "CMD_NO_CMD") { mprf(MSGCH_ERROR, "Cannot bind command #%d to a key.", (int) cmd); return; } mprf(MSGCH_ERROR, "Cannot bind command '%s' to a key.", command_name.c_str()); return; } if (is_userfunction(key)) { mpr("Cannot bind user function keys to a command.", MSGCH_ERROR); return; } if (is_synthetic_key(key)) { mpr("Cannot bind synthetic keys to a command.", MSGCH_ERROR); return; } // We're good. key_to_cmd_map &key_map = _keys_to_cmds[context]; cmd_to_key_map &cmd_map = _cmds_to_keys[context]; key_map[key] = cmd; cmd_map[cmd] = key; } static std::string _special_keys_to_string(int key) { const bool shift = (key >= CK_SHIFT_UP && key <= CK_SHIFT_PGDN); const bool ctrl = (key >= CK_CTRL_UP && key <= CK_CTRL_PGDN); std::string cmd = ""; if (shift) { key -= (CK_SHIFT_UP - CK_UP); cmd = "Shift-"; } else if (ctrl) { key -= (CK_CTRL_UP - CK_UP); cmd = "Ctrl-"; } switch (key) { case CK_ENTER: cmd += "Enter"; break; case CK_BKSP: cmd += "Backspace"; break; case CK_ESCAPE: cmd += "Esc"; break; case CK_DELETE: cmd += "Del"; break; case CK_UP: cmd += "Up"; break; case CK_DOWN: cmd += "Down"; break; case CK_LEFT: cmd += "Left"; break; case CK_RIGHT: cmd += "Right"; break; case CK_INSERT: cmd += "Ins"; break; case CK_HOME: cmd += "Home"; break; case CK_END: cmd += "End"; break; case CK_CLEAR: cmd += "Clear"; break; case CK_PGUP: cmd += "PgUp"; break; case CK_PGDN: cmd += "PgDn"; break; } return (cmd); } std::string command_to_string(command_type cmd) { const int key = command_to_key(cmd); const std::string desc = _special_keys_to_string(key); if (!desc.empty()) return (desc); if (key >= 32 && key < 256) snprintf(info, INFO_SIZE, "%c", (char) key); else if (key > 1000 && key <= 1009) { const int numpad = (key - 1000); snprintf(info, INFO_SIZE, "Numpad %d", numpad); } else { const int ch = key + 'A' - 1; if (ch >= 'A' && ch <= 'Z') snprintf(info, INFO_SIZE, "Ctrl-%c", (char) ch); else snprintf(info, INFO_SIZE, "%d", key); } std::string result = info; return (result); } void list_all_commands(std::string &commands) { for (int i = CMD_NO_CMD; i < CMD_MAX_CMD; i++) { const command_type cmd = (command_type) i; const std::string command_name = command_to_name(cmd); if (command_name == "CMD_NO_CMD") continue; if (context_for_command(cmd) != KMC_DEFAULT) continue; snprintf(info, INFO_SIZE, "%s: %s\n", command_name.c_str(), command_to_string(cmd).c_str()); commands += info; } commands += "\n"; }