/*
* File: macro.cc
* Summary: Crude macro-capability
* Written by: Juho Snellman <jsnell@lyseo.edu.ouka.fi>
*/
/*
* 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 <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <map>
#include <deque>
#include <vector>
#include <cstdio> // for snprintf
#include <cctype> // for tolower
#include <cstdlib>
#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<int> keybuf;
typedef std::map<keyseq,keyseq> 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<std::string> userfunctions;
static std::vector<key_recorder*> recorders;
typedef std::map<std::string, int> name_to_cmd_map;
typedef std::map<int, std::string> 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<int, int> key_to_cmd_map;
typedef std::map<int, int> 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<char>(*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<std::string>& files = Options.additional_macro_files;
for (std::vector<std::string>::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<key_recorder*>::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<command_type>(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<command_type>(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";
}