commit a4ab655cc97c6c7f7383e9567b413fd34819bb42
Author: lumidify <nobody@lumidify.org>
Date: Mon, 1 Jun 2020 21:14:20 +0200
Initial commit
Diffstat:
A | .gitignore | | | 3 | +++ |
A | LICENSE | | | 22 | ++++++++++++++++++++++ |
A | Makefile | | | 15 | +++++++++++++++ |
A | README.md | | | 1 | + |
A | button.c | | | 131 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | button.h | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | grid.c | | | 270 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | grid.h | | | 122 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | ini.c | | | 201 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | ini.h | | | 104 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | khash.h | | | 627 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | ltk.c | | | 563 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | ltk.h | | | 158 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | test.sh | | | 6 | ++++++ |
A | theme.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, <k_button_draw, <k_button_destroy, 1);
+ button->widget.mouse_release = <k_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, <k_draw_grid, <k_destroy_grid, 0);
+ grid->widget.mouse_press = <k_grid_mouse_press;
+ grid->widget.mouse_release = <k_grid_mouse_release;
+ grid->widget.motion_notify = <k_grid_motion_notify;
+ grid->widget.resize = <k_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 = <k_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