URI: 
       tRework command system - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit dfedb3c646b7a664329ae1453e58d07451f9c200
   DIR parent 8427dcdb18de419d9d158dfa8eeedefe5baafc61
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Wed,  3 Jun 2020 21:44:17 +0200
       
       Rework command system
       
       Diffstat:
         M LICENSE                             |       3 +++
         M Makefile                            |       8 +++++---
         M README.md                           |       8 ++++++++
         M button.c                            |      62 +++++++++++++++++++++++++++----
         M button.h                            |       9 +++++----
         M grid.c                              |     312 ++++++++++++++++++++++++++++---
         M grid.h                              |      79 +------------------------------
         M ltk.c                               |     147 ++++++++++++++++++-------------
         M ltk.h                               |      22 ++++++++++++++++++++--
         A strtonum.c                          |      66 +++++++++++++++++++++++++++++++
         M test.gui                            |      14 ++++++++++----
         A util.h                              |       5 +++++
       
       12 files changed, 557 insertions(+), 178 deletions(-)
       ---
   DIR diff --git a/LICENSE b/LICENSE
       t@@ -1,3 +1,6 @@
       +See khash.h, ini.*, and strtonum.c for third-party licenses.
       +
       +
        MIT/X Consortium License
        
        The Lumidify ToolKit (LTK)
   DIR diff --git a/Makefile b/Makefile
       t@@ -2,9 +2,11 @@ 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
       +COMPATOBJ = 
       +#COMPATOBJ = strtonum.o
        
       -ltk: $(OBJ)
       -        $(CC) $(STD) -o $@ $(OBJ) $(LIBS)
       +ltk: $(OBJ) $(COMPATOBJ)
       +        $(CC) $(STD) -o $@ $(OBJ) $(COMPATOBJ) $(LIBS)
        
        %.o: %.c
                $(CC) -c -o $@ $< $(CFLAGS)
       t@@ -12,4 +14,4 @@ ltk: $(OBJ)
        .PHONY: clean
        
        clean:
       -        rm -f $(OBJ) ltk ltk_in
       +        rm -f $(OBJ) ltk *.core
   DIR diff --git a/README.md b/README.md
       t@@ -1 +1,9 @@
        Not much to see here.
       +
       +To test:
       +
       +make
       +./ltk < test.gui
       +
       +Note: you need to uncomment "COMPATOBJ = strtonum.c" in Makefile
       +if you're not using OpenBSD.
   DIR diff --git a/button.c b/button.c
       t@@ -1,6 +1,6 @@
        /*
         * This file is part of the Lumidify ToolKit (LTK)
       - * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org>
       + * 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
       t@@ -26,10 +26,22 @@
        #include <string.h>
        #include <X11/Xlib.h>
        #include <X11/Xutil.h>
       +#include "util.h"
        #include "khash.h"
        #include "ltk.h"
        #include "button.h"
        
       +static void ltk_button_draw(ltk_button *button);
       +static void ltk_button_mouse_release(ltk_button *button, XEvent event);
       +static ltk_button *ltk_button_create(ltk_window *window,
       +    const char *id, const char *text);
       +static void ltk_button_destroy(ltk_button *button, int shallow);
       +
       +static void ltk_grid_cmd_create(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens);
       +
        void
        ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) {
                ltk_theme *theme = window->theme;
       t@@ -64,7 +76,7 @@ ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) 
                }
        }
        
       -void
       +static void
        ltk_button_draw(ltk_button *button) {
                ltk_window *window = button->widget.window;
                ltk_button_theme *theme = window->theme->button;
       t@@ -102,30 +114,66 @@ ltk_button_draw(ltk_button *button) {
        }
        
        
       -void
       +static void
        ltk_button_mouse_release(ltk_button *button, XEvent event) {
                ltk_queue_event(button->widget.window, button->widget.id, "button_click");
        }
        
       -ltk_button *
       +static 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);
       +        ltk_fill_widget_defaults(&button->widget, id, window,
       +            &ltk_button_draw, &ltk_button_destroy, 1, LTK_BUTTON);
                button->widget.mouse_release = &ltk_button_mouse_release;
                button->text = strdup(text);
        
                return button;
        }
        
       -void
       -ltk_button_destroy(ltk_button *button) {
       +static void
       +ltk_button_destroy(ltk_button *button, int shallow) {
                if (!button) {
                        (void)printf("WARNING: Tried to destroy NULL button.\n");
                        return;
                }
                free(button->text);
       +        ltk_remove_widget(button->widget.window, button->widget.id);
                free(button->widget.id);
                free(button);
        }
       +
       +/* button <button id> create */
       +static void
       +ltk_button_cmd_create(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens) {
       +        ltk_button *button;
       +        if (num_tokens != 3) {
       +                (void)fprintf(stderr, "button create: Invalid number of arguments.\n");
       +                return;
       +        }
       +        if (!ltk_check_widget_id_free(window, tokens[1], "button create"))
       +                return;
       +        button = ltk_button_create(window, tokens[1], "tmp");
       +        ltk_set_widget(window, button, tokens[1]);
       +}
       +
       +/* button <button id> <command> ... */
       +void
       +ltk_button_cmd(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens) {
       +        if (num_tokens < 3) {
       +                (void)fprintf(stderr, "button: Invalid number of arguments.\n");
       +                return;
       +        }
       +        if (strcmp(tokens[2], "create") == 0) {
       +                ltk_button_cmd_create(window, tokens, num_tokens);
       +        } else {
       +                (void)fprintf(stderr, "button: Invalid command.\n");
       +        }
       +}
   DIR diff --git a/button.h b/button.h
       t@@ -49,10 +49,11 @@ typedef struct ltk_button_theme {
                XColor fill_disabled;
        } ltk_button_theme;
        
       -void ltk_button_draw(ltk_button *button);
       +void ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value);
        
       -ltk_button *ltk_button_create(ltk_window *window, const char *id, const char *text);
       -
       -void ltk_button_destroy(ltk_button *button);
       +void ltk_button_cmd(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens);
        
        #endif
   DIR diff --git a/grid.c b/grid.c
       t@@ -28,21 +28,62 @@
        #include <stdlib.h>
        #include <X11/Xlib.h>
        #include <X11/Xutil.h>
       +#include "util.h"
        #include "khash.h"
        #include "ltk.h"
        #include "grid.h"
        
       -void ltk_set_row_weight(ltk_grid * grid, int row, int weight) {
       +static void ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight);
       +static void ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight);
       +static void ltk_grid_draw(ltk_grid *grid);
       +static ltk_grid *ltk_grid_create(ltk_window *window, const char *id,
       +    int rows, int columns);
       +static void ltk_grid_destroy(ltk_grid *grid, int shallow);
       +static void ltk_recalculate_grid(ltk_grid *grid);
       +static void ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
       +    int row, int column, int row_span, int column_span, unsigned short sticky);
       +static void ltk_grid_ungrid(ltk_window *window, ltk_widget *widget, ltk_grid *grid);
       +static int ltk_grid_find_nearest_column(ltk_grid *grid, int x);
       +static int ltk_grid_find_nearest_row(ltk_grid *grid, int y);
       +static void ltk_grid_mouse_press(ltk_grid *grid, XEvent event);
       +static void ltk_grid_mouse_release(ltk_grid *grid, XEvent event);
       +static void ltk_grid_motion_notify(ltk_grid *grid, XEvent event);
       +
       +static void ltk_grid_cmd_add(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens);
       +static void ltk_grid_cmd_ungrid(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens);
       +static void ltk_grid_cmd_create(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens);
       +static void ltk_grid_cmd_set_row_weight(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens);
       +static void ltk_grid_cmd_set_column_weight(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens);
       +
       +static void
       +ltk_grid_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) {
       +static void
       +ltk_grid_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) {
       +static void
       +ltk_grid_draw(ltk_grid *grid) {
                int i;
                for (i = 0; i < grid->rows * grid->columns; i++) {
                        if (!grid->widget_grid[i])
       t@@ -52,10 +93,12 @@ void ltk_draw_grid(ltk_grid *grid) {
                }
        }
        
       -ltk_grid *ltk_create_grid(ltk_window *window, const char *id, int rows, int columns) {
       +static ltk_grid *
       +ltk_grid_create(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);
       +        ltk_fill_widget_defaults(&grid->widget, id, window, &ltk_grid_draw,
       +            &ltk_grid_destroy, 0, LTK_GRID);
                grid->widget.mouse_press = &ltk_grid_mouse_press;
                grid->widget.mouse_release = &ltk_grid_mouse_release;
                grid->widget.motion_notify = &ltk_grid_motion_notify;
       t@@ -92,13 +135,22 @@ ltk_grid *ltk_create_grid(ltk_window *window, const char *id, int rows, int colu
                return grid;
        }
        
       -void ltk_destroy_grid(ltk_grid *grid) {
       +static void
       +ltk_grid_destroy(ltk_grid *grid, int shallow) {
                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);
       +        if (!shallow) {
       +                for (int i = 0; i < grid->rows * grid->columns; i++) {
       +                        if (grid->widget_grid[i]) {
       +                                ptr = grid->widget_grid[i];
       +                                /* required to avoid freeing a widget multiple times
       +                                   if row_span or column_span is not 1 */
       +                                for (int r = ptr->row; r < ptr->row + ptr->row_span; r++) {
       +                                        for (int c = ptr->column; c < ptr->column + ptr->column_span; c++) {
       +                                                grid->widget_grid[r * grid->columns + c] = NULL;
       +                                        }
       +                                }
       +                                ptr->destroy(ptr, shallow);
       +                        }
                        }
                }
                free(grid->widget_grid);
       t@@ -108,10 +160,13 @@ void ltk_destroy_grid(ltk_grid *grid) {
                free(grid->column_weights);
                free(grid->row_pos);
                free(grid->column_pos);
       +        ltk_remove_widget(grid->widget.window, grid->widget.id);
       +        free(grid->widget.id);
                free(grid);
        }
        
       -void ltk_recalculate_grid(ltk_grid *grid) {
       +static 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;
       t@@ -155,10 +210,11 @@ void ltk_recalculate_grid(ltk_grid *grid) {
                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]) {
       +                        if (!grid->widget_grid[i * grid->columns + j])
                                        continue;
       -                        }
                                ltk_widget *ptr = grid->widget_grid[i * grid->columns + j];
       +                        if (ptr->row != i || ptr->column != j)
       +                                continue;
                                orig_width = ptr->rect.w;
                                orig_height = ptr->rect.h;
                                end_row = i + ptr->row_span;
       t@@ -194,8 +250,10 @@ void ltk_recalculate_grid(ltk_grid *grid) {
                }
        }
        
       -void ltk_grid_widget(ltk_widget *widget, ltk_grid *grid, int row, int column, int row_span, int column_span, unsigned short sticky) {
       -        if (row >= grid->rows || column >= grid->columns) {
       +static void
       +ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
       +    int row, int column, int row_span, int column_span, unsigned short sticky) {
       +        if (row + row_span > grid->rows || column + column_span > grid->columns) {
                        (void)fprintf(stderr, "Invalid row or column.\n");
                        return;
                }
       t@@ -210,12 +268,33 @@ void ltk_grid_widget(ltk_widget *widget, ltk_grid *grid, int row, int column, in
                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;
       +        for (int i = row; i < row + row_span; i++) {
       +                for (int j = column; j < column + column_span; j++) {
       +                        grid->widget_grid[i * grid->columns + j] = widget;
       +                }
       +        }
                widget->parent = grid;
                ltk_recalculate_grid(grid);
       +        ltk_window_invalidate_rect(window, grid->widget.rect);
       +}
       +
       +static void
       +ltk_grid_ungrid(ltk_window *window, ltk_widget *widget, ltk_grid *grid) {
       +        if (widget->parent != grid) {
       +                (void)fprintf(stderr, "grid remove: Widget isn't gridded in given grid.\n");
       +                return;
       +        }
       +        widget->parent = NULL;
       +        for (int i = widget->row; i < widget->row + widget->row_span; i++) {
       +                for (int j = widget->column; j < widget->column + widget->column_span; j++) {
       +                        grid->widget_grid[i * grid->columns + j] = NULL;
       +                }
       +        }
       +        ltk_window_invalidate_rect(window, grid->widget.rect);
        }
        
       -static int ltk_grid_find_nearest_column(ltk_grid *grid, int x) {
       +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) {
       t@@ -225,7 +304,8 @@ static int ltk_grid_find_nearest_column(ltk_grid *grid, int x) {
                return -1;
        }
        
       -static int ltk_grid_find_nearest_row(ltk_grid *grid, int y) {
       +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) {
       t@@ -235,7 +315,8 @@ static int ltk_grid_find_nearest_row(ltk_grid *grid, int y) {
                return -1;
        }
        
       -void ltk_grid_mouse_press(ltk_grid *grid, XEvent event) {
       +static 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);
       t@@ -247,7 +328,8 @@ void ltk_grid_mouse_press(ltk_grid *grid, XEvent event) {
                        ltk_widget_mouse_press_event(ptr, event);
        }
        
       -void ltk_grid_mouse_release(ltk_grid *grid, XEvent event) {
       +static 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);
       t@@ -259,7 +341,8 @@ void ltk_grid_mouse_release(ltk_grid *grid, XEvent event) {
                        ltk_widget_mouse_release_event(ptr, event);
        }
        
       -void ltk_grid_motion_notify(ltk_grid *grid, XEvent event) {
       +static void
       +ltk_grid_motion_notify(ltk_grid *grid, XEvent event) {
                short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
                if (pressed)
                        return;
       t@@ -273,3 +356,188 @@ void ltk_grid_motion_notify(ltk_grid *grid, XEvent event) {
                if (ptr && ltk_collide_rect(ptr->rect, x, y))
                        ltk_widget_motion_notify_event(ptr, event);
        }
       +
       +/* grid <grid id> add <widget id> <row> <column> <row_span> <column_span> <sticky> */
       +static void
       +ltk_grid_cmd_add(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens) {
       +        ltk_grid *grid;
       +        ltk_widget *widget;
       +        const char *errstr;
       +
       +        int row, column, row_span, column_span, sticky;
       +        if (num_tokens != 9) {
       +                (void)fprintf(stderr, "grid: Invalid number of arguments.\n");
       +                return;
       +        }
       +        grid = ltk_get_widget(window, tokens[1], LTK_GRID, "grid add");
       +        widget = ltk_get_widget(window, tokens[3], LTK_WIDGET, "grid add");
       +        if (!grid || !widget) return;
       +        row         = strtonum(tokens[4], 0, grid->rows - 1, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid add: Invalid row number: %s\n", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        column      = strtonum(tokens[5], 0, grid->columns - 1, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid add: Invalid column number: %s\n", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        row_span    = strtonum(tokens[6], 1, grid->rows, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid add: Invalid row_span: %s\n", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        column_span = strtonum(tokens[7], 1, grid->columns, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid add: Invalid column_span: %s\n", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        sticky      = strtonum(tokens[8], 0, 15, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid add: Invalid sticky setting: %s\n", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        ltk_grid_add(window, widget, grid, row, column, row_span, column_span, sticky);
       +}
       +
       +/* grid <grid id> remove <widget id> */
       +static void
       +ltk_grid_cmd_ungrid(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens) {
       +        ltk_grid *grid;
       +        ltk_widget *widget;
       +        if (num_tokens != 4) {
       +                (void)fprintf(stderr, "grid ungrid: Invalid number of arguments.\n");
       +                return;
       +        }
       +        grid = ltk_get_widget(window, tokens[1], LTK_GRID, "grid remove");
       +        widget = ltk_get_widget(window, tokens[3], LTK_WIDGET, "grid ungrid");
       +        if (!grid || !widget) return;
       +        ltk_grid_ungrid(window, widget, grid);
       +}
       +
       +/* grid <grid id> create <rows> <columns> */
       +static void
       +ltk_grid_cmd_create(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens) {
       +        int rows, columns;
       +        ltk_grid *grid;
       +        const char *errstr;
       +        if (num_tokens != 5) {
       +                (void)fprintf(stderr, "grid create: Invalid number of arguments.\n");
       +                return;
       +        }
       +        if (!ltk_check_widget_id_free(window, tokens[1], "grid create"))
       +                return;
       +        rows    = strtonum(tokens[3], 1, 64, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid create: Invalid number of rows: %s\n ", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        columns = strtonum(tokens[4], 1, 64, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid create: Invalid number of columns: %s\n ", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        grid = ltk_grid_create(window, tokens[1], rows, columns);
       +        ltk_set_widget(window, grid, tokens[1]);
       +}
       +
       +/* grid <grid id> set-row-weight <row> <weight> */
       +static void
       +ltk_grid_cmd_set_row_weight(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens) {
       +        ltk_grid *grid;
       +        int row, weight;
       +        const char *errstr;
       +        if (num_tokens != 5) {
       +                (void)fprintf(stderr, "grid set-row-weight: Invalid number of arguments.\n");
       +                return;
       +        }
       +        grid = ltk_get_widget(window, tokens[1], LTK_GRID, "grid set-row-weight");
       +        if (!grid) return;
       +        row    = strtonum(tokens[3], 0, grid->rows, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid set-row-weight: Invalid row number: %s\n ", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        weight = strtonum(tokens[4], 0, 64, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid set-row-weight: Invalid weight: %s\n ", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        ltk_grid_set_row_weight(grid, row, weight);
       +}
       +
       +/* grid <grid id> set-column-weight <column> <weight> */
       +static void
       +ltk_grid_cmd_set_column_weight(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens) {
       +        ltk_grid *grid;
       +        int column, weight;
       +        const char *errstr;
       +        if (num_tokens != 5) {
       +                (void)fprintf(stderr, "grid set-column-weight: Invalid number of arguments.\n");
       +                return;
       +        }
       +        grid = ltk_get_widget(window, tokens[1], LTK_GRID, "grid set-column-weight");
       +        if (!grid) return;
       +        column = strtonum(tokens[3], 0, grid->columns, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid set-column-weight: Invalid column number: %s\n ", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        weight = strtonum(tokens[4], 0, 64, &errstr);
       +        if (errstr) {
       +                (void)fprintf("grid set-column-weight: Invalid weight: %s\n ", errstr);
       +                free(errstr);
       +                return;
       +        }
       +        ltk_grid_set_column_weight(grid, column, weight);
       +}
       +
       +/* grid <grid id> <command> ... */
       +void
       +ltk_grid_cmd(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    size_t num_tokens) {
       +        if (num_tokens < 3) {
       +                (void)fprintf(stderr, "grid: Invalid number of arguments.\n");
       +                return;
       +        }
       +        if (strcmp(tokens[2], "add") == 0) {
       +                ltk_grid_cmd_add(window, tokens, num_tokens);
       +        } else if (strcmp(tokens[2], "ungrid") == 0) {
       +                ltk_grid_cmd_ungrid(window, tokens, num_tokens);
       +        } else if (strcmp(tokens[2], "create") == 0) {
       +                ltk_grid_cmd_create(window, tokens, num_tokens);
       +        } else if (strcmp(tokens[2], "set-row-weight") == 0) {
       +                ltk_grid_cmd_set_row_weight(window, tokens, num_tokens);
       +        } else if (strcmp(tokens[2], "set-column-weight") == 0) {
       +                ltk_grid_cmd_set_column_weight(window, tokens, num_tokens);
       +        } else {
       +                (void)fprintf(stderr, "button: Invalid command.\n");
       +        }
       +}
   DIR diff --git a/grid.h b/grid.h
       t@@ -24,7 +24,7 @@
        #ifndef _LTK_GRID_H_
        #define _LTK_GRID_H_
        
       -/* Requires the following includes: <X11/Xlib.h>, "ltk.h" */
       +/* Requires the following includes: "ltk.h" */
        
        /*
         * Struct to represent a grid widget.
       t@@ -42,81 +42,6 @@ typedef struct {
                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);
       +void ltk_grid_cmd(ltk_window *window, char tokens[10][20], size_t num_tokens);
        
        #endif
   DIR diff --git a/ltk.c b/ltk.c
       t@@ -31,6 +31,7 @@
        #include <sys/select.h>
        #include <X11/Xlib.h>
        #include <X11/Xutil.h>
       +#include "util.h"
        #include "khash.h"
        #include "ini.h"
        #include "ltk.h"
       t@@ -105,50 +106,25 @@ ltk_queue_event(ltk_window *window, const char *id, const char *name) {
        }
        
        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_set_root_widget_cmd(
       +    ltk_window *window,
       +    char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH],
       +    int num_tokens) {
                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");
       +        if (num_tokens != 2) {
       +                (void)fprintf(stderr, "set-root-widget: Invalid number of arguments.\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 = ltk_get_widget(window, tokens[1], LTK_WIDGET, "set-root-widget");
       +        if (!widget) return;
       +        window->root_widget = widget;
       +        int w = widget->rect.w;
       +        int h = widget->rect.h;
       +        widget->rect.w = window->rect.w;
       +        widget->rect.h = window->rect.h;
       +        if (widget->resize) {
       +                widget->resize(widget, w, h);
                }
       -        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 */
       t@@ -167,7 +143,7 @@ read_line(int fd, char *buf, size_t bufsiz)
        }
        
        static size_t
       -tokenize(char tokens[10][20], char *buf) {
       +tokenize(char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH], char *buf) {
                char *c;
                if (!buf || buf[0] == '\0') return 0;
                for (c = buf; *c == ' '; c++)
       t@@ -175,13 +151,13 @@ tokenize(char tokens[10][20], char *buf) {
                size_t cur_tok = 0;
                size_t cur_tok_len = 0;
                while (*c != '\0') {
       -                if (cur_tok >= 10) {
       +                if (cur_tok >= MAX_TOKENS) {
                                return 0;
                        } else if (*c == ' ') {
                                tokens[cur_tok][cur_tok_len] = '\0';
                                cur_tok++;
                                cur_tok_len = 0;
       -                } else if (cur_tok_len >= 19) {
       +                } else if (cur_tok_len >= MAX_TOKEN_LENGTH - 1) {
                                return 0;
                        } else {
                                tokens[cur_tok][cur_tok_len++] = *c;
       t@@ -194,19 +170,20 @@ tokenize(char tokens[10][20], char *buf) {
        
        static void
        proc_cmds(ltk_window *window, char *buf) {
       -        char tokens[10][20];
       +        char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH];
                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);
       +        if (strcmp(tokens[0], "grid") == 0) {
       +                ltk_grid_cmd(window, tokens, num_tokens);
       +        } else if (strcmp(tokens[0], "button") == 0) {
       +                ltk_button_cmd(window, tokens, num_tokens);
       +        } else if (strcmp(tokens[0], "set-root-widget") == 0) {
       +                ltk_set_root_widget_cmd(window, tokens, num_tokens);
                } else {
                        (void)fprintf(stderr, "Invalid command.\n");
                }
        }
        
       -/* FIXME: destroy remaining widgets in hash on exit */
        int
        ltk_mainloop(ltk_window *window) {
                XEvent event;
       t@@ -215,7 +192,7 @@ ltk_mainloop(ltk_window *window) {
                struct timeval tv;
                fd_set rfds;
                int fd_in = fileno(stdin);
       -        char buf[200]; /* FIXME: what would be sensible? */
       +        char buf[MAX_CMD_LENGTH];
                int retval;
                tick.tv_sec = 0;
                tick.tv_nsec = 10000;
       t@@ -246,7 +223,7 @@ ltk_mainloop(ltk_window *window) {
                                        free(last);
                                } while (cur);
                                window->first_event = window->last_event = NULL;
       -                } else if (retval != -1 && retval != 0 && !read_line(fd_in, buf, sizeof(buf))) {
       +                } else if (retval > 0 && !read_line(fd_in, buf, sizeof(buf))) {
                                proc_cmds(window, buf);
                        }
                        /* yes, this should be improved */
       t@@ -322,9 +299,17 @@ ltk_create_window(const char *theme_path, const char *title, int x, int y, unsig
        
        void
        ltk_destroy_window(ltk_window *window) {
       +        khint_t k;
                ltk_widget *ptr = window->root_widget;
       -        if (ptr) ptr->destroy(ptr);
       +        if (ptr) ptr->destroy(ptr, 0);
                XDestroyWindow(window->dpy, window->xwindow);
       +        for (k = kh_begin(window->widget_hash); k != kh_end(window->widget_hash); k++) {
       +                if (kh_exist(window->widget_hash, k)) {
       +                        ptr = kh_value(window->widget_hash, k);
       +                        ptr->destroy(ptr, 1);
       +                }
       +        }
       +        kh_destroy(widget, window->widget_hash);
                free(window);
        }
        
       t@@ -438,11 +423,13 @@ ltk_window_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 (*draw) (void *), void (*destroy) (void *, int), unsigned int needs_redraw,
       +    ltk_widget_type type) {
                widget->id = strdup(id);
                widget->window = window;
                widget->active_widget = NULL;
                widget->parent = NULL;
       +        widget->type = type;
        
                widget->key_press = NULL;
                widget->key_release = NULL;
       t@@ -546,13 +533,55 @@ ltk_handle_event(ltk_window *window, XEvent event) {
                }
        }
        
       +int
       +ltk_check_widget_id_free(ltk_window *window, const char *id, const char *caller) {
       +        khint_t k;
       +        k = kh_get(widget, window->widget_hash, id);
       +        if (k != kh_end(window->widget_hash)) {
       +                (void)fprintf(stderr, "%s: Widget \"%s\" already exists.\n", caller, id);
       +                return 0;
       +        }
       +        return 1;
       +}
       +
       +ltk_widget *
       +ltk_get_widget(ltk_window *window, const char *id, ltk_widget_type type,
       +    const char *caller) {
       +        khint_t k;
       +        ltk_widget *widget;
       +        k = kh_get(widget, window->widget_hash, id);
       +        if (k == kh_end(window->widget_hash)) {
       +                (void)fprintf(stderr, "%s: Widget \"%s\" doesn't exist.\n", caller, id);
       +                return NULL;
       +        }
       +        widget = kh_value(window->widget_hash, k);
       +        if (type != LTK_WIDGET && widget->type != type) {
       +                (void)fprintf(stderr, "%s: Widget \"%s\" has wrong type.\n", caller, id);
       +                return NULL;
       +        }
       +        return widget;
       +}
       +
       +void
       +ltk_set_widget(ltk_window *window, ltk_widget *widget, const char *id) {
       +        int ret;
       +        khint_t k;
       +        /* apparently, khash requires the string to stay accessible */
       +        char *tmp = strdup(id);
       +        k = kh_put(widget, window->widget_hash, tmp, &ret);
       +        kh_value(window->widget_hash, k) = widget;
       +}
       +
       +void
       +ltk_remove_widget(ltk_window *window, const char *id) {
       +        khint_t k;
       +        k = kh_get(widget, window->widget_hash, id);
       +        if (k != kh_end(window->widget_hash)) {
       +                kh_del(widget, window->widget_hash, k);
       +        }
       +}
       +
        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);
        }
   DIR diff --git a/ltk.h b/ltk.h
       t@@ -24,6 +24,10 @@
        #ifndef _LTK_H_
        #define _LTK_H_
        
       +#define MAX_TOKENS 20
       +#define MAX_TOKEN_LENGTH 20
       +#define MAX_CMD_LENGTH 400
       +
        /* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h>, "drw.h" */
        
        typedef struct {
       t@@ -47,12 +51,19 @@ typedef enum {
                LTK_DISABLED
        } ltk_widget_state;
        
       +typedef enum {
       +        LTK_GRID,
       +        LTK_BUTTON,
       +        LTK_WIDGET
       +} ltk_widget_type;
       +
        typedef struct ltk_window ltk_window;
        
        typedef struct ltk_widget {
                ltk_window *window;
                struct ltk_widget *active_widget;
                struct ltk_widget *parent;
       +        ltk_widget_type type;
                char *id;
        
                void (*key_press) (void *, XEvent event);
       t@@ -65,7 +76,7 @@ typedef struct ltk_widget {
        
                void (*resize) (void *, int, int);
                void (*draw) (void *);
       -        void (*destroy) (void *);
       +        void (*destroy) (void *, int);
        
                ltk_rect rect;
                unsigned int row;
       t@@ -145,7 +156,8 @@ 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 (*draw) (void *), void (*destroy) (void *, int), unsigned int needs_redraw,
       +    ltk_widget_type type);
        
        void ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event);
        
       t@@ -155,4 +167,10 @@ void ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event);
        
        void ltk_handle_event(ltk_window *window, XEvent event);
        
       +int ltk_check_widget_id_free(ltk_window *window, const char *id,
       +    const char *caller);
       +ltk_widget *ltk_get_widget(ltk_window *window, const char *id,
       +    ltk_widget_type type, const char *caller);
       +void ltk_set_widget(ltk_window *window, ltk_widget *widget, const char *id);
       +
        #endif
   DIR diff --git a/strtonum.c b/strtonum.c
       t@@ -0,0 +1,66 @@
       +/*        $OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $        */
       +
       +/*
       + * Copyright (c) 2004 Ted Unangst and Todd Miller
       + * All rights reserved.
       + *
       + * Permission to use, copy, modify, and distribute this software for any
       + * purpose with or without fee is hereby granted, provided that the above
       + * copyright notice and this permission notice appear in all copies.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
       + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
       + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
       + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
       + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
       + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
       + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
       + */
       +
       +#include <errno.h>
       +#include <limits.h>
       +#include <stdlib.h>
       +
       +#define        INVALID                1
       +#define        TOOSMALL        2
       +#define        TOOLARGE        3
       +
       +long long
       +strtonum(const char *numstr, long long minval, long long maxval,
       +    const char **errstrp)
       +{
       +        long long ll = 0;
       +        int error = 0;
       +        char *ep;
       +        struct errval {
       +                const char *errstr;
       +                int err;
       +        } ev[4] = {
       +                { NULL,                0 },
       +                { "invalid",        EINVAL },
       +                { "too small",        ERANGE },
       +                { "too large",        ERANGE },
       +        };
       +
       +        ev[0].err = errno;
       +        errno = 0;
       +        if (minval > maxval) {
       +                error = INVALID;
       +        } else {
       +                ll = strtoll(numstr, &ep, 10);
       +                if (numstr == ep || *ep != '\0')
       +                        error = INVALID;
       +                else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
       +                        error = TOOSMALL;
       +                else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
       +                        error = TOOLARGE;
       +        }
       +        if (errstrp != NULL)
       +                *errstrp = ev[error].errstr;
       +        errno = ev[error].err;
       +        if (error)
       +                ll = 0;
       +
       +        return (ll);
       +}
       +DEF_WEAK(strtonum);
   DIR diff --git a/test.gui b/test.gui
       t@@ -1,4 +1,10 @@
       -create button btn1
       -grid btn1 0 0
       -create button btn2
       -grid btn2 1 1
       +grid grd1 create 2 2
       +grid grd1 set-row-weight 0 1
       +grid grd1 set-row-weight 1 1
       +grid grd1 set-column-weight 0 1
       +grid grd1 set-column-weight 1 1
       +set-root-widget grd1
       +button btn1 create
       +grid grd1 add btn1 0 0 1 1 0
       +button btn2 create
       +grid grd1 add btn2 1 0 1 2 3
   DIR diff --git a/util.h b/util.h
       t@@ -0,0 +1,5 @@
       +#ifndef __OpenBSD__
       +long long
       +strtonum(const char *numstr, long long minval, long long maxval,
       +    const char **errstrp);
       +#endif