commit 4f891285a531b95894eb8ecb397d91f84285bfaa
parent 7d8235cc4e3c173b19461ff5eaf5991a43f15994
Author: lumidify <nobody@lumidify.org>
Date:   Mon, 22 Feb 2021 22:05:56 +0100
Move some functions from ltkd.c to widget.c and rect.c
Diffstat:
21 files changed, 396 insertions(+), 335 deletions(-)
diff --git a/Makefile b/Makefile
@@ -37,6 +37,8 @@ OBJ = \
 	src/util.o \
 	src/memory.o \
 	src/color.o \
+	src/rect.o \
+	src/widget.o \
 	src/ltkd.o \
 	src/ini.o \
 	src/grid.o \
@@ -59,6 +61,8 @@ HDR = \
 	src/ini.h \
 	src/khash.h \
 	src/label.h \
+	src/rect.h \
+	src/widget.h \
 	src/ltk.h \
 	src/memory.h \
 	src/scrollbar.h \
diff --git a/src/box.c b/src/box.c
@@ -32,6 +32,8 @@
 
 #include "memory.h"
 #include "color.h"
+#include "rect.h"
+#include "widget.h"
 #include "ltk.h"
 #include "util.h"
 #include "scrollbar.h"
diff --git a/src/box.h b/src/box.h
@@ -24,7 +24,7 @@
 #ifndef _LTK_BOX_H_
 #define _LTK_BOX_H_
 
