ltkx

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltkx.git
Log | Files | Refs | README | LICENSE

commit 6d318819d95cceedcc5bca92e45cee03142ed3b5
Author: lumidify <nobody@lumidify.org>
Date:   Sun,  1 Jan 2017 09:45:55 +0100

Initial commit

Diffstat:
ALICENSE | 22++++++++++++++++++++++
AMakefile | 7+++++++
AREADME.md | 11+++++++++++
Abutton.c | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abutton.h | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AcJSON.c | 1492+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AcJSON.h | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommon.c | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommon.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aevent.c | 42++++++++++++++++++++++++++++++++++++++++++
Aevent.h | 31+++++++++++++++++++++++++++++++
Agrid.c | 313+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agrid.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Altk.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Altk.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest1.c | 41+++++++++++++++++++++++++++++++++++++++++
Atheme.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atheme.h | 39+++++++++++++++++++++++++++++++++++++++
Athemes/default.json | 34++++++++++++++++++++++++++++++++++
Authash.h | 1074+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awidget.c | 23+++++++++++++++++++++++
Awidget.h | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awindow.c | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awindow.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
25 files changed, 4350 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,22 @@ +MIT/X Consortium License + +The Lumidify ToolKit (LTK) +Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile @@ -0,0 +1,7 @@ +LIBS = -lX11 -lm -ldl +STD = -std=c89 +FLAGS = -g -w -Wall -Werror -Wextra -pedantic +CFILES = ltk.c event.c cJSON.c common.c widget.c grid.c window.c theme.c button.c test1.c + +all: test1.c + gcc $(STD) $(FLAGS) $(LIBS) $(CFILES) -o test diff --git a/README.md b/README.md @@ -0,0 +1,11 @@ +# LTK - Lumidify Toolkit + +This is work in progress. + +Please do not attempt to actually use any of the code. + +## Licenses of Other Libraries Used + +[uthash](https://troydhanson.github.io/uthash/): [BSD Revised](https://troydhanson.github.io/uthash/license.html) + +[cJSON](https://github.com/DaveGamble/cJSON): [MIT/X](https://github.com/DaveGamble/cJSON/blob/master/LICENSE) diff --git a/button.c b/button.c @@ -0,0 +1,257 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ltk.h" + +LtkButtonTheme *ltk_parse_button_theme(cJSON *button_json) +{ + LtkButtonTheme *button_theme = malloc(sizeof(LtkButtonTheme)); + cJSON *normal_json = cJSON_GetObjectItem(button_json, "normal"); + if (!normal_json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + } + cJSON *border_width = cJSON_GetObjectItem(normal_json, "border-width"); + cJSON *font_size = cJSON_GetObjectItem(normal_json, "font-size"); + cJSON *border_color = cJSON_GetObjectItem(normal_json, "border-color"); + cJSON *fill_color = cJSON_GetObjectItem(normal_json, "fill-color"); + cJSON *padding_left = cJSON_GetObjectItem(normal_json, "padding-left"); + cJSON *padding_right = cJSON_GetObjectItem(normal_json, "padding-right"); + cJSON *padding_top = cJSON_GetObjectItem(normal_json, "padding-top"); + cJSON *padding_bottom = cJSON_GetObjectItem(normal_json, "padding-bottom"); + + button_theme->border_width = border_width != NULL ? border_width->valueint : 0; + button_theme->font_size = font_size != NULL ? font_size->valueint : 20; + button_theme->border_color = ltk_create_xcolor(border_color->valuestring); + button_theme->fill_color = ltk_create_xcolor(fill_color->valuestring); + button_theme->padding_left = padding_left != NULL ? padding_left->valueint : 0; + button_theme->padding_right = padding_right != NULL ? padding_right->valueint : 0; + button_theme->padding_top = padding_top != NULL ? padding_top->valueint : 0; + button_theme->padding_bottom = padding_bottom != NULL ? padding_bottom->valueint : 0; + + cJSON *hover_json = cJSON_GetObjectItem(button_json, "hover"); + if (!hover_json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + } + cJSON *border_width_hover = cJSON_GetObjectItem(hover_json, "border-width"); + cJSON *font_size_hover = cJSON_GetObjectItem(hover_json, "font-size"); + cJSON *border_color_hover = cJSON_GetObjectItem(hover_json, "border-color"); + cJSON *fill_color_hover = cJSON_GetObjectItem(hover_json, "fill-color"); + + button_theme->border_width_hover = border_width_hover != NULL ? border_width_hover->valueint : button_theme->border_width; + button_theme->font_size_hover = font_size_hover != NULL ? font_size_hover->valueint : button_theme->font_size; + button_theme->border_color_hover = border_color_hover != NULL ? ltk_create_xcolor(border_color_hover->valuestring) : button_theme->border_color; + button_theme->fill_color_hover = fill_color_hover != NULL ? ltk_create_xcolor(fill_color_hover->valuestring) : button_theme->fill_color; + + cJSON *pressed_json = cJSON_GetObjectItem(button_json, "pressed"); + if (!pressed_json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + } + cJSON *border_width_pressed = cJSON_GetObjectItem(pressed_json, "border-width"); + cJSON *font_size_pressed = cJSON_GetObjectItem(pressed_json, "font-size"); + cJSON *border_color_pressed = cJSON_GetObjectItem(pressed_json, "border-color"); + cJSON *fill_color_pressed = cJSON_GetObjectItem(pressed_json, "fill-color"); + + button_theme->border_width_pressed = border_width_pressed != NULL ? border_width_pressed->valueint : button_theme->border_width; + button_theme->font_size_pressed = font_size_hover != NULL ? font_size_pressed->valueint : button_theme->font_size; + button_theme->border_color_pressed = border_color_pressed != NULL ? ltk_create_xcolor(border_color_pressed->valuestring) : button_theme->border_color; + button_theme->fill_color_pressed = fill_color_pressed != NULL ? ltk_create_xcolor(fill_color_pressed->valuestring) : button_theme->fill_color; + + cJSON *active_json = cJSON_GetObjectItem(button_json, "active"); + if (!active_json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + } + cJSON *border_width_active = cJSON_GetObjectItem(active_json, "border-width"); + cJSON *font_size_active = cJSON_GetObjectItem(active_json, "font-size"); + cJSON *border_color_active = cJSON_GetObjectItem(active_json, "border-color"); + cJSON *fill_color_active = cJSON_GetObjectItem(active_json, "fill-color"); + + button_theme->border_width_active = border_width_active != NULL ? border_width_active->valueint : button_theme->border_width; + button_theme->font_size_active = font_size_active != NULL ? font_size_active->valueint : button_theme->font_size; + button_theme->border_color_active = border_color_active != NULL ? ltk_create_xcolor(border_color_active->valuestring) : button_theme->border_color; + button_theme->fill_color_active = fill_color_active != NULL ? ltk_create_xcolor(fill_color_active->valuestring) : button_theme->fill_color; + + cJSON *disabled_json = cJSON_GetObjectItem(button_json, "disabled"); + if (!disabled_json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + } + cJSON *border_width_disabled = cJSON_GetObjectItem(disabled_json, "border-width"); + cJSON *font_size_disabled = cJSON_GetObjectItem(disabled_json, "font-size"); + cJSON *border_color_disabled = cJSON_GetObjectItem(disabled_json, "border-color"); + cJSON *fill_color_disabled = cJSON_GetObjectItem(disabled_json, "fill-color"); + + button_theme->border_width_disabled = border_width_disabled != NULL ? border_width_disabled->valueint : button_theme->border_width; + button_theme->font_size_disabled = font_size_disabled != NULL ? font_size_disabled->valueint : button_theme->font_size; + button_theme->border_color_disabled = border_color_disabled != NULL ? ltk_create_xcolor(border_color_disabled->valuestring) : button_theme->border_color; + button_theme->fill_color_disabled = fill_color_disabled != NULL ? ltk_create_xcolor(fill_color_disabled->valuestring) : button_theme->fill_color; + + return button_theme; +} + +void ltk_draw_button(void *widget) +{ + LtkButton *button = widget; + LtkButtonTheme *theme = ltk_global->theme->button; + LtkWindow *window = button->widget.window; + LtkRect rect = button->widget.rect; + XColor border_color; + XColor fill_color; + int border_width; + switch (button->widget.state) + { + case NORMAL: + border_color = theme->border_color; + fill_color = theme->fill_color; + border_width = theme->border_width; + break; + case HOVERACTIVE: + case HOVER: + border_color = theme->border_color_hover; + fill_color = theme->fill_color_hover; + border_width = theme->border_width_hover; + break; + case PRESSED: + border_color = theme->border_color_pressed; + fill_color = theme->fill_color_pressed; + border_width = theme->border_width_pressed; + break; + case ACTIVE: + border_color = theme->border_color_active; + fill_color = theme->fill_color_active; + border_width = theme->border_width_active; + break; + case DISABLED: + border_color = theme->border_color_disabled; + fill_color = theme->fill_color_disabled; + border_width = theme->border_width_disabled; + break; + default: + ltk_fatal("No style found for button!\n"); + } + XSetForeground(ltk_global->display, window->gc, fill_color.pixel); + XFillRectangle(ltk_global->display, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h); + XSetForeground(ltk_global->display, window->gc, border_color.pixel); + XSetLineAttributes(ltk_global->display, window->gc, border_width, LineSolid, CapButt, JoinMiter); + XDrawRectangle(ltk_global->display, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h); +} + +LtkButton *ltk_create_button(LtkWindow *window, const char *text, void (*callback)(void)) +{ + LtkButton *button = malloc(sizeof(LtkButton)); + + if (button == NULL) + { + printf("Button could not be created.\n"); + exit(1); + } + + button->widget.window = window; + button->widget.parent = NULL; + button->widget.active_widget = NULL; + button->widget.hover_widget = NULL; + button->widget.key_func = &ltk_button_key_event; + button->widget.mouse_func = &ltk_button_mouse_event; + button->widget.update_function = NULL; + button->widget.draw_function = &ltk_draw_button; + button->widget.destroy_function = &ltk_destroy_button; + button->widget.rect.x = 0; + button->widget.rect.y = 0; + /* For testing, will default to size of text once text is implemented */ + button->widget.rect.w = 100; + button->widget.rect.h = 100; + button->widget.state = NORMAL; + + button->callback = callback; + button->text = strdup(text); + + return button; +} + +void ltk_destroy_button(void *widget) +{ + LtkButton *button = (LtkButton *)widget; + if (!button) + { + printf("Tried to destroy NULL button.\n"); + } + free(button->text); + free(button); +} + +void ltk_button_key_event(void *widget, XEvent event) +{ +} + +void ltk_button_mouse_event(void *widget, XEvent event) +{ + LtkButton *button = widget; + if (button->widget.state == DISABLED) + { + return; + } + if (event.type == ButtonPress && event.xbutton.button == 1) + { + LtkWidget *parent = button->widget.parent; + if (parent) + { + ltk_remove_active_widget(parent); + parent->active_widget = button; + } + button->widget.state = PRESSED; + ltk_draw_button(button); + } + else if (event.type == ButtonRelease) + { + if (button->widget.state == PRESSED) + { + button->widget.state = HOVERACTIVE; + ltk_draw_button(button); + } + } + else if (event.type == MotionNotify) + { + if (button->widget.state == NORMAL || button->widget.state == ACTIVE) + { + if (button->widget.state == ACTIVE) + { + button->widget.state = HOVERACTIVE; + } + else + { + button->widget.state = HOVER; + } + LtkWidget *parent = button->widget.parent; + LtkWidget *hover_widget; + if (parent) + { + ltk_remove_hover_widget(parent); + parent->hover_widget = button; + } + ltk_draw_button(button); + } + } +} diff --git a/button.h b/button.h @@ -0,0 +1,78 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LTK_BUTTON_H_ +#define _LTK_BUTTON_H_ + +#include "widget.h" + +typedef struct +{ + LtkWidget widget; + void (*callback)(void); + char *text; +} LtkButton; + +typedef struct LtkButtonTheme +{ + int border_width; + int font_size; + XColor border_color; + XColor fill_color; + int padding_left; + int padding_right; + int padding_top; + int padding_bottom; + + int border_width_hover; + int font_size_hover; + XColor border_color_hover; + XColor fill_color_hover; + + int border_width_pressed; + int font_size_pressed; + XColor border_color_pressed; + XColor fill_color_pressed; + + int border_width_active; + int font_size_active; + XColor border_color_active; + XColor fill_color_active; + + int border_width_disabled; + int font_size_disabled; + XColor border_color_disabled; + XColor fill_color_disabled; + +} LtkButtonTheme; + +LtkButtonTheme *ltk_parse_button_theme(cJSON *button_json); +void ltk_draw_button(void *widget); +LtkButton *ltk_create_button(LtkWindow *window, const char *text, void (*callback)(void)); +void ltk_button_key_event(void *widget, XEvent event); +void ltk_button_mouse_event(void *widget, XEvent event); +void ltk_destroy_button(void *widget); +void ltk_button_key_event(void *widget, XEvent event); +void ltk_button_mouse_event(void *widget, XEvent event); + +#endif diff --git a/cJSON.c b/cJSON.c @@ -0,0 +1,1492 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <stdlib.h> +#include <float.h> +#include <limits.h> +#include <ctype.h> +#include "cJSON.h" + +static const char *global_ep; + +const char *cJSON_GetErrorPtr(void) +{ + return global_ep; +} + +static int cJSON_strcasecmp(const char *s1, const char *s2) +{ + if (!s1) + return (s1 == s2) ? 0 : 1; + if (!s2) + return 1; + for (; tolower(*s1) == tolower(*s2); ++s1, ++s2) + if (*s1 == 0) + return 0; + return tolower(*(const unsigned char *)s1) - + tolower(*(const unsigned char *)s2); +} + +static void *(*cJSON_malloc) (size_t sz) = malloc; +static void (*cJSON_free) (void *ptr) = free; + +static char *cJSON_strdup(const char *str) +{ + size_t len; + char *copy; + + len = strlen(str) + 1; + if (!(copy = (char *)cJSON_malloc(len))) + return 0; + memcpy(copy, str, len); + return copy; +} + +void cJSON_InitHooks(cJSON_Hooks * hooks) +{ + if (!hooks) { /* Reset hooks */ + cJSON_malloc = malloc; + cJSON_free = free; + return; + } + + cJSON_malloc = (hooks->malloc_fn) ? hooks->malloc_fn : malloc; + cJSON_free = (hooks->free_fn) ? hooks->free_fn : free; +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(void) +{ + cJSON *node = (cJSON *) cJSON_malloc(sizeof(cJSON)); + if (node) + memset(node, 0, sizeof(cJSON)); + return node; +} + +/* Delete a cJSON structure. */ +void cJSON_Delete(cJSON * c) +{ + cJSON *next; + while (c) { + next = c->next; + if (!(c->type & cJSON_IsReference) && c->child) + cJSON_Delete(c->child); + if (!(c->type & cJSON_IsReference) && c->valuestring) + cJSON_free(c->valuestring); + if (!(c->type & cJSON_StringIsConst) && c->string) + cJSON_free(c->string); + cJSON_free(c); + c = next; + } +} + +/* Parse the input text to generate a number, and populate the result into item. */ +static const char *parse_number(cJSON * item, const char *num) +{ + double n = 0, sign = 1, scale = 0; + int subscale = 0, signsubscale = 1; + + if (*num == '-') + sign = -1, num++; /* Has sign? */ + if (*num == '0') + num++; /* is zero */ + if (*num >= '1' && *num <= '9') + do + n = (n * 10.0) + (*num++ - '0'); + while (*num >= '0' && *num <= '9'); /* Number? */ + if (*num == '.' && num[1] >= '0' && num[1] <= '9') { + num++; + do + n = (n * 10.0) + (*num++ - '0'), scale--; + while (*num >= '0' && *num <= '9'); + } /* Fractional part? */ + if (*num == 'e' || *num == 'E') { /* Exponent? */ + num++; + if (*num == '+') + num++; + else if (*num == '-') + signsubscale = -1, num++; /* With sign? */ + while (*num >= '0' && *num <= '9') + subscale = (subscale * 10) + (*num++ - '0'); /* Number? */ + } + + n = sign * n * pow(10.0, (scale + subscale * signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ + + item->valuedouble = n; + item->valueint = (int)n; + item->type = cJSON_Number; + return num; +} + +static int pow2gt(int x) +{ + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; +} + +typedef struct { + char *buffer; + int length; + int offset; +} printbuffer; + +static char *ensure(printbuffer * p, int needed) +{ + char *newbuffer; + int newsize; + if (!p || !p->buffer) + return 0; + needed += p->offset; + if (needed <= p->length) + return p->buffer + p->offset; + + newsize = pow2gt(needed); + newbuffer = (char *)cJSON_malloc(newsize); + if (!newbuffer) { + cJSON_free(p->buffer); + p->length = 0, p->buffer = 0; + return 0; + } + if (newbuffer) + memcpy(newbuffer, p->buffer, p->length); + cJSON_free(p->buffer); + p->length = newsize; + p->buffer = newbuffer; + return newbuffer + p->offset; +} + +static int update(printbuffer * p) +{ + char *str; + if (!p || !p->buffer) + return 0; + str = p->buffer + p->offset; + return p->offset + strlen(str); +} + +/* Render the number nicely from the given item into a string. */ +static char *print_number(cJSON * item, printbuffer * p) +{ + char *str = 0; + double d = item->valuedouble; + if (d == 0) { + if (p) + str = ensure(p, 2); + else + str = (char *)cJSON_malloc(2); /* special case for 0. */ + if (str) + strcpy(str, "0"); + } else if (fabs(((double)item->valueint) - d) <= DBL_EPSILON + && d <= INT_MAX && d >= INT_MIN) { + if (p) + str = ensure(p, 21); + else + str = (char *)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */ + if (str) + sprintf(str, "%d", item->valueint); + } else { + if (p) + str = ensure(p, 64); + else + str = (char *)cJSON_malloc(64); /* This is a nice tradeoff. */ + if (str) { + if (d * 0 != 0) + sprintf(str, "null"); /* This checks for NaN and Infinity */ + else if (fabs(floor(d) - d) <= DBL_EPSILON + && fabs(d) < 1.0e60) + sprintf(str, "%.0f", d); + else if (fabs(d) < 1.0e-6 || fabs(d) > 1.0e9) + sprintf(str, "%e", d); + else + sprintf(str, "%f", d); + } + } + return str; +} + +static unsigned parse_hex4(const char *str) +{ + unsigned h = 0; + if (*str >= '0' && *str <= '9') + h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') + h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') + h += 10 + (*str) - 'a'; + else + return 0; + h = h << 4; + str++; + if (*str >= '0' && *str <= '9') + h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') + h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') + h += 10 + (*str) - 'a'; + else + return 0; + h = h << 4; + str++; + if (*str >= '0' && *str <= '9') + h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') + h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') + h += 10 + (*str) - 'a'; + else + return 0; + h = h << 4; + str++; + if (*str >= '0' && *str <= '9') + h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') + h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') + h += 10 + (*str) - 'a'; + else + return 0; + return h; +} + +/* Parse the input text into an unescaped cstring, and populate item. */ +static const unsigned char firstByteMark[7] = + { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; +static const char *parse_string(cJSON * item, const char *str, const char **ep) +{ + const char *ptr = str + 1, *end_ptr = str + 1; + char *ptr2; + char *out; + int len = 0; + unsigned uc, uc2; + if (*str != '\"') { + *ep = str; + return 0; + } + /* not a string! */ + while (*end_ptr != '\"' && *end_ptr && ++len) + if (*end_ptr++ == '\\') + end_ptr++; /* Skip escaped quotes. */ + + out = (char *)cJSON_malloc(len + 1); /* This is how long we need for the string, roughly. */ + if (!out) + return 0; + item->valuestring = out; /* assign here so out will be deleted during cJSON_Delete() later */ + item->type = cJSON_String; + + ptr = str + 1; + ptr2 = out; + while (ptr < end_ptr) { + if (*ptr != '\\') + *ptr2++ = *ptr++; + else { + ptr++; + switch (*ptr) { + case 'b': + *ptr2++ = '\b'; + break; + case 'f': + *ptr2++ = '\f'; + break; + case 'n': + *ptr2++ = '\n'; + break; + case 'r': + *ptr2++ = '\r'; + break; + case 't': + *ptr2++ = '\t'; + break; + case 'u': /* transcode utf16 to utf8. */ + uc = parse_hex4(ptr + 1); + ptr += 4; /* get the unicode char. */ + if (ptr >= end_ptr) { + *ep = str; + return 0; + } + /* invalid */ + if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) { + *ep = str; + return 0; + } + /* check for invalid. */ + if (uc >= 0xD800 && uc <= 0xDBFF) { /* UTF16 surrogate pairs. */ + if (ptr + 6 > end_ptr) { + *ep = str; + return 0; + } /* invalid */ + if (ptr[1] != '\\' || ptr[2] != 'u') { + *ep = str; + return 0; + } /* missing second-half of surrogate. */ + uc2 = parse_hex4(ptr + 3); + ptr += 6; + if (uc2 < 0xDC00 || uc2 > 0xDFFF) { + *ep = str; + return 0; + } /* invalid second-half of surrogate. */ + uc = 0x10000 + + (((uc & 0x3FF) << 10) | + (uc2 & 0x3FF)); + } + + len = 4; + if (uc < 0x80) + len = 1; + else if (uc < 0x800) + len = 2; + else if (uc < 0x10000) + len = 3; + ptr2 += len; + + switch (len) { + case 4: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + case 3: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + case 2: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + case 1: + *--ptr2 = (uc | firstByteMark[len]); + } + ptr2 += len; + break; + default: + *ptr2++ = *ptr; + break; + } + ptr++; + } + } + *ptr2 = 0; + if (*ptr == '\"') + ptr++; + return ptr; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static char *print_string_ptr(const char *str, printbuffer * p) +{ + const char *ptr; + char *ptr2, *out; + int len = 0, flag = 0; + unsigned char token; + + if (!str) { + if (p) + out = ensure(p, 3); + else + out = (char *)cJSON_malloc(3); + if (!out) + return 0; + strcpy(out, "\"\""); + return out; + } + + for (ptr = str; *ptr; ptr++) + flag |= ((*ptr > 0 && *ptr < 32) || (*ptr == '\"') + || (*ptr == '\\')) ? 1 : 0; + if (!flag) { + len = ptr - str; + if (p) + out = ensure(p, len + 3); + else + out = (char *)cJSON_malloc(len + 3); + if (!out) + return 0; + ptr2 = out; + *ptr2++ = '\"'; + strcpy(ptr2, str); + ptr2[len] = '\"'; + ptr2[len + 1] = 0; + return out; + } + + ptr = str; + while ((token = *ptr) && ++len) { + if (strchr("\"\\\b\f\n\r\t", token)) + len++; + else if (token < 32) + len += 5; + ptr++; + } + + if (p) + out = ensure(p, len + 3); + else + out = (char *)cJSON_malloc(len + 3); + if (!out) + return 0; + + ptr2 = out; + ptr = str; + *ptr2++ = '\"'; + while (*ptr) { + if ((unsigned char)*ptr > 31 && *ptr != '\"' && *ptr != '\\') + *ptr2++ = *ptr++; + else { + *ptr2++ = '\\'; + switch (token = *ptr++) { + case '\\': + *ptr2++ = '\\'; + break; + case '\"': + *ptr2++ = '\"'; + break; + case '\b': + *ptr2++ = 'b'; + break; + case '\f': + *ptr2++ = 'f'; + break; + case '\n': + *ptr2++ = 'n'; + break; + case '\r': + *ptr2++ = 'r'; + break; + case '\t': + *ptr2++ = 't'; + break; + default: + sprintf(ptr2, "u%04x", token); + ptr2 += 5; + break; /* escape and print */ + } + } + } + *ptr2++ = '\"'; + *ptr2++ = 0; + return out; +} + +/* Invote print_string_ptr (which is useful) on an item. */ +static char *print_string(cJSON * item, printbuffer * p) +{ + return print_string_ptr(item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static const char *parse_value(cJSON * item, const char *value, + const char **ep); +static char *print_value(cJSON * item, int depth, int fmt, printbuffer * p); +static const char *parse_array(cJSON * item, const char *value, + const char **ep); +static char *print_array(cJSON * item, int depth, int fmt, printbuffer * p); +static const char *parse_object(cJSON * item, const char *value, + const char **ep); +static char *print_object(cJSON * item, int depth, int fmt, printbuffer * p); + +/* Utility to jump whitespace and cr/lf */ +static const char *skip(const char *in) +{ + while (in && *in && (unsigned char)*in <= 32) + in++; + return in; +} + +/* Parse an object - create a new root, and populate. */ +cJSON *cJSON_ParseWithOpts(const char *value, const char **return_parse_end, + int require_null_terminated) +{ + const char *end = 0, **ep = + return_parse_end ? return_parse_end : &global_ep; + cJSON *c = cJSON_New_Item(); + *ep = 0; + if (!c) + return 0; /* memory fail */ + + end = parse_value(c, skip(value), ep); + if (!end) { + cJSON_Delete(c); + return 0; + } + + /* parse failure. ep is set. */ + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) { + end = skip(end); + if (*end) { + cJSON_Delete(c); + *ep = end; + return 0; + } + } + if (return_parse_end) + *return_parse_end = end; + return c; +} + +/* Default options for cJSON_Parse */ +cJSON *cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +/* Render a cJSON item/entity/structure to text. */ +char *cJSON_Print(cJSON * item) +{ + return print_value(item, 0, 1, 0); +} + +char *cJSON_PrintUnformatted(cJSON * item) +{ + return print_value(item, 0, 0, 0); +} + +char *cJSON_PrintBuffered(cJSON * item, int prebuffer, int fmt) +{ + printbuffer p; + p.buffer = (char *)cJSON_malloc(prebuffer); + p.length = prebuffer; + p.offset = 0; + return print_value(item, 0, fmt, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static const char *parse_value(cJSON * item, const char *value, const char **ep) +{ + if (!value) + return 0; /* Fail on null. */ + if (!strncmp(value, "null", 4)) { + item->type = cJSON_NULL; + return value + 4; + } + if (!strncmp(value, "false", 5)) { + item->type = cJSON_False; + return value + 5; + } + if (!strncmp(value, "true", 4)) { + item->type = cJSON_True; + item->valueint = 1; + return value + 4; + } + if (*value == '\"') { + return parse_string(item, value, ep); + } + if (*value == '-' || (*value >= '0' && *value <= '9')) { + return parse_number(item, value); + } + if (*value == '[') { + return parse_array(item, value, ep); + } + if (*value == '{') { + return parse_object(item, value, ep); + } + + *ep = value; + return 0; /* failure. */ +} + +/* Render a value to text. */ +static char *print_value(cJSON * item, int depth, int fmt, printbuffer * p) +{ + char *out = 0; + if (!item) + return 0; + if (p) { + switch ((item->type) & 255) { + case cJSON_NULL: + { + out = ensure(p, 5); + if (out) + strcpy(out, "null"); + break; + } + case cJSON_False: + { + out = ensure(p, 6); + if (out) + strcpy(out, "false"); + break; + } + case cJSON_True: + { + out = ensure(p, 5); + if (out) + strcpy(out, "true"); + break; + } + case cJSON_Number: + out = print_number(item, p); + break; + case cJSON_String: + out = print_string(item, p); + break; + case cJSON_Array: + out = print_array(item, depth, fmt, p); + break; + case cJSON_Object: + out = print_object(item, depth, fmt, p); + break; + } + } else { + switch ((item->type) & 255) { + case cJSON_NULL: + out = cJSON_strdup("null"); + break; + case cJSON_False: + out = cJSON_strdup("false"); + break; + case cJSON_True: + out = cJSON_strdup("true"); + break; + case cJSON_Number: + out = print_number(item, 0); + break; + case cJSON_String: + out = print_string(item, 0); + break; + case cJSON_Array: + out = print_array(item, depth, fmt, 0); + break; + case cJSON_Object: + out = print_object(item, depth, fmt, 0); + break; + } + } + return out; +} + +/* Build an array from input text. */ +static const char *parse_array(cJSON * item, const char *value, const char **ep) +{ + cJSON *child; + if (*value != '[') { + *ep = value; + return 0; + } + /* not an array! */ + item->type = cJSON_Array; + value = skip(value + 1); + if (*value == ']') + return value + 1; /* empty array. */ + + item->child = child = cJSON_New_Item(); + if (!item->child) + return 0; /* memory fail */ + value = skip(parse_value(child, skip(value), ep)); /* skip any spacing, get the value. */ + if (!value) + return 0; + + while (*value == ',') { + cJSON *new_item; + if (!(new_item = cJSON_New_Item())) + return 0; /* memory fail */ + child->next = new_item; + new_item->prev = child; + child = new_item; + value = skip(parse_value(child, skip(value + 1), ep)); + if (!value) + return 0; /* memory fail */ + } + + if (*value == ']') + return value + 1; /* end of array */ + *ep = value; + return 0; /* malformed. */ +} + +/* Render an array to text */ +static char *print_array(cJSON * item, int depth, int fmt, printbuffer * p) +{ + char **entries; + char *out = 0, *ptr, *ret; + int len = 5; + cJSON *child = item->child; + int numentries = 0, i = 0, fail = 0; + size_t tmplen = 0; + + /* How many entries in the array? */ + while (child) + numentries++, child = child->next; + /* Explicitly handle numentries==0 */ + if (!numentries) { + if (p) + out = ensure(p, 3); + else + out = (char *)cJSON_malloc(3); + if (out) + strcpy(out, "[]"); + return out; + } + + if (p) { + /* Compose the output array. */ + i = p->offset; + ptr = ensure(p, 1); + if (!ptr) + return 0; + *ptr = '['; + p->offset++; + child = item->child; + while (child && !fail) { + print_value(child, depth + 1, fmt, p); + p->offset = update(p); + if (child->next) { + len = fmt ? 2 : 1; + ptr = ensure(p, len + 1); + if (!ptr) + return 0; + *ptr++ = ','; + if (fmt) + *ptr++ = ' '; + *ptr = 0; + p->offset += len; + } + child = child->next; + } + ptr = ensure(p, 2); + if (!ptr) + return 0; + *ptr++ = ']'; + *ptr = 0; + out = (p->buffer) + i; + } else { + /* Allocate an array to hold the values for each */ + entries = (char **)cJSON_malloc(numentries * sizeof(char *)); + if (!entries) + return 0; + memset(entries, 0, numentries * sizeof(char *)); + /* Retrieve all the results: */ + child = item->child; + while (child && !fail) { + ret = print_value(child, depth + 1, fmt, 0); + entries[i++] = ret; + if (ret) + len += strlen(ret) + 2 + (fmt ? 1 : 0); + else + fail = 1; + child = child->next; + } + + /* If we didn't fail, try to malloc the output string */ + if (!fail) + out = (char *)cJSON_malloc(len); + /* If that fails, we fail. */ + if (!out) + fail = 1; + + /* Handle failure. */ + if (fail) { + for (i = 0; i < numentries; i++) + if (entries[i]) + cJSON_free(entries[i]); + cJSON_free(entries); + return 0; + } + + /* Compose the output array. */ + *out = '['; + ptr = out + 1; + *ptr = 0; + for (i = 0; i < numentries; i++) { + tmplen = strlen(entries[i]); + memcpy(ptr, entries[i], tmplen); + ptr += tmplen; + if (i != numentries - 1) { + *ptr++ = ','; + if (fmt) + *ptr++ = ' '; + *ptr = 0; + } + cJSON_free(entries[i]); + } + cJSON_free(entries); + *ptr++ = ']'; + *ptr++ = 0; + } + return out; +} + +/* Build an object from the text. */ +static const char *parse_object(cJSON * item, const char *value, + const char **ep) +{ + cJSON *child; + if (*value != '{') { + *ep = value; + return 0; + } + /* not an object! */ + item->type = cJSON_Object; + value = skip(value + 1); + if (*value == '}') + return value + 1; /* empty array. */ + + item->child = child = cJSON_New_Item(); + if (!item->child) + return 0; + value = skip(parse_string(child, skip(value), ep)); + if (!value) + return 0; + child->string = child->valuestring; + child->valuestring = 0; + if (*value != ':') { + *ep = value; + return 0; + } /* fail! */ + value = skip(parse_value(child, skip(value + 1), ep)); /* skip any spacing, get the value. */ + if (!value) + return 0; + + while (*value == ',') { + cJSON *new_item; + if (!(new_item = cJSON_New_Item())) + return 0; /* memory fail */ + child->next = new_item; + new_item->prev = child; + child = new_item; + value = skip(parse_string(child, skip(value + 1), ep)); + if (!value) + return 0; + child->string = child->valuestring; + child->valuestring = 0; + if (*value != ':') { + *ep = value; + return 0; + } /* fail! */ + value = skip(parse_value(child, skip(value + 1), ep)); /* skip any spacing, get the value. */ + if (!value) + return 0; + } + + if (*value == '}') + return value + 1; /* end of array */ + *ep = value; + return 0; /* malformed. */ +} + +/* Render an object to text. */ +static char *print_object(cJSON * item, int depth, int fmt, printbuffer * p) +{ + char **entries = 0, **names = 0; + char *out = 0, *ptr, *ret, *str; + int len = 7, i = 0, j; + cJSON *child = item->child; + int numentries = 0, fail = 0; + size_t tmplen = 0; + /* Count the number of entries. */ + while (child) + numentries++, child = child->next; + /* Explicitly handle empty object case */ + if (!numentries) { + if (p) + out = ensure(p, fmt ? depth + 4 : 3); + else + out = (char *)cJSON_malloc(fmt ? depth + 4 : 3); + if (!out) + return 0; + ptr = out; + *ptr++ = '{'; + if (fmt) { + *ptr++ = '\n'; + for (i = 0; i < depth; i++) + *ptr++ = '\t'; + } + *ptr++ = '}'; + *ptr++ = 0; + return out; + } + if (p) { + /* Compose the output: */ + i = p->offset; + len = fmt ? 2 : 1; + ptr = ensure(p, len + 1); + if (!ptr) + return 0; + *ptr++ = '{'; + if (fmt) + *ptr++ = '\n'; + *ptr = 0; + p->offset += len; + child = item->child; + depth++; + while (child) { + if (fmt) { + ptr = ensure(p, depth); + if (!ptr) + return 0; + for (j = 0; j < depth; j++) + *ptr++ = '\t'; + p->offset += depth; + } + print_string_ptr(child->string, p); + p->offset = update(p); + + len = fmt ? 2 : 1; + ptr = ensure(p, len); + if (!ptr) + return 0; + *ptr++ = ':'; + if (fmt) + *ptr++ = '\t'; + p->offset += len; + + print_value(child, depth, fmt, p); + p->offset = update(p); + + len = (fmt ? 1 : 0) + (child->next ? 1 : 0); + ptr = ensure(p, len + 1); + if (!ptr) + return 0; + if (child->next) + *ptr++ = ','; + if (fmt) + *ptr++ = '\n'; + *ptr = 0; + p->offset += len; + child = child->next; + } + ptr = ensure(p, fmt ? (depth + 1) : 2); + if (!ptr) + return 0; + if (fmt) + for (i = 0; i < depth - 1; i++) + *ptr++ = '\t'; + *ptr++ = '}'; + *ptr = 0; + out = (p->buffer) + i; + } else { + /* Allocate space for the names and the objects */ + entries = (char **)cJSON_malloc(numentries * sizeof(char *)); + if (!entries) + return 0; + names = (char **)cJSON_malloc(numentries * sizeof(char *)); + if (!names) { + cJSON_free(entries); + return 0; + } + memset(entries, 0, sizeof(char *) * numentries); + memset(names, 0, sizeof(char *) * numentries); + + /* Collect all the results into our arrays: */ + child = item->child; + depth++; + if (fmt) + len += depth; + while (child && !fail) { + names[i] = str = print_string_ptr(child->string, 0); + entries[i++] = ret = print_value(child, depth, fmt, 0); + if (str && ret) + len += + strlen(ret) + strlen(str) + 2 + (fmt ? 2 + + depth : 0); + else + fail = 1; + child = child->next; + } + + /* Try to allocate the output string */ + if (!fail) + out = (char *)cJSON_malloc(len); + if (!out) + fail = 1; + + /* Handle failure */ + if (fail) { + for (i = 0; i < numentries; i++) { + if (names[i]) + cJSON_free(names[i]); + if (entries[i]) + cJSON_free(entries[i]); + } + cJSON_free(names); + cJSON_free(entries); + return 0; + } + + /* Compose the output: */ + *out = '{'; + ptr = out + 1; + if (fmt) + *ptr++ = '\n'; + *ptr = 0; + for (i = 0; i < numentries; i++) { + if (fmt) + for (j = 0; j < depth; j++) + *ptr++ = '\t'; + tmplen = strlen(names[i]); + memcpy(ptr, names[i], tmplen); + ptr += tmplen; + *ptr++ = ':'; + if (fmt) + *ptr++ = '\t'; + strcpy(ptr, entries[i]); + ptr += strlen(entries[i]); + if (i != numentries - 1) + *ptr++ = ','; + if (fmt) + *ptr++ = '\n'; + *ptr = 0; + cJSON_free(names[i]); + cJSON_free(entries[i]); + } + + cJSON_free(names); + cJSON_free(entries); + if (fmt) + for (i = 0; i < depth - 1; i++) + *ptr++ = '\t'; + *ptr++ = '}'; + *ptr++ = 0; + } + return out; +} + +/* Get Array size/item / object item. */ +int cJSON_GetArraySize(cJSON * array) +{ + cJSON *c = array->child; + int i = 0; + while (c) + i++, c = c->next; + return i; +} + +cJSON *cJSON_GetArrayItem(cJSON * array, int item) +{ + cJSON *c = array ? array->child : 0; + while (c && item > 0) + item--, c = c->next; + return c; +} + +cJSON *cJSON_GetObjectItem(cJSON * object, const char *string) +{ + cJSON *c = object ? object->child : 0; + while (c && cJSON_strcasecmp(c->string, string)) + c = c->next; + return c; +} + +int cJSON_HasObjectItem(cJSON * object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON * prev, cJSON * item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(cJSON * item) +{ + cJSON *ref = cJSON_New_Item(); + if (!ref) + return 0; + memcpy(ref, item, sizeof(cJSON)); + ref->string = 0; + ref->type |= cJSON_IsReference; + ref->next = ref->prev = 0; + return ref; +} + +/* Add item to array/object. */ +void cJSON_AddItemToArray(cJSON * array, cJSON * item) +{ + cJSON *c = array->child; + if (!item) + return; + if (!c) { + array->child = item; + } else { + while (c && c->next) + c = c->next; + suffix_object(c, item); + } +} + +void cJSON_AddItemToObject(cJSON * object, const char *string, cJSON * item) +{ + if (!item) + return; + if (item->string) + cJSON_free(item->string); + item->string = cJSON_strdup(string); + cJSON_AddItemToArray(object, item); +} + +void cJSON_AddItemToObjectCS(cJSON * object, const char *string, cJSON * item) +{ + if (!item) + return; + if (!(item->type & cJSON_StringIsConst) && item->string) + cJSON_free(item->string); + item->string = (char *)string; + item->type |= cJSON_StringIsConst; + cJSON_AddItemToArray(object, item); +} + +void cJSON_AddItemReferenceToArray(cJSON * array, cJSON * item) +{ + cJSON_AddItemToArray(array, create_reference(item)); +} + +void +cJSON_AddItemReferenceToObject(cJSON * object, const char *string, cJSON * item) +{ + cJSON_AddItemToObject(object, string, create_reference(item)); +} + +cJSON *cJSON_DetachItemFromArray(cJSON * array, int which) +{ + cJSON *c = array->child; + while (c && which > 0) + c = c->next, which--; + if (!c) + return 0; + if (c->prev) + c->prev->next = c->next; + if (c->next) + c->next->prev = c->prev; + if (c == array->child) + array->child = c->next; + c->prev = c->next = 0; + return c; +} + +void cJSON_DeleteItemFromArray(cJSON * array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +cJSON *cJSON_DetachItemFromObject(cJSON * object, const char *string) +{ + int i = 0; + cJSON *c = object->child; + while (c && cJSON_strcasecmp(c->string, string)) + i++, c = c->next; + if (c) + return cJSON_DetachItemFromArray(object, i); + return 0; +} + +void cJSON_DeleteItemFromObject(cJSON * object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +/* Replace array/object items with new ones. */ +void cJSON_InsertItemInArray(cJSON * array, int which, cJSON * newitem) +{ + cJSON *c = array->child; + while (c && which > 0) + c = c->next, which--; + if (!c) { + cJSON_AddItemToArray(array, newitem); + return; + } + newitem->next = c; + newitem->prev = c->prev; + c->prev = newitem; + if (c == array->child) + array->child = newitem; + else + newitem->prev->next = newitem; +} + +void cJSON_ReplaceItemInArray(cJSON * array, int which, cJSON * newitem) +{ + cJSON *c = array->child; + while (c && which > 0) + c = c->next, which--; + if (!c) + return; + newitem->next = c->next; + newitem->prev = c->prev; + if (newitem->next) + newitem->next->prev = newitem; + if (c == array->child) + array->child = newitem; + else + newitem->prev->next = newitem; + c->next = c->prev = 0; + cJSON_Delete(c); +} + +void +cJSON_ReplaceItemInObject(cJSON * object, const char *string, cJSON * newitem) +{ + int i = 0; + cJSON *c = object->child; + while (c && cJSON_strcasecmp(c->string, string)) + i++, c = c->next; + if (c) { + newitem->string = cJSON_strdup(string); + cJSON_ReplaceItemInArray(object, i, newitem); + } +} + +/* Create basic types: */ +cJSON *cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(); + if (item) + item->type = cJSON_NULL; + return item; +} + +cJSON *cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(); + if (item) + item->type = cJSON_True; + return item; +} + +cJSON *cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(); + if (item) + item->type = cJSON_False; + return item; +} + +cJSON *cJSON_CreateBool(int b) +{ + cJSON *item = cJSON_New_Item(); + if (item) + item->type = b ? cJSON_True : cJSON_False; + return item; +} + +cJSON *cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(); + if (item) { + item->type = cJSON_Number; + item->valuedouble = num; + item->valueint = (int)num; + } + return item; +} + +cJSON *cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(); + if (item) { + item->type = cJSON_String; + item->valuestring = cJSON_strdup(string); + if (!item->valuestring) { + cJSON_Delete(item); + return 0; + } + } + return item; +} + +cJSON *cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(); + if (item) + item->type = cJSON_Array; + return item; +} + +cJSON *cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(); + if (item) + item->type = cJSON_Object; + return item; +} + +/* Create Arrays: */ +cJSON *cJSON_CreateIntArray(const int *numbers, int count) +{ + int i; + cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); + for (i = 0; a && i < count; i++) { + n = cJSON_CreateNumber(numbers[i]); + if (!n) { + cJSON_Delete(a); + return 0; + } + if (!i) + a->child = n; + else + suffix_object(p, n); + p = n; + } + return a; +} + +cJSON *cJSON_CreateFloatArray(const float *numbers, int count) +{ + int i; + cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); + for (i = 0; a && i < count; i++) { + n = cJSON_CreateNumber(numbers[i]); + if (!n) { + cJSON_Delete(a); + return 0; + } + if (!i) + a->child = n; + else + suffix_object(p, n); + p = n; + } + return a; +} + +cJSON *cJSON_CreateDoubleArray(const double *numbers, int count) +{ + int i; + cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); + for (i = 0; a && i < count; i++) { + n = cJSON_CreateNumber(numbers[i]); + if (!n) { + cJSON_Delete(a); + return 0; + } + if (!i) + a->child = n; + else + suffix_object(p, n); + p = n; + } + return a; +} + +cJSON *cJSON_CreateStringArray(const char **strings, int count) +{ + int i; + cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); + for (i = 0; a && i < count; i++) { + n = cJSON_CreateString(strings[i]); + if (!n) { + cJSON_Delete(a); + return 0; + } + if (!i) + a->child = n; + else + suffix_object(p, n); + p = n; + } + return a; +} + +/* Duplication */ +cJSON *cJSON_Duplicate(cJSON * item, int recurse) +{ + cJSON *newitem, *cptr, *nptr = 0, *newchild; + /* Bail on bad ptr */ + if (!item) + return 0; + /* Create new item */ + newitem = cJSON_New_Item(); + if (!newitem) + return 0; + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference), newitem->valueint = + item->valueint, newitem->valuedouble = item->valuedouble; + if (item->valuestring) { + newitem->valuestring = cJSON_strdup(item->valuestring); + if (!newitem->valuestring) { + cJSON_Delete(newitem); + return 0; + } + } + if (item->string) { + newitem->string = cJSON_strdup(item->string); + if (!newitem->string) { + cJSON_Delete(newitem); + return 0; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + return newitem; + /* Walk the ->next chain for the child. */ + cptr = item->child; + while (cptr) { + newchild = cJSON_Duplicate(cptr, 1); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) { + cJSON_Delete(newitem); + return 0; + } + if (nptr) { + nptr->next = newchild, newchild->prev = nptr; + nptr = newchild; + } /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + else { + newitem->child = newchild; + nptr = newchild; + } /* Set newitem->child and move to it */ + cptr = cptr->next; + } + return newitem; +} + +void cJSON_Minify(char *json) +{ + char *into = json; + while (*json) { + if (*json == ' ') + json++; + else if (*json == '\t') + json++; /* Whitespace characters. */ + else if (*json == '\r') + json++; + else if (*json == '\n') + json++; + else if (*json == '/' && json[1] == '/') + while (*json && *json != '\n') + json++; /* double-slash comments, to end of line. */ + else if (*json == '/' && json[1] == '*') { + while (*json && !(*json == '*' && json[1] == '/')) + json++; + json += 2; + } /* multiline comments. */ + else if (*json == '\"') { + *into++ = *json++; + while (*json && *json != '\"') { + if (*json == '\\') + *into++ = *json++; + *into++ = *json++; + } + *into++ = *json++; + } /* string literals, which are \" sensitive. */ + else + *into++ = *json++; /* All other characters. */ + } + *into = 0; /* and null-terminate. */ +} diff --git a/cJSON.h b/cJSON.h @@ -0,0 +1,160 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" { +#endif + +/* cJSON Types: */ +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ + typedef struct cJSON { + struct cJSON *next, *prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + + int type; /* The type of the item, as above. */ + + char *valuestring; /* The item's string, if type==cJSON_String */ + int valueint; /* The item's number, if type==cJSON_Number */ + double valuedouble; /* The item's number, if type==cJSON_Number */ + + char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + } cJSON; + + typedef struct cJSON_Hooks { + void *(*malloc_fn) (size_t sz); + void (*free_fn) (void *ptr); + } cJSON_Hooks; + +/* Supply malloc, realloc and free functions to cJSON */ + extern void cJSON_InitHooks(cJSON_Hooks * hooks); + +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ + extern cJSON *cJSON_Parse(const char *value); +/* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ + extern char *cJSON_Print(cJSON * item); +/* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ + extern char *cJSON_PrintUnformatted(cJSON * item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ + extern char *cJSON_PrintBuffered(cJSON * item, int prebuffer, int fmt); +/* Delete a cJSON entity and all subentities. */ + extern void cJSON_Delete(cJSON * c); + +/* Returns the number of items in an array (or object). */ + extern int cJSON_GetArraySize(cJSON * array); +/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ + extern cJSON *cJSON_GetArrayItem(cJSON * array, int item); +/* Get item "string" from object. Case insensitive. */ + extern cJSON *cJSON_GetObjectItem(cJSON * object, const char *string); + extern int cJSON_HasObjectItem(cJSON * object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ + extern const char *cJSON_GetErrorPtr(void); + +/* These calls create a cJSON item of the appropriate type. */ + extern cJSON *cJSON_CreateNull(void); + extern cJSON *cJSON_CreateTrue(void); + extern cJSON *cJSON_CreateFalse(void); + extern cJSON *cJSON_CreateBool(int b); + extern cJSON *cJSON_CreateNumber(double num); + extern cJSON *cJSON_CreateString(const char *string); + extern cJSON *cJSON_CreateArray(void); + extern cJSON *cJSON_CreateObject(void); + +/* These utilities create an Array of count items. */ + extern cJSON *cJSON_CreateIntArray(const int *numbers, int count); + extern cJSON *cJSON_CreateFloatArray(const float *numbers, int count); + extern cJSON *cJSON_CreateDoubleArray(const double *numbers, int count); + extern cJSON *cJSON_CreateStringArray(const char **strings, int count); + +/* Append item to the specified array/object. */ + extern void cJSON_AddItemToArray(cJSON * array, cJSON * item); + extern void cJSON_AddItemToObject(cJSON * object, const char *string, + cJSON * item); + extern void cJSON_AddItemToObjectCS(cJSON * object, const char *string, cJSON * item); /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object */ +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ + extern void cJSON_AddItemReferenceToArray(cJSON * array, cJSON * item); + extern void cJSON_AddItemReferenceToObject(cJSON * object, + const char *string, + cJSON * item); + +/* Remove/Detatch items from Arrays/Objects. */ + extern cJSON *cJSON_DetachItemFromArray(cJSON * array, int which); + extern void cJSON_DeleteItemFromArray(cJSON * array, int which); + extern cJSON *cJSON_DetachItemFromObject(cJSON * object, + const char *string); + extern void cJSON_DeleteItemFromObject(cJSON * object, + const char *string); + +/* Update array items. */ + extern void cJSON_InsertItemInArray(cJSON * array, int which, cJSON * newitem); /* Shifts pre-existing items to the right. */ + extern void cJSON_ReplaceItemInArray(cJSON * array, int which, + cJSON * newitem); + extern void cJSON_ReplaceItemInObject(cJSON * object, + const char *string, + cJSON * newitem); + +/* Duplicate a cJSON item */ + extern cJSON *cJSON_Duplicate(cJSON * item, int recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will +need to be released. With recurse!=0, it will duplicate any children connected to the item. +The item->next and ->prev pointers are always zero on return from Duplicate. */ + +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error. If not, then cJSON_GetErrorPtr() does the job. */ + extern cJSON *cJSON_ParseWithOpts(const char *value, + const char **return_parse_end, + int require_null_terminated); + + extern void cJSON_Minify(char *json); + +/* Macros for creating things quickly. */ +#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) +#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) +#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) +#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) +#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) +#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) +#define cJSON_SetNumberValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) + +/* Macro for iterating over an array */ +#define cJSON_ArrayForEach(pos, head) for(pos = (head)->child; pos != NULL; pos = pos->next) + +#ifdef __cplusplus +} +#endif +#endif diff --git a/common.c b/common.c @@ -0,0 +1,80 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ltk.h" + +char *ltk_read_file(const char *path) +{ + FILE *f; + long len; + char *file_contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + file_contents = malloc(len + 1); + fread(file_contents, 1, len, f); + file_contents[len] = '\0'; + fclose(f); + + return file_contents; +} + +int ltk_collide_rect(LtkRect rect, int x, int y) +{ + return (rect.x <= x && (rect.x + rect.w) >= x && rect.y <= y && (rect.y + rect.h) >= y); +} + +/* Recursively set all active_widget states to NORMAL, redraw them, + * and remove the references to them in their parent functions */ +void ltk_remove_active_widget(void *widget) +{ + if (!widget) return; + LtkWidget *parent = widget; + LtkWidget *child; + while (parent->active_widget) + { + child = parent->active_widget; + child->state = NORMAL; + child->draw_function(child); + parent->active_widget = NULL; + parent = child; + } +} + +/* Recursively set all hover_widget states to NORMAL, redraw them, + * and remove the references to them in their parent functions */ +void ltk_remove_hover_widget(void *widget) +{ + if (!widget) return; + LtkWidget *parent = widget; + LtkWidget *child; + while (parent->hover_widget) + { + child = parent->hover_widget; + child->state = child->state == HOVERACTIVE ? ACTIVE : NORMAL; + child->draw_function(child); + parent->hover_widget = NULL; + parent = child; + } +} diff --git a/common.h b/common.h @@ -0,0 +1,52 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LTK_COMMON_H_ +#define _LTK_COMMON_H_ + +typedef struct LtkWidget LtkWidget; + +typedef enum +{ + NORMAL, + HOVER, + PRESSED, + ACTIVE, + HOVERACTIVE, + DISABLED +} LtkWidgetState; + +typedef struct +{ + int x; + int y; + int w; + int h; +} LtkRect; + +int ltk_collide_rect(LtkRect rect, int x, int y); +char *ltk_read_file(const char *path); +void ltk_remove_active_widget(void *widget); +void ltk_remove_hover_widget(void *widget); + +#endif diff --git a/event.c b/event.c @@ -0,0 +1,42 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ltk.h" + +void ltk_handle_event(XEvent event) +{ + LtkWindow *window; + HASH_FIND_INT(ltk_global->window_hash, &event.xany.window, window); + if ((event.type == KeyPress || event.type == KeyRelease) && window->key_func) + { + window->key_func(window, event); + } + else if ((event.type == ButtonPress || event.type == ButtonRelease || event.type == MotionNotify) && window->mouse_func) + { + window->mouse_func(window, event); + } + else if (window->other_func) + { + window->other_func(window, event); + } +} diff --git a/event.h b/event.h @@ -0,0 +1,31 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ltk.h" + +#ifndef _LTK_EVENT_H_ +#define _LTK_EVENT_H_ + +typedef void (*LTK_EVENT_FUNC)(void *widget, XEvent event); + +#endif diff --git a/grid.c b/grid.c @@ -0,0 +1,313 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* TODO: remove_widget function that also adjusts static width */ + +#include "ltk.h" + +void ltk_set_row_weight(LtkGrid *grid, int row, int weight) +{ + grid->row_weights[row] = weight; + ltk_recalculate_grid(grid); +} + +void ltk_set_column_weight(LtkGrid *grid, int column, int weight) +{ + grid->column_weights[column] = weight; + ltk_recalculate_grid(grid); +} + +void ltk_draw_grid(LtkGrid *grid) +{ + int i; + for (i = 0; i < grid->rows * grid->columns; i++) + { + if (!grid->widget_grid[i]) + { + continue; + } + LtkWidget *ptr = grid->widget_grid[i]; + ptr->draw_function(ptr); + } +} + +LtkGrid *ltk_create_grid(LtkWindow *window, int rows, int columns) +{ + LtkGrid *grid = malloc(sizeof(LtkGrid)); + + grid->widget.window = window; + grid->widget.parent = NULL; + grid->widget.key_func = &ltk_grid_key_event; + grid->widget.mouse_func = &ltk_grid_mouse_event; + grid->widget.update_function = &ltk_recalculate_grid; + grid->widget.draw_function = &ltk_draw_grid; + grid->widget.destroy_function = &ltk_destroy_grid; + grid->widget.rect.x = 0; + grid->widget.rect.y = 0; + grid->widget.rect.w = 0; + grid->widget.rect.h = 0; + grid->widget.active_widget = NULL; + grid->widget.state = NORMAL; + + grid->rows = rows; + grid->columns = columns; + grid->widget_grid = malloc(rows * columns * sizeof(LtkWidget)); + grid->row_heights = malloc(rows * sizeof(int)); + grid->column_widths = malloc(rows * sizeof(int)); + grid->row_weights = malloc(rows * sizeof(int)); + grid->column_weights = malloc(columns * sizeof(int)); + /* Positions have one extra for the end */ + grid->row_pos = malloc((rows + 1) * sizeof(int)); + grid->column_pos = malloc((columns + 1) * sizeof(int)); + int i; + for (i = 0; i < rows; i++) + { + grid->row_heights[i] = 0; + grid->row_weights[i] = 0; + grid->row_pos[i] = 0; + } + grid->row_pos[rows] = 0; + for (i = 0; i < columns; i++) + { + grid->column_widths[i] = 0; + grid->column_weights[i] = 0; + grid->column_pos[i] = 0; + } + grid->column_pos[columns] = 0; + for (i = 0; i < rows * columns; i++) + { + grid->widget_grid[i] = NULL; + } + + ltk_recalculate_grid(grid); + return grid; +} + +void ltk_destroy_grid(void *widget) +{ + LtkGrid *grid = widget; + LtkWidget *ptr; + int i; + for (i = 0; i < grid->rows * grid->columns; i++) + { + if (grid->widget_grid[i]) + { + ptr = grid->widget_grid[i]; + ptr->destroy_function(ptr); + } + } + free(grid->widget_grid); + free(grid->row_heights); + free(grid->column_widths); + free(grid->row_weights); + free(grid->column_weights); + free(grid->row_pos); + free(grid->column_pos); + free(grid); +} + +void ltk_recalculate_grid(void *widget) +{ + LtkGrid *grid = widget; + unsigned int height_static = 0, width_static = 0; + unsigned int total_row_weight = 0, total_column_weight = 0; + float height_unit = 0, width_unit = 0; + unsigned int currentx = 0, currenty = 0; + int i, j; + for (i = 0; i < grid->rows; i++) + { + total_row_weight += grid->row_weights[i]; + if (grid->row_weights[i] == 0) + { + height_static += grid->row_heights[i]; + } + } + for (i = 0; i < grid->columns; i++) + { + total_column_weight += grid->column_weights[i]; + if (grid->column_weights[i] == 0) + { + width_static += grid->column_widths[i]; + } + } + if (total_row_weight > 0) + { + height_unit = (float)(grid->widget.rect.h - height_static) / (float)total_row_weight; + } + if (total_column_weight > 0) + { + width_unit = (float)(grid->widget.rect.w - width_static) / (float)total_column_weight; + } + for (i = 0; i < grid->rows; i++) + { + grid->row_pos[i] = currenty; + if (grid->row_weights[i] > 0) + { + grid->row_heights[i] = grid->row_weights[i] * height_unit; + } + currenty += grid->row_heights[i]; + } + grid->row_pos[grid->rows] = currenty; + for (i = 0; i < grid->columns; i++) + { + grid->column_pos[i] = currentx; + if (grid->column_weights[i] > 0) + { + grid->column_widths[i] = grid->column_weights[i] * width_unit; + } + currentx += grid->column_widths[i]; + } + grid->column_pos[grid->columns] = currentx; + int orig_width, orig_height; + int end_column, end_row; + for (i = 0; i < grid->rows; i++) + { + for (j = 0; j < grid->columns; j++) + { + if (!grid->widget_grid[i * grid->columns + j]) + { + continue; + } + LtkWidget *ptr = grid->widget_grid[i * grid->columns + j]; + orig_width = ptr->rect.w; + orig_height = ptr->rect.h; + end_row = i + ptr->row_span; + end_column = j + ptr->column_span; + if (ptr->sticky[1] == 1 && ptr->sticky[3] == 1) + { + ptr->rect.w = grid->column_pos[end_column] - grid->column_pos[j]; + } + if (ptr->sticky[0] == 1 && ptr->sticky[2] == 1) + { + ptr->rect.h = grid->row_pos[end_row] - grid->row_pos[i]; + } + if (orig_width != ptr->rect.w || orig_height != ptr->rect.h) + { + if (ptr->update_function) + { + ptr->update_function(ptr); + } + } + + if (ptr->sticky[1] == 1) + { + ptr->rect.x = grid->column_pos[end_column] - ptr->rect.w; + } + else if (ptr->sticky[3] == 1) + { + ptr->rect.x = grid->column_pos[j]; + } + else + { + ptr->rect.x = grid->column_pos[j] + ((grid->column_pos[end_column] - grid->column_pos[j]) / 2 - ptr->rect.w / 2); + } + + if (ptr->sticky[2] == 1) + { + ptr->rect.y = grid->row_pos[end_row] - ptr->rect.h; + } + else if (ptr->sticky[0] == 1) + { + ptr->rect.y = grid->row_pos[i]; + } + else + { + ptr->rect.y = grid->row_pos[i] + ((grid->row_pos[end_row] - grid->row_pos[i]) / 2 - ptr->rect.h / 2); + } + } + } +} + +void ltk_grid_widget(void *ptr, LtkGrid *grid, int row, int column, int row_span, int column_span, int sticky[4]) +{ + LtkWidget *widget = ptr; + memcpy(widget->sticky, sticky, 4 * sizeof(int)); + widget->row = row; + widget->column = column; + widget->row_span = row_span; + widget->column_span = column_span; + if (grid->column_weights[column] == 0 && widget->rect.w > grid->column_widths[column]) { + grid->column_widths[column] = widget->rect.w; + } + if (grid->row_weights[row] == 0 && widget->rect.h > grid->row_heights[row]) { + grid->row_heights[row] = widget->rect.h; + } + grid->widget_grid[widget->row * grid->columns + widget->column] = widget; + widget->parent = grid; + ltk_recalculate_grid(grid); +} + +void ltk_grid_key_event(void *widget, XEvent event) +{ + LtkGrid *grid = widget; + LtkWidget *ptr = grid->widget.active_widget; + if (ptr && ptr->key_func) + { + ptr->key_func(ptr, event); + } +} + +void ltk_grid_mouse_event(void *widget, XEvent event) +{ + LtkGrid *grid = widget; + LtkWidget *ptr; + int i; + int x, y; + int row, column; + if (event.type == ButtonPress || event.type == ButtonRelease) + { + x = event.xbutton.x; + y = event.xbutton.y; + } + else if (event.type == MotionNotify) + { + x = event.xmotion.x; + y = event.xmotion.y; + } + for (i = 0; i < grid->columns; i++) + { + if (grid->column_pos[i] <= x && grid->column_pos[i + 1] >= x) + { + column = i; + } + } + for (i = 0; i < grid->rows; i++) + { + if (grid->row_pos[i] <= y && grid->row_pos[i + 1] >= y) + { + row = i; + } + } + ptr = grid->widget_grid[row * grid->columns + column]; + if (ptr && ptr->mouse_func) + { + if (ltk_collide_rect(ptr->rect, x, y)) + { + ptr->mouse_func(ptr, event); + } + else if (grid->widget.hover_widget) + { + ltk_remove_hover_widget(grid); + } + } +} diff --git a/grid.h b/grid.h @@ -0,0 +1,53 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LTK_GRID_H_ +#define _LTK_GRID_H_ + +#include "ltk.h" + +typedef struct LtkGrid +{ + LtkWidget widget; + unsigned int rows; + unsigned int columns; + void **widget_grid; + unsigned int *row_heights; + unsigned int *column_widths; + unsigned int *row_weights; + unsigned int *column_weights; + unsigned int *row_pos; + unsigned int *column_pos; +} LtkGrid; + +void ltk_set_row_weight(LtkGrid *grid, int row, int weight); +void ltk_set_column_weight(LtkGrid *grid, int column, int weight); +void ltk_draw_grid(LtkGrid *grid); +LtkGrid *ltk_create_grid(LtkWindow *window, int rows, int columns); +void ltk_destroy_grid(void *widget); +void ltk_recalculate_grid(void *widget); +void ltk_grid_key_event(void *widget, XEvent event); +void ltk_grid_mouse_event(void *widget, XEvent event); +void ltk_grid_widget(void *ptr, LtkGrid *grid, int row, int column, int rowspan, int columnspan, int sticky[4]); + +#endif diff --git a/ltk.c b/ltk.c @@ -0,0 +1,79 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ltk.h" + +void ltk_init(const char *theme_path) +{ + ltk_global = malloc(sizeof(Ltk)); + Ltk *ltk = ltk_global; /* For convenience */ + ltk->display = XOpenDisplay(NULL); + ltk->screen = DefaultScreen(ltk->display); + ltk->colormap = DefaultColormap(ltk->display, ltk->screen); + ltk->theme = ltk_load_theme(theme_path); + ltk->window_hash = NULL; +} + +void ltk_quit(void) +{ + printf("CLEAN UP!\n"); + exit(1); +} + +void ltk_fatal(const char *msg) +{ + printf(msg); + ltk_quit(); +}; + +XColor ltk_create_xcolor(const char *hex) +{ + XColor color; + XParseColor(ltk_global->display, ltk_global->colormap, hex, &color); + XAllocColor(ltk_global->display, ltk_global->colormap, &color); + + return color; +} + +void ltk_mainloop(void) +{ + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(ltk_global->display, &event); + ltk_handle_event(event); + /* + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + if (text[0] == 'q') + { + XCloseDisplay(ltk_global->display); + exit(0); + } + } + */ + } +} diff --git a/ltk.h b/ltk.h @@ -0,0 +1,57 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LTK_H_ +#define _LTK_H_ + +#include <stdio.h> +#include <stdlib.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "cJSON.h" +#include "uthash.h" +#include "common.h" +#include "widget.h" +#include "event.h" +#include "window.h" +#include "theme.h" +#include "grid.h" +#include "button.h" + +typedef struct +{ + LtkTheme *theme; + Display *display; + int screen; + Colormap colormap; + LtkWindow *window_hash; +} Ltk; + +Ltk *ltk_global; + +void ltk_init(const char *theme_path); +void ltk_fatal(const char *msg); +XColor ltk_create_xcolor(const char *hex); +void ltk_mainloop(void); + +#endif diff --git a/main.c b/main.c @@ -0,0 +1,52 @@ +#include <stdio.h> +#include <stdlib.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xos.h> + +int main(int argc, char *argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + XColor green; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + XParseColor(display, colormap, "#00FF00", &green); + XAllocColor(display, colormap, &green); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 200, 300, 0, white, green.pixel); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, white); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + if (text[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } +} diff --git a/test1.c b/test1.c @@ -0,0 +1,41 @@ +#include "ltk.h" + +void bob(void *window, XEvent event) +{ + KeySym key; + char text[255]; + if (XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + if (text[0] == 'q') + { + ltk_quit(); + } + } +} + +void bob1(void *window, XEvent event) +{ + printf("mouse\n"); +} + +int main(int argc, char *argv[]) +{ + ltk_init("themes/default.json"); + LtkWindow *window1 = ltk_create_window("Cool Window!", 0, 0, 500, 500); + LtkGrid *grid1 = ltk_create_grid(window1, 2, 2); + window1->root_widget = grid1; + ltk_set_row_weight(grid1, 0, 1); + ltk_set_row_weight(grid1, 1, 1); + ltk_set_column_weight(grid1, 0, 1); + ltk_set_column_weight(grid1, 1, 1); + LtkButton *button1 = ltk_create_button(window1, "I'm a button!", NULL); + int sticky1[4] = {0, 1, 0, 1}; + ltk_grid_widget(button1, grid1, 0, 0, 1, 1, sticky1); + LtkButton *button2 = ltk_create_button(window1, "I'm a button!", NULL); + ltk_grid_widget(button2, grid1, 0, 1, 1, 1, sticky1); + LtkButton *button3 = ltk_create_button(window1, "I'm a button!", NULL); + ltk_grid_widget(button3, grid1, 1, 0, 1, 1, sticky1); + LtkButton *button4 = ltk_create_button(window1, "I'm a button!", NULL); + ltk_grid_widget(button4, grid1, 1, 1, 1, 1, sticky1); + ltk_mainloop(); +} diff --git a/theme.c b/theme.c @@ -0,0 +1,64 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ltk.h" + +LtkTheme *ltk_load_theme(const char *path) +{ + char *file_contents = ltk_read_file(path); + + cJSON *json = cJSON_Parse(file_contents); + if (!json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + cJSON *button_json = cJSON_GetObjectItem(json, "button"); + if (!button_json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + cJSON *window_json = cJSON_GetObjectItem(json, "window"); + if (!window_json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + LtkTheme *theme = malloc(sizeof(LtkTheme)); + theme->button = ltk_parse_button_theme(button_json); + theme->window = ltk_parse_window_theme(window_json); + + free(file_contents); + cJSON_Delete(json); + + return theme; +} + +void ltk_destroy_theme(LtkTheme *theme) +{ + free(theme->button); + free(theme->window); + free(theme); +} diff --git a/theme.h b/theme.h @@ -0,0 +1,39 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LTK_THEME_H_ +#define _LTK_THEME_H_ + +typedef struct LtkWindowTheme LtkWindowTheme; +typedef struct LtkButtonTheme LtkButtonTheme; + +typedef struct +{ + LtkWindowTheme *window; + LtkButtonTheme *button; +} LtkTheme; + +LtkTheme *ltk_load_theme(const char *path); +void ltk_destroy_theme(LtkTheme *theme); + +#endif diff --git a/themes/default.json b/themes/default.json @@ -0,0 +1,34 @@ +{ + "window": { + "border-width": 0, + "background": "#000000", + "foreground": "#FFFFFF" + }, + "button": { + "normal": { + "border-width": 2, + "font-size": 20, + "border-color": "#339999", + "fill-color": "#113355", + "padding-left": 5, + "padding-right": 5, + "padding-top": 5, + "padding-bottom": 5 + }, + "hover": { + "fill-color": "#738194" + }, + "pressed": { + "border-color": "#FFFFFF", + "fill-color": "#738194" + }, + "active": { + "border-color": "#FFFFFF", + "fill-color": "#113355" + }, + "disabled": { + "border-color": "#FFFFFF", + "fill-color": "#292929" + } + } +} diff --git a/uthash.h b/uthash.h @@ -0,0 +1,1074 @@ +/* +Copyright (c) 2003-2016, Troy D. Hanson http://troydhanson.github.com/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.0.1 + +#include <string.h> /* memcmp,strlen */ +#include <stddef.h> /* ptrdiff_t */ +#include <stdlib.h> /* exit() */ + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#define DECLTYPE(x) +#endif +#elif defined(__BORLANDC__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#define DECLTYPE(x) +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +/* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */ +#if defined(_WIN32) +#if defined(_MSC_VER) && _MSC_VER >= 1600 +#include <stdint.h> +#elif defined(__WATCOMC__) || defined(__MINGW32__) || defined(__CYGWIN__) +#include <stdint.h> +#else +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#endif +#elif defined(__GNUC__) && !defined(__VXWORKS__) +#include <stdint.h> +#else +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#endif + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */ +#endif +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif +#ifndef uthash_memcmp +#define uthash_memcmp(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle *)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FCN(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \ + memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \ + sizeof(UT_hash_table)); \ + if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl->buckets, 0, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + unsigned _ha_bkt; \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + (head) = (add); \ + HASH_MAKE_TABLE(hh, head); \ + } else { \ + struct UT_hash_handle *_hs_iter = &(head)->hh; \ + (add)->hh.tbl = (head)->hh.tbl; \ + do { \ + if (cmpfcn(DECLTYPE(head) ELMT_FROM_HH((head)->hh.tbl, _hs_iter), add) > 0) \ + break; \ + } while ((_hs_iter = _hs_iter->next)); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = _hs_iter->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter->prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + _hs_iter->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + HASH_FSCK(hh, head); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + unsigned _ha_bkt; \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + (head) = (add); \ + HASH_MAKE_TABLE(hh, head); \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + HASH_FSCK(hh, head); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ +do { \ + struct UT_hash_handle *_hd_hh_del; \ + if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + head = NULL; \ + } else { \ + unsigned _hd_bkt; \ + _hd_hh_del = &((delptr)->hh); \ + if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \ + (head)->hh.tbl->tail = \ + (UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho); \ + } \ + if ((delptr)->hh.prev != NULL) { \ + ((UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho))->next = (delptr)->hh.next; \ + } else { \ + DECLTYPE_ASSIGN(head,(delptr)->hh.next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + ((UT_hash_handle*)((ptrdiff_t)_hd_hh_del->next + \ + (head)->hh.tbl->hho))->prev = \ + _hd_hh_del->prev; \ + } \ + HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh,head); \ +} while (0) + + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ + HASH_FIND(hh,head,findstr,(unsigned)uthash_strlen(findstr),out) +#define HASH_ADD_STR(head,strfield,add) \ + HASH_ADD(hh,head,strfield[0],(unsigned)uthash_strlen(add->strfield),add) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ + HASH_REPLACE(hh,head,strfield[0],(unsigned)uthash_strlen(add->strfield),add,replaced) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count; \ + char *_prev; \ + _count = 0; \ + for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("invalid hh_prev %p, actual %p\n", \ + _thh->hh_prev, _prev ); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("invalid bucket count %u, actual %u\n", \ + (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid hh item count %u, actual %u\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + /* traverse hh in app order; check next/prev integrity, count */ \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev !=(char*)(_thh->prev)) { \ + HASH_OOPS("invalid prev %p, actual %p\n", \ + _thh->prev, _prev ); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \ + (head)->hh.tbl->hho) : NULL ); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid app item count %u, actual %u\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include <unistd.h> to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ +#ifdef HASH_FUNCTION +#define HASH_FCN HASH_FUNCTION +#else +#define HASH_FCN HASH_JEN +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen=(unsigned)keylen; \ + const unsigned char *_hb_key=(const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key=(const unsigned char*)(key); \ + hashv = 2166136261U; \ + for(_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +#ifdef HASH_USING_NO_STRICT_ALIASING +/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. + * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. + * MurmurHash uses the faster approach only on CPU's where we know it's safe. + * + * Note the preprocessor built-in defines can be emitted using: + * + * gcc -m64 -dM -E - < /dev/null (on gcc) + * cc -## a.c (where a.c is a simple test file) (Sun Studio) + */ +#if (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86)) +#define MUR_GETBLOCK(p,i) p[i] +#else /* non intel */ +#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 3UL) == 0UL) +#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 3UL) == 1UL) +#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 3UL) == 2UL) +#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 3UL) == 3UL) +#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) +#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) +#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) +#else /* assume little endian non-intel */ +#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) +#endif +#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ + (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ + (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ + MUR_ONE_THREE(p)))) +#endif +#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) +#define MUR_FMIX(_h) \ +do { \ + _h ^= _h >> 16; \ + _h *= 0x85ebca6bu; \ + _h ^= _h >> 13; \ + _h *= 0xc2b2ae35u; \ + _h ^= _h >> 16; \ +} while (0) + +#define HASH_MUR(key,keylen,hashv) \ +do { \ + const uint8_t *_mur_data = (const uint8_t*)(key); \ + const int _mur_nblocks = (int)(keylen) / 4; \ + uint32_t _mur_h1 = 0xf88D5353u; \ + uint32_t _mur_c1 = 0xcc9e2d51u; \ + uint32_t _mur_c2 = 0x1b873593u; \ + uint32_t _mur_k1 = 0; \ + const uint8_t *_mur_tail; \ + const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+(_mur_nblocks*4)); \ + int _mur_i; \ + for(_mur_i = -_mur_nblocks; _mur_i!=0; _mur_i++) { \ + _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + \ + _mur_h1 ^= _mur_k1; \ + _mur_h1 = MUR_ROTL32(_mur_h1,13); \ + _mur_h1 = (_mur_h1*5U) + 0xe6546b64u; \ + } \ + _mur_tail = (const uint8_t*)(_mur_data + (_mur_nblocks*4)); \ + _mur_k1=0; \ + switch((keylen) & 3U) { \ + case 3: _mur_k1 ^= (uint32_t)_mur_tail[2] << 16; /* FALLTHROUGH */ \ + case 2: _mur_k1 ^= (uint32_t)_mur_tail[1] << 8; /* FALLTHROUGH */ \ + case 1: _mur_k1 ^= (uint32_t)_mur_tail[0]; \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + _mur_h1 ^= _mur_k1; \ + } \ + _mur_h1 ^= (uint32_t)(keylen); \ + MUR_FMIX(_mur_h1); \ + hashv = _mur_h1; \ +} while (0) +#endif /* HASH_USING_NO_STRICT_ALIASING */ + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (uthash_memcmp((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,addhh) \ +do { \ + head.count++; \ + (addhh)->hh_next = head.hh_head; \ + (addhh)->hh_prev = NULL; \ + if (head.hh_head != NULL) { (head).hh_head->hh_prev = (addhh); } \ + (head).hh_head=addhh; \ + if ((head.count >= ((head.expand_mult+1U) * HASH_BKT_CAPACITY_THRESH)) \ + && ((addhh)->tbl->noexpand != 1U)) { \ + HASH_EXPAND_BUCKETS((addhh)->tbl); \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(hh,head,hh_del) \ + (head).count--; \ + if ((head).hh_head == hh_del) { \ + (head).hh_head = hh_del->hh_next; \ + } \ + if (hh_del->hh_prev) { \ + hh_del->hh_prev->hh_next = hh_del->hh_next; \ + } \ + if (hh_del->hh_next) { \ + hh_del->hh_next->hh_prev = hh_del->hh_prev; \ + } + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(tbl) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + 2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \ + memset(_he_new_buckets, 0, \ + 2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + tbl->ideal_chain_maxlen = \ + (tbl->num_items >> (tbl->log2_num_buckets+1U)) + \ + (((tbl->num_items & ((tbl->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + tbl->nonideal_items = 0; \ + for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \ + { \ + _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \ + if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \ + tbl->nonideal_items++; \ + _he_newbkt->expand_mult = _he_newbkt->count / \ + tbl->ideal_chain_maxlen; \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { _he_newbkt->hh_head->hh_prev = \ + _he_thh; } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ + tbl->num_buckets *= 2U; \ + tbl->log2_num_buckets++; \ + tbl->buckets = _he_new_buckets; \ + tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \ + (tbl->ineff_expands+1U) : 0U; \ + if (tbl->ineff_expands > 1U) { \ + tbl->noexpand=1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \ + _hs_psize++; \ + _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + if (! (_hs_q) ) { break; } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize > 0U) || ((_hs_qsize > 0U) && (_hs_q != NULL))) {\ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } else if ( (_hs_qsize == 0U) || (_hs_q == NULL) ) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL){ \ + _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + } \ + _hs_psize--; \ + } else if (( \ + cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \ + ) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL){ \ + _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL){ \ + _hs_tail->next = NULL; \ + } \ + if ( _hs_nmerges <= 1U ) { \ + _hs_looping=0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh,head); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt=NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if (src != NULL) { \ + for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { _last_elt_hh->next = _elt; } \ + if (dst == NULL) { \ + DECLTYPE_ASSIGN(dst,_elt); \ + HASH_MAKE_TABLE(hh_dst,dst); \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \ + (dst)->hh_dst.tbl->num_items++; \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst,dst); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if (head != NULL) { \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)=NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + ((head != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/widget.c b/widget.c @@ -0,0 +1,23 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + diff --git a/widget.h b/widget.h @@ -0,0 +1,68 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LTK_WIDGET_H_ +#define _LTK_WIDGET_H_ + +#include "event.h" + +typedef struct LtkWindow LtkWindow; + +typedef struct LtkWidget +{ + /* The window the widget will be displayed on */ + LtkWindow *window; + /* For container widgets; the widget that is currently active */ + void *active_widget; + /* For container widgets; the widget that is currently highlighted */ + void *hover_widget; + /* Parent widget */ + void *parent; + /* Function to be called on a KeyPress or KeyRelease event */ + LTK_EVENT_FUNC key_func; + /* Function to be called on a ButtonPress, ButtonRelease, or MotionNotify event */ + LTK_EVENT_FUNC mouse_func; + /* For container widgets; function to be called when the widget is resized */ + void (*update_function)(void *); + /* Function to draw the widget */ + void (*draw_function)(void *); + /* State of the widget; NORMAL, PRESSED, ACTIVE, HOVER, or DISABLED */ + LtkWidgetState state; + /* Function to destroy the widget; used by containers to destroy child widgets */ + void (*destroy_function)(void *); + /* Position and size of the widget */ + LtkRect rect; + /* Row of widget if gridded */ + unsigned int row; + /* Column of widget if gridded */ + unsigned int column; + /* Row span of widget if gridded */ + unsigned int row_span; + /* Column span of widget if gridded */ + unsigned int column_span; + /* Similar to sticky in tk */ + /* -y, +x, +y, -x */ + int sticky[4]; +} LtkWidget; + +#endif diff --git a/window.c b/window.c @@ -0,0 +1,165 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ltk.h" + +LtkWindowTheme *ltk_parse_window_theme(cJSON *window_json) +{ + LtkWindowTheme *window_theme = malloc(sizeof(LtkWindowTheme)); + if (!window_theme) ltk_fatal("No memory for new LtkWindowTheme\n"); + cJSON *border_width = cJSON_GetObjectItem(window_json, "border-width"); + cJSON *fg = cJSON_GetObjectItem(window_json, "foreground"); + cJSON *bg = cJSON_GetObjectItem(window_json, "background"); + window_theme->border_width = border_width ? border_width->valueint : 0; + window_theme->fg = ltk_create_xcolor(fg->valuestring); + window_theme->bg = ltk_create_xcolor(bg->valuestring); + + return window_theme; +} + +void ltk_redraw_window(LtkWindow *window) +{ + LtkWidget *ptr; + if (!window) + { + return; + } + if (!window->root_widget) + { + return; + } + ptr = window->root_widget; + ptr->draw_function(ptr); +} + +LtkWindow *ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int h) +{ + LtkWindow *window = malloc(sizeof(LtkWindow)); + if (!window) ltk_fatal("Not enough memory left for window!\n"); + LtkWindowTheme *wtheme = ltk_global->theme->window; /* For convenience */ + Display *display = ltk_global->display; /* For convenience */ + window->xwindow = XCreateSimpleWindow(display, DefaultRootWindow(display), x, y, w, h, wtheme->border_width, wtheme->fg.pixel, wtheme->bg.pixel); + window->gc = XCreateGC(display, window->xwindow, 0, 0); + XSetForeground(display, window->gc, wtheme->fg.pixel); + XSetBackground(display, window->gc, wtheme->bg.pixel); + XSetStandardProperties(display, window->xwindow, title, NULL, None, NULL, 0, NULL); + window->root_widget = NULL; + + window->key_func = &ltk_window_key_event; + window->mouse_func = &ltk_window_mouse_event; + window->other_func = &ltk_window_other_event; + + window->rect.w = 0; + window->rect.h = 0; + window->rect.x = 0; + window->rect.y = 0; + + XClearWindow(display, window->xwindow); + XMapRaised(display, window->xwindow); + XSelectInput(display, window->xwindow, ExposureMask|KeyPressMask|KeyReleaseMask|ButtonPressMask|ButtonReleaseMask|StructureNotifyMask|PointerMotionMask); + + HASH_ADD_INT(ltk_global->window_hash, xwindow, window); + + return window; +} + +void ltk_destroy_window(LtkWindow *window) +{ + LtkWidget *ptr = window->root_widget; + if (ptr) + { + ptr->destroy_function(ptr); + } + free(window); +} + +void ltk_window_key_event(void *widget, XEvent event) +{ + LtkWindow *window = widget; + LtkWidget *ptr = window->root_widget; + if (ptr && ptr->key_func) + { + ptr->key_func(ptr, event); + } +} + +void ltk_window_mouse_event(void *widget, XEvent event) +{ + LtkWindow *window = widget; + LtkWidget *ptr = window->root_widget; + if (ptr && ptr->mouse_func) + { + ptr->mouse_func(ptr, event); + } +} + +void ltk_window_other_event(void *widget, XEvent event) +{ + LtkWindow *window = widget; + LtkWidget *ptr = window->root_widget; + if (event.type == ConfigureNotify) + { + unsigned int w, h; + w = event.xconfigure.width; + h = event.xconfigure.height; + if (ptr && ptr->update_function && (window->rect.w != w || window->rect.h != h)) + { + window->rect.w = w; + window->rect.h = h; + ptr->rect.w = w; + ptr->rect.h = h; + ptr->update_function(ptr); + ltk_redraw_window(window); + } + } + if (event.type == Expose && event.xexpose.count == 0) + { + ltk_redraw_window(window); + } +} + +/* +void ltk_resize_window(Uint32 id, int w, int h) +{ + LtkWindow *window; + LtkWidget *ptr; + HASH_FIND_INT(ltk_window_hash, &id, window); + if (!window) + { + return; + } + ptr = window->root_widget; + if (!ptr) + { + return; + } + ptr->rect.w = w; + ptr->rect.h = h; + ptr->update_function(ptr); +} + +void ltk_window_set_root_widget(LtkWindow *window, void *root_widget) +{ + window->root_widget = root_widget; +} +*/ diff --git a/window.h b/window.h @@ -0,0 +1,56 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LTK_WINDOW_H_ +#define _LTK_WINDOW_H_ + +#include <X11/Xlib.h> +#include "event.h" + +typedef struct LtkWindow +{ + Window xwindow; + GC gc; + void *root_widget; + LTK_EVENT_FUNC key_func; /* Called on any keyboard event */ + LTK_EVENT_FUNC mouse_func; /* Called on any mouse event */ + LTK_EVENT_FUNC other_func; /* Called on any other event */ + LtkRect rect; + UT_hash_handle hh; +} LtkWindow; + +typedef struct LtkWindowTheme +{ + int border_width; + XColor fg; + XColor bg; +} LtkWindowTheme; + +LtkWindow *ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int h); +void ltk_redraw_window(LtkWindow *window); +void ltk_destroy_window(LtkWindow *window); +void ltk_window_key_event(void *widget, XEvent event); +void ltk_window_mouse_event(void *widget, XEvent event); +void ltk_window_other_event(void *widget, XEvent event); + +#endif