ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit a4ab655cc97c6c7f7383e9567b413fd34819bb42
Author: lumidify <nobody@lumidify.org>
Date:   Mon,  1 Jun 2020 21:14:20 +0200

Initial commit

Diffstat:
A.gitignore | 3+++
ALICENSE | 22++++++++++++++++++++++
AMakefile | 15+++++++++++++++
AREADME.md | 1+
Abutton.c | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abutton.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agrid.c | 270+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agrid.h | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aini.c | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aini.h | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Akhash.h | 627+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Altk.c | 563+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Altk.h | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest.sh | 6++++++
Atheme.ini | 18++++++++++++++++++
15 files changed, 2299 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +ltk +*.o +*.core diff --git a/LICENSE b/LICENSE @@ -0,0 +1,22 @@ +MIT/X Consortium License + +The Lumidify ToolKit (LTK) +Copyright (c) 2016, 2017, 2018, 2019, 2020 lumidify <nobody@lumidify.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,15 @@ +LIBS = -lm `pkg-config --libs x11` +STD = -std=c99 +CFLAGS = -g -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11` -pedantic +OBJ = ltk.o ini.o grid.o button.o + +ltk: $(OBJ) + $(CC) $(STD) -o $@ $(OBJ) $(LIBS) + +%.o: %.c + $(CC) -c -o $@ $< $(CFLAGS) + +.PHONY: clean + +clean: + rm -f $(OBJ) ltk ltk_in diff --git a/README.md b/README.md @@ -0,0 +1 @@ +Not much to see here. diff --git a/button.c b/button.c @@ -0,0 +1,131 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "khash.h" +#include "ltk.h" +#include "button.h" + +void +ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) { + ltk_theme *theme = window->theme; + if (!theme->button) + theme->button = malloc(sizeof(ltk_button_theme)); + if (!theme->button) + ltk_fatal("Unable to allocate ltk_button_theme.\n"); + if (strcmp(prop, "border_width") == 0) { + theme->button->border_width = atoi(value); + } else if (strcmp(prop, "pad") == 0) { + theme->button->pad = atoi(value); + } else if (strcmp(prop, "border") == 0) { + theme->button->border = ltk_create_xcolor(window, value); + } else if (strcmp(prop, "fill") == 0) { + theme->button->fill = ltk_create_xcolor(window, value); + } else if (strcmp(prop, "border_pressed") == 0) { + theme->button->border_pressed = ltk_create_xcolor(window, value); + } else if (strcmp(prop, "fill_pressed") == 0) { + theme->button->fill_pressed = ltk_create_xcolor(window, value); + } else if (strcmp(prop, "border_active") == 0) { + theme->button->border_active = ltk_create_xcolor(window, value); + } else if (strcmp(prop, "fill_active") == 0) { + theme->button->fill_active = ltk_create_xcolor(window, value); + } else if (strcmp(prop, "border_disabled") == 0) { + theme->button->border_disabled = ltk_create_xcolor(window, value); + } else if (strcmp(prop, "fill_disabled") == 0) { + theme->button->fill_disabled = ltk_create_xcolor(window, value); + } else if (strcmp(prop, "text_color") == 0) { + theme->button->text_color = ltk_create_xcolor(window, value); + } else { + (void)printf("WARNING: Unknown property \"%s\" for button style.\n", prop); + } +} + +void +ltk_button_draw(ltk_button *button) { + ltk_window *window = button->widget.window; + ltk_button_theme *theme = window->theme->button; + ltk_rect rect = button->widget.rect; + int bw = theme->border_width; + XColor border; + XColor fill; + switch (button->widget.state) { + case LTK_NORMAL: + border = theme->border; + fill = theme->fill; + break; + case LTK_PRESSED: + border = theme->border_pressed; + fill = theme->fill_pressed; + break; + case LTK_ACTIVE: + border = theme->border_active; + fill = theme->fill_active; + break; + case LTK_DISABLED: + border = theme->border_disabled; + fill = theme->fill_disabled; + break; + default: + ltk_fatal("No style found for button!\n"); + } + XSetForeground(window->dpy, window->gc, fill.pixel); + XFillRectangle(window->dpy, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h); + /* FIXME: Why did I do this? */ + if (bw < 1) return; + XSetForeground(window->dpy, window->gc, border.pixel); + XSetLineAttributes(window->dpy, window->gc, bw, LineSolid, CapButt, JoinMiter); + XDrawRectangle(window->dpy, window->xwindow, window->gc, rect.x + bw / 2, rect.y + bw / 2, rect.w - bw, rect.h - bw); +} + + +void +ltk_button_mouse_release(ltk_button *button, XEvent event) { + ltk_queue_event(button->widget.window, button->widget.id, "button_click"); +} + +ltk_button * +ltk_button_create(ltk_window *window, const char *id, const char *text) { + ltk_button *button = malloc(sizeof(ltk_button)); + if (!button) ltk_fatal("ERROR: Unable to allocate memory for ltk_button.\n"); + + ltk_fill_widget_defaults(&button->widget, id, window, &ltk_button_draw, &ltk_button_destroy, 1); + button->widget.mouse_release = &ltk_button_mouse_release; + button->text = strdup(text); + + return button; +} + +void +ltk_button_destroy(ltk_button *button) { + if (!button) { + (void)printf("WARNING: Tried to destroy NULL button.\n"); + return; + } + free(button->text); + free(button->widget.id); + free(button); +} diff --git a/button.h b/button.h @@ -0,0 +1,58 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.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_ + +/* Requires the following includes: <X11/Xlib.h>, "ltk.h" */ + +typedef struct { + ltk_widget widget; + char *text; +} ltk_button; + +typedef struct ltk_button_theme { + int border_width; + XColor text_color; + int pad; + + XColor border; + XColor fill; + + XColor border_pressed; + XColor fill_pressed; + + XColor border_active; + XColor fill_active; + + XColor border_disabled; + XColor fill_disabled; +} ltk_button_theme; + +void ltk_button_draw(ltk_button *button); + +ltk_button *ltk_button_create(ltk_window *window, const char *id, const char *text); + +void ltk_button_destroy(ltk_button *button); + +#endif diff --git a/grid.c b/grid.c @@ -0,0 +1,270 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.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 */ +/* TODO: widget size request */ + +#include <stdlib.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "khash.h" +#include "ltk.h" +#include "grid.h" + +void ltk_set_row_weight(ltk_grid * grid, int row, int weight) { + grid->row_weights[row] = weight; + ltk_recalculate_grid(grid); +} + +void ltk_set_column_weight(ltk_grid * grid, int column, int weight) { + grid->column_weights[column] = weight; + ltk_recalculate_grid(grid); +} + +void ltk_draw_grid(ltk_grid *grid) { + int i; + for (i = 0; i < grid->rows * grid->columns; i++) { + if (!grid->widget_grid[i]) + continue; + ltk_widget *ptr = grid->widget_grid[i]; + ptr->draw(ptr); + } +} + +ltk_grid *ltk_create_grid(ltk_window *window, const char *id, int rows, int columns) { + ltk_grid *grid = malloc(sizeof(ltk_grid)); + + ltk_fill_widget_defaults(&grid->widget, id, window, &ltk_draw_grid, &ltk_destroy_grid, 0); + grid->widget.mouse_press = &ltk_grid_mouse_press; + grid->widget.mouse_release = &ltk_grid_mouse_release; + grid->widget.motion_notify = &ltk_grid_motion_notify; + grid->widget.resize = &ltk_recalculate_grid; + + grid->rows = rows; + grid->columns = columns; + grid->widget_grid = malloc(rows * columns * sizeof(ltk_widget)); + 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(ltk_grid *grid) { + ltk_widget *ptr; + int i; + for (i = 0; i < grid->rows * grid->columns; i++) { + if (grid->widget_grid[i]) { + ptr = grid->widget_grid[i]; + ptr->destroy(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(ltk_grid *grid) { + 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; + } + ltk_widget *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 & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT) { + ptr->rect.w = grid->column_pos[end_column] - grid->column_pos[j]; + } + if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM) { + 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->resize) { + ptr->resize(ptr, orig_width, orig_height); + } + } + + if (ptr->sticky & LTK_STICKY_RIGHT) { + ptr->rect.x = grid->column_pos[end_column] - ptr->rect.w; + } else if (ptr->sticky & LTK_STICKY_LEFT) { + 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 & LTK_STICKY_BOTTOM) { + ptr->rect.y = grid->row_pos[end_row] - ptr->rect.h; + } else if (ptr->sticky & LTK_STICKY_TOP) { + 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(ltk_widget *widget, ltk_grid *grid, int row, int column, int row_span, int column_span, unsigned short sticky) { + widget->sticky = sticky; + 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); +} + +static int ltk_grid_find_nearest_column(ltk_grid *grid, int x) { + int i; + for (i = 0; i < grid->columns; i++) { + if (grid->column_pos[i] <= x && grid->column_pos[i + 1] >= x) { + return i; + } + } + return -1; +} + +static int ltk_grid_find_nearest_row(ltk_grid *grid, int y) { + int i; + for (i = 0; i < grid->rows; i++) { + if (grid->row_pos[i] <= y && grid->row_pos[i + 1] >= y) { + return i; + } + } + return -1; +} + +void ltk_grid_mouse_press(ltk_grid *grid, XEvent event) { + int x = event.xbutton.x; + int y = event.xbutton.y; + int row = ltk_grid_find_nearest_row(grid, y); + int column = ltk_grid_find_nearest_column(grid, x); + if (row == -1 || column == -1) + return; + ltk_widget *ptr = grid->widget_grid[row * grid->columns + column]; + if (ptr && ltk_collide_rect(ptr->rect, x, y)) + ltk_widget_mouse_press_event(ptr, event); +} + +void ltk_grid_mouse_release(ltk_grid *grid, XEvent event) { + int x = event.xbutton.x; + int y = event.xbutton.y; + int row = ltk_grid_find_nearest_row(grid, y); + int column = ltk_grid_find_nearest_column(grid, x); + if (row == -1 || column == -1) + return; + ltk_widget *ptr = grid->widget_grid[row * grid->columns + column]; + if (ptr && ltk_collide_rect(ptr->rect, x, y)) + ltk_widget_mouse_release_event(ptr, event); +} + +void ltk_grid_motion_notify(ltk_grid *grid, XEvent event) { + short pressed = (event.xmotion.state & Button1Mask) == Button1Mask; + if (pressed) + return; + int x = event.xbutton.x; + int y = event.xbutton.y; + int row = ltk_grid_find_nearest_row(grid, y); + int column = ltk_grid_find_nearest_column(grid, x); + if (row == -1 || column == -1) + return; + ltk_widget *ptr = grid->widget_grid[row * grid->columns + column]; + if (ptr && ltk_collide_rect(ptr->rect, x, y)) + ltk_widget_motion_notify_event(ptr, event); +} diff --git a/grid.h b/grid.h @@ -0,0 +1,122 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.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_ + +/* Requires the following includes: <X11/Xlib.h>, "ltk.h" */ + +/* + * Struct to represent a grid widget. + */ +typedef struct { + ltk_widget widget; + unsigned int rows; + unsigned int columns; + ltk_widget **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; +} ltk_grid; + +/* + * Set the weight of a row in a grid. + * grid: The grid. + * row: The row. + * weight: The weight to set the row to. + */ +void ltk_set_row_weight(ltk_grid *grid, int row, int weight); + +/* + * Set the weight of a column in a grid. + * grid: The grid. + * column: The column. + * weight: The weight to set the row to. + */ +void ltk_set_column_weight(ltk_grid *grid, int column, int weight); + +/* + * Draw all the widgets in a grid. + * grid: The grid to draw the widgets of. + */ +void ltk_draw_grid(ltk_grid *grid); + +/* + * Create a grid. + * window: The window the grid will displayed on. + * rows: The number of rows in the grid. + * columns: The number of columns in the grid. + */ +ltk_grid *ltk_create_grid(ltk_window *window, const char *id, int rows, int columns); + +/* + * Destroy a grid. + * grid: Pointer to the grid. + */ +void ltk_destroy_grid(ltk_grid *grid); + +/* + * Recalculate the positions and dimensions of the + * columns, rows, and widgets in a grid. + * grid: Pointer to the grid. + */ +void ltk_recalculate_grid(ltk_grid *grid); + +/* + * Grid a widget. + * widget: Pointer to the widget. + * grid: The grid. + * row: The row to grid the widget in. + * column: The column to grid the widget in. + * rowspan: The amount of rows the widget should span. + * columnspan: The amount of columns the widget should span. + * sticky: Mask of the sticky values (LTK_STICKY_*). + */ +void ltk_grid_widget(ltk_widget *widget, ltk_grid *grid, int row, int column, + int rowspan, int columnspan, unsigned short sticky); + +/* + * Delegate a mouse press event on the grid to the proper widget. + * grid: The grid. + * event: The event to be handled. + */ +void ltk_grid_mouse_press(ltk_grid *grid, XEvent event); + +/* + * Delegate a mouse release event on the grid to the proper widget. + * grid: The grid. + * event: The event to be handled. + */ +void ltk_grid_mouse_release(ltk_grid *grid, XEvent event); + +/* + * Delegate a mouse motion event on the grid to the proper widget. + * grid: The grid. + * event: The event to be handled. + */ +void ltk_grid_motion_notify(ltk_grid *grid, XEvent event); + +#endif diff --git a/ini.c b/ini.c @@ -0,0 +1,201 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +#include "ini.h" + +#if !INI_USE_STACK +#include <stdlib.h> +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; +#else + char* line; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_MAX_LINE); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, INI_MAX_LINE, stream) != NULL) { + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python configparser, allow both ; and # comments at the + start of a line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} diff --git a/ini.h b/ini.h @@ -0,0 +1,104 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Maximum line length for any line in INI file. */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ diff --git a/khash.h b/khash.h @@ -0,0 +1,627 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk> + + 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. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2013-05-02 (0.2.8): + + * Use quadratic probing. When the capacity is power of 2, stepping function + i*(i+1)/2 guarantees to traverse each bucket. It is better than double + hashing on cache performance and is more robust than linear probing. + + In theory, double hashing should be more robust than quadratic probing. + However, my implementation is probably not for large hash tables, because + the second hash function is closely tied to the first hash function, + which reduce the effectiveness of double hashing. + + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php + + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.8" + +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +/* compiler specific configuration */ + +#if UINT_MAX == 0xffffffffu +typedef unsigned int khint32_t; +#elif ULONG_MAX == 0xffffffffu +typedef unsigned long khint32_t; +#endif + +#if ULONG_MAX == ULLONG_MAX +typedef unsigned long khint64_t; +#else +typedef unsigned long long khint64_t; +#endif + +#ifndef kh_inline +#ifdef _MSC_VER +#define kh_inline __inline +#else +#define kh_inline inline +#endif +#endif /* kh_inline */ + +#ifndef klib_unused +#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) +#define klib_unused __attribute__ ((__unused__)) +#else +#define klib_unused +#endif +#endif /* klib_unused */ + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct kh_##name##_s { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + kfree((void *)h->keys); kfree(h->flags); \ + kfree((void *)h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t k, i, last, mask, step = 0; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + (++step)) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (!new_flags) return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (!new_keys) { kfree(new_flags); return -1; } \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + if (!new_vals) { kfree(new_flags); return -1; } \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t k, i, step = 0; \ + k = __hash_func(key); \ + i = k & new_mask; \ + while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + (++step)) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static kh_inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static kh_inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: -1 if the operation failed; + 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/* More conenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/ltk.c b/ltk.c @@ -0,0 +1,563 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> +#include <sys/stat.h> +#include <sys/select.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "khash.h" +#include "ini.h" +#include "ltk.h" +#include "grid.h" +#include "button.h" + +static void ltk_load_theme(ltk_window *window, const char *path); +static void ltk_destroy_theme(ltk_theme *theme); + +static int running = 1; + +static ltk_rect +ltk_rect_union(ltk_rect r1, ltk_rect r2) { + ltk_rect u; + u.x = r1.x < r2.x ? r1.x : r2.x; + u.y = r1.y < r2.y ? r1.y : r2.y; + int x2 = r1.x + r1.w < r2.x + r2.w ? r2.x + r2.w : r1.x + r1.w; + int y2 = r1.y + r1.h < r2.y + r2.h ? r2.y + r2.h : r1.y + r1.h; + u.w = x2 - u.x; + u.h = y2 - u.y; + return u; +} + +void +ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) { + if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0) + window->dirty_rect = rect; + else + window->dirty_rect = ltk_rect_union(rect, window->dirty_rect); +} + +void +ltk_clean_up(ltk_window *window) { + XCloseDisplay(window->dpy); + ltk_destroy_theme(window->theme); + ltk_destroy_window(window); +} + +void +ltk_quit(ltk_window *window) { + ltk_clean_up(window); + running = 0; +} + +void +ltk_fatal(const char *msg) { + (void)fprintf(stderr, msg); + /* FIXME: clean up first */ + exit(1); +}; + +XColor +ltk_create_xcolor(ltk_window *window, const char *hex) { + XColor color; + XParseColor(window->dpy, window->cm, hex, &color); + XAllocColor(window->dpy, window->cm, &color); + + return color; +} + +void +ltk_queue_event(ltk_window *window, const char *id, const char *name) { + struct ltk_event_queue *new = malloc(sizeof(struct ltk_event_queue)); + if (!new) ltk_fatal("Unable to queue event.\n"); + new->id = strdup(id); + new->name = strdup(name); + new->next = window->first_event; + window->first_event = new; + new->prev = NULL; + if (!window->last_event) + window->last_event = new; +} + +static void +create_widget(ltk_window *window, char tokens[10][20], size_t num_tokens) { + if (num_tokens < 3) { + (void)fprintf(stderr, "Invalid number of arguments.\n"); + return; + } + ltk_widget *widget; + khint_t k = kh_get(widget, window->widget_hash, tokens[2]); + if (k != kh_end(window->widget_hash)) { + (void)fprintf(stderr, "Widget id already exists.\n"); + return; + } + if (strcmp(tokens[1], "button") == 0) { + /* yeah, text is currently ignored... */ + widget = ltk_button_create(window, tokens[2], "I'm a button!"); + } else { + (void)fprintf(stderr, "Invalid widget type.\n"); + return; + } + int ret; + /* apparently, khash requires the string to stay accessible */ + /* FIXME: actually free this hash table in the end... */ + char *tmp = strdup(tokens[2]); + k = kh_put(widget, window->widget_hash, tmp, &ret); + kh_value(window->widget_hash, k) = widget; +} + +static void +grid_widget(ltk_window *window, char tokens[10][20], size_t num_tokens) { + if (num_tokens < 4) { + (void)fprintf(stderr, "Invalid number of arguments.\n"); + return; + } + ltk_widget *widget; + khint_t k = kh_get(widget, window->widget_hash, tokens[1]); + if (k == kh_end(window->widget_hash)) { + (void)fprintf(stderr, "Widget with given id doesn't exist.\n"); + return; + } + widget = kh_value(window->widget_hash, k); + /* FIXME: error checking */ + int row = atoi(tokens[2]); + int column = atoi(tokens[3]); + ltk_grid_widget(widget, window->root_widget, row, column, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_RIGHT); + ltk_window_invalidate_rect(window, window->root_widget->rect); +} + +/* copied from suckless ii */ +static int +read_line(int fd, char *buf, size_t bufsiz) +{ + size_t i = 0; + char c = '\0'; + do { + if (read(fd, &c, sizeof(char)) != sizeof(char)) + return -1; + buf[i++] = c; + } while (c != '\n' && i < bufsiz); + buf[i - 1] = '\0'; /* eliminates '\n' */ + return 0; +} + +static size_t +tokenize(char tokens[10][20], char *buf) { + char *c; + if (!buf || buf[0] == '\0') return 0; + for (c = buf; *c == ' '; c++) + ; + size_t cur_tok = 0; + size_t cur_tok_len = 0; + while (*c != '\0') { + if (cur_tok >= 10) { + return 0; + } else if (*c == ' ') { + tokens[cur_tok][cur_tok_len] = '\0'; + cur_tok++; + cur_tok_len = 0; + } else if (cur_tok_len >= 19) { + return 0; + } else { + tokens[cur_tok][cur_tok_len++] = *c; + } + c++; + } + tokens[cur_tok][cur_tok_len] = '\0'; + return cur_tok + 1; +} + +static void +proc_cmds(ltk_window *window, char *buf) { + char tokens[10][20]; + int num_tokens; + if (!(num_tokens = tokenize(tokens, buf))) return; + if (strcmp(tokens[0], "create") == 0) { + create_widget(window, tokens, num_tokens); + } else if (strcmp(tokens[0], "grid") == 0) { + grid_widget(window, tokens, num_tokens); + } else { + (void)fprintf(stderr, "Invalid command.\n"); + } +} + +/* copied from suckless st */ +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) + +/* FIXME: destroy remaining widgets in hash on exit */ +int +ltk_mainloop(ltk_window *window) { + XEvent event; + int fd_in; + struct stat st; + struct timespec tick; + tick.tv_sec = 0; + tick.tv_nsec = 10000; + char buf[200]; /* FIXME: what would be sensible? */ + + if (lstat("ltk_in", &st) != -1) { + /* FIXME... */ + unlink("ltk_in"); + } + + if (mkfifo("ltk_in", S_IRWXU)) return -1; + fd_in = open("ltk_in", O_RDONLY | O_NONBLOCK, 0); + if (fd_in == -1) return -1; + while (running) { + while (XPending(window->dpy)) { + XNextEvent(window->dpy, &event); + ltk_handle_event(window, event); + } + if (window->dirty_rect.w != 0 && window->dirty_rect.h != 0) { + ltk_redraw_window(window); + window->dirty_rect.w = 0; + window->dirty_rect.h = 0; + } else if (window->last_event) { + struct ltk_event_queue *cur = window->last_event; + struct ltk_event_queue *last; + do { + printf("%s %s\n", cur->id, cur->name); + free(cur->id); + free(cur->name); + last = cur; + cur = cur->prev; + free(last); + } while (cur); + window->first_event = window->last_event = NULL; + } else if (!read_line(fd_in, buf, sizeof(buf))) { + proc_cmds(window, buf); + } + /* yes, this should be improved */ + nanosleep(&tick, NULL); + + } + unlink("ltk_in"); +} + +void +ltk_redraw_window(ltk_window *window) { + ltk_widget *ptr; + if (!window) return; + if (window->dirty_rect.x >= window->rect.w) return; + if (window->dirty_rect.y >= window->rect.h) return; + if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w) + window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w; + if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h) + window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h; + XClearArea(window->dpy, window->xwindow, window->dirty_rect.x, window->dirty_rect.y, window->dirty_rect.w, window->dirty_rect.h, False); + if (!window->root_widget) return; + /* FIXME: actually respect the dirty rect... */ + ptr = window->root_widget; + ptr->draw(ptr); +} + +ltk_window * +ltk_create_window(const char *theme_path, const char *title, int x, int y, unsigned int w, unsigned int h) { + ltk_window *window = malloc(sizeof(ltk_window)); + if (!window) + ltk_fatal("Not enough memory left for window!\n"); + + window->dpy = XOpenDisplay(NULL); + window->screen = DefaultScreen(window->dpy); + window->cm = DefaultColormap(window->dpy, window->screen); + ltk_load_theme(window, theme_path); + window->wm_delete_msg = XInternAtom(window->dpy, "WM_DELETE_WINDOW", False); + + ltk_window_theme *wtheme = window->theme->window; + window->xwindow = + XCreateSimpleWindow(window->dpy, DefaultRootWindow(window->dpy), x, y, + w, h, wtheme->border_width, + wtheme->fg.pixel, wtheme->bg.pixel); + window->gc = XCreateGC(window->dpy, window->xwindow, 0, 0); + XSetForeground(window->dpy, window->gc, wtheme->fg.pixel); + XSetBackground(window->dpy, window->gc, wtheme->bg.pixel); + XSetStandardProperties(window->dpy, window->xwindow, title, NULL, None, + NULL, 0, NULL); + XSetWMProtocols(window->dpy, window->xwindow, &window->wm_delete_msg, 1); + window->root_widget = NULL; + + window->other_event = &ltk_window_other_event; + + window->rect.w = 0; + window->rect.h = 0; + window->rect.x = 0; + window->rect.y = 0; + window->dirty_rect.w = 0; + window->dirty_rect.h = 0; + window->dirty_rect.x = 0; + window->dirty_rect.y = 0; + + window->widget_hash = kh_init(widget); + + XClearWindow(window->dpy, window->xwindow); + XMapRaised(window->dpy, window->xwindow); + XSelectInput(window->dpy, window->xwindow, + ExposureMask | KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + StructureNotifyMask | PointerMotionMask); + + return window; +} + +void +ltk_destroy_window(ltk_window *window) { + ltk_widget *ptr = window->root_widget; + if (ptr) ptr->destroy(ptr); + XDestroyWindow(window->dpy, window->xwindow); + free(window); +} + +void +ltk_window_other_event(ltk_window *window, XEvent event) { + ltk_widget *ptr = window->root_widget; + int retval = 0; + if (event.type == ConfigureNotify) { + unsigned int w, h; + w = event.xconfigure.width; + h = event.xconfigure.height; + int orig_w = window->rect.w; + int orig_h = window->rect.h; + if (orig_w != w || orig_h != h) { + window->rect.w = w; + window->rect.h = h; + if (ptr && ptr->resize) { + ptr->rect.w = w; + ptr->rect.h = h; + ptr->resize(ptr, orig_w, orig_h); + } + } + } else if (event.type == Expose && event.xexpose.count == 0) { + ltk_rect r; + r.x = event.xexpose.x; + r.y = event.xexpose.y; + r.w = event.xexpose.width; + r.h = event.xexpose.height; + ltk_window_invalidate_rect(window, r); + } else if (event.type == ClientMessage + && event.xclient.data.l[0] == window->wm_delete_msg) { + ltk_destroy_window(window); + exit(0); /* FIXME */ + } +} + +void +ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) { + if (strcmp(prop, "border_width") == 0) { + window->theme->window->border_width = atoi(value); + } else if (strcmp(prop, "bg") == 0) { + window->theme->window->bg = ltk_create_xcolor(window, value); + } else if (strcmp(prop, "fg") == 0) { + window->theme->window->fg = ltk_create_xcolor(window, value); + } else if (strcmp(prop, "font") == 0) { + window->theme->window->font = strdup(value); + } +} + +int +ltk_ini_handler(ltk_window *window, const char *widget, const char *prop, const char *value) { + if (strcmp(widget, "window") == 0) { + ltk_window_ini_handler(window, prop, value); + } else if (strcmp(widget, "button") == 0) { + ltk_button_ini_handler(window, prop, value); + } +} + +static void +ltk_load_theme(ltk_window *window, const char *path) { + window->theme = malloc(sizeof(ltk_theme)); + if (!window->theme) ltk_fatal("Unable to allocate memory for theme.\n"); + window->theme->window = malloc(sizeof(ltk_window_theme)); + if (!window->theme->window) ltk_fatal("Unable to allocate memory for window theme.\n"); + window->theme->button = NULL; + if (ini_parse(path, ltk_ini_handler, window) < 0) { + (void)fprintf(stderr, "ERROR: Can't load theme %s\n.", path); + exit(1); + } +} + +static void +ltk_destroy_theme(ltk_theme *theme) { + free(theme->button); + free(theme->window); + free(theme); +} + +int +ltk_collide_rect(ltk_rect rect, int x, int y) { + return (rect.x <= x && (rect.x + rect.w) >= x && rect.y <= y + && (rect.y + rect.h) >= y); +} + +void +ltk_window_remove_active_widget(ltk_window *window) { + ltk_widget *widget = window->active_widget; + if (!widget) return; + while (widget) { + widget->state = LTK_NORMAL; + widget->active_widget = NULL; + if (widget->needs_redraw) + ltk_window_invalidate_rect(window, widget->rect); + widget = widget->parent; + } + window->active_widget = NULL; +} + +void +ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) { + window->active_widget = widget; + ltk_widget *parent = widget->parent; + widget->state = LTK_ACTIVE; + while (parent) { + widget->state = LTK_ACTIVE; + parent->active_widget = widget; + widget = parent; + parent = widget->parent; + } +} + +void +ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window, + void (*draw) (void *), void (*destroy) (void *), unsigned int needs_redraw) { + widget->id = strdup(id); + widget->window = window; + widget->active_widget = NULL; + widget->parent = NULL; + + widget->key_press = NULL; + widget->key_release = NULL; + widget->mouse_press = NULL; + widget->mouse_release = NULL; + widget->motion_notify = NULL; + widget->mouse_enter = NULL; + widget->mouse_leave = NULL; + + widget->resize = NULL; + widget->draw = draw; + widget->destroy = destroy; + + widget->needs_redraw = needs_redraw; + widget->state = LTK_NORMAL; + widget->row = 0; + widget->rect.x = 0; + widget->rect.y = 0; + widget->rect.w = 100; + widget->rect.h = 100; + + widget->row = NULL; + widget->column = NULL; + widget->row_span = NULL; + widget->column_span = NULL; + widget->sticky = 0; +} + +void +ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event) { + if (!widget || widget->state == LTK_DISABLED) + return; + if (event.xbutton.button == 1) { + ltk_widget *parent = widget->parent; + widget->state = LTK_PRESSED; + if (widget->needs_redraw) + ltk_window_invalidate_rect(widget->window, widget->rect); + } + if (widget->mouse_press) { + widget->mouse_press(widget, event); + } +} + +void +ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) { + if (!widget || widget->state == LTK_DISABLED) + return; + if (widget->state == LTK_PRESSED) { + widget->state = LTK_ACTIVE; + if (widget->needs_redraw) + ltk_window_invalidate_rect(widget->window, widget->rect); + } + if (widget->mouse_release) { + widget->mouse_release(widget, event); + } +} + +void +ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) { + if (!widget) return; + short pressed = (event.xmotion.state & Button1Mask) == Button1Mask; + if ((widget->state == LTK_NORMAL) && !pressed) { + widget->state = LTK_ACTIVE; + if (widget->mouse_enter) + widget->mouse_enter(widget, event); + /* FIXME: do this properly */ + if (widget->window->active_widget != widget && !widget->motion_notify) { + ltk_window_remove_active_widget(widget->window); + ltk_window_set_active_widget(widget->window, widget); + } + if (widget->needs_redraw) + ltk_window_invalidate_rect(widget->window, widget->rect); + } + if (widget->motion_notify) + widget->motion_notify(widget, event); +} + +void +ltk_handle_event(ltk_window *window, XEvent event) { + ltk_widget *root_widget = window->root_widget; + switch (event.type) { + case KeyPress: + break; + case KeyRelease: + break; + case ButtonPress: + if (root_widget) + ltk_widget_mouse_press_event(root_widget, event); + break; + case ButtonRelease: + if (root_widget) + ltk_widget_mouse_release_event(root_widget, event); + break; + case MotionNotify: + if (root_widget) + ltk_widget_motion_notify_event(root_widget, event); + break; + default: + if (window->other_event) + window->other_event(window, event); + } +} + +int main(int argc, char *argv[]) { + ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500); + ltk_grid *grid = ltk_create_grid(window, "grid", 2, 2); + window->root_widget = grid; + ltk_set_row_weight(grid, 0, 1); + ltk_set_row_weight(grid, 1, 1); + ltk_set_column_weight(grid, 0, 1); + ltk_set_column_weight(grid, 1, 1); + ltk_mainloop(window); +} diff --git a/ltk.h b/ltk.h @@ -0,0 +1,158 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.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_ + +/* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h>, "drw.h" */ + +typedef struct { + int x; + int y; + int w; + int h; +} ltk_rect; + +typedef enum { + LTK_STICKY_LEFT = 1 << 0, + LTK_STICKY_RIGHT = 1 << 1, + LTK_STICKY_TOP = 1 << 2, + LTK_STICKY_BOTTOM = 1 << 3 +} ltk_sticky_mask; + +typedef enum { + LTK_NORMAL, + LTK_PRESSED, + LTK_ACTIVE, + LTK_DISABLED +} ltk_widget_state; + +typedef struct ltk_window ltk_window; + +typedef struct ltk_widget { + ltk_window *window; + struct ltk_widget *active_widget; + struct ltk_widget *parent; + char *id; + + void (*key_press) (void *, XEvent event); + void (*key_release) (void *, XEvent event); + void (*mouse_press) (void *, XEvent event); + void (*mouse_release) (void *, XEvent event); + void (*motion_notify) (void *, XEvent event); + void (*mouse_leave) (void *, XEvent event); + void (*mouse_enter) (void *, XEvent event); + + void (*resize) (void *, int, int); + void (*draw) (void *); + void (*destroy) (void *); + + ltk_rect rect; + unsigned int row; + unsigned int column; + unsigned int row_span; + unsigned int column_span; + unsigned int needs_redraw; + ltk_widget_state state; + unsigned short sticky; +} ltk_widget; + +typedef struct { + int border_width; + char *font; + XColor fg; + XColor bg; +} ltk_window_theme; + +typedef struct ltk_button_theme ltk_button_theme; + +typedef struct { + ltk_window_theme *window; + ltk_button_theme *button; +} ltk_theme; + +struct ltk_event_queue { + char *id; + char *name; + struct ltk_event_queue *prev; + struct ltk_event_queue *next; +}; + +KHASH_MAP_INIT_STR(widget, ltk_widget *) + +typedef struct ltk_window { + Display *dpy; + Colormap cm; + GC gc; + int screen; + Atom wm_delete_msg; + Window xwindow; + ltk_widget *root_widget; + ltk_widget *active_widget; + int (*other_event) (ltk_window *, XEvent event); + ltk_rect rect; + ltk_theme *theme; + ltk_rect dirty_rect; + struct ltk_event_queue *first_event; + struct ltk_event_queue *last_event; + khash_t(widget) *widget_hash; +} ltk_window; + +void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect); + +void ltk_fatal(const char *msg); + +XColor ltk_create_xcolor(ltk_window *window, const char *hex); + +void ltk_queue_event(ltk_window *window, const char *id, const char *name); + +int ltk_mainloop(ltk_window *window); + +ltk_window *ltk_create_window( + const char *theme_path, const char *title, + int x, int y, unsigned int w, unsigned int h); + +void ltk_redraw_window(ltk_window *window); + +void ltk_destroy_window(ltk_window *window); + +void ltk_window_other_event(ltk_window *window, XEvent event); + +int ltk_collide_rect(ltk_rect rect, int x, int y); + +void ltk_remove_active_widget(ltk_widget *widget); + +void ltk_set_active_widget(ltk_window *window, ltk_widget *widget); + +void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window * window, + void (*draw) (void *), void (*destroy) (void *), unsigned int needs_redraw); + +void ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event); + +void ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event); + +void ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event); + +void ltk_handle_event(ltk_window *window, XEvent event); + +#endif diff --git a/test.sh b/test.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo "create button btn1" > ltk_in +echo "grid btn1 0 0" > ltk_in +echo "create button btn2" > ltk_in +echo "grid btn2 1 1" > ltk_in diff --git a/theme.ini b/theme.ini @@ -0,0 +1,18 @@ +[window] +border_width = 0 +bg = #000000 +fg = #FFFFFF +font = Awami Nastaliq + +[button] +border_width = 2 +text_color = #FFFFFF +pad = 5 +border = #339999 +fill = #113355 +border_pressed = #FFFFFF +fill_pressed = #113355 +border_active = #FFFFFF +fill_active = #738194 +border_disabled = #FFFFFF +fill_disabled = #292929