-/* Requires the following includes: "scrollbar.h" "ltk.h" */
+/* Requires the following includes: "scrollbar.h", "rect.h", "widget.h", "ltk.h" */
 
 typedef struct {
 	ltk_widget widget;
diff --git a/src/button.c b/src/button.c
@@ -32,6 +32,8 @@
 
 #include "memory.h"
 #include "color.h"
+#include "rect.h"
+#include "widget.h"
 #include "ltk.h"
 #include "util.h"
 #include "text.h"
diff --git a/src/button.h b/src/button.h
@@ -24,7 +24,7 @@
 #ifndef _LTK_BUTTON_H_
 #define _LTK_BUTTON_H_
 
-/* Requires the following includes: <X11/Xlib.h>, "ltk.h", "color.h", "text.h" */
+/* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */
 
 typedef struct {
 	ltk_widget widget;
diff --git a/src/draw.c b/src/draw.c
@@ -32,6 +32,8 @@
 
 #include "memory.h"
 #include "color.h"
+#include "rect.h"
+#include "widget.h"
 #include "ltk.h"
 #include "util.h"
 #include "draw.h"
diff --git a/src/draw.h b/src/draw.h
@@ -24,7 +24,7 @@
 #ifndef _LTK_DRAW_H_
 #define _LTK_DRAW_H_
 
-/* Requires the following includes: <X11/Xlib.h>, "ltk.h" */
+/* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h" */
 
 typedef struct {
 	ltk_widget widget;
diff --git a/src/grid.c b/src/grid.c
@@ -39,6 +39,8 @@
 
 #include "memory.h"
 #include "color.h"
+#include "rect.h"
+#include "widget.h"
 #include "ltk.h"
 #include "util.h"
 #include "grid.h"
diff --git a/src/grid.h b/src/grid.h
@@ -24,7 +24,7 @@
 #ifndef _LTK_GRID_H_
 #define _LTK_GRID_H_
 
-/* Requires the following includes: "ltk.h" */
+/* Requires the following includes: "rect.h", "widget.h", "ltk.h" */
 
 /*
  * Struct to represent a grid widget.
diff --git a/src/label.c b/src/label.c
@@ -32,6 +32,8 @@
 
 #include "memory.h"
 #include "color.h"
+#include "rect.h"
+#include "widget.h"
 #include "ltk.h"
 #include "util.h"
 #include "text.h"
diff --git a/src/label.h b/src/label.h
@@ -24,7 +24,7 @@
 #ifndef _LTK_LABEL_H_
 #define _LTK_LABEL_H_
 
-/* Requires the following includes: <X11/Xlib.h>, "ltk.h", "color.h", "text.h" */
+/* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */
 
 typedef struct {
 	ltk_widget widget;
diff --git a/src/ltk.h b/src/ltk.h
@@ -24,7 +24,7 @@
 #ifndef _LTK_H_
 #define _LTK_H_
 
-/* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h>, <stdarg.h> */
+/* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h>, "widget.h" */
 
 typedef enum {
 	LTK_EVENT_RESIZE = 1 << 0,
@@ -33,88 +33,6 @@ typedef enum {
 } ltk_event_type;
 
 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_VERTICAL,
-	LTK_HORIZONTAL
-} ltk_orientation;
-
-typedef enum {
-	LTK_NORMAL,
-	LTK_PRESSED,
-	LTK_ACTIVE,
-	LTK_DISABLED
-} ltk_widget_state;
-
-typedef enum {
-	/* for e.g. scrollbar, which can't be directly accessed by users */
-	LTK_UNKNOWN,
-	LTK_GRID,
-	LTK_BUTTON,
-	LTK_DRAW,
-	LTK_LABEL,
-	LTK_WIDGET,
-	LTK_BOX
-} ltk_widget_type;
-
-struct ltk_window;
-
-struct ltk_widget_vtable;
-
-typedef struct ltk_widget {
-	struct ltk_window *window;
-	struct ltk_widget *active_widget;
-	struct ltk_widget *parent;
-	char *id;
-
-	struct ltk_widget_vtable *vtable;
-
-	ltk_rect rect;
-	unsigned int ideal_w;
-	unsigned int ideal_h;
-
-	ltk_widget_type type;
-	ltk_widget_state state;
-	unsigned int sticky;
-	unsigned short row;
-	unsigned short column;
-	unsigned short row_span;
-	unsigned short column_span;
-	unsigned char needs_redraw;
-} ltk_widget;
-
-struct ltk_widget_vtable {
-	void (*key_press) (struct ltk_widget *, XEvent);
-	void (*key_release) (struct ltk_widget *, XEvent);
-	int (*mouse_press) (struct ltk_widget *, XEvent);
-	int (*mouse_release) (struct ltk_widget *, XEvent);
-	int (*mouse_wheel) (struct ltk_widget *, XEvent);
-	int (*motion_notify) (struct ltk_widget *, XEvent);
-	void (*mouse_leave) (struct ltk_widget *, XEvent);
-	void (*mouse_enter) (struct ltk_widget *, XEvent);
-
-	void (*resize) (struct ltk_widget *);
-	void (*draw) (struct ltk_widget *, ltk_rect);
-	void (*change_state) (struct ltk_widget *);
-	void (*destroy) (struct ltk_widget *, int);
-
-	void (*child_size_change) (struct ltk_widget *, struct ltk_widget *);
-	int (*remove_child) (struct ltk_window *, struct ltk_widget *, struct ltk_widget *, char **);
-};
-
-typedef struct {
 	int border_width;
 	uint16_t font_size;
 	char *font;
@@ -158,23 +76,11 @@ typedef struct ltk_window {
 	struct ltk_event_queue *last_event;
 } ltk_window;
 
-ltk_rect ltk_rect_intersect(ltk_rect r1, ltk_rect r2);
-ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2);
 void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect);
 int ltk_create_xcolor(ltk_window *window, const char *hex, XColor *col);
 void ltk_queue_event(ltk_window *window, ltk_event_type type, const char *id, const char *data);
-int ltk_collide_rect(ltk_rect rect, int x, int y);
 void ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget);
 void ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget);
-void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
-    struct ltk_widget_vtable *vtable, unsigned int needs_redraw, ltk_widget_type type);
-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);
-int ltk_widget_id_free(const char *id);
-ltk_widget *ltk_get_widget(const char *id, ltk_widget_type type, char **errstr);
-void ltk_set_widget(ltk_widget *widget, const char *id);
-void ltk_remove_widget(const char *id);
 void ltk_quit(ltk_window *window);
 
 #endif
diff --git a/src/ltkd.c b/src/ltkd.c
@@ -1,5 +1,5 @@
 /* FIXME: backslashes should be parsed properly! */
