diff options
author | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2006-08-02 12:54:15 +0000 |
---|---|---|
committer | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2006-08-02 12:54:15 +0000 |
commit | d5e5340c3926d1cf97f6cba151ffaecb20bfb35f (patch) | |
tree | d1faf7d5b27df8f3c523a8dd33357804118e62b1 /trunk/source/libutil.cc | |
parent | 7b2204d69f21d7075e4666ee032d7a129081bc4b (diff) | |
download | crawl-ref-d5e5340c3926d1cf97f6cba151ffaecb20bfb35f.tar.gz crawl-ref-d5e5340c3926d1cf97f6cba151ffaecb20bfb35f.zip |
Integrated travel patch as of 20060727
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'trunk/source/libutil.cc')
-rw-r--r-- | trunk/source/libutil.cc | 635 |
1 files changed, 634 insertions, 1 deletions
diff --git a/trunk/source/libutil.cc b/trunk/source/libutil.cc index 5434eb3b0d..734ae2dc0a 100644 --- a/trunk/source/libutil.cc +++ b/trunk/source/libutil.cc @@ -12,10 +12,63 @@ */ #include "AppHdr.h" +#include "defines.h" +#include "libutil.h" +#include "externs.h" #include <stdio.h> #include <ctype.h> +#include <stdarg.h> #include <string.h> +#ifdef WIN32CONSOLE + #include <windows.h> + + #ifdef WINMM_PLAY_SOUNDS + #include <mmsystem.h> + #endif +#endif + +#ifdef REGEX_PCRE + // Statically link pcre on Windows + #ifdef WIN32CONSOLE + #define PCRE_STATIC + #endif + + #include <pcre.h> +#endif + +#ifdef REGEX_POSIX + // Do we still need to include sys/types.h? + #include <sys/types.h> + #include <regex.h> +#endif + +// Should return true if the filename contains nothing that +// the shell can do damage with. +bool shell_safe(const char *file) +{ + int match = strcspn(file, "`$*?|><"); + return !(match >= 0 && file[match]); +} + +void play_sound( const char *file ) +{ +#if defined(WINMM_PLAY_SOUNDS) + // Check whether file exists, is readable, etc.? + if (file && *file) + sndPlaySound(file, SND_ASYNC | SND_NODEFAULT); +#elif defined(SOUND_PLAY_COMMAND) + char command[255]; + command[0] = '\0'; + if (file && *file && (strlen(file) + strlen(SOUND_PLAY_COMMAND) < 255) + && shell_safe(file)) + { + snprintf(command, sizeof command, SOUND_PLAY_COMMAND, file); + system(command); + } +#endif +} + void get_input_line( char *const buff, int len ) { buff[0] = '\0'; // just in case @@ -45,6 +98,273 @@ void get_input_line( char *const buff, int len ) } } +#ifdef DOS +static int getch_ck() { + int c = getch(); + if (!c) { + switch (c = getch()) { + case 'O': return CK_END; + case 'P': return CK_DOWN; + case 'I': return CK_PGUP; + case 'H': return CK_UP; + case 'G': return CK_HOME; + case 'K': return CK_LEFT; + case 'Q': return CK_PGDN; + case 'M': return CK_RIGHT; + case 119: return CK_CTRL_HOME; + case 141: return CK_CTRL_UP; + case 132: return CK_CTRL_PGUP; + case 116: return CK_CTRL_RIGHT; + case 118: return CK_CTRL_PGDN; + case 145: return CK_CTRL_DOWN; + case 117: return CK_CTRL_END; + case 115: return CK_CTRL_LEFT; + case 'S': return CK_DELETE; + } + } + return c; +} +#endif + +// Hacky wrapper around getch() that returns CK_ codes for keys +// we want to use in cancelable_get_line() and menus. +int c_getch() { +#if defined(DOS) || defined(LINUX) || defined(WIN32CONSOLE) + return getch_ck(); +#else + return getch(); +#endif +} + +// cprintf that knows how to wrap down lines (primitive, but what the heck) +int wrapcprintf( int wrapcol, const char *s, ... ) +{ + char buf[1000]; // Hard max + va_list args; + va_start(args, s); + + // XXX: If snprintf isn't available, vsnprintf probably isn't, either. + int len = vsnprintf(buf, sizeof buf, s, args), olen = len; + va_end(args); + + char *run = buf; + while (len > 0) + { + int x = wherex(), y = wherey(); + + if (x > wrapcol) // Somebody messed up! + return 0; + + int avail = wrapcol - x + 1; + int c = 0; + if (len > avail) { + c = run[avail]; + run[avail] = 0; + } + cprintf("%s", run); + + if (len > avail) + run[avail] = c; + + if ((len -= avail) > 0) + gotoxy(1, y + 1); + run += avail; + } + return (olen); +} + +#define WX(x) ( ((x) - 1) % maxcol + 1 ) +#define WY(x,y) ( (y) + ((x) - 1) / maxcol ) +#define WC(x,y) WX(x), WY(x,y) +#define GOTOXY(x,y) gotoxy( WC(x,y) ) +bool cancelable_get_line( char *buf, int len, int maxcol, + input_history *mh ) +{ + if (len <= 0) return false; + + buf[0] = 0; + + char *cur = buf; + int start = wherex(), line = wherey(); + int length = 0, pos = 0; + + if (mh) + mh->go_end(); + + for ( ; ; ) { + int ch = c_getch(); + + switch (ch) { + case CK_ESCAPE: + return false; + case CK_UP: + case CK_DOWN: + { + if (!mh) + break; + const std::string *text = ch == CK_UP? mh->prev() : mh->next(); + if (text) + { + int olen = length; + length = text->length(); + if (length >= len) + length = len - 1; + memcpy(buf, text->c_str(), length); + buf[length] = 0; + GOTOXY(start, line); + + int clear = length < olen? olen - length : 0; + wrapcprintf(maxcol, "%s%*s", buf, clear, ""); + + pos = length; + cur = buf + pos; + GOTOXY(start + pos, line); + } + break; + } + case CK_ENTER: + buf[length] = 0; + if (mh && length) + mh->new_input(buf); + return true; + case CONTROL('K'): + { + // Kill to end of line + int erase = length - pos; + if (erase) + { + length = pos; + buf[length] = 0; + wrapcprintf( maxcol, "%*s", erase, "" ); + GOTOXY(start + pos, line); + } + break; + } + case CK_DELETE: + if (pos < length) { + char *c = cur; + while (c - buf < length) { + *c = c[1]; + c++; + } + --length; + + GOTOXY( start + pos, line ); + buf[length] = 0; + wrapcprintf( maxcol, "%s ", cur ); + GOTOXY( start + pos, line ); + } + break; + case CK_BKSP: + if (pos) { + --cur; + char *c = cur; + while (*c) { + *c = c[1]; + c++; + } + --pos; + --length; + + GOTOXY( start + pos, line ); + buf[length] = 0; + wrapcprintf( maxcol, "%s ", cur ); + GOTOXY( start + pos, line ); + } + break; + case CK_LEFT: + if (pos) { + --pos; + cur = buf + pos; + GOTOXY( start + pos, line ); + } + break; + case CK_RIGHT: + if (pos < length) { + ++pos; + cur = buf + pos; + GOTOXY( start + pos, line ); + } + break; + case CK_HOME: + case CONTROL('A'): + pos = 0; + cur = buf + pos; + GOTOXY( start + pos, line ); + break; + case CK_END: + case CONTROL('E'): + pos = length; + cur = buf + pos; + GOTOXY( start + pos, line ); + break; + default: + if (isprint(ch) && length < len - 1) { + if (pos < length) { + char *c = buf + length - 1; + while (c >= cur) { + c[1] = *c; + c--; + } + } + *cur++ = (char) ch; + ++length; + ++pos; + putch(ch); + if (pos < length) { + buf[length] = 0; + wrapcprintf( maxcol, "%s", cur ); + } + GOTOXY(start + pos, line); + } + break; + } + } +} +#undef GOTOXY +#undef WC +#undef WX +#undef WY + +// also used with macros +std::string & trim_string( std::string &str ) +{ + // OK, this is really annoying. Borland C++ seems to define + // basic_string::erase to take iterators, and basic_string::remove + // to take size_t or integer. This is ass-backwards compared to + // nearly all other C++ compilers. Crap. (GDL) + // + // Borland 5.5 does this correctly now... leaving the old code + // around for now in case anyone needs it. -- bwr +// #ifdef __BCPLUSPLUS__ +// str.remove( 0, str.find_first_not_of( " \t\n\r" ) ); +// str.remove( str.find_last_not_of( " \t\n\r" ) + 1 ); +// #else + str.erase( 0, str.find_first_not_of( " \t\n\r" ) ); + str.erase( str.find_last_not_of( " \t\n\r" ) + 1 ); +// #endif + + return (str); +} + +std::vector<std::string> split_string(const char *sep, std::string s) +{ + std::vector<std::string> segments; + + std::string::size_type pos; + while ((pos = s.find(sep, 0)) != std::string::npos) { + if (pos > 0) + segments.push_back(s.substr(0, pos)); + s.erase(0, pos + 1); + } + if (s.length() > 0) + segments.push_back(s); + + for (int i = 0, count = segments.size(); i < count; ++i) + trim_string(segments[i]); + return segments; +} + // The old school way of doing short delays via low level I/O sync. // Good for systems like old versions of Solaris that don't have usleep. #ifdef NEED_USLEEP @@ -72,7 +392,6 @@ void usleep(unsigned long time) // and returns the number of characters that would have been written). -- bwr #ifdef NEED_SNPRINTF -#include <stdarg.h> #include <string.h> int snprintf( char *str, size_t size, const char *format, ... ) @@ -94,4 +413,318 @@ int snprintf( char *str, size_t size, const char *format, ... ) return (ret); } + +#endif + +/////////////////////////////////////////////////////////////////////// +// Pattern matching + +inline int pm_lower(int ch, bool icase) { + return icase? tolower(ch) : ch; +} + +// Determines whether the pattern specified by 'pattern' matches the given +// text. A pattern is a simple glob, with the traditional * and ? wildcards. +static bool glob_match( const char *pattern, const char *text, bool icase ) +{ + char p, t; + bool special; + + for (;;) + { + p = pm_lower(*pattern++, icase); + t = pm_lower(*text++, icase); + special = true; + + if (!p) return t == 0; + if (p == '\\' && *pattern) + { + p = pm_lower(*pattern++, icase); + special = false; + } + + if (p == '*' && special) + // Try to match exactly at the current text position... + return !*pattern || glob_match(pattern, text - 1, icase)? true : + // Or skip one character in the text and try the wildcard + // match again. If this is the end of the text, the match has + // failed. + t? glob_match(pattern - 1, text, icase) : false; + else if (!t || (p != t && (p != '?' || !special))) + return false; + } +} + +#if defined(REGEX_PCRE) +//////////////////////////////////////////////////////////////////// +// Perl Compatible Regular Expressions + +void *compile_pattern(const char *pattern, bool icase) { + const char *error; + int erroffset; + int flags = icase? PCRE_CASELESS : 0; + return pcre_compile(pattern, + flags, + &error, + &erroffset, + NULL); +} + +void free_compiled_pattern(void *cp) { + if (cp) + pcre_free(cp); +} + +bool pattern_match(void *compiled_pattern, const char *text, int length) +{ + int ovector[42]; + int pcre_rc = pcre_exec(static_cast<pcre *>(compiled_pattern), + NULL, + text, + length, + 0, + 0, + ovector, + sizeof(ovector) / sizeof(*ovector)); + return (pcre_rc >= 0); +} + +//////////////////////////////////////////////////////////////////// +#elif defined(REGEX_POSIX) +//////////////////////////////////////////////////////////////////// +// POSIX regular expressions + +void *compile_pattern(const char *pattern, bool icase) { + regex_t *re = new regex_t; + if (!re) + return NULL; + + int flags = REG_EXTENDED | REG_NOSUB; + if (icase) + flags |= REG_ICASE; + int rc = regcomp(re, pattern, flags); + // Nonzero return code == failure + if (rc) { + delete re; + return NULL; + } + return re; +} + +void free_compiled_pattern(void *cp) { + if (cp) { + regex_t *re = static_cast<regex_t *>( cp ); + regfree(re); + delete re; + } +} + +bool pattern_match(void *compiled_pattern, const char *text, int length) +{ + regex_t *re = static_cast<regex_t *>( compiled_pattern ); + return !regexec(re, text, 0, NULL, 0); +} + +//////////////////////////////////////////////////////////////////// +#else +//////////////////////////////////////////////////////////////////// +// Basic glob + +struct glob_info +{ + std::string s; + bool ignore_case; +}; + +void *compile_pattern(const char *pattern, bool icase) +{ + // If we're using simple globs, we need to box the pattern with '*' + std::string s = std::string("*") + pattern + "*"; + glob_info *gi = new glob_info; + if (gi) { + gi->s = s; + gi->ignore_case = icase; + } + return gi; +} + +void free_compiled_pattern(void *compiled_pattern) +{ + delete static_cast<glob_info *>( compiled_pattern ); +} + +bool pattern_match(void *compiled_pattern, const char *text, int length) +{ + glob_info *gi = static_cast<glob_info *>( compiled_pattern ); + return glob_match(gi->s.c_str(), text, gi->ignore_case); +} +//////////////////////////////////////////////////////////////////// + #endif + + + +//////////////////////////////////////////////////////////////////// +// formatted_string +// + +formatted_string::formatted_string(const std::string &s) + : ops() +{ + ops.push_back( s ); +} + +formatted_string::operator std::string() const +{ + std::string s; + for (int i = 0, size = ops.size(); i < size; ++i) + { + if (ops[i] == FSOP_TEXT) + s += ops[i].text; + } + return s; +} + +inline void cap(int &i, int max) +{ + if (i < 0 && -i <= max) + i += max; + if (i >= max) + i = max - 1; + if (i < 0) + i = 0; +} + +std::string formatted_string::tostring(int s, int e) const +{ + std::string st; + + int size = ops.size(); + cap(s, size); + cap(e, size); + + for (int i = s; i <= e && i < size; ++i) + { + if (ops[i] == FSOP_TEXT) + st += ops[i].text; + } + return st; +} + +void formatted_string::display(int s, int e) const +{ + int size = ops.size(); + if (!size) + return ; + + cap(s, size); + cap(e, size); + + for (int i = s; i <= e && i < size; ++i) + ops[i].display(); +} + +void formatted_string::gotoxy(int x, int y) +{ + ops.push_back( fs_op(x, y) ); +} + +void formatted_string::textcolor(int color) +{ + ops.push_back(color); +} + +void formatted_string::cprintf(const char *s, ...) +{ + char buf[1000]; + va_list args; + va_start(args, s); + vsnprintf(buf, sizeof buf, s, args); + va_end(args); + + cprintf(std::string(buf)); +} + +void formatted_string::cprintf(const std::string &s) +{ + ops.push_back(s); +} + +void formatted_string::fs_op::display() const +{ + switch (type) + { + case FSOP_CURSOR: + { + int cx = (x == -1? wherex() : x); + int cy = (y == -1? wherey() : y); + ::gotoxy(cx, cy); + break; + } + case FSOP_COLOUR: + ::textcolor(x); + break; + case FSOP_TEXT: + ::cprintf("%s", text.c_str()); + break; + } +} + +///////////////////////////////////////////////////////////// +// input_history +// + +input_history::input_history(size_t size) + : history(), pos(), maxsize(size) +{ + if (maxsize < 2) + maxsize = 2; + + pos = history.end(); +} + +void input_history::new_input(const std::string &s) +{ + history.remove(s); + + if (history.size() == maxsize) + history.pop_front(); + + history.push_back(s); + + // Force the iterator to the end (also revalidates it) + go_end(); +} + +const std::string *input_history::prev() +{ + if (history.empty()) + return NULL; + + if (pos == history.begin()) + pos = history.end(); + + return &*--pos; +} + +const std::string *input_history::next() +{ + if (history.empty()) + return NULL; + + if (pos == history.end() || ++pos == history.end()) + pos = history.begin(); + + return &*pos; +} + +void input_history::go_end() +{ + pos = history.end(); +} + +void input_history::clear() +{ + history.clear(); + go_end(); +} |