added KEY_DEL=127 (formerly KEY_BACKSPACE) added KEY_BS=8 C-H to handle control-h as backspace on some ttys fixed KEY_F6 thru KEY_F12 (were off by one) added strtokeys() to convert a string with key names to a key buffer added define-kbd-macro() from key string added execute-macro-keys() to execute commands from a key string improved qe_key_process(). changed method to update shell cursor : tty_update_cursor disabled shell_display_hook() used instead greatly improved shell buffer behaviour, charset handling. much improved tty input handling : support more key combinations and modes. added missing charset declarations in qe.h added borders_invalid and complete_refresh window flags made C-L completely refresh display moved window border display to separate function changed edit_display() to allow complete redisplay even with popups fixed window delete crash bugs by improving consistency with edit_attach and edit_detach.
946 lines
22 KiB
C
946 lines
22 KiB
C
/*
|
|
* 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 <dirent.h>
|
|
|
|
#ifdef WIN32
|
|
#include <sys/timeb.h>
|
|
|
|
/* 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 <fnmatch.h>
|
|
#endif
|
|
|
|
struct FindFileState {
|
|
char path[1024];
|
|
char dirpath[1024]; /* current dir path */
|
|
char pattern[1024]; /* 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[1024];
|
|
|
|
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[1024];
|
|
char path[1024];
|
|
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;
|
|
}
|
|
|
|
/* extract the pathname (and the trailing '/') */
|
|
char *pathname(char *buf, int buf_size, const char *filename)
|
|
{
|
|
const char *p;
|
|
int len;
|
|
|
|
p = strrchr(filename, '/');
|
|
if (!p) {
|
|
/* CG: this is bogus: path is not filename! */
|
|
pstrcpy(buf, buf_size, filename);
|
|
} else {
|
|
/* CG: pstrncpy(buf, buf_size, filename, p + 1 - filename) */
|
|
len = p - filename + 1;
|
|
if (len > buf_size - 1)
|
|
len = buf_size - 1;
|
|
memcpy(buf, filename, len);
|
|
buf[len] = '\0';
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* copy the nth first char of a string and truncate it. */
|
|
char *pstrncpy(char *buf, int buf_size, const char *s, int len)
|
|
{
|
|
char *q, *q_end;
|
|
int c;
|
|
|
|
if (buf_size > 0) {
|
|
q = buf;
|
|
q_end = buf + buf_size - 1;
|
|
while (q < q_end && len > 0) {
|
|
c = *s++;
|
|
if (c == '\0')
|
|
break;
|
|
*q++ = c;
|
|
len--;
|
|
}
|
|
*q = '\0';
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/* 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(int *keys, int *nb_keys)
|
|
{
|
|
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;
|
|
int 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(int *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));
|
|
}
|
|
|