-/* FIXME Figure out how to properly print window id */
+/* FIXME: Figure out how to properly print window id */
 /* FIXME: PROPERLY CLEANUP ALL THEMES */
 /* FIXME: error checking in tokenizer (is this necessary?) */
 /* FIXME: parsing doesn't work properly with bs? */
@@ -51,6 +51,8 @@
 
 #include "memory.h"
 #include "color.h"
+#include "rect.h"
+#include "widget.h"
 #include "ltk.h"
 #include "util.h"
 #include "text.h"
@@ -90,16 +92,12 @@ static struct ltk_sock_info {
 	struct token_list tokens;  /* current tokens */
 } sockets[MAX_SOCK_CONNS];
 
-KHASH_MAP_INIT_STR(widget, ltk_widget *)
-static khash_t(widget) *widget_hash = NULL;
-
 static int ltk_mainloop(ltk_window *window);
 static char *get_sock_path(char *basedir, Window id);
 static FILE *open_log(char *dir);
 static void daemonize(void);
 static ltk_window *ltk_create_window(const char *title, int x, int y,
     unsigned int w, unsigned int h);
-static void ltk_destroy_widget_hash(void);
 static void ltk_destroy_window(ltk_window *window);
 static void ltk_redraw_window(ltk_window *window);
 static void ltk_window_other_event(ltk_window *window, XEvent event);
@@ -116,7 +114,6 @@ static void process_commands(ltk_window *window, struct ltk_sock_info *sock);
 static int add_client(int fd);
 static int listen_sock(const char *sock_path);
 static int accept_sock(int listenfd);
-static int ltk_widget_destroy(ltk_window *window, char **tokens, size_t num_tokens, char **errstr);
 
 static short maxsocket = -1;
 static short running = 1;
@@ -147,8 +144,7 @@ int main(int argc, char *argv[]) {
 	ltk_logfile = open_log(ltk_dir);
 	if (!ltk_logfile) ltk_fatal_errno("Unable to open log file.\n");
 
-	widget_hash = kh_init(widget);
-	if (!widget_hash) ltk_fatal_errno("Unable to initialize widget hash table.\n");
+	ltk_widgets_init();
 
 	/* FIXME: set window size properly - I only run it in a tiling WM
 	   anyways, so it doesn't matter, but still... */
@@ -386,8 +382,7 @@ ltk_cleanup(void) {
 			ltk_free(sockets[i].tokens.tokens);
 	}
 
-	if (widget_hash)
-		ltk_destroy_widget_hash();
+	ltk_widgets_cleanup();
 	if (main_window)
 		ltk_destroy_window(main_window);
 }
@@ -438,32 +433,6 @@ ltk_set_root_widget_cmd(
 	return 0;
 }
 
-/* FIXME */
-#undef MAX
-#undef MIN
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-
-ltk_rect
-ltk_rect_intersect(ltk_rect r1, ltk_rect r2) {
-	ltk_rect i;
-	i.x = MAX(r1.x, r2.x);
-	i.y = MAX(r1.y, r2.y);
-	i.w = MIN(r1.x + r1.w, r2.x + r2.w) - i.x;
-	i.h = MIN(r1.y + r1.h, r2.y + r2.h) - i.y;
-	return i;
-}
-
-ltk_rect
-ltk_rect_union(ltk_rect r1, ltk_rect r2) {
-	ltk_rect u;
-	u.x = MIN(r1.x, r2.x);
-	u.y = MIN(r1.y, r2.y);
-	u.w = MAX(r1.x + r1.w, r2.x + r2.w) - u.x;
-	u.h = MAX(r1.y + r1.h, r2.y + r2.h) - 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)
@@ -611,19 +580,6 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int 
 }
 
 static void
-ltk_destroy_widget_hash(void) {
-	khint_t k;
-	ltk_widget *ptr;
-	for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
-		if (kh_exist(widget_hash, k)) {
-			ptr = kh_value(widget_hash, k);
-			ptr->vtable->destroy(ptr, 1);
-		}
-	}
-	kh_destroy(widget, widget_hash);
-}
-
-static void
 ltk_destroy_window(ltk_window *window) {
 	XDestroyWindow(window->dpy, window->xwindow);
 	ltk_cleanup_text();
@@ -691,52 +647,6 @@ ltk_load_theme(ltk_window *window, const char *path) {
 	}
 }
 
