URI: 
       tImprove theme handling - 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 3a21eba1783e1d29204d54e006fe9f5efb016fc3
   DIR parent 185426535af1a1f3ba649607d5346f59fa687803
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu,  2 Jun 2022 08:48:16 +0200
       
       Improve theme handling
       
       Diffstat:
         M .ltk/theme.ini                      |      30 +++++++++++++++---------------
         M Makefile                            |       2 ++
         M src/button.c                        |      83 ++++++++++---------------------
         M src/button.h                        |       5 +++--
         M src/color.c                         |      17 ++++++++++++++---
         M src/color.h                         |       3 ++-
         M src/label.c                         |      42 +++++++++++++++----------------
         M src/label.h                         |       7 ++++---
         M src/ltk.h                           |      14 ++++++++------
         M src/ltkd.c                          |     167 ++++++++++++++++---------------
         M src/menu.c                          |     253 ++++++++++---------------------
         M src/menu.h                          |       9 ++++++---
         M src/scrollbar.c                     |      60 ++++++++++++--------------------
         M src/scrollbar.h                     |       6 ++++--
         M src/text.h                          |       1 +
         A src/theme.c                         |     161 +++++++++++++++++++++++++++++++
         A src/theme.h                         |      48 +++++++++++++++++++++++++++++++
         M src/util.h                          |       7 ++++++-
       
       18 files changed, 509 insertions(+), 406 deletions(-)
       ---
   DIR diff --git a/.ltk/theme.ini b/.ltk/theme.ini
       t@@ -1,32 +1,32 @@
        [window]
       -font_size = 15
       -border_width = 0
       +font-size = 15
       +border-width = 0
        bg = #000000
        fg = #FFFFFF
        font = Liberation Mono
        
        [button]
       -border_width = 2
       -text_color = #FFFFFF
       +border-width = 2
       +text-color = #FFFFFF
        pad = 5
        border = #339999
        fill = #113355
       -border_pressed = #FFFFFF
       -fill_pressed = #113355
       -border_active = #FFFFFF
       -fill_active = #738194
       -border_disabled = #FFFFFF
       -fill_disabled = #292929
       +border-pressed = #FFFFFF
       +fill-pressed = #113355
       +border-active = #FFFFFF
       +fill-active = #738194
       +border-disabled = #FFFFFF
       +fill-disabled = #292929
        
        [label]
       -text_color = #FFFFFF
       +text-color = #FFFFFF
        pad = 5
        
        [scrollbar]
        size = 15
        bg = #000000
       -bg_disabled = #555555
       +bg-disabled = #555555
        fg = #113355
       -fg_pressed = #113355
       -fg_active = #738194
       -fg_disabled = #292929
       +fg-pressed = #113355
       +fg-active = #738194
       +fg-disabled = #292929
   DIR diff --git a/Makefile b/Makefile
       t@@ -50,6 +50,7 @@ OBJ = \
                src/button.o \
                src/label.o \
                src/menu.o \
       +        src/theme.o \
                src/graphics_xlib.o \
                src/surface_cache.o \
                $(EXTRA_OBJ)
       t@@ -75,6 +76,7 @@ HDR = \
                src/text.h \
                src/util.h \
                src/menu.h \
       +        src/theme.h \
                src/graphics.h \
                src/surface_cache.h \
                src/macros.h
   DIR diff --git a/src/button.c b/src/button.c
       t@@ -33,6 +33,7 @@
        #include "button.h"
        #include "graphics.h"
        #include "surface_cache.h"
       +#include "theme.h"
        
        #define MAX_BUTTON_BORDER_WIDTH 100
        #define MAX_BUTTON_PADDING 500
       t@@ -83,64 +84,34 @@ static struct {
                ltk_color fill_disabled;
        } theme;
        
       -/* FIXME: do this more efficiently (don't create color twice if
       -   the color is again defined in the theme file) */
       -void
       -ltk_button_setup_theme_defaults(ltk_window *window) {
       -        theme.border_width = 2;
       -        theme.pad = 5;
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &theme.text_color);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#339999", &theme.border);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#113355", &theme.fill);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &theme.border_pressed);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#113355", &theme.fill_pressed);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &theme.border_active);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#738194", &theme.fill_active);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &theme.border_disabled);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#292929", &theme.fill_disabled);
       +static ltk_theme_parseinfo parseinfo[] = {
       +        {"border", THEME_COLOR, {.color = &theme.border}, {.color = "#339999"}, 0, 0, 0},
       +        {"border-active", THEME_COLOR, {.color = &theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"border-disabled", THEME_COLOR, {.color = &theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"border-pressed", THEME_COLOR, {.color = &theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"border-width", THEME_INT, {.i = &theme.border_width}, {.i = 2}, 0, MAX_BUTTON_BORDER_WIDTH, 0},
       +        {"fill", THEME_COLOR, {.color = &theme.fill}, {.color = "#113355"}, 0, 0, 0},
       +        {"fill-active", THEME_COLOR, {.color = &theme.fill_active}, {.color = "#738194"}, 0, 0, 0},
       +        {"fill-disabled", THEME_COLOR, {.color = &theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
       +        {"fill-pressed", THEME_COLOR, {.color = &theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0},
       +        {"pad", THEME_INT, {.i = &theme.pad}, {.i = 5}, 0, MAX_BUTTON_PADDING, 0},
       +        {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
       +};
       +static int parseinfo_sorted = 0;
       +
       +int
       +ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) {
       +        return ltk_theme_handle_value(window, "button", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
       +}
       +
       +int
       +ltk_button_fill_theme_defaults(ltk_window *window) {
       +        return ltk_theme_fill_defaults(window, "button", parseinfo, LENGTH(parseinfo));
        }
        
        void
       -ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) {
       -        const char *errstr;
       -        if (strcmp(prop, "border_width") == 0) {
       -                theme.border_width = ltk_strtonum(value, 0, MAX_BUTTON_BORDER_WIDTH, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid button border width '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "pad") == 0) {
       -                theme.pad = ltk_strtonum(value, 0, MAX_BUTTON_PADDING, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid button padding '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "border") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.border))
       -                        ltk_warn("Error setting button border color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fill") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.fill))
       -                        ltk_warn("Error setting button fill color to '%s'.\n", value);
       -        } else if (strcmp(prop, "border_pressed") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.border_pressed))
       -                        ltk_warn("Error setting button pressed border color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fill_pressed") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.fill_pressed))
       -                        ltk_warn("Error setting button pressed fill color to '%s'.\n", value);
       -        } else if (strcmp(prop, "border_active") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.border_active))
       -                        ltk_warn("Error setting button active border color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fill_active") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.fill_active))
       -                        ltk_warn("Error setting button active fill color to '%s'.\n", value);
       -        } else if (strcmp(prop, "border_disabled") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.border_disabled))
       -                        ltk_warn("Error setting button disabled border color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fill_disabled") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.fill_disabled))
       -                        ltk_warn("Error setting button disabled fill color to '%s'.\n", value);
       -        } else if (strcmp(prop, "text_color") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.text_color))
       -                        ltk_warn("Error setting button text color to '%s'.\n", value);
       -        } else {
       -                ltk_warn("Unknown property '%s' for button style.\n", prop);
       -        }
       +ltk_button_uninitialize_theme(ltk_window *window) {
       +        ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo));
        }
        
        static void
       t@@ -213,7 +184,7 @@ static ltk_button *
        ltk_button_create(ltk_window *window, const char *id, char *text) {
                ltk_button *button = ltk_malloc(sizeof(ltk_button));
        
       -        uint16_t font_size = window->theme.font_size;
       +        uint16_t font_size = window->theme->font_size;
                button->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1);
                int text_w, text_h;
                ltk_text_line_get_size(button->tl, &text_w, &text_h);
   DIR diff --git a/src/button.h b/src/button.h
       t@@ -24,8 +24,9 @@ typedef struct {
                ltk_text_line *tl;
        } ltk_button;
        
       -void ltk_button_setup_theme_defaults(ltk_window *window);
       -void ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value);
       +int ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value);
       +int ltk_button_fill_theme_defaults(ltk_window *window);
       +void ltk_button_uninitialize_theme(ltk_window *window);
        
        int ltk_button_cmd(
            ltk_window *window,
   DIR diff --git a/src/color.c b/src/color.c
       t@@ -26,17 +26,28 @@
        /* FIXME: better error codes */
        /* FIXME: I think xcolor is unneeded when xft is enabled */
        int
       -ltk_color_create(Display *dpy, int screen, Colormap cm, const char *hex, ltk_color *col) {
       +ltk_color_create(Display *dpy, Visual *vis, Colormap cm, const char *hex, ltk_color *col) {
                if (!XParseColor(dpy, cm, hex, &col->xcolor))
                        return 1;
                if (!XAllocColor(dpy, cm, &col->xcolor))
                        return 1;
                /* FIXME: replace with XftColorAllocValue */
                #if USE_XFT == 1
       -        if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), cm, hex, &col->xftcolor))
       +        if (!XftColorAllocName(dpy, vis, cm, hex, &col->xftcolor))
                        return 1;
                #else
       -        (void)screen;
       +        (void)vis;
                #endif
                return 0;
        }
       +
       +void
       +ltk_color_destroy(Display *dpy, Visual *vis, Colormap cm, ltk_color *col) {
       +        /* FIXME: what should the 'planes' argument be? */
       +        XFreeColors(dpy, cm, &col->xcolor.pixel, 1, 0);
       +        #if USE_XFT == 1
       +        XftColorFree(dpy, vis, cm, &col->xftcolor);
       +        #else
       +        (void)vis;
       +        #endif
       +}
   DIR diff --git a/src/color.h b/src/color.h
       t@@ -31,6 +31,7 @@ typedef struct {
        } ltk_color;
        
        /* returns 1 on failure, 0 on success */
       -int ltk_color_create(Display *dpy, int screen, Colormap cm, const char *hex, ltk_color *col);
       +int ltk_color_create(Display *dpy, Visual *vis, Colormap cm, const char *hex, ltk_color *col);
       +void ltk_color_destroy(Display *dpy, Visual *vis, Colormap cm, ltk_color *col);
        
        #endif /* _LTK_COLOR_H_ */
   DIR diff --git a/src/label.c b/src/label.c
       t@@ -33,6 +33,7 @@
        #include "label.h"
        #include "graphics.h"
        #include "surface_cache.h"
       +#include "theme.h"
        
        #define MAX_LABEL_PADDING 500
        
       t@@ -68,30 +69,27 @@ static struct {
                int pad;
        } theme;
        
       -void
       -ltk_label_setup_theme_defaults(ltk_window *window) {
       -        theme.pad = 5;
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &theme.text_color);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#000000", &theme.bg_color);
       +int parseinfo_sorted = 0;
       +
       +static ltk_theme_parseinfo parseinfo[] = {
       +        {"bg-color", THEME_COLOR, {.color = &theme.bg_color}, {.color = "#000000"}, 0, 0, 0},
       +        {"pad", THEME_INT, {.i = &theme.pad}, {.i = 5}, 0, MAX_LABEL_PADDING, 0},
       +        {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
       +};
       +
       +int
       +ltk_label_ini_handler(ltk_window *window, const char *prop, const char *value) {
       +        return ltk_theme_handle_value(window, "label", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
       +}
       +
       +int
       +ltk_label_fill_theme_defaults(ltk_window *window) {
       +        return ltk_theme_fill_defaults(window, "label", parseinfo, LENGTH(parseinfo));
        }
        
        void
       -ltk_label_ini_handler(ltk_window *window, const char *prop, const char *value) {
       -        const char *errstr;
       -        /* FIXME: store generic max padding somewhere for all widgets? */
       -        if (strcmp(prop, "pad") == 0) {
       -                theme.pad = ltk_strtonum(value, 0, MAX_LABEL_PADDING, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid label padding '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "text_color") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.text_color))
       -                        ltk_warn("Error setting label text color to '%s'.\n", value);
       -        } else if (strcmp(prop, "bg_color") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.bg_color))
       -                        ltk_warn("Error setting label background color to '%s'.\n", value);
       -        } else {
       -                ltk_warn("Unknown property '%s' for label style.\n", prop);
       -        }
       +ltk_label_uninitialize_theme(ltk_window *window) {
       +        ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo));
        }
        
        static void
       t@@ -123,7 +121,7 @@ static ltk_label *
        ltk_label_create(ltk_window *window, const char *id, char *text) {
                ltk_label *label = ltk_malloc(sizeof(ltk_label));
        
       -        uint16_t font_size = window->theme.font_size;
       +        uint16_t font_size = window->theme->font_size;
                label->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1);
                int text_w, text_h;
                ltk_text_line_get_size(label->tl, &text_w, &text_h);
   DIR diff --git a/src/label.h b/src/label.h
       t@@ -1,5 +1,5 @@
        /*
       - * Copyright (c) 2021 lumidify <nobody@lumidify.org>
       + * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
         *
         * Permission to use, copy, modify, and/or distribute this software for any
         * purpose with or without fee is hereby granted, provided that the above
       t@@ -24,8 +24,9 @@ typedef struct {
                ltk_text_line *tl;
        } ltk_label;
        
       -void ltk_label_setup_theme_defaults(ltk_window *window);
       -void ltk_label_ini_handler(ltk_window *window, const char *prop, const char *value);
       +int ltk_label_ini_handler(ltk_window *window, const char *prop, const char *value);
       +int ltk_label_fill_theme_defaults(ltk_window *window);
       +void ltk_label_uninitialize_theme(ltk_window *window);
        
        int ltk_label_cmd(
            ltk_window *window,
   DIR diff --git a/src/ltk.h b/src/ltk.h
       t@@ -22,8 +22,6 @@
        #include <stdint.h>
        #include <X11/extensions/Xdbe.h>
        #include "color.h"
       -#include "widget.h"
       -#include "surface_cache.h"
        
        typedef enum {
                LTK_EVENT_RESIZE = 1 << 0,
       t@@ -34,7 +32,7 @@ typedef enum {
        
        typedef struct {
                int border_width;
       -        uint16_t font_size;
       +        int font_size;
                char *font;
                ltk_color fg;
                ltk_color bg;
       t@@ -58,10 +56,14 @@ struct ltk_event_queue {
        */
        
        /* FIXME: fix this ugliness; remove circular dependencies */
       +typedef struct ltk_window ltk_window;
        typedef struct ltk_text_context ltk_text_context;
        typedef struct ltk_surface ltk_surface;
        
       -typedef struct ltk_window {
       +#include "widget.h"
       +#include "surface_cache.h"
       +
       +struct ltk_window {
                Display *dpy;
                Visual *vis;
                ltk_surface_cache *surface_cache;
       t@@ -80,7 +82,7 @@ typedef struct ltk_window {
                ltk_widget *pressed_widget;
                void (*other_event) (struct ltk_window *, XEvent event);
                ltk_rect rect;
       -        ltk_window_theme theme;
       +        ltk_window_theme *theme;
                ltk_rect dirty_rect;
                struct ltk_event_queue *first_event;
                struct ltk_event_queue *last_event;
       t@@ -92,7 +94,7 @@ typedef struct ltk_window {
                   call hide for all popup widgets even if the hide function
                   already calls ltk_window_unregister_popup */
                char popups_locked;
       -} ltk_window;
       +};
        
        void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect);
        int ltk_create_xcolor(ltk_window *window, const char *hex, XColor *col);
   DIR diff --git a/src/ltkd.c b/src/ltkd.c
       t@@ -42,6 +42,7 @@
        #include "ini.h"
        #include "khash.h"
        
       +#include "theme.h"
        #include "memory.h"
        #include "color.h"
        #include "rect.h"
       t@@ -59,7 +60,7 @@
        #include "macros.h"
        
        #define MAX_WINDOW_BORDER_WIDTH 100
       -#define MAX_FONT_SIZE 200
       +#define MAX_WINDOW_FONT_SIZE 200
        
        #define MAX_SOCK_CONNS 20
        #define READ_BLK_SIZE 128
       t@@ -114,7 +115,13 @@ 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);
        static void ltk_handle_event(ltk_window *window, XEvent event);
       +
        static void ltk_load_theme(ltk_window *window, const char *path);
       +static void ltk_uninitialize_theme(ltk_window *window);
       +static int ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value);
       +static int ltk_window_fill_theme_defaults(ltk_window *window);
       +static int ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value);
       +
        static int read_sock(struct ltk_sock_info *sock);
        static int push_token(struct token_list *tl, char *token);
        static int read_sock(struct ltk_sock_info *sock);
       t@@ -428,8 +435,10 @@ ltk_cleanup(void) {
                }
        
                ltk_widgets_cleanup();
       -        if (main_window)
       +        if (main_window) {
       +                ltk_uninitialize_theme(main_window);
                        ltk_destroy_window(main_window);
       +        }
                main_window = NULL;
        }
        
       t@@ -526,7 +535,7 @@ ltk_redraw_window(ltk_window *window) {
                        window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w;
                if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h)
                        window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
       -        XSetForeground(window->dpy, window->gc, window->theme.bg.xcolor.pixel);
       +        XSetForeground(window->dpy, window->gc, window->theme->bg.xcolor.pixel);
                /* FIXME: this should use window->dirty_rect, but that doesn't work
                   properly with double buffering */
                XFillRectangle(
       t@@ -703,6 +712,75 @@ ltk_window_unregister_all_popups(ltk_window *window) {
                ltk_window_invalidate_rect(window, window->rect);
        }
        
       +ltk_window_theme window_theme;
       +static ltk_theme_parseinfo theme_parseinfo[] = {
       +        {"border-width", THEME_INT, {.i = &window_theme.border_width}, {.i = 0}, 0, MAX_WINDOW_BORDER_WIDTH, 0},
       +        {"font-size", THEME_INT, {.i = &window_theme.font_size}, {.i = 15}, 0, MAX_WINDOW_FONT_SIZE, 0},
       +        {"font", THEME_STRING, {.str = &window_theme.font}, {.str = "Liberation Mono"}, 0, 0, 0},
       +        {"bg", THEME_COLOR, {.color = &window_theme.bg}, {.color = "#000000"}, 0, 0, 0},
       +        {"fg", THEME_COLOR, {.color = &window_theme.fg}, {.color = "#FFFFFF"}, 0, 0, 0},
       +};
       +static int theme_parseinfo_sorted = 0;
       +
       +static int
       +ltk_window_fill_theme_defaults(ltk_window *window) {
       +        return ltk_theme_fill_defaults(window, "window", theme_parseinfo, LENGTH(theme_parseinfo));
       +}
       +
       +static int
       +ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) {
       +        return ltk_theme_handle_value(window, "window", prop, value, theme_parseinfo, LENGTH(theme_parseinfo), &theme_parseinfo_sorted);
       +}
       +
       +/* FIXME: standardize return codes - usually, 0 is returned on success, but ini.h
       +   uses 1 on success, so this is all a bit confusing */
       +static int
       +ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value) {
       +        if (strcmp(widget, "window") == 0) {
       +                ltk_window_ini_handler(window, prop, value);
       +        } else if (strcmp(widget, "button") == 0) {
       +                ltk_button_ini_handler(window, prop, value);
       +        } else if (strcmp(widget, "label") == 0) {
       +                ltk_label_ini_handler(window, prop, value);
       +        } else if (strcmp(widget, "scrollbar") == 0) {
       +                ltk_scrollbar_ini_handler(window, prop, value);
       +        } else if (strcmp(widget, "menu") == 0) {
       +                ltk_menu_ini_handler(window, prop, value);
       +        } else if (strcmp(widget, "submenu") == 0) {
       +                ltk_submenu_ini_handler(window, prop, value);
       +        } else {
       +                return 0;
       +        }
       +        return 1;
       +}
       +
       +static void
       +ltk_load_theme(ltk_window *window, const char *path) {
       +        /* FIXME: give line number in error message */
       +        if (ini_parse(path, ltk_ini_handler, window) != 0) {
       +                ltk_warn("Unable to load theme.\n");
       +        }
       +        if (ltk_window_fill_theme_defaults(window)    ||
       +            ltk_button_fill_theme_defaults(window)    ||
       +            ltk_label_fill_theme_defaults(window)     ||
       +            ltk_scrollbar_fill_theme_defaults(window) ||
       +            ltk_menu_fill_theme_defaults(window)      ||
       +            ltk_submenu_fill_theme_defaults(window)) {
       +                ltk_uninitialize_theme(window);
       +                ltk_fatal("Unable to load theme defaults.\n");
       +        }
       +}
       +
       +static void
       +ltk_uninitialize_theme(ltk_window *window) {
       +        ltk_theme_uninitialize(window, theme_parseinfo, LENGTH(theme_parseinfo));
       +        ltk_button_uninitialize_theme(window);
       +        ltk_label_uninitialize_theme(window);
       +        ltk_scrollbar_uninitialize_theme(window);
       +        ltk_menu_uninitialize_theme(window);
       +        ltk_submenu_uninitialize_theme(window);
       +}
       +
        static ltk_window *
        ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int h) {
                char *theme_path;
       t@@ -754,17 +832,16 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int 
                //printf("%d   %d\n", WidthOfScreen(XDefaultScreenOfDisplay(window->dpy)), WidthMMOfScreen(XDefaultScreenOfDisplay(window->dpy)));
                window->cm = DefaultColormap(window->dpy, window->screen);
                theme_path = ltk_strcat_useful(ltk_dir, "/theme.ini");
       -        window->theme.font = NULL;
       -        if (!theme_path)
       -                ltk_fatal_errno("Not enough memory for theme path.\n");
       +        window->theme = &window_theme;
                ltk_load_theme(window, theme_path);
                ltk_free(theme_path);
       +        /* FIXME: fix theme memory leaks when exit happens between here and the end of this function */
                window->wm_delete_msg = XInternAtom(window->dpy, "WM_DELETE_WINDOW", False);
        
                memset(&attrs, 0, sizeof(attrs));
       -        attrs.background_pixel = window->theme.bg.xcolor.pixel;
       +        attrs.background_pixel = window->theme->bg.xcolor.pixel;
                attrs.colormap = window->cm;
       -        attrs.border_pixel = window->theme.fg.xcolor.pixel;
       +        attrs.border_pixel = window->theme->fg.xcolor.pixel;
                /* this causes the window contents to be kept
                 * when it is resized, leading to less flicker */
                attrs.bit_gravity = NorthWestGravity;
       t@@ -775,7 +852,7 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int 
                window->depth = DefaultDepth(window->dpy, window->screen);
                window->xwindow = XCreateWindow(
                    window->dpy, DefaultRootWindow(window->dpy), x, y,
       -            w, h, window->theme.border_width, window->depth,
       +            w, h, window->theme->border_width, window->depth,
                    InputOutput, window->vis,
                    CWBackPixel | CWColormap | CWBitGravity | CWEventMask | CWBorderPixel, &attrs
                );
       t@@ -789,8 +866,8 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int 
                }
                window->drawable = window->back_buf;
                window->gc = XCreateGC(window->dpy, window->xwindow, 0, 0);
       -        XSetForeground(window->dpy, window->gc, window->theme.fg.xcolor.pixel);
       -        XSetBackground(window->dpy, window->gc, window->theme.bg.xcolor.pixel);
       +        XSetForeground(window->dpy, window->gc, window->theme->fg.xcolor.pixel);
       +        XSetBackground(window->dpy, window->gc, window->theme->bg.xcolor.pixel);
                XSetStandardProperties(
                        window->dpy, window->xwindow,
                        title, NULL, None, NULL, 0, NULL
       t@@ -815,7 +892,7 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int 
                window->dirty_rect.y = 0;
                window->surface = ltk_surface_from_window(window);
        
       -        window->text_context = ltk_text_context_create(window, window->theme.font);
       +        window->text_context = ltk_text_context_create(window, window->theme->font);
        
                XClearWindow(window->dpy, window->xwindow);
                XMapRaised(window->dpy, window->xwindow);
       t@@ -833,76 +910,10 @@ ltk_destroy_window(ltk_window *window) {
                XCloseDisplay(window->dpy);
                ltk_surface_destroy(window->surface);
                ltk_surface_cache_destroy(window->surface_cache);
       -        if (window->theme.font)
       -                ltk_free(window->theme.font);
                ltk_free(window);
        }
        
        void
       -ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) {
       -        const char *errstr;
       -        if (strcmp(prop, "border_width") == 0) {
       -                window->theme.border_width = ltk_strtonum(value, 0, MAX_WINDOW_BORDER_WIDTH, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid window border width '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "bg") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &window->theme.bg))
       -                        ltk_warn("Error setting window background color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fg") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &window->theme.fg))
       -                        ltk_warn("Error setting window foreground color to '%s'.\n", value);
       -        } else if (strcmp(prop, "font") == 0) {
       -                window->theme.font = ltk_strdup(value);
       -        } else if (strcmp(prop, "font_size") == 0) {
       -                window->theme.font_size = ltk_strtonum(value, 0, MAX_FONT_SIZE, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid window font size '%s': %s.\n", value, errstr);
       -        }
       -}
       -
       -int
       -ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value) {
       -        if (strcmp(widget, "window") == 0) {
       -                ltk_window_ini_handler(window, prop, value);
       -        } else if (strcmp(widget, "button") == 0) {
       -                ltk_button_ini_handler(window, prop, value);
       -        } else if (strcmp(widget, "label") == 0) {
       -                ltk_label_ini_handler(window, prop, value);
       -        } else if (strcmp(widget, "scrollbar") == 0) {
       -                ltk_scrollbar_ini_handler(window, prop, value);
       -        } else if (strcmp(widget, "menu") == 0) {
       -                ltk_menu_ini_handler(window, prop, value);
       -        } else if (strcmp(widget, "submenu") == 0) {
       -                ltk_submenu_ini_handler(window, prop, value);
       -        } else {
       -                return 0;
       -        }
       -        return 1;
       -}
       -
       -static void
       -ltk_window_setup_theme_defaults(ltk_window *window) {
       -        window->theme.border_width = 0;
       -        window->theme.font_size = 15;
       -        window->theme.font = ltk_strdup("Liberation Mono");
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#000000", &window->theme.bg);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &window->theme.fg);
       -}
       -
       -static void
       -ltk_load_theme(ltk_window *window, const char *path) {
       -        /* FIXME: Error checking, especially when creating colors! */
       -        ltk_window_setup_theme_defaults(window);
       -        ltk_button_setup_theme_defaults(window);
       -        ltk_label_setup_theme_defaults(window);
       -        ltk_scrollbar_setup_theme_defaults(window);
       -        ltk_menu_setup_theme_defaults(window);
       -        if (ini_parse(path, ltk_ini_handler, window) < 0) {
       -                ltk_warn("Can't load theme.\n");
       -        }
       -}
       -
       -void
        ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
                if (window->active_widget == widget)
                        return;
   DIR diff --git a/src/menu.c b/src/menu.c
       t@@ -34,6 +34,7 @@
        #include "menu.h"
        #include "graphics.h"
        #include "surface_cache.h"
       +#include "theme.h"
        
        #define MAX_MENU_BORDER_WIDTH 100
        #define MAX_MENU_PAD 500
       t@@ -77,7 +78,6 @@ static struct theme {
                ltk_color fill_disabled;
        } menu_theme, submenu_theme;
        
       -static void ini_handler(ltk_window *window, struct theme *t, const char *prop, const char *value);
        static void ltk_menu_resize(ltk_widget *self);
        static void ltk_menu_change_state(ltk_widget *self);
        static void ltk_menu_draw(ltk_widget *self, ltk_rect clip);
       t@@ -140,189 +140,90 @@ static struct ltk_widget_vtable vtable = {
                .needs_surface = 1
        };
        
       -/* FIXME: maybe just store colors as pointers and check after
       -   ini handling if any are null */
       +static ltk_theme_parseinfo menu_parseinfo[] = {
       +        {"border-width", THEME_INT, {.i = &menu_theme.border_width}, {.i = 2}, 0, MAX_MENU_BORDER_WIDTH, 0},
       +        {"pad", THEME_INT, {.i = &menu_theme.pad}, {.i = 0}, 0, MAX_MENU_PAD, 0},
       +        {"text-pad", THEME_INT, {.i = &menu_theme.text_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0},
       +        {"arrow-size", THEME_INT, {.i = &menu_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0},
       +        {"arrow-pad", THEME_INT, {.i = &menu_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0},
       +        {"compress-borders", THEME_BOOL, {.b = &menu_theme.compress_borders}, {.b = 1}, 0, 0, 0},
       +        {"border-sides", THEME_BORDERSIDES, {.border = &menu_theme.border_sides}, {.border = LTK_BORDER_ALL}, 0, 0, 0},
       +        {"menu-border-width", THEME_INT, {.i = &menu_theme.menu_border_width}, {.i = 0}, 0, MAX_MENU_BORDER_WIDTH, 0},
       +        {"background", THEME_COLOR, {.color = &menu_theme.background}, {.color = "#555555"}, 0, 0, 0},
       +        {"scroll-background", THEME_COLOR, {.color = &menu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0},
       +        {"scroll-arrow-color", THEME_COLOR, {.color = &menu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0},
       +        {"text", THEME_COLOR, {.color = &menu_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"border", THEME_COLOR, {.color = &menu_theme.border}, {.color = "#339999"}, 0, 0, 0},
       +        {"fill", THEME_COLOR, {.color = &menu_theme.fill}, {.color = "#113355"}, 0, 0, 0},
       +        {"text-pressed", THEME_COLOR, {.color = &menu_theme.text_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"border-pressed", THEME_COLOR, {.color = &menu_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"fill-pressed", THEME_COLOR, {.color = &menu_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0},
       +        {"text-active", THEME_COLOR, {.color = &menu_theme.text_active}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"border-active", THEME_COLOR, {.color = &menu_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"fill-active", THEME_COLOR, {.color = &menu_theme.fill_active}, {.color = "#738194"}, 0, 0, 0},
       +        {"text-disabled", THEME_COLOR, {.color = &menu_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"border-disabled", THEME_COLOR, {.color = &menu_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"fill-disabled", THEME_COLOR, {.color = &menu_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
       +        {"menu-border", THEME_COLOR, {.color = &menu_theme.menu_border}, {.color = "#000000"}, 0, 0, 0},
       +};
       +static int menu_parseinfo_sorted = 0;
        
       -void
       -ltk_menu_setup_theme_defaults(ltk_window *window) {
       -        menu_theme.border_width = 2;
       -        menu_theme.pad = 0;
       -        menu_theme.text_pad = 5;
       -        menu_theme.arrow_size = 10;
       -        menu_theme.arrow_pad = 5;
       -        menu_theme.compress_borders = 1;
       -        menu_theme.border_sides = LTK_BORDER_ALL;
       -        menu_theme.menu_border_width = 0;
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#555555", &menu_theme.background);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#333333", &menu_theme.scroll_background);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#000000", &menu_theme.scroll_arrow_color);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &menu_theme.text);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#339999", &menu_theme.border);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#113355", &menu_theme.fill);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &menu_theme.text_pressed);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &menu_theme.border_pressed);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#113355", &menu_theme.fill_pressed);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &menu_theme.text_active);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &menu_theme.border_active);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#738194", &menu_theme.fill_active);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &menu_theme.text_disabled);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &menu_theme.border_disabled);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#292929", &menu_theme.fill_disabled);
       -
       -        /* FIXME: actually unnecessary since border width is 0 */
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#000000", &menu_theme.menu_border);
       -
       -        submenu_theme.border_width = 0;
       -        submenu_theme.pad = 5;
       -        submenu_theme.text_pad = 5;
       -        submenu_theme.arrow_size = 10;
       -        submenu_theme.arrow_pad = 5;
       -        submenu_theme.compress_borders = 0;
       -        submenu_theme.border_sides = LTK_BORDER_NONE;
       -        submenu_theme.menu_border_width = 1;
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#555555", &submenu_theme.background);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#333333", &submenu_theme.scroll_background);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#000000", &submenu_theme.scroll_arrow_color);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &submenu_theme.menu_border);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &submenu_theme.text);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#113355", &submenu_theme.fill);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#000000", &submenu_theme.text_pressed);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#113355", &submenu_theme.fill_pressed);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#000000", &submenu_theme.text_active);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#113355", &submenu_theme.fill_active);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &submenu_theme.text_disabled);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#292929", &submenu_theme.fill_disabled);
       -
       -        /* FIXME: make this unnecessary if border width is 0 */
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &submenu_theme.border);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &submenu_theme.border_pressed);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &submenu_theme.border_active);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#FFFFFF", &submenu_theme.border_disabled);
       +int
       +ltk_menu_ini_handler(ltk_window *window, const char *prop, const char *value) {
       +        return ltk_theme_handle_value(window, "menu", prop, value, menu_parseinfo, LENGTH(menu_parseinfo), &menu_parseinfo_sorted);
        }
        
       -/* FIXME: use border-width, etc. */
       -/* FIXME: make theme parsing more convenient */
       -/* FIXME: DEALLOCATE COLORS INSTEAD OF OVERWRITING DEFAULTS! */
       -static void
       -ini_handler(ltk_window *window, struct theme *t, const char *prop, const char *value) {
       -        const char *errstr;
       -        if (strcmp(prop, "border_width") == 0) {
       -                t->border_width = ltk_strtonum(value, 0, MAX_MENU_BORDER_WIDTH, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid menu border width '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "menu_border_width") == 0) {
       -                t->menu_border_width = ltk_strtonum(value, 0, MAX_MENU_BORDER_WIDTH, &errstr);
       -                /* FIXME: clarify different types of border width in error message */
       -                if (errstr)
       -                        ltk_warn("Invalid menu border width '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "pad") == 0) {
       -                t->pad = ltk_strtonum(value, 0, MAX_MENU_PAD, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid menu pad '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "text_pad") == 0) {
       -                t->text_pad = ltk_strtonum(value, 0, MAX_MENU_PAD, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid menu text pad '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "arrow_size") == 0) {
       -                /* FIXME: should be error when used for menu instead of submenu */
       -                t->arrow_size = ltk_strtonum(value, 0, MAX_MENU_ARROW_SIZE, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid menu arrow size '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "arrow_pad") == 0) {
       -                /* FIXME: should be error when used for menu instead of submenu */
       -                t->arrow_pad = ltk_strtonum(value, 0, MAX_MENU_PAD, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid menu arrow pad '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "compress_borders") == 0) {
       -                if (strcmp(value, "true") == 0)
       -                        t->compress_borders = 1;
       -                else if (strcmp(value, "false") == 0)
       -                        t->compress_borders = 0;
       -                else
       -                        ltk_warn("Invalid menu compress_borders '%s'.\n", value);
       -        } else if (strcmp(prop, "border_sides") == 0) {
       -                t->border_sides = LTK_BORDER_NONE;
       -                for (const char *c = value; *c != '\0'; c++) {
       -                        switch (*c) {
       -                        case 't':
       -                                t->border_sides |= LTK_BORDER_TOP;
       -                                break;
       -                        case 'b':
       -                                t->border_sides |= LTK_BORDER_BOTTOM;
       -                                break;
       -                        case 'l':
       -                                t->border_sides |= LTK_BORDER_LEFT;
       -                                break;
       -                        case 'r':
       -                                t->border_sides |= LTK_BORDER_RIGHT;
       -                                break;
       -                        default:
       -                                ltk_warn("Invalid menu border_sides '%s'.\n", value);
       -                                return;
       -                        }
       -                }
       -        } else if (strcmp(prop, "background") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->background))
       -                        ltk_warn("Error setting menu background color to '%s'.\n", value);
       -        } else if (strcmp(prop, "menu_border") == 0) {
       -                /* FIXME: clarify different type of menu border color */
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->menu_border))
       -                        ltk_warn("Error setting menu border color to '%s'.\n", value);
       -        } else if (strcmp(prop, "scroll_background") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->scroll_background))
       -                        ltk_warn("Error setting menu scroll background color to '%s'.\n", value);
       -        } else if (strcmp(prop, "scroll_arrow_color") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->scroll_arrow_color))
       -                        ltk_warn("Error setting menu scroll arrow color to '%s'.\n", value);
       -        } else if (strcmp(prop, "text") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->text))
       -                        ltk_warn("Error setting menu text color to '%s'.\n", value);
       -        } else if (strcmp(prop, "border") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->border))
       -                        ltk_warn("Error setting menu border color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fill") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->fill))
       -                        ltk_warn("Error setting menu fill color to '%s'.\n", value);
       -        } else if (strcmp(prop, "text_pressed") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->text_pressed))
       -                        ltk_warn("Error setting menu pressed text color to '%s'.\n", value);
       -        } else if (strcmp(prop, "border_pressed") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->border_pressed))
       -                        ltk_warn("Error setting menu pressed border color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fill_pressed") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->fill_pressed))
       -                        ltk_warn("Error setting menu pressed fill color to '%s'.\n", value);
       -        } else if (strcmp(prop, "text_active") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->text_active))
       -                        ltk_warn("Error setting menu active text color to '%s'.\n", value);
       -        } else if (strcmp(prop, "border_active") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->border_active))
       -                        ltk_warn("Error setting menu active border color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fill_active") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->fill_active))
       -                        ltk_warn("Error setting menu active fill color to '%s'.\n", value);
       -        } else if (strcmp(prop, "text_disabled") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->text_disabled))
       -                        ltk_warn("Error setting menu disabled text color to '%s'.\n", value);
       -        } else if (strcmp(prop, "border_disabled") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->border_disabled))
       -                        ltk_warn("Error setting menu disabled border color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fill_disabled") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &t->fill_disabled))
       -                        ltk_warn("Error setting menu disabled fill color to '%s'.\n", value);
       -        } else {
       -                ltk_warn("Unknown property '%s' for button style.\n", prop);
       -        }
       +int
       +ltk_menu_fill_theme_defaults(ltk_window *window) {
       +        return ltk_theme_fill_defaults(window, "menu", menu_parseinfo, LENGTH(menu_parseinfo));
        }
        
        void
       -ltk_menu_ini_handler(ltk_window *window, const char *prop, const char *value) {
       -        ini_handler(window, &menu_theme, prop, value);
       +ltk_menu_uninitialize_theme(ltk_window *window) {
       +        ltk_theme_uninitialize(window, menu_parseinfo, LENGTH(menu_parseinfo));
        }
        
       -void
       +static ltk_theme_parseinfo submenu_parseinfo[] = {
       +        {"border-width", THEME_INT, {.i = &submenu_theme.border_width}, {.i = 0}, 0, MAX_MENU_BORDER_WIDTH, 0},
       +        {"pad", THEME_INT, {.i = &submenu_theme.pad}, {.i = 5}, 0, MAX_MENU_PAD, 0},
       +        {"text-pad", THEME_INT, {.i = &submenu_theme.text_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0},
       +        {"arrow-size", THEME_INT, {.i = &submenu_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0},
       +        {"arrow-pad", THEME_INT, {.i = &submenu_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0},
       +        {"compress-borders", THEME_BOOL, {.b = &submenu_theme.compress_borders}, {.b = 0}, 0, 0, 0},
       +        {"border-sides", THEME_BORDERSIDES, {.border = &submenu_theme.border_sides}, {.border = LTK_BORDER_NONE}, 0, 0, 0},
       +        {"menu-border-width", THEME_INT, {.i = &submenu_theme.menu_border_width}, {.i = 1}, 0, MAX_MENU_BORDER_WIDTH, 0},
       +        {"background", THEME_COLOR, {.color = &submenu_theme.background}, {.color = "#555555"}, 0, 0, 0},
       +        {"scroll-background", THEME_COLOR, {.color = &submenu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0},
       +        {"scroll-arrow-color", THEME_COLOR, {.color = &submenu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0},
       +        {"text", THEME_COLOR, {.color = &submenu_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"border", THEME_COLOR, {.color = &submenu_theme.border}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"fill", THEME_COLOR, {.color = &submenu_theme.fill}, {.color = "#113355"}, 0, 0, 0},
       +        {"text-pressed", THEME_COLOR, {.color = &submenu_theme.text_pressed}, {.color = "#000000"}, 0, 0, 0},
       +        {"border-pressed", THEME_COLOR, {.color = &submenu_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"fill-pressed", THEME_COLOR, {.color = &submenu_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0},
       +        {"text-active", THEME_COLOR, {.color = &submenu_theme.text_active}, {.color = "#000000"}, 0, 0, 0},
       +        {"border-active", THEME_COLOR, {.color = &submenu_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"fill-active", THEME_COLOR, {.color = &submenu_theme.fill_active}, {.color = "#113355"}, 0, 0, 0},
       +        {"text-disabled", THEME_COLOR, {.color = &submenu_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"border-disabled", THEME_COLOR, {.color = &submenu_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"fill-disabled", THEME_COLOR, {.color = &submenu_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
       +        {"menu-border", THEME_COLOR, {.color = &submenu_theme.menu_border}, {.color = "#FFFFFF"}, 0, 0, 0},
       +};
       +static int submenu_parseinfo_sorted = 0;
       +
       +int
        ltk_submenu_ini_handler(ltk_window *window, const char *prop, const char *value) {
       -        ini_handler(window, &submenu_theme, prop, value);
       +        return ltk_theme_handle_value(window, "submenu", prop, value, submenu_parseinfo, LENGTH(submenu_parseinfo), &submenu_parseinfo_sorted);
       +}
       +
       +int
       +ltk_submenu_fill_theme_defaults(ltk_window *window) {
       +        return ltk_theme_fill_defaults(window, "submenu", submenu_parseinfo, LENGTH(submenu_parseinfo));
       +}
       +
       +void
       +ltk_submenu_uninitialize_theme(ltk_window *window) {
       +        ltk_theme_uninitialize(window, submenu_parseinfo, LENGTH(submenu_parseinfo));
        }
        
        static void
       t@@ -1018,7 +919,7 @@ ltk_menu_insert_entry(ltk_menu *menu, const char *id, const char *text, ltk_menu
                e->id = ltk_strdup(id);
                ltk_window *w = menu->widget.window;
                /* FIXME: pass const text */
       -        e->text = ltk_text_line_create(w->text_context, w->theme.font_size, (char *)text, 0, -1);
       +        e->text = ltk_text_line_create(w->text_context, w->theme->font_size, (char *)text, 0, -1);
                e->submenu = submenu;
                if (submenu)
                        submenu->widget.parent = (ltk_widget *)menu;
   DIR diff --git a/src/menu.h b/src/menu.h
       t@@ -50,9 +50,12 @@ struct ltk_menuentry {
                int disabled;
        };
        
       -void ltk_menu_setup_theme_defaults(ltk_window *window);
       -void ltk_menu_ini_handler(ltk_window *window, const char *prop, const char *value);
       -void ltk_submenu_ini_handler(ltk_window *window, const char *prop, const char *value);
       +int ltk_menu_ini_handler(ltk_window *window, const char *prop, const char *value);
       +int ltk_menu_fill_theme_defaults(ltk_window *window);
       +void ltk_menu_uninitialize_theme(ltk_window *window);
       +int ltk_submenu_ini_handler(ltk_window *window, const char *prop, const char *value);
       +int ltk_submenu_fill_theme_defaults(ltk_window *window);
       +void ltk_submenu_uninitialize_theme(ltk_window *window);
        
        int ltk_menu_cmd(
                ltk_window *window,
   DIR diff --git a/src/scrollbar.c b/src/scrollbar.c
       t@@ -31,6 +31,7 @@
        #include "ltk.h"
        #include "util.h"
        #include "scrollbar.h"
       +#include "theme.h"
        
        #define MAX_SCROLLBAR_WIDTH 100 /* completely arbitrary */
        
       t@@ -67,47 +68,30 @@ static struct {
                ltk_color fg_disabled;
        } theme;
        
       -void
       -ltk_scrollbar_setup_theme_defaults(ltk_window *window) {
       -        theme.size = 15;
       -        /* FIXME: error checking - but if these fail, there is probably a bigger
       -           problem, so it might be best to just die completely */
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#000000", &theme.bg_normal);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#555555", &theme.bg_disabled);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#113355", &theme.fg_normal);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#738194", &theme.fg_active);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#113355", &theme.fg_pressed);
       -        ltk_color_create(window->dpy, window->screen, window->cm, "#292929", &theme.fg_disabled);
       +static ltk_theme_parseinfo parseinfo[] = {
       +        {"size", THEME_INT, {.i = &theme.size}, {.i = 15}, 0, MAX_SCROLLBAR_WIDTH, 0},
       +        {"bg", THEME_COLOR, {.color = &theme.bg_normal}, {.color = "#000000"}, 0, 0, 0},
       +        {"bg-disabled", THEME_COLOR, {.color = &theme.bg_disabled}, {.color = "#555555"}, 0, 0, 0},
       +        {"fg", THEME_COLOR, {.color = &theme.fg_normal}, {.color = "#113355"}, 0, 0, 0},
       +        {"fg-active", THEME_COLOR, {.color = &theme.fg_active}, {.color = "#738194"}, 0, 0, 0},
       +        {"fg-pressed", THEME_COLOR, {.color = &theme.fg_pressed}, {.color = "#113355"}, 0, 0, 0},
       +        {"fg-disabled", THEME_COLOR, {.color = &theme.fg_disabled}, {.color = "#292929"}, 0, 0, 0},
       +};
       +static int parseinfo_sorted = 0;
       +
       +int
       +ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *value) {
       +        return ltk_theme_handle_value(window, "scrollbar", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
       +}
       +
       +int
       +ltk_scrollbar_fill_theme_defaults(ltk_window *window) {
       +        return ltk_theme_fill_defaults(window, "scrollbar", parseinfo, LENGTH(parseinfo));
        }
        
        void
       -ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *value) {
       -        const char *errstr;
       -        if (strcmp(prop, "size") == 0) {
       -                theme.size = ltk_strtonum(value, 1, MAX_SCROLLBAR_WIDTH, &errstr);
       -                if (errstr)
       -                        ltk_warn("Invalid scrollbar size '%s': %s.\n", value, errstr);
       -        } else if (strcmp(prop, "bg") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.bg_normal))
       -                        ltk_warn("Error setting scrollbar background color to '%s'.\n", value);
       -        } else if (strcmp(prop, "bg_disabled") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.bg_disabled))
       -                        ltk_warn("Error setting scrollbar disabled background color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fg") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.fg_normal))
       -                        ltk_warn("Error setting scrollbar foreground color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fg_active") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.fg_active))
       -                        ltk_warn("Error setting scrollbar active foreground color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fg_pressed") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.fg_pressed))
       -                        ltk_warn("Error setting scrollbar pressed foreground color to '%s'.\n", value);
       -        } else if (strcmp(prop, "fg_disabled") == 0) {
       -                if (ltk_color_create(window->dpy, window->screen, window->cm, value, &theme.fg_disabled))
       -                        ltk_warn("Error setting scrollbar disabled foreground color to '%s'.\n", value);
       -        } else {
       -                ltk_warn("Unknown property '%s' for scrollbar style.\n", prop);
       -        }
       +ltk_scrollbar_uninitialize_theme(ltk_window *window) {
       +        ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo));
        }
        
        void
   DIR diff --git a/src/scrollbar.h b/src/scrollbar.h
       t@@ -31,9 +31,11 @@ typedef struct {
        } ltk_scrollbar;
        
        void ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size);
       -void ltk_scrollbar_setup_theme_defaults(ltk_window *window);
       -void ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *value);
        ltk_scrollbar *ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback)(ltk_widget *), void *data);
        void ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled);
        
       +int ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *value);
       +int ltk_scrollbar_fill_theme_defaults(ltk_window *window);
       +void ltk_scrollbar_uninitialize_theme(ltk_window *window);
       +
        #endif /* _LTK_SCROLLBAR_H_ */
   DIR diff --git a/src/text.h b/src/text.h
       t@@ -30,6 +30,7 @@ ltk_text_context *ltk_text_context_create(ltk_window *window, char *default_font
        void ltk_text_context_destroy(ltk_text_context *ctx);
        
        /* FIXME: allow to give length of text */
       +/* FIXME: uint16_t as size is kind of ugly (also see window theme) */
        ltk_text_line *ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, char *text, int take_over_text, int width);
        void ltk_text_line_set_width(ltk_text_line *tl, int width);
        void ltk_text_line_get_size(ltk_text_line *tl, int *w, int *h);
   DIR diff --git a/src/theme.c b/src/theme.c
       t@@ -0,0 +1,161 @@
       +#include "util.h"
       +#include "theme.h"
       +#include "memory.h"
       +
       +/* FIXME: handle '#' or no '#' in color specification */
       +static int
       +search_helper(const void *keyv, const void *entryv) {
       +        char *key = (char *)keyv;
       +        ltk_theme_parseinfo *entry = (ltk_theme_parseinfo *)entryv;
       +        return strcmp(key, entry->key);
       +}
       +
       +static int
       +sort_helper(const void *entry1v, const void *entry2v) {
       +        ltk_theme_parseinfo *entry1 = (ltk_theme_parseinfo *)entry1v;
       +        ltk_theme_parseinfo *entry2 = (ltk_theme_parseinfo *)entry2v;
       +        return strcmp(entry1->key, entry2->key);
       +}
       +
       +/* FIXME: more information for errors */
       +int
       +ltk_theme_handle_value(ltk_window *window, char *debug_name, const char *prop, const char *value, ltk_theme_parseinfo *parseinfo, size_t len, int *sorted) {
       +        if (!*sorted) {
       +                qsort(parseinfo, len, sizeof(ltk_theme_parseinfo), &sort_helper);
       +                *sorted = 1;
       +        }
       +        ltk_theme_parseinfo *entry = bsearch(prop, parseinfo, len, sizeof(ltk_theme_parseinfo), &search_helper);
       +        if (!entry) {
       +                ltk_warn("Invalid property '%s:%s'.\n", debug_name, prop);
       +                return 1;
       +        } else if (entry->initialized) {
       +                ltk_warn("Duplicate setting for property '%s:%s'.\n", debug_name, prop);
       +                return 1;
       +        }
       +        const char *errstr = NULL;
       +        switch (entry->type) {
       +        case THEME_INT:
       +                *(entry->ptr.i) = ltk_strtonum(value, entry->min, entry->max, &errstr);
       +                if (errstr) {
       +                        ltk_warn("Invalid value '%s' for property '%s:%s'.\n", value, debug_name, prop);
       +                        return 1;
       +                } else {
       +                        entry->initialized = 1;
       +                }
       +                break;
       +        case THEME_STRING:
       +                *(entry->ptr.str) = ltk_strdup(value);
       +                entry->initialized = 1;
       +                break;
       +        case THEME_COLOR:
       +                if (ltk_color_create(window->dpy, window->vis, window->cm, value, entry->ptr.color)) {
       +                        ltk_warn("Unable to create color '%s' for property '%s:%s'.\n", value, debug_name, prop);
       +                        return 1;
       +                } else {
       +                        entry->initialized = 1;
       +                }
       +                break;
       +        case THEME_BOOL:
       +                if (strcmp(value, "true") == 0) {
       +                        *(entry->ptr.b) = 1;
       +                } else if (strcmp(value, "false") == 0) {
       +                        *(entry->ptr.b) = 0;
       +                } else {
       +                        ltk_warn("Invalid value '%s' for property '%s:%s'.\n", value, debug_name, prop);
       +                        return 1;
       +                }
       +                entry->initialized = 1;
       +                break;
       +        case THEME_BORDERSIDES:
       +                *(entry->ptr.border) = LTK_BORDER_NONE;
       +                for (const char *c = value; *c != '\0'; c++) {
       +                        switch (*c) {
       +                        case 't':
       +                                *(entry->ptr.border) |= LTK_BORDER_TOP;
       +                                break;
       +                        case 'b':
       +                                *(entry->ptr.border) |= LTK_BORDER_BOTTOM;
       +                                break;
       +                        case 'l':
       +                                *(entry->ptr.border) |= LTK_BORDER_LEFT;
       +                                break;
       +                        case 'r':
       +                                *(entry->ptr.border) |= LTK_BORDER_RIGHT;
       +                                break;
       +                        default:
       +                                ltk_warn("Invalid value '%s' for property '%s:%s'.\n", value, debug_name, prop);
       +                                return 1;
       +                        }
       +                }
       +                entry->initialized = 1;
       +                break;
       +        default:
       +                ltk_fatal("Invalid theme setting type. This should not happen.\n");
       +                /* TODO: ltk_assert(0); */
       +        }
       +        return 0;
       +}
       +
       +int
       +ltk_theme_fill_defaults(ltk_window *window, char *debug_name, ltk_theme_parseinfo *parseinfo, size_t len) {
       +        for (size_t i = 0; i < len; i++) {
       +                ltk_theme_parseinfo *e = &parseinfo[i];
       +                switch (e->type) {
       +                case THEME_INT:
       +                        *(e->ptr.i) = e->defaultval.i;
       +                        e->initialized = 1;
       +                        break;
       +                case THEME_STRING:
       +                        *(e->ptr.str) = ltk_strdup(e->defaultval.str);
       +                        e->initialized = 1;
       +                        break;
       +                case THEME_COLOR:
       +                        if (ltk_color_create(window->dpy, window->vis, window->cm, e->defaultval.color, e->ptr.color)) {
       +                                ltk_warn("Unable to create default color '%s' for property '%s:%s'.\n", e->defaultval.color, debug_name, e->key);
       +                                return 1;
       +                        } else {
       +                                e->initialized = 1;
       +                        }
       +                        break;
       +                case THEME_BOOL:
       +                        *(e->ptr.b) = e->defaultval.b;
       +                        e->initialized = 1;
       +                        break;
       +                case THEME_BORDERSIDES:
       +                        *(e->ptr.border) = e->defaultval.border;
       +                        e->initialized = 1;
       +                        break;
       +                default:
       +                        ltk_fatal("Invalid theme setting type. This should not happen.\n");
       +                        /* TODO: ltk_assert(0); */
       +                }
       +        }
       +        return 0;
       +}
       +
       +void
       +ltk_theme_uninitialize(ltk_window *window, ltk_theme_parseinfo *parseinfo, size_t len) {
       +        for (size_t i = 0; i < len; i++) {
       +                ltk_theme_parseinfo *e = &parseinfo[i];
       +                if (!e->initialized)
       +                        continue;
       +                switch (e->type) {
       +                case THEME_STRING:
       +                        free(*(e->ptr.str));
       +                        e->initialized = 0;
       +                        break;
       +                case THEME_COLOR:
       +                        ltk_color_destroy(window->dpy, window->vis, window->cm, e->ptr.color);
       +                        e->initialized = 0;
       +                        break;
       +                case THEME_INT:
       +                case THEME_BOOL:
       +                case THEME_BORDERSIDES:
       +                        e->initialized = 0;
       +                        break;
       +                default:
       +                        ltk_fatal("Invalid theme setting type. This should not happen.\n");
       +                        /* TODO: ltk_assert(0); */
       +                }
       +        }
       +}
   DIR diff --git a/src/theme.h b/src/theme.h
       t@@ -0,0 +1,48 @@
       +#ifndef _LTK_THEME_H_
       +#define _LTK_THEME_H_
       +
       +#include "ltk.h"
       +
       +typedef enum {
       +        THEME_STRING,
       +        THEME_COLOR,
       +        THEME_INT,
       +        THEME_BOOL,
       +        THEME_BORDERSIDES
       +} ltk_theme_datatype;
       +
       +typedef struct {
       +        char *key;
       +        ltk_theme_datatype type;
       +        /* Note: Bool and int are both integers, but they are
       +           separate just to make it a bit clearer */
       +        union {
       +                char **str;
       +                ltk_color *color;
       +                int *i;
       +                int *b;
       +                ltk_border_sides *border;
       +        } ptr;
       +        /* Note: The default color is also given as a string
       +           because it has to be allocated first (it is only a
       +           different entry in the union in order to make it
       +           a bit clearer) */
       +        union {
       +                char *str;
       +                char *color;
       +                int i;
       +                int b;
       +                ltk_border_sides border;
       +        } defaultval;
       +        int min, max; /* only for integers */
       +        int initialized;
       +} ltk_theme_parseinfo;
       +
       +/* Both return 1 on error, 0 on success */
       +int ltk_theme_handle_value(ltk_window *window, char *debug_name, const char *prop, const char *value, ltk_theme_parseinfo *parseinfo, size_t len, int *sorted);
       +int ltk_theme_fill_defaults(ltk_window *window, char *debug_name, ltk_theme_parseinfo *parseinfo, size_t len);
       +void ltk_theme_uninitialize(ltk_window *window, ltk_theme_parseinfo *parseinfo, size_t len);
       +
       +#define LENGTH(X) (sizeof(X) / sizeof(X[0]))
       +
       +#endif /* _LTK_THEME_H_ */
   DIR diff --git a/src/util.h b/src/util.h
       t@@ -14,7 +14,10 @@
         * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
         */
        
       -/* Requires: <stdarg.h> */
       +#ifndef _LTK_UTIL_H_
       +#define _LTK_UTIL_H_
       +
       +#include <stdarg.h>
        
        long long ltk_strtonum(
            const char *numstr, long long minval,
       t@@ -35,3 +38,5 @@ void ltk_fatal_errno(const char *format, ...);
        void ltk_warn_errno(const char *format, ...);
        void ltk_fatal(const char *format, ...);
        void ltk_warn(const char *format, ...);
       +
       +#endif /* _LTK_UTIL_H_ */