changed command definitions accordingly. added C-x C-g binding for abort added C-\ binding for call-last-kbd-macro changed binding for switch-input-method to C-x C-\ added commands: not-modified on A-~ (reset buffer modified flag) load-file-from-path(file) mark-whole-buffer() on C-x h count-lines() on C-x l what-cursor-position on C-x = allow key_grabber to quit and unget last key. added splitpath() to dispatch directory and filename into separate buffers. make separate functions for show_version and show_usage. make separate function for parse_command_line. added command line options: --help -h display help message and exit --no-init-file -q do not load config files --user -u load ~USER/.qe/config instead of your own --version -V display version information and exit added -- as a marker for end of command line options. allow most keys to be transmitted to shell process, including function keys. improved shell buffer escape sequence parser. added automatic detection of full screen shell applications and grab all keys try mc, Menuconfig, or even qemacs within qemacs ! improved *trace* buffer: do not trace keys when trace buffer is current, thus allowing navigation without side-effects improved Makefile to not make html stuff if disabled reduced CONFIG_TINY feature set
959 lines
23 KiB
C
959 lines
23 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);
|
|
}
|
|
|
|
void splitpath(char *dirname, int dirname_size,
|
|
char *filename, int filename_size, const char *pathname)
|
|
{
|
|
const char *base;
|
|
|
|
base = basename(pathname);
|
|
pstrncpy(dirname, dirname_size, pathname, base - pathname);
|
|
pstrcpy(filename, filename_size, base);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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));
|
|
}
|
|
|