-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_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
-    struct ltk_widget_vtable *vtable, unsigned int needs_redraw, ltk_widget_type type) {
-	if (id)
-		widget->id = ltk_strdup(id);
-	else
-		widget->id = NULL;
-	widget->window = window;
-	widget->active_widget = NULL;
-	widget->parent = NULL;
-	widget->type = type;
-
-	/* FIXME: possibly check that draw and destroy aren't NULL */
-	widget->vtable = vtable;
-
-	widget->needs_redraw = needs_redraw;
-	widget->state = LTK_NORMAL;
-	widget->row = 0;
-	widget->rect.x = 0;
-	widget->rect.y = 0;
-	widget->rect.w = 0;
-	widget->rect.h = 0;
-	widget->ideal_w = 0;
-	widget->ideal_h = 0;
-
-	widget->row = 0;
-	widget->column = 0;
-	widget->row_span = 0;
-	widget->column_span = 0;
-	widget->sticky = 0;
-}
-
-static void
-ltk_widget_change_state(ltk_widget *widget) {
-	if (widget->vtable->change_state)
-		widget->vtable->change_state(widget);
-	if (widget->needs_redraw)
-		ltk_window_invalidate_rect(widget->window, widget->rect);
-}
-
 void
 ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
 	if (window->active_widget == widget)
@@ -772,59 +682,6 @@ ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget) {
 	}
 }
 
-void
-ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event) {
-	if (!widget || widget->state == LTK_DISABLED)
-		return;
-	int default_handler = 1;
-	if (widget->vtable->mouse_press)
-		default_handler = widget->vtable->mouse_press(widget, event);
-	if (default_handler) {
-		if (event.xbutton.button == 1)
-			ltk_window_set_pressed_widget(widget->window, widget);
-	}
-}
-
-void
-ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) {
-	if (!widget || widget->state == LTK_DISABLED)
-		return;
-	int default_handler = 1;
-	if (widget->vtable->mouse_release)
-		default_handler = widget->vtable->mouse_release(widget, event);
-	if (default_handler)
-		ltk_window_set_pressed_widget(widget->window, NULL);
-}
-
-/* FIXME: ONLY SET ACTIVE WIDGET AT BOTTOM OF HIERARCHY
-   -> Don't first set parent as active widget and then child */
-void
-ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) {
-	if (!widget || widget->state == LTK_DISABLED)
-		return;
-	/* FIXME: THIS WHOLE STATE HANDLING IS STILL PARTIALLY BROKEN */
-	/*
-	if (((widget->state == LTK_NORMAL) ||
-	     (widget->state == LTK_ACTIVE && widget->window->active_widget != widget)) &&
-	     !widget->window->pressed_widget) {
-		widget->state = LTK_ACTIVE;
-		if (widget->vtable->change_state)
-			widget->change_statevtable->(widget);
-		if (widget->vtable->mouse_enter)
-			widget->mouse_entervtable->(widget, event);
-		ltk_window_remove_active_widget(widget->window);
-		ltk_window_set_active_widget(widget);
-	}
-	*/
-	int default_handler = 1;
-	if (widget->window->pressed_widget && widget->window->pressed_widget->vtable->motion_notify)
-		default_handler = widget->window->pressed_widget->vtable->motion_notify(widget->window->pressed_widget, event);
-	else if (widget->vtable->motion_notify)
-		default_handler = widget->vtable->motion_notify(widget, event);
-	if (default_handler)
-		ltk_window_set_active_widget(widget->window, widget);
-}
-
 static void
 ltk_handle_event(ltk_window *window, XEvent event) {
 	ltk_widget *root_widget = window->root_widget;
@@ -851,53 +708,6 @@ ltk_handle_event(ltk_window *window, XEvent event) {
 	}
 }
 
