/* * Utilities for qemacs. * Copyright (c) 2001 Fabrice Bellard. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "qe.h" #include #ifdef WIN32 #include /* XXX: not suffisant, but OK for basic operations */ int fnmatch(const char *pattern, const char *string, int flags) { if (pattern[0] == '*') return 0; else return strcmp(pattern, string) != 0; } #else #include #endif struct FindFileState { char path[MAX_FILENAME_SIZE]; char dirpath[MAX_FILENAME_SIZE]; /* current dir path */ char pattern[MAX_FILENAME_SIZE]; /* search pattern */ const char *bufptr; DIR *dir; }; FindFileState *find_file_open(const char *path, const char *pattern) { FindFileState *s; s = malloc(sizeof(FindFileState)); if (!s) return NULL; pstrcpy(s->path, sizeof(s->path), path); pstrcpy(s->pattern, sizeof(s->pattern), pattern); s->bufptr = s->path; s->dir = NULL; return s; } int find_file_next(FindFileState *s, char *filename, int filename_size_max) { struct dirent *dirent; const char *p; char *q; if (s->dir == NULL) goto redo; for (;;) { dirent = readdir(s->dir); if (dirent == NULL) { redo: if (s->dir) { closedir(s->dir); s->dir = NULL; } p = s->bufptr; if (*p == '\0') return -1; /* CG: get_str(&p, s->dirpath, sizeof(s->dirpath), ":") */ q = s->dirpath; while (*p != ':' && *p != '\0') { if ((q - s->dirpath) < (int)sizeof(s->dirpath) - 1) *q++ = *p; p++; } *q = '\0'; if (*p == ':') p++; s->bufptr = p; s->dir = opendir(s->dirpath); if (!s->dir) goto redo; } else { if (fnmatch(s->pattern, dirent->d_name, 0) == 0) { makepath(filename, filename_size_max, s->dirpath, dirent->d_name); return 0; } } } } void find_file_close(FindFileState *s) { if (s->dir) closedir(s->dir); free(s); } #ifdef WIN32 /* convert '/' to '\' */ static void path_win_to_unix(char *buf) { char *p; p = buf; while (*p) { if (*p == '\\') *p = '/'; p++; } } #endif /* suppress redundant ".", ".." and "/" from paths */ /* XXX: make it better */ static void canonize_path1(char *buf, int buf_size, const char *path) { const char *p; char *q, *q1; int c, abs_path; char file[MAX_FILENAME_SIZE]; p = path; abs_path = (p[0] == '/'); buf[0] = '\0'; for (;;) { /* extract file */ q = file; for (;;) { c = *p; if (c == '\0') break; p++; if (c == '/') break; if ((q - file) < (int)sizeof(file) - 1) *q++ = c; } *q = '\0'; if (file[0] == '\0') { /* nothing to do */ } else if (file[0] == '.' && file[1] == '\0') { /* nothing to do */ } else if (file[0] == '.' && file[1] == '.' && file[2] == '\0') { /* go up one dir */ if (buf[0] == '\0') { if (!abs_path) goto copy; } else { /* go to previous directory, if possible */ q1 = strrchr(buf, '/'); /* if already going up, cannot do more */ if (!q1 || (q1[1] == '.' && q1[2] == '.' && q1[3] == '\0')) goto copy; else *q1 = '\0'; } } else { copy: /* add separator if needed */ if (buf[0] != '\0' || (buf[0] == '\0' && abs_path)) pstrcat(buf, buf_size, "/"); pstrcat(buf, buf_size, file); } if (c == '\0') break; } /* add at least '.' or '/' */ if (buf[0] == '\0') { if (abs_path) pstrcat(buf, buf_size, "/"); else pstrcat(buf, buf_size, "."); } } void canonize_path(char *buf, int buf_size, const char *path) { const char *p; /* check for URL protocol or windows drive */ /* CG: should not skip '/' */ p = strchr(path, ':'); if (p) { if ((p - path) == 1) { /* windows drive : we canonize only the following path */ buf[0] = p[0]; buf[1] = p[1]; /* CG: this will not work for non current drives */ canonize_path1(buf + 2, buf_size - 2, p); } else { /* URL: it is already canonized */ pstrcpy(buf, buf_size, path); } } else { /* simple unix path */ canonize_path1(buf, buf_size, path); } } /* return TRUE if absolute path. works for files and URLs */ static int is_abs_path(const char *path) { const char *p; p = strchr(path, ':'); if (p) p++; else p = path; return *p == '/'; } /* canonize the path and make it absolute */ void canonize_absolute_path(char *buf, int buf_size, const char *path1) { char cwd[MAX_FILENAME_SIZE]; char path[MAX_FILENAME_SIZE]; char *homedir; if (!is_abs_path(path1)) { /* XXX: should call it again */ if (*path1 == '~' && (path1[1] == '\0' || path1[1] == '/')) { homedir = getenv("HOME"); if (homedir) { pstrcpy(path, sizeof(path), homedir); pstrcat(path, sizeof(path), path1 + 1); goto next; } } /* CG: not sufficient for windows drives */ getcwd(cwd, sizeof(cwd)); #ifdef WIN32 path_win_to_unix(cwd); #endif makepath(path, sizeof(path), cwd, path1); } else { pstrcpy(path, sizeof(path), path1); } next: canonize_path(buf, buf_size, path); } /* last filename in a path */ const char *basename(const char *filename) { const char *p; p = strrchr(filename, '/'); if (!p) { /* should also scan for ':' */ return filename; } else { p++; return p; } } /* last extension in a path, ignoring leading dots */ const char *extension(const char *filename) { const char *p, *ext; ext = NULL; p = filename; restart: while (*p == '.') p++; ext = NULL; for (; *p; p++) { if (*p == '/') { p++; goto restart; } if (*p == '.') ext = p; } return ext ? ext : p; } char *makepath(char *buf, int buf_size, const char *path, const char *filename) { int len; pstrcpy(buf, buf_size, path); len = strlen(path); if (len > 0 && path[len - 1] != '/' && len + 1 < buf_size) { buf[len++] = '/'; buf[len] = '\0'; } return pstrcat(buf, buf_size, filename); } void splitpath(char *dirname, int dirname_size, char *filename, int filename_size, const char *pathname) { const char *base; base = basename(pathname); if (dirname) pstrncpy(dirname, dirname_size, pathname, base - pathname); if (filename) pstrcpy(filename, filename_size, base); } /* find a word in a list using '|' as separator, * optionally fold case to lower case. */ int strfind(const char *keytable, const char *str, int casefold) { char buf[128]; int c, len; const char *p; if (casefold) { pstrcpy(buf, sizeof(buf), str); str = buf; css_strtolower(buf, sizeof(buf)); } c = *str; len = strlen(str); /* need to special case the empty string */ if (len == 0) return strstr(keytable, "||") != NULL; /* initial and trailing | are optional */ /* they do not cause the empty string to match */ for (p = keytable;;) { if (!memcmp(p, str, len) && (p[len] == '|' || p[len] == '\0')) return 1; for (;;) { p = strchr(p + 1, c); if (!p) return 0; if (p[-1] == '|') break; } } } void skip_spaces(const char **pp) { const char *p; p = *pp; while (css_is_space(*p)) p++; *pp = p; } /* need this for >= 256 */ static inline int utoupper(int c) { if (c >= 'a' && c <= 'z') c += 'A' - 'a'; return c; } int ustristart(const unsigned int *str, const char *val, const unsigned int **ptr) { const unsigned int *p; const char *q; p = str; q = val; while (*q != '\0') { if (utoupper(*p) != utoupper(*q)) return 0; p++; q++; } if (ptr) *ptr = p; return 1; } /* Read a token from a string, stop a set of characters. * Skip spaces before and after token. */ void get_str(const char **pp, char *buf, int buf_size, const char *stop) { char *q; const char *p; int c; skip_spaces(pp); p = *pp; q = buf; for (;;) { c = *p; /* Should stop on spaces and eat them */ if (c == '\0' || css_is_space(c) || strchr(stop, c)) break; if ((q - buf) < buf_size - 1) *q++ = c; p++; } *q = '\0'; *pp = p; skip_spaces(pp); } int css_get_enum(const char *str, const char *enum_str) { int val, len; const char *s, *s1; s = enum_str; val = 0; len = strlen(str); for (;;) { s1 = strchr(s, ','); if (!s1) { if (!strcmp(s, str)) return val; else break; } else { if (len == (s1 - s) && !memcmp(s, str, len)) return val; s = s1 + 1; } val++; } return -1; } unsigned short keycodes[] = { KEY_SPC, KEY_DEL, KEY_RET, KEY_ESC, KEY_TAB, KEY_SHIFT_TAB, KEY_CTRL(' '), KEY_DEL, KEY_CTRL('\\'), KEY_CTRL(']'), KEY_CTRL('^'), KEY_CTRL('_'), KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_HOME, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, KEY_CTRL_LEFT, KEY_CTRL_RIGHT, KEY_CTRL_UP, KEY_CTRL_DOWN, KEY_CTRL_HOME, KEY_CTRL_END, KEY_CTRL_PAGEUP, KEY_CTRL_PAGEDOWN, KEY_PAGEUP, KEY_PAGEDOWN, KEY_CTRL_PAGEUP, KEY_CTRL_PAGEDOWN, KEY_INSERT, KEY_DELETE, KEY_DEFAULT, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20, }; const char *keystr[] = { "SPC", "DEL", "RET", "ESC", "TAB", "S-TAB", "C-SPC", "C-?", "C-\\", "C-]", "C-^", "C-_", "left", "right", "up", "down", "home", "end", "prior", "next", "C-left", "C-right", "C-up", "C-down", "C-home", "C-end", "C-prior", "C-next", "pageup", "pagedown", "C-pageup", "C-pagedown", "insert", "delete", "default", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", }; int compose_keys(unsigned int *keys, int *nb_keys) { unsigned int *keyp; if (*nb_keys < 2) return 0; /* compose KEY_ESC as META prefix */ keyp = keys + *nb_keys - 2; if (keyp[0] == KEY_ESC) { if (keyp[1] <= 0xff) { keyp[0] = KEY_META(keyp[1]); --*nb_keys; return 1; } } return 0; } /* CG: this code is still quite inelegant */ static int strtokey1(const char **pp) { const char *p, *p1, *q; int i, key; /* should return KEY_NONE at end and KEY_UNKNOWN if unrecognized */ p = *pp; /* scan for separator */ for (p1 = p; *p1 && *p1 != ' '; p1++) continue; for (i = 0; i < sizeof(keycodes)/sizeof(keycodes[0]); i++) { if (strstart(p, keystr[i], &q) && q == p1) { key = keycodes[i]; *pp = p1; return key; } } #if 0 if (p[0] == 'f' && p[1] >= '1' && p[1] <= '9') { i = p[1] - '0'; p += 2; if (p1 == isdigit((unsigned char)*p)) i = i * 10 + *p++ - '0'; key = KEY_F1 + i - 1; *pp = p1; return key; } #endif if (p[0] == 'C' && p[1] == '-' && p1 == p + 3) { /* control */ key = KEY_CTRL(p[2]); } else { key = utf8_decode(&p); } *pp = p1; return key; } int strtokey(const char **pp) { const char *p; int key; p = *pp; if (p[0] == 'M' && p[1] == '-') { p += 2; key = KEY_META(strtokey1(&p)); } else if (p[0] == 'C' && p[1] == '-' && p[0] == 'M' && p[1] == '-') { p += 4; key = KEY_META(KEY_CTRL(strtokey1(&p))); } else { key = strtokey1(&p); } *pp = p; return key; } int strtokeys(const char *keystr, unsigned int *keys, int max_keys) { int key, nb_keys; const char *p; p = keystr; nb_keys = 0; for (;;) { skip_spaces(&p); if (*p == '\0') break; key = strtokey(&p); keys[nb_keys++] = key; compose_keys(keys, &nb_keys); if (nb_keys >= max_keys) break; } return nb_keys; } void keytostr(char *buf, int buf_size, int key) { int i; char buf1[32]; for (i = 0; i < (int)(sizeof(keycodes)/sizeof(keycodes[0])); i++) { if (keycodes[i] == key) { pstrcpy(buf, buf_size, keystr[i]); return; } } if (key >= KEY_META(0) && key <= KEY_META(0xff)) { keytostr(buf1, sizeof(buf1), key & 0xff); snprintf(buf, buf_size, "M-%s", buf1); } else if (key >= KEY_CTRL('a') && key <= KEY_CTRL('z')) { snprintf(buf, buf_size, "C-%c", key + 'a' - 1); } else if (key >= KEY_F1 && key <= KEY_F20) { snprintf(buf, buf_size, "f%d", key - KEY_F1 + 1); } else if (key > 32 && key < 127 && buf_size >= 2) { buf[0] = key; buf[1] = '\0'; } else { char *q; q = utf8_encode(buf1, key); *q = '\0'; pstrcpy(buf, buf_size, buf1); } } int to_hex(int key) { if (key >= '0' && key <= '9') return key - '0'; else if (key >= 'a' && key <= 'f') return key - 'a' + 10; else if (key >= 'A' && key <= 'F') return key - 'A' + 10; else return -1; } typedef struct ColorDef { const char *name; unsigned int color; } ColorDef; static ColorDef css_colors[] = { /*from HTML 4.0 spec */ { "black", QERGB(0x00, 0x00, 0x00) }, { "green", QERGB(0x00, 0x80, 0x00) }, { "silver", QERGB(0xc0, 0xc0, 0xc0) }, { "lime", QERGB(0x00, 0xff, 0x00) }, { "gray", QERGB(0xbe, 0xbe, 0xbe) }, { "olive", QERGB(0x80, 0x80, 0x00) }, { "white", QERGB(0xff, 0xff, 0xff) }, { "yellow", QERGB(0xff, 0xff, 0x00) }, { "maroon", QERGB(0x80, 0x00, 0x00) }, { "navy", QERGB(0x00, 0x00, 0x80) }, { "red", QERGB(0xff, 0x00, 0x00) }, { "blue", QERGB(0x00, 0x00, 0xff) }, { "purple", QERGB(0x80, 0x00, 0x80) }, { "teal", QERGB(0x00, 0x80, 0x80) }, { "fuchsia", QERGB(0xff, 0x00, 0xff) }, { "aqua", QERGB(0x00, 0xff, 0xff) }, /* more colors */ { "cyan", QERGB(0x00, 0xff, 0xff) }, { "magenta", QERGB(0xff, 0x00, 0xff) }, { "transparent", COLOR_TRANSPARENT }, }; #define nb_css_colors (sizeof(css_colors) / sizeof(css_colors[0])) static ColorDef *custom_colors = css_colors; static int nb_custom_colors; void color_completion(StringArray *cs, const char *input) { ColorDef *def; int len, count; len = strlen(input); def = custom_colors; count = nb_css_colors + nb_custom_colors; while (count > 0) { if (!strncmp(def->name, input, len)) add_string(cs, def->name); def++; count--; } } static ColorDef *css_lookup_color(ColorDef *def, int count, const char *name) { while (count > 0) { if (!strcasecmp(name, def->name)) { return def; } def++; count--; } return NULL; } int css_define_color(const char *name, const char *value) { ColorDef *def; QEColor color; /* Check color validity */ if (css_get_color(&color, value)) return -1; /* Make room: reallocate table in chunks of 8 entries */ if ((nb_custom_colors & 7) == 0) { def = malloc((nb_css_colors + nb_custom_colors + 8) * sizeof(ColorDef)); if (!def) return -1; memcpy(def, custom_colors, (nb_css_colors + nb_custom_colors) * sizeof(ColorDef)); if (custom_colors != css_colors) free(custom_colors); custom_colors = def; } /* Check for redefinition */ def = css_lookup_color(custom_colors, nb_css_colors + nb_custom_colors, name); if (def) { def->color = color; return 0; } def = &custom_colors[nb_css_colors + nb_custom_colors]; def->name = strdup(name); def->color = color; nb_custom_colors++; return 0; } /* XXX: make HTML parsing optional ? */ int css_get_color(QEColor *color_ptr, const char *p) { const ColorDef *def; int len, v, i, n; unsigned char rgba[4]; /* search in table */ def = css_lookup_color(custom_colors, nb_css_colors + nb_custom_colors, p); if (def) { *color_ptr = def->color; return 0; } rgba[3] = 0xff; if (isxdigit((unsigned char)*p)) { goto parse_num; } else if (*p == '#') { /* handle '#' notation */ p++; parse_num: len = strlen(p); switch (len) { case 3: for (i = 0; i < 3; i++) { v = to_hex(*p++); rgba[i] = v | (v << 4); } break; case 6: for (i = 0; i < 3; i++) { v = to_hex(*p++) << 4; v |= to_hex(*p++); rgba[i] = v; } break; default: /* error */ return -1; } } else if (strstart(p, "rgb(", &p)) { n = 3; goto parse_rgba; } else if (strstart(p, "rgba(", &p)) { /* extension for alpha */ n = 4; parse_rgba: for (i = 0; i < n; i++) { /* XXX: floats ? */ skip_spaces(&p); v = strtol(p, (char **)&p, 0); if (*p == '%') { v = (v * 255) / 100; p++; } rgba[i] = v; skip_spaces(&p); if (*p == ',') p++; } } else { return -1; } *color_ptr = (rgba[0] << 16) | (rgba[1] << 8) | (rgba[2]) | (rgba[3] << 24); return 0; } /* return 0 if unknown font */ int css_get_font_family(const char *str) { int v; if (!strcasecmp(str, "serif") || !strcasecmp(str, "times")) v = QE_FAMILY_SERIF; else if (!strcasecmp(str, "sans") || !strcasecmp(str, "arial") || !strcasecmp(str, "helvetica")) v = QE_FAMILY_SANS; else if (!strcasecmp(str, "fixed") || !strcasecmp(str, "monospace") || !strcasecmp(str, "courier")) v = QE_FAMILY_FIXED; else v = 0; /* inherit */ return v; } /* a = a union b */ void css_union_rect(CSSRect *a, CSSRect *b) { if (css_is_null_rect(b)) return; if (css_is_null_rect(a)) { *a = *b; } else { if (b->x1 < a->x1) a->x1 = b->x1; if (b->y1 < a->y1) a->y1 = b->y1; if (b->x2 > a->x2) a->x2 = b->x2; if (b->y2 > a->y2) a->y2 = b->y2; } } #ifdef __TINYC__ /* the glibc folks use wrappers, but forgot to put a compatibility function for non GCC compilers ! */ int stat(__const char *__path, struct stat *__statbuf) { return __xstat(_STAT_VER, __path, __statbuf); } #endif int get_clock_ms(void) { #ifdef CONFIG_WIN32 struct _timeb tb; _ftime(&tb); return tb.time * 1000 + tb.millitm; #else struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000 + (tv.tv_usec / 1000); #endif } /* set one string. */ StringItem *set_string(StringArray *cs, int index, const char *str) { StringItem *v; if (index >= cs->nb_items) return NULL; v = malloc(sizeof(StringItem) + strlen(str)); if (!v) return NULL; v->selected = 0; strcpy(v->str, str); if (cs->items[index]) free(cs->items[index]); cs->items[index] = v; return v; } /* make a generic array alloc */ StringItem *add_string(StringArray *cs, const char *str) { StringItem **tmp; int n; if (cs->nb_items >= cs->nb_allocated) { n = cs->nb_allocated + 32; tmp = realloc(cs->items, n * sizeof(StringItem *)); if (!tmp) return NULL; cs->items = tmp; cs->nb_allocated = n; } cs->items[cs->nb_items++] = NULL; return set_string(cs, cs->nb_items - 1, str); } void free_strings(StringArray *cs) { int i; for (i = 0; i < cs->nb_items; i++) free(cs->items[i]); free(cs->items); memset(cs, 0, sizeof(StringArray)); } void set_color(unsigned int *buf, int len, int style) { int i; style <<= STYLE_SHIFT; for (i = 0; i < len; i++) buf[i] |= style; } void css_strtolower(char *buf, int buf_size) { int c; /* XXX: handle unicode */ while (*buf) { c = tolower(*buf); *buf++ = c; } } void umemmove(unsigned int *dest, unsigned int *src, int len) { memmove(dest, src, len * sizeof(unsigned int)); } /* copy the n first char of a string and truncate it. */ char *pstrncpy(char *buf, int buf_size, const char *s, int len) { char *q; int c; if (buf_size > 0) { q = buf; if (len >= buf_size) len = buf_size - 1; while (len > 0) { c = *s++; if (c == '\0') break; *q++ = c; len--; } *q = '\0'; } return buf; } /** * Add a memory region to a dynamic string. In case of allocation * failure, the data is not added. The dynamic string is guaranted to * be 0 terminated, although it can be longer if it contains zeros. * * @return 0 if OK, -1 if allocation error. */ int qmemcat(QString *q, const unsigned char *data1, int len1) { int new_len, len, alloc_size; unsigned char *data; data = q->data; len = q->len; new_len = len + len1; /* see if we got a new power of two */ /* NOTE: we got this trick from the excellent 'links' browser */ if ((len ^ new_len) >= len) { /* find immediately bigger 2^n - 1 */ alloc_size = new_len; alloc_size |= (alloc_size >> 1); alloc_size |= (alloc_size >> 2); alloc_size |= (alloc_size >> 4); alloc_size |= (alloc_size >> 8); alloc_size |= (alloc_size >> 16); /* allocate one more byte for end of string marker */ data = realloc(data, alloc_size + 1); if (!data) return -1; q->data = data; } memcpy(data + len, data1, len1); data[new_len] = '\0'; /* we force a trailing '\0' */ q->len = new_len; return 0; } /* * add a string to a dynamic string */ int qstrcat(QString *q, const char *str) { return qmemcat(q, (unsigned char *)str, strlen(str)); } /* XXX: we use a fixed size buffer */ int qprintf(QString *q, const char *fmt, ...) { char buf[4096]; va_list ap; int len, ret; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); /* avoid problems for non C99 snprintf() which can return -1 if overflow */ if (len < 0) len = strlen(buf); ret = qmemcat(q, (unsigned char *)buf, len); va_end(ap); return ret; }