commit dfedb3c646b7a664329ae1453e58d07451f9c200
parent 8427dcdb18de419d9d158dfa8eeedefe5baafc61
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(-)
diff --git a/LICENSE b/LICENSE
@@ -1,3 +1,6 @@
+See khash.h, ini.*, and strtonum.c for third-party licenses.
+
+
 MIT/X Consortium License
 
 The Lumidify ToolKit (LTK)
diff --git a/Makefile b/Makefile
@@ -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)
@@ -12,4 +14,4 @@ ltk: $(OBJ)
 .PHONY: clean
 
 clean:
-	rm -f $(OBJ) ltk ltk_in
+	rm -f $(OBJ) ltk *.core
diff --git a/README.md b/README.md
@@ -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.
diff --git a/button.c b/button.c
@@ -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
@@ -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;
@@ -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;
@@ -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, <k_button_draw, <k_button_destroy, 1);
+	ltk_fill_widget_defaults(&button->widget, id, window,
+	    <k_button_draw, <k_button_destroy, 1, LTK_BUTTON);
 	button->widget.mouse_release = <k_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");
+	}
+}
diff --git a/button.h b/button.h
@@ -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
diff --git a/grid.c b/grid.c
@@ -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])
@@ -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, <k_draw_grid, <k_destroy_grid, 0);
+	ltk_fill_widget_defaults(&grid->widget, id, window, <k_grid_draw,
+	    <k_grid_destroy, 0, LTK_GRID);
 	grid->widget.mouse_press = <k_grid_mouse_press;
 	grid->widget.mouse_release = <k_grid_mouse_release;
 	grid->widget.motion_notify = <k_grid_motion_notify;
@@ -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);
@@ -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;
@@ -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;
@@ -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;
 	}
@@ -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) {
@@ -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) {
@@ -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);
@@ -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);
@@ -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;
@@ -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");
+	}
+}
diff --git a/grid.h b/grid.h
@@ -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.
@@ -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
diff --git a/ltk.c b/ltk.c
@@ -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"
@@ -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 */
@@ -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++)
@@ -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;
@@ -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;
@@ -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;
@@ -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 */
@@ -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);
 }
 
@@ -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;
@@ -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);
 }
diff --git a/ltk.h b/ltk.h
@@ -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 {
@@ -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);
@@ -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;
@@ -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);
 
@@ -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
diff --git a/strtonum.c b/strtonum.c
@@ -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);
diff --git a/test.gui b/test.gui
@@ -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
diff --git a/util.h b/util.h
@@ -0,0 +1,5 @@
+#ifndef __OpenBSD__
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+    const char **errstrp);
+#endif