-int
-ltk_widget_id_free(const char *id) {
-	khint_t k;
-	k = kh_get(widget, widget_hash, id);
-	if (k != kh_end(widget_hash)) {
-		return 0;
-	}
-	return 1;
-}
-
-ltk_widget *
-ltk_get_widget(const char *id, ltk_widget_type type, char **errstr) {
-	khint_t k;
-	ltk_widget *widget;
-	k = kh_get(widget, widget_hash, id);
-	if (k == kh_end(widget_hash)) {
-		*errstr = "Widget with given ID doesn't exist.\n";
-		return NULL;
-	}
-	widget = kh_value(widget_hash, k);
-	if (type != LTK_WIDGET && widget->type != type) {
-		*errstr = "Widget with given ID has wrong type.\n";
-		return NULL;
-	}
-	return widget;
-}
-
-void
-ltk_set_widget(ltk_widget *widget, const char *id) {
-	int ret;
-	khint_t k;
-	/* apparently, khash requires the string to stay accessible */
-	/* FIXME: How is this freed? */
-	char *tmp = ltk_strdup(id);
-	k = kh_put(widget, widget_hash, tmp, &ret);
-	kh_value(widget_hash, k) = widget;
-}
-
-void
-ltk_remove_widget(const char *id) {
-	khint_t k;
-	k = kh_get(widget, widget_hash, id);
-	if (k != kh_end(widget_hash)) {
-		kh_del(widget, widget_hash, k);
-	}
-}
-
 /* Push a token onto `token_list`, resizing the buffer if necessary.
    Returns -1 on error, 0 otherwise.
    Note: The token is not copied, it is only added directly. */
@@ -1178,42 +988,3 @@ process_commands(ltk_window *window, struct ltk_sock_info *sock) {
 		sock->read_cur = 0;
 	}
 }
-
-static int
-ltk_widget_destroy(
-    ltk_window *window,
-    char **tokens,
-    size_t num_tokens,
-    char **errstr) {
-	int err = 0, shallow = 1;
-	if (num_tokens != 2 && num_tokens != 3) {
-		*errstr = "Invalid number of arguments.\n";
-		return 1;
-	}
-	if (num_tokens == 3) {
-		if (strcmp(tokens[2], "deep") == 0) {
-			shallow = 0;
-		} else if (strcmp(tokens[2], "shallow") == 0) {
-			shallow = 1;
-		} else {
-			*errstr = "Invalid argument: must be 'shallow' or 'deep'.\n";
-			return 1;
-		}
-	}
-	ltk_widget *widget = ltk_get_widget(tokens[1], LTK_WIDGET, errstr);
-	if (!widget) {
-		*errstr = "Invalid widget ID.\n";
-		return 1;
-	}
-	ltk_remove_widget(tokens[1]);
-	/* widget->parent->remove_child should never be NULL because of the fact that
-	   the widget is set as parent, but let's just check anyways... */
-	if (widget->parent && widget->parent->vtable->remove_child) {
-		err = widget->parent->vtable->remove_child(
-		    window, widget, widget->parent, errstr
-		);
-	}
-	widget->vtable->destroy(widget, shallow);
-
-	return err;
-}
diff --git a/src/rect.c b/src/rect.c
@@ -0,0 +1,33 @@
+#include "rect.h"
+
+/* FIXME */
+#undef MAX
+#undef MIN
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+ltk_rect
+ltk_rect_intersect(ltk_rect r1, ltk_rect r2) {
+	ltk_rect i;
+	i.x = MAX(r1.x, r2.x);
+	i.y = MAX(r1.y, r2.y);
+	i.w = MIN(r1.x + r1.w, r2.x + r2.w) - i.x;
+	i.h = MIN(r1.y + r1.h, r2.y + r2.h) - i.y;
+	return i;
+}
+
+ltk_rect
+ltk_rect_union(ltk_rect r1, ltk_rect r2) {
+	ltk_rect u;
+	u.x = MIN(r1.x, r2.x);
+	u.y = MIN(r1.y, r2.y);
+	u.w = MAX(r1.x + r1.w, r2.x + r2.w) - u.x;
+	u.h = MAX(r1.y + r1.h, r2.y + r2.h) - u.y;
+	return u;
+}
+
+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);
+}
diff --git a/src/rect.h b/src/rect.h
@@ -0,0 +1,15 @@
+#ifndef _LTK_RECT_H_
+#define _LTK_RECT_H_
+
+typedef struct {
+	int x;
+	int y;
+	int w;
+	int h;
+} ltk_rect;
+
+ltk_rect ltk_rect_intersect(ltk_rect r1, ltk_rect r2);
+ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2);
+int ltk_collide_rect(ltk_rect rect, int x, int y);
+
+#endif /* _LTK_RECT_H_ */
diff --git a/src/scrollbar.c b/src/scrollbar.c
@@ -32,6 +32,8 @@
 
 #include "memory.h"
 #include "color.h"
