diff options
Diffstat (limited to 'src/emu/inputseq.c')
-rw-r--r-- | src/emu/inputseq.c | 630 |
1 files changed, 630 insertions, 0 deletions
diff --git a/src/emu/inputseq.c b/src/emu/inputseq.c new file mode 100644 index 00000000000..d979f2d5434 --- /dev/null +++ b/src/emu/inputseq.c @@ -0,0 +1,630 @@ +/*************************************************************************** + + inputseq.c + + Input sequence abstractions. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include "inputseq.h" +#include "restrack.h" +#include <ctype.h> + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* max time between key presses */ +#define RECORD_TIME (osd_ticks_per_second() * 2 / 3) + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +/* information about the current sequence being recorded */ +static input_seq record_seq; +static osd_ticks_t record_last; +static input_item_class record_class; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +/* internal functions */ +static int input_seq_is_valid(const input_seq *seq); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + input_seq_length - return the length of the + sequence +-------------------------------------------------*/ + +INLINE int input_seq_length(const input_seq *seq) +{ + int seqnum; + + /* find the end token; error if none found */ + for (seqnum = 0; seqnum < ARRAY_LENGTH(seq->code); seqnum++) + if (seq->code[seqnum] == SEQCODE_END) + return seqnum; + return ARRAY_LENGTH(seq->code); +} + + +/*------------------------------------------------- + input_seq_append - append a code to the end + of an input sequence +-------------------------------------------------*/ + +INLINE int input_seq_append(input_seq *seq, input_code code) +{ + int length = input_seq_length(seq); + + /* if not enough room, return FALSE */ + if (length >= ARRAY_LENGTH(seq->code) - 1) + return FALSE; + + /* otherwise, append the code and add a new end */ + seq->code[length++] = code; + seq->code[length] = SEQCODE_END; + return TRUE; +} + + +/*------------------------------------------------- + input_seq_get_last - return the last code + in a sequence +-------------------------------------------------*/ + +INLINE input_code input_seq_get_last(const input_seq *seq) +{ + int length = input_seq_length(seq); + return (length == 0) ? SEQCODE_END : seq->code[length - 1]; +} + + +/*------------------------------------------------- + input_seq_backspace - "backspace" over the + last entry in a sequence +-------------------------------------------------*/ + +INLINE void input_seq_backspace(input_seq *seq) +{ + int length = input_seq_length(seq); + + /* if we have at least one entry, remove it */ + if (length > 0) + seq->code[length - 1] = SEQCODE_END; +} + + + +/*************************************************************************** + STATE QUERIES +***************************************************************************/ + +/*------------------------------------------------- + input_seq_pressed - return true if the given + sequence of switch inputs is "pressed" +-------------------------------------------------*/ + +int input_seq_pressed(const input_seq *seq) +{ + int result = FALSE; + int invert = FALSE; + int first = TRUE; + int codenum; + + /* iterate over all of the codes */ + for (codenum = 0; codenum < ARRAY_LENGTH(seq->code); codenum++) + { + input_code code = seq->code[codenum]; + + /* handle NOT */ + if (code == SEQCODE_NOT) + invert = TRUE; + + /* handle OR and END */ + else if (code == SEQCODE_OR || code == SEQCODE_END) + { + /* if we have a positive result from the previous set, we're done */ + if (result || code == SEQCODE_END) + break; + + /* otherwise, reset our state */ + result = FALSE; + invert = FALSE; + first = TRUE; + } + + /* handle everything else as a series of ANDs */ + else + { + /* if this is the first in the sequence, result is set equal */ + if (first) + result = input_code_pressed(code) ^ invert; + + /* further values are ANDed */ + else if (result) + result &= input_code_pressed(code) ^ invert; + + /* no longer first, and clear the invert flag */ + first = invert = FALSE; + } + } + + /* return the result if we queried at least one switch */ + return result; +} + + +/*------------------------------------------------- + input_seq_axis_value - return the value of an + axis defined in an input sequence +-------------------------------------------------*/ + +INT32 input_seq_axis_value(const input_seq *seq, input_item_class *itemclass_ptr) +{ + input_item_class itemclasszero = ITEM_CLASS_ABSOLUTE; + input_item_class itemclass = ITEM_CLASS_INVALID; + int result = 0; + int invert = FALSE; + int enable = TRUE; + int codenum; + + /* iterate over all of the codes */ + for (codenum = 0; codenum < ARRAY_LENGTH(seq->code); codenum++) + { + input_code code = seq->code[codenum]; + + /* handle NOT */ + if (code == SEQCODE_NOT) + invert = TRUE; + + /* handle OR and END */ + else if (code == SEQCODE_OR || code == SEQCODE_END) + { + /* if we have a positive result from the previous set, we're done */ + if (itemclass != ITEM_CLASS_INVALID || code == SEQCODE_END) + break; + + /* otherwise, reset our state */ + result = 0; + invert = FALSE; + enable = TRUE; + } + + /* handle everything else only if we're still enabled */ + else if (enable) + { + /* switch codes serve as enables */ + if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_SWITCH) + { + /* AND against previous digital codes */ + if (enable) + enable &= input_code_pressed(code) ^ invert; + } + + /* non-switch codes are analog values */ + else + { + INT32 value = input_code_value(code); + + /* if we got a 0 value, don't do anything except remember the first type */ + if (value == 0) + { + if (itemclasszero == ITEM_CLASS_INVALID) + itemclasszero = INPUT_CODE_ITEMCLASS(code); + } + + /* non-zero absolute values stick */ + else if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_ABSOLUTE) + { + itemclass = ITEM_CLASS_ABSOLUTE; + result = value; + } + + /* non-zero relative values accumulate */ + else if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_RELATIVE) + { + itemclass = ITEM_CLASS_RELATIVE; + result += value; + } + } + + /* clear the invert flag */ + invert = FALSE; + } + } + + /* if the caller wants to know the type, provide it */ + if (itemclass_ptr != NULL) + *itemclass_ptr = (result == 0) ? itemclasszero : itemclass; + return result; +} + + + +/*************************************************************************** + SEQUENCE POLLING +***************************************************************************/ + +/*------------------------------------------------- + input_seq_poll_start - begin polling for a + new sequence of the given itemclass +-------------------------------------------------*/ + +void input_seq_poll_start(input_item_class itemclass, const input_seq *startseq) +{ + input_code dummycode; + + assert(itemclass == ITEM_CLASS_SWITCH || itemclass == ITEM_CLASS_ABSOLUTE || itemclass == ITEM_CLASS_RELATIVE); + + /* reset the recording count and the clock */ + record_last = 0; + record_class = itemclass; + + /* grab the starting sequence to append to */ + if (startseq != NULL) + record_seq = *startseq; + else + input_seq_set_0(&record_seq); + + /* append an OR if this is not a NULL sequence */ + if (input_seq_length(&record_seq) > 0) + input_seq_append(&record_seq, SEQCODE_OR); + + /* flush out any goobers */ + dummycode = (record_class == ITEM_CLASS_SWITCH) ? input_code_poll_switches(TRUE) : input_code_poll_axes(TRUE); + while (dummycode != INPUT_CODE_INVALID) + dummycode = (record_class == ITEM_CLASS_SWITCH) ? input_code_poll_switches(FALSE) : input_code_poll_axes(FALSE); +} + + +/*------------------------------------------------- + input_seq_poll - continue polling +-------------------------------------------------*/ + +int input_seq_poll(input_seq *finalseq) +{ + input_code lastcode = input_seq_get_last(&record_seq); + input_code newcode; + + /* switch case: see if we have a new code to process */ + if (record_class == ITEM_CLASS_SWITCH) + { + newcode = input_code_poll_switches(FALSE); + if (newcode != INPUT_CODE_INVALID) + { + /* if code is duplicate, toggle the NOT state on the code */ + if (input_seq_length(&record_seq) > 0 && newcode == lastcode) + { + /* back up over the code */ + input_seq_backspace(&record_seq); + + /* if there was a NOT preceding it, just delete it, otherwise append one */ + if (input_seq_get_last(&record_seq) == SEQCODE_NOT) + input_seq_backspace(&record_seq); + else + input_seq_append(&record_seq, SEQCODE_NOT); + } + } + } + + /* absolute/relative case: see if we have an analog change of sufficient amount */ + else + { + newcode = input_code_poll_axes(FALSE); + + /* if the last code doesn't match absolute/relative of this code, ignore the new one */ + if ((INPUT_CODE_ITEMCLASS(lastcode) == ITEM_CLASS_ABSOLUTE && INPUT_CODE_ITEMCLASS(newcode) != ITEM_CLASS_ABSOLUTE) || + (INPUT_CODE_ITEMCLASS(lastcode) == ITEM_CLASS_RELATIVE && INPUT_CODE_ITEMCLASS(newcode) != ITEM_CLASS_RELATIVE)) + newcode = INPUT_CODE_INVALID; + + if (newcode != INPUT_CODE_INVALID) + { + /* if code is duplicate and an absolute control, toggle to half axis */ + if (input_seq_length(&record_seq) > 0 && INPUT_CODE_ITEMCLASS(newcode) == ITEM_CLASS_ABSOLUTE) + if (newcode == INPUT_CODE_SET_MODIFIER(lastcode, ITEM_MODIFIER_NONE)) + { + /* increment the modifier, wrapping back to none */ + input_item_modifier oldmod = INPUT_CODE_MODIFIER(lastcode); + input_item_modifier newmod = (oldmod < ITEM_MODIFIER_NEG) ? oldmod + 1 : ITEM_MODIFIER_NONE; + newcode = INPUT_CODE_SET_MODIFIER(newcode, newmod); + + /* back up over the previous code so we can re-append */ + input_seq_backspace(&record_seq); + } + } + } + + /* if we got a new code to append it, append it and reset the timer */ + if (newcode != INPUT_CODE_INVALID) + { + input_seq_append(&record_seq, newcode); + record_last = osd_ticks(); + } + + /* if we're recorded at least one item and the RECORD_TIME has passed, we're done */ + if (record_last != 0 && osd_ticks() > record_last + RECORD_TIME) + { + /* if the final result is invalid, reset to nothing */ + if (!input_seq_is_valid(&record_seq)) + input_seq_set_0(&record_seq); + + /* return TRUE to indicate that we are finished */ + *finalseq = record_seq; + return TRUE; + } + + /* return FALSE to indicate we are still polling */ + return FALSE; +} + + + +/*************************************************************************** + STRINGS AND TOKENIZATION +***************************************************************************/ + +/*------------------------------------------------- + input_seq_name - generate the friendly name + of a sequence +-------------------------------------------------*/ + +astring *input_seq_name(astring *string, const input_seq *seq) +{ + astring *codestr = astring_alloc(); + int codenum, copycodenum; + input_seq seqcopy; + + /* walk the sequence first, removing any pieces that are invalid */ + for (codenum = copycodenum = 0; codenum < ARRAY_LENGTH(seq->code) && seq->code[codenum] != SEQCODE_END; codenum++) + { + input_code code = seq->code[codenum]; + + /* if this is a code item which is not valid, don't copy it and remove any preceding ORs/NOTs */ + if (!INPUT_CODE_IS_INTERNAL(code) && astring_len(input_code_name(codestr, code)) == 0) + { + while (copycodenum > 0 && INPUT_CODE_IS_INTERNAL(seqcopy.code[copycodenum - 1])) + copycodenum--; + } + else if (copycodenum > 0 || !INPUT_CODE_IS_INTERNAL(code)) + seqcopy.code[copycodenum++] = code; + } + seqcopy.code[copycodenum] = SEQCODE_END; + + /* special case: empty */ + if (copycodenum == 0) + { + astring_free(codestr); + return astring_cpyc(string, (seq->code[0] == SEQCODE_END) ? "None" : "n/a"); + } + + /* start with an empty buffer */ + astring_reset(string); + + /* loop until we hit the end */ + for (codenum = 0; codenum < ARRAY_LENGTH(seqcopy.code) && seqcopy.code[codenum] != SEQCODE_END; codenum++) + { + input_code code = seqcopy.code[codenum]; + + /* append a space if not the first code */ + if (codenum != 0) + astring_catc(string, " "); + + /* handle OR/NOT codes here */ + if (code == SEQCODE_OR) + astring_catc(string, "or"); + else if (code == SEQCODE_NOT) + astring_catc(string, "not"); + + /* otherwise, assume it is an input code and ask the input system to generate it */ + else + astring_cat(string, input_code_name(codestr, code)); + } + + astring_free(codestr); + return string; +} + + +/*------------------------------------------------- + input_seq_to_tokens - generate the tokenized + form of a sequence +-------------------------------------------------*/ + +astring *input_seq_to_tokens(astring *string, const input_seq *seq) +{ + astring *codestr = astring_alloc(); + int codenum; + + /* start with an empty buffer */ + astring_reset(string); + + /* loop until we hit the end */ + for (codenum = 0; codenum < ARRAY_LENGTH(seq->code) && seq->code[codenum] != SEQCODE_END; codenum++) + { + input_code code = seq->code[codenum]; + + /* append a space if not the first code */ + if (codenum != 0) + astring_catc(string, " "); + + /* handle OR/NOT codes here */ + if (code == SEQCODE_OR) + astring_catc(string, "OR"); + else if (code == SEQCODE_NOT) + astring_catc(string, "NOT"); + else if (code == SEQCODE_DEFAULT) + astring_catc(string, "DEFAULT"); + + /* otherwise, assume it is an input code and ask the input system to generate it */ + else + astring_cat(string, input_code_to_token(codestr, code)); + } + + astring_free(codestr); + return string; +} + + +/*------------------------------------------------- + input_seq_from_tokens - generate the tokenized + form of a sequence +-------------------------------------------------*/ + +int input_seq_from_tokens(const char *string, input_seq *seq) +{ + char *strcopy = malloc_or_die(strlen(string) + 1); + char *str = strcopy; + int result = FALSE; + + /* start with a blank sequence */ + input_seq_set_0(seq); + + /* loop until we're done */ + strcpy(strcopy, string); + while (1) + { + input_code code; + char origspace; + char *strtemp; + + /* trim any leading spaces */ + while (*str != 0 && isspace(*str)) + str++; + + /* bail if we're done */ + if (*str == 0) + { + result = TRUE; + break; + } + + /* find the end of the token and make it upper-case along the way */ + for (strtemp = str; *strtemp != 0 && !isspace(*strtemp); strtemp++) + *strtemp = toupper(*strtemp); + origspace = *strtemp; + *strtemp = 0; + + /* look for common stuff */ + if (strcmp(str, "OR") == 0) + code = SEQCODE_OR; + else if (strcmp(str, "NOT") == 0) + code = SEQCODE_NOT; + else if (strcmp(str, "DEFAULT") == 0) + code = SEQCODE_DEFAULT; + else + code = input_code_from_token(str); + + /* translate and add to the sequence */ + input_seq_append(seq, code); + + /* advance */ + if (origspace == 0) + { + result = TRUE; + break; + } + str = strtemp + 1; + } + + free(strcopy); + return result; +} + + + +/*************************************************************************** + INTERNAL FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + input_seq_is_valid - return TRUE if a given + sequence is valid +-------------------------------------------------*/ + +static int input_seq_is_valid(const input_seq *seq) +{ + input_item_class lastclass = ITEM_CLASS_INVALID; + input_code lastcode = INPUT_CODE_INVALID; + int positive_code_count = 0; + int seqnum; + + /* scan the sequence for valid codes */ + for (seqnum = 0; seqnum < ARRAY_LENGTH(seq->code); seqnum++) + { + input_code code = seq->code[seqnum]; + + /* invalid codes are invalid */ + if (code == INPUT_CODE_INVALID) + return FALSE; + + /* if we hit an OR or the end, validate the previous chunk */ + if (code == SEQCODE_OR || code == SEQCODE_END) + { + /* must be at least one positive code */ + if (positive_code_count == 0) + return FALSE; + + /* last code must not have been an internal code */ + if (INPUT_CODE_IS_INTERNAL(lastcode)) + return FALSE; + + /* if this is the end, we're ok */ + if (code == SEQCODE_END) + return TRUE; + + /* reset the state for the next chunk */ + positive_code_count = 0; + lastclass = ITEM_CLASS_INVALID; + } + + /* if we hit a NOT, make sure we don't have a double */ + else if (code == SEQCODE_NOT) + { + if (lastcode == SEQCODE_NOT) + return FALSE; + } + + /* anything else */ + else + { + input_item_class itemclass = INPUT_CODE_ITEMCLASS(code); + + /* count positive codes */ + if (lastcode != SEQCODE_NOT) + positive_code_count++; + + /* non-switch items can't have a NOT */ + if (itemclass != ITEM_CLASS_SWITCH && lastcode == SEQCODE_NOT) + return FALSE; + + /* absolute/relative items must all be the same class */ + if ((lastclass == ITEM_CLASS_ABSOLUTE && itemclass != ITEM_CLASS_ABSOLUTE) || + (lastclass == ITEM_CLASS_RELATIVE && itemclass != ITEM_CLASS_RELATIVE)) + return FALSE; + } + + /* remember the last code */ + lastcode = code; + } + + /* if we got here, we were missing an END token; fail */ + return FALSE; +} |