+#include "rect.h"
+#include "widget.h"
 #include "ltk.h"
 #include "util.h"
 #include "scrollbar.h"
diff --git a/src/scrollbar.h b/src/scrollbar.h
@@ -24,7 +24,7 @@
 #ifndef _LTK_SCROLLBAR_H_
 #define _LTK_SCROLLBAR_H_
 
-/* Requires: "ltk.h" */
+/* Requires: "rect.h", "widget.h", "ltk.h" */
 
 typedef struct {
 	ltk_widget widget;
diff --git a/src/text_pango.c b/src/text_pango.c
@@ -12,6 +12,8 @@
 
 #include "memory.h"
 #include "color.h"
+#include "rect.h"
+#include "widget.h"
 #include "ltk.h"
 #include "util.h"
 #include "text.h"
diff --git a/src/text_stb.c b/src/text_stb.c
@@ -37,6 +37,8 @@
 
 #include "memory.h"
 #include "color.h"
+#include "rect.h"
+#include "widget.h"
 #include "ltk.h"
 #include "util.h"
 #include "text.h"
diff --git a/src/widget.c b/src/widget.c
@@ -0,0 +1,221 @@
+#include <stdarg.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "rect.h"
+#include "widget.h"
+#include "color.h"
+#include "ltk.h"
+#include "memory.h"
+#include "util.h"
+#include "khash.h"
+
+static void ltk_destroy_widget_hash(void);
+
+KHASH_MAP_INIT_STR(widget, ltk_widget *)
+static khash_t(widget) *widget_hash = NULL;
+
+static void
+ltk_destroy_widget_hash(void) {
+	khint_t k;
+	ltk_widget *ptr;
+	for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
+		if (kh_exist(widget_hash, k)) {
+			ptr = kh_value(widget_hash, k);
+			ptr->vtable->destroy(ptr, 1);
+		}
+	}
+	kh_destroy(widget, widget_hash);
+}
+
+void
+ltk_widgets_init() {
+	widget_hash = kh_init(widget);
+	if (!widget_hash) ltk_fatal_errno("Unable to initialize widget hash table.\n");
+}
+
+void
+ltk_widgets_cleanup() {
+	if (widget_hash)
+		ltk_destroy_widget_hash();
+}
+
+void
+ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
+    struct ltk_widget_vtable *vtable, unsigned int needs_redraw, ltk_widget_type type) {
+	if (id)
+		widget->id = ltk_strdup(id);
+	else
+		widget->id = NULL;
+	widget->window = window;
+	widget->active_widget = NULL;
+	widget->parent = NULL;
+	widget->type = type;
+
+	/* FIXME: possibly check that draw and destroy aren't NULL */
+	widget->vtable = vtable;
+
+	widget->needs_redraw = needs_redraw;
+	widget->state = LTK_NORMAL;
+	widget->row = 0;
+	widget->rect.x = 0;
+	widget->rect.y = 0;
+	widget->rect.w = 0;
+	widget->rect.h = 0;
+	widget->ideal_w = 0;
+	widget->ideal_h = 0;
+
+	widget->row = 0;
+	widget->column = 0;
+	widget->row_span = 0;
+	widget->column_span = 0;
+	widget->sticky = 0;
+}
+
+void
+ltk_widget_change_state(ltk_widget *widget) {
+	if (widget->vtable->change_state)
+		widget->vtable->change_state(widget);
+	if (widget->needs_redraw)
+		ltk_window_invalidate_rect(widget->window, widget->rect);
+}
+
+void
+ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event) {
+	if (!widget || widget->state == LTK_DISABLED)
+		return;
+	int default_handler = 1;
+	if (widget->vtable->mouse_press)
+		default_handler = widget->vtable->mouse_press(widget, event);
+	if (default_handler) {
+		if (event.xbutton.button == 1)
+			ltk_window_set_pressed_widget(widget->window, widget);
+	}
+}
+
+void
+ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) {
+	if (!widget || widget->state == LTK_DISABLED)
+		return;
+	int default_handler = 1;
+	if (widget->vtable->mouse_release)
+		default_handler = widget->vtable->mouse_release(widget, event);
+	if (default_handler)
+		ltk_window_set_pressed_widget(widget->window, NULL);
+}
+
+/* FIXME: ONLY SET ACTIVE WIDGET AT BOTTOM OF HIERARCHY
+   -> Don't first set parent as active widget and then child */
+void
+ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) {
+	if (!widget || widget->state == LTK_DISABLED)
+		return;
+	/* FIXME: THIS WHOLE STATE HANDLING IS STILL PARTIALLY BROKEN */
+	/*
+	if (((widget->state == LTK_NORMAL) ||
+	     (widget->state == LTK_ACTIVE && widget->window->active_widget != widget)) &&
+	     !widget->window->pressed_widget) {
+		widget->state = LTK_ACTIVE;
+		if (widget->vtable->change_state)
+			widget->vtable->change_state(widget);
+		if (widget->vtable->mouse_enter)
+			widget->vtable->mouse_enter(widget, event);
+		ltk_window_remove_active_widget(widget->window);
+		ltk_window_set_active_widget(widget);
+	}
+	*/
+	int default_handler = 1;
+	if (widget->window->pressed_widget && widget->window->pressed_widget->vtable->motion_notify)
+		default_handler = widget->window->pressed_widget->vtable->motion_notify(widget->window->pressed_widget, event);
+	else if (widget->vtable->motion_notify)
+		default_handler = widget->vtable->motion_notify(widget, event);
+	if (default_handler)
+		ltk_window_set_active_widget(widget->window, widget);
+}
+
+int
+ltk_widget_id_free(const char *id) {
+	khint_t k;
+	k = kh_get(widget, widget_hash, id);
+	if (k != kh_end(widget_hash)) {
+		return 0;
+	}
+	return 1;
+}
+
+ltk_widget *
+ltk_get_widget(const char *id, ltk_widget_type type, char **errstr) {
+	khint_t k;
+	ltk_widget *widget;
+	k = kh_get(widget, widget_hash, id);
+	if (k == kh_end(widget_hash)) {
+		*errstr = "Widget with given ID doesn't exist.\n";
+		return NULL;
+	}
+	widget = kh_value(widget_hash, k);
+	if (type != LTK_WIDGET && widget->type != type) {
+		*errstr = "Widget with given ID has wrong type.\n";
+		return NULL;
+	}
+	return widget;
+}
+
+void
+ltk_set_widget(ltk_widget *widget, const char *id) {
+	int ret;
+	khint_t k;
+	/* apparently, khash requires the string to stay accessible */
+	/* FIXME: How is this freed? */
+	char *tmp = ltk_strdup(id);
+	k = kh_put(widget, widget_hash, tmp, &ret);
+	kh_value(widget_hash, k) = widget;
+}
+
+void
+ltk_remove_widget(const char *id) {
+	khint_t k;
+	k = kh_get(widget, widget_hash, id);
+	if (k != kh_end(widget_hash)) {
+		kh_del(widget, widget_hash, k);
+	}
+}
+
+int
+ltk_widget_destroy(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens,
+    char **errstr) {
+	int err = 0, shallow = 1;
+	if (num_tokens != 2 && num_tokens != 3) {
+		*errstr = "Invalid number of arguments.\n";
+		return 1;
+	}
+	if (num_tokens == 3) {
+		if (strcmp(tokens[2], "deep") == 0) {
+			shallow = 0;
+		} else if (strcmp(tokens[2], "shallow") == 0) {
+			shallow = 1;
+		} else {
+			*errstr = "Invalid argument: must be 'shallow' or 'deep'.\n";
+			return 1;
+		}
+	}
+	ltk_widget *widget = ltk_get_widget(tokens[1], LTK_WIDGET, errstr);
+	if (!widget) {
+		*errstr = "Invalid widget ID.\n";
+		return 1;
+	}
+	ltk_remove_widget(tokens[1]);
+	/* widget->parent->remove_child should never be NULL because of the fact that
+	   the widget is set as parent, but let's just check anyways... */
+	if (widget->parent && widget->parent->vtable->remove_child) {
+		err = widget->parent->vtable->remove_child(
+		    window, widget, widget->parent, errstr
+		);
+	}
+	widget->vtable->destroy(widget, shallow);
+
+	return err;
+}
diff --git a/src/widget.h b/src/widget.h
@@ -0,0 +1,95 @@
+/* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h>, "rect.h" */
+
+#ifndef _LTK_WIDGET_H_
+#define _LTK_WIDGET_H_
+
+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_VERTICAL,
+	LTK_HORIZONTAL
+} ltk_orientation;
+
+typedef enum {
+	LTK_NORMAL,
+	LTK_PRESSED,
+	LTK_ACTIVE,
+	LTK_DISABLED
+} ltk_widget_state;
+
+typedef enum {
+	/* for e.g. scrollbar, which can't be directly accessed by users */
+	LTK_UNKNOWN,
+	LTK_GRID,
+	LTK_BUTTON,
+	LTK_DRAW,
+	LTK_LABEL,
+	LTK_WIDGET,
+	LTK_BOX
+} ltk_widget_type;
+
+struct ltk_window;
+
+struct ltk_widget_vtable;
+
+typedef struct ltk_widget {
+	struct ltk_window *window;
+	struct ltk_widget *active_widget;
+	struct ltk_widget *parent;
+	char *id;
+
+	struct ltk_widget_vtable *vtable;
+
+	ltk_rect rect;
+	unsigned int ideal_w;
+	unsigned int ideal_h;
+
+	ltk_widget_type type;
+	ltk_widget_state state;
+	unsigned int sticky;
+	unsigned short row;
+	unsigned short column;
+	unsigned short row_span;
+	unsigned short column_span;
+	unsigned char needs_redraw;
+} ltk_widget;
+
+struct ltk_widget_vtable {
+	void (*key_press) (struct ltk_widget *, XEvent);
+	void (*key_release) (struct ltk_widget *, XEvent);
+	int (*mouse_press) (struct ltk_widget *, XEvent);
+	int (*mouse_release) (struct ltk_widget *, XEvent);
+	int (*mouse_wheel) (struct ltk_widget *, XEvent);
+	int (*motion_notify) (struct ltk_widget *, XEvent);
+	void (*mouse_leave) (struct ltk_widget *, XEvent);
+	void (*mouse_enter) (struct ltk_widget *, XEvent);
+
+	void (*resize) (struct ltk_widget *);
+	void (*draw) (struct ltk_widget *, ltk_rect);
+	void (*change_state) (struct ltk_widget *);
+	void (*destroy) (struct ltk_widget *, int);
+
+	void (*child_size_change) (struct ltk_widget *, struct ltk_widget *);
+	int (*remove_child) (struct ltk_window *, struct ltk_widget *, struct ltk_widget *, char **);
+};
+
+int ltk_widget_destroy(struct ltk_window *window, char **tokens, size_t num_tokens, char **errstr);
+void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, struct ltk_window *window,
+    struct ltk_widget_vtable *vtable, unsigned int needs_redraw, ltk_widget_type type);
+void ltk_widget_change_state(ltk_widget *widget);
+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);
+int ltk_widget_id_free(const char *id);
+ltk_widget *ltk_get_widget(const char *id, ltk_widget_type type, char **errstr);
+void ltk_set_widget(ltk_widget *widget, const char *id);
+void ltk_remove_widget(const char *id);
+void ltk_widgets_cleanup();
+void ltk_widgets_init();
+
+#endif /* _LTK_WIDGET_H_ */