URI: 
       Implement theme inheritance - ltk - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ltk.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 07a20345dc4d1ff7a311dc1359c36394dfbc3769
   DIR parent b44497a0e20b249923f01284f85163b61b7e7609
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu,  2 May 2024 22:03:08 +0200
       
       Implement theme inheritance
       
       Diffstat:
         M src/ltk/button.c                    |      14 +++++++++-----
         M src/ltk/button.h                    |       2 +-
         M src/ltk/color.h                     |       1 +
         M src/ltk/color_xlib.c                |      19 +++++++++++++++++++
         M src/ltk/config.c                    |     145 +++++++++++++++++++------------
         M src/ltk/entry.c                     |      13 +++++++++----
         M src/ltk/entry.h                     |       2 +-
         M src/ltk/label.c                     |      13 +++++++++----
         M src/ltk/label.h                     |       2 +-
         M src/ltk/ltk.c                       |      12 ++++++++----
         M src/ltk/ltk.h                       |       3 ++-
         M src/ltk/menu.c                      |      39 +++++++++++++++++++++++++------
         M src/ltk/text.h                      |      10 ++++++----
         M src/ltk/text_pango.c                |      39 +++++++++++++++++++++++--------
         M src/ltk/window.c                    |      18 +++++++++---------
         M src/ltk/window.h                    |      11 -----------
       
       16 files changed, 226 insertions(+), 117 deletions(-)
       ---
   DIR diff --git a/src/ltk/button.c b/src/ltk/button.c
       @@ -32,7 +32,6 @@
        
        static void ltk_button_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
        static int ltk_button_release(ltk_widget *self);
       -ltk_button *ltk_button_create(ltk_window *window, char *text);
        static void ltk_button_destroy(ltk_widget *self, int shallow);
        static void ltk_button_recalc_ideal_size(ltk_widget *self);
        
       @@ -77,8 +76,10 @@ static struct {
                ltk_color *border_disabled;
                ltk_color *fill_disabled;
        
       +        char *font;
                ltk_size border_width;
                ltk_size pad;
       +        ltk_size font_size;
        } theme;
        
        static ltk_theme_parseinfo parseinfo[] = {
       @@ -88,6 +89,7 @@ static ltk_theme_parseinfo parseinfo[] = {
                {"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_SIZE, {.size = &theme.border_width}, {.size = {.val = 50, .unit = LTK_UNIT_MM}}, 0, MAX_BUTTON_BORDER_WIDTH, 0},
       +        {"font-size", THEME_SIZE, {.size = &theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0},
                {"fill", THEME_COLOR, {.color = &theme.fill}, {.color = "#113355"}, 0, 0, 0},
                {"fill-hover", THEME_COLOR, {.color = &theme.fill_hover}, {.color = "#738194"}, 0, 0, 0},
                {"fill-active", THEME_COLOR, {.color = &theme.fill_active}, {.color = "#113355"}, 0, 0, 0},
       @@ -95,6 +97,7 @@ static ltk_theme_parseinfo parseinfo[] = {
                {"fill-pressed", THEME_COLOR, {.color = &theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0},
                {"pad", THEME_SIZE, {.size = &theme.pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_BUTTON_PADDING, 0},
                {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"font", THEME_STRING, {.str = &theme.font}, {.str = "Monospace"}, 0, 0, 0},
        };
        
        void
       @@ -169,18 +172,19 @@ recalc_ideal_size(ltk_button *button) {
        static void
        ltk_button_recalc_ideal_size(ltk_widget *self) {
                ltk_button *button = LTK_CAST_BUTTON(self);
       -        int font_size = ltk_size_to_pixel(self->window->theme->font_size, self->last_dpi);
       +        int font_size = ltk_size_to_pixel(theme.font_size, self->last_dpi);
                ltk_text_line_set_font_size(button->tl, font_size);
                recalc_ideal_size(button);
        }
        
        ltk_button *
       -ltk_button_create(ltk_window *window, char *text) {
       +ltk_button_create(ltk_window *window, const char *text) {
                ltk_button *button = ltk_malloc(sizeof(ltk_button));
                ltk_fill_widget_defaults(LTK_CAST_WIDGET(button), window, &vtable, 0, 0);
        
       -        ltk_size font_size = window->theme->font_size;
       -        button->tl = ltk_text_line_create_default(ltk_size_to_pixel(font_size, button->widget.last_dpi), text, 0, -1);
       +        button->tl = ltk_text_line_create_const_text_default(
       +                theme.font, ltk_size_to_pixel(theme.font_size, button->widget.last_dpi), text, -1
       +        );
                recalc_ideal_size(button);
                button->widget.dirty = 1;
        
   DIR diff --git a/src/ltk/button.h b/src/ltk/button.h
       @@ -30,6 +30,6 @@ typedef struct {
                ltk_text_line *tl;
        } ltk_button;
        
       -ltk_button *ltk_button_create(ltk_window *window, char *text);
       +ltk_button *ltk_button_create(ltk_window *window, const char *text);
        
        #endif /* LTK_BUTTON_H */
   DIR diff --git a/src/ltk/color.h b/src/ltk/color.h
       @@ -22,6 +22,7 @@ typedef struct ltk_color ltk_color;
        #include "graphics.h"
        
        ltk_color *ltk_color_create(ltk_renderdata *renderdata, const char *hex);
       +ltk_color *ltk_color_copy(ltk_renderdata *renderdata, const ltk_color *color);
        void ltk_color_destroy(ltk_renderdata *renderdata, ltk_color *col);
        
        #endif /* LTK_COLOR_H */
   DIR diff --git a/src/ltk/color_xlib.c b/src/ltk/color_xlib.c
       @@ -46,6 +46,25 @@ ltk_color_create(ltk_renderdata *renderdata, const char *hex) {
                return col;
        }
        
       +/* FIXME: does this actually work the way I think it does? */
       +ltk_color *
       +ltk_color_copy(ltk_renderdata *renderdata, const ltk_color *color) {
       +        ltk_color *col = ltk_malloc(sizeof(ltk_color));
       +        col->xcolor = color->xcolor;
       +        if (!XAllocColor(renderdata->dpy, renderdata->cm, &col->xcolor)) {
       +                ltk_free(col);
       +                return NULL;
       +        }
       +        #if USE_XFT == 1
       +        if (!XftColorAllocValue(renderdata->dpy, renderdata->vis, renderdata->cm, &color->xftcolor.color, &col->xftcolor)) {
       +                XFreeColors(renderdata->dpy, renderdata->cm, &col->xcolor.pixel, 1, 0);
       +                ltk_free(col);
       +                return NULL;
       +        }
       +        #endif
       +        return col;
       +}
       +
        void
        ltk_color_destroy(ltk_renderdata *renderdata, ltk_color *col) {
                /* FIXME: what should the 'planes' argument be? */
   DIR diff --git a/src/ltk/config.c b/src/ltk/config.c
       @@ -72,17 +72,18 @@ static struct theme_handlerinfo {
                const char *name;
                void (*get_parseinfo)(ltk_theme_parseinfo **parseinfo, size_t *len);
                const char *parent;
       +        int finished;
        } theme_handlers[] = {
       -        {"general", &ltk_general_get_theme_parseinfo, NULL},
       -        {"theme:window", &ltk_window_get_theme_parseinfo, NULL},
       -        {"theme:button", &ltk_button_get_theme_parseinfo, "window"},
       -        {"theme:entry", &ltk_entry_get_theme_parseinfo, "window"},
       -        {"theme:label", &ltk_label_get_theme_parseinfo, "window"},
       -        {"theme:scrollbar", &ltk_scrollbar_get_theme_parseinfo, "window"},
       -        {"theme:menu", &ltk_menu_get_theme_parseinfo, "window"},
       -        {"theme:menuentry", &ltk_menuentry_get_theme_parseinfo, "window"},
       -        {"theme:submenu", &ltk_submenu_get_theme_parseinfo, "window"},
       -        {"theme:submenuentry", &ltk_submenuentry_get_theme_parseinfo, "window"},
       +        {"general", &ltk_general_get_theme_parseinfo, NULL, 0},
       +        {"theme:window", &ltk_window_get_theme_parseinfo, NULL, 0},
       +        {"theme:button", &ltk_button_get_theme_parseinfo, "theme:window", 0},
       +        {"theme:entry", &ltk_entry_get_theme_parseinfo, "theme:window", 0},
       +        {"theme:label", &ltk_label_get_theme_parseinfo, "theme:window", 0},
       +        {"theme:scrollbar", &ltk_scrollbar_get_theme_parseinfo, "theme:window", 0},
       +        {"theme:menu", &ltk_menu_get_theme_parseinfo, "theme:window", 0},
       +        {"theme:menuentry", &ltk_menuentry_get_theme_parseinfo, "theme:window", 0},
       +        {"theme:submenu", &ltk_submenu_get_theme_parseinfo, "theme:window", 0},
       +        {"theme:submenuentry", &ltk_submenuentry_get_theme_parseinfo, "theme:window", 0},
        };
        
        GEN_SORT_SEARCH_HELPERS(themehandler, struct theme_handlerinfo, name)
       @@ -206,57 +207,90 @@ handle_theme_setting(ltk_renderdata *renderdata, ltk_theme_parseinfo *entry, con
        }
        
        static int
       -fill_theme_defaults(ltk_renderdata *renderdata) {
       +fill_single_theme_defaults(ltk_renderdata *renderdata, struct theme_handlerinfo *handler) {
                ltk_theme_parseinfo *parseinfo;
                size_t len;
       -        for (size_t j = 0; j < LENGTH(theme_handlers); j++) {
       -                theme_handlers[j].get_parseinfo(&parseinfo, &len);
       -                for (size_t i = 0; i < len; i++) {
       -                        ltk_theme_parseinfo *e = &parseinfo[i];
       -                        if (e->initialized)
       -                                continue;
       -                        switch (e->type) {
       -                        case THEME_INT:
       -                                *(e->ptr.i) = e->defaultval.i;
       -                                e->initialized = 1;
       -                                break;
       -                        case THEME_UINT:
       -                                *(e->ptr.u) = e->defaultval.u;
       -                                e->initialized = 1;
       -                                break;
       -                        case THEME_DOUBLE:
       -                                *(e->ptr.d) = e->defaultval.d;
       -                                e->initialized = 1;
       -                                break;
       -                        case THEME_SIZE:
       -                                *(e->ptr.size) = e->defaultval.size;
       -                                e->initialized = 1;
       -                                break;
       -                        case THEME_STRING:
       -                                if (e->defaultval.str)
       -                                        *(e->ptr.str) = ltk_strdup(e->defaultval.str);
       -                                else
       -                                        *(e->ptr.str) = NULL;
       -                                e->initialized = 1;
       -                                break;
       -                        case THEME_COLOR:
       -                                if (!(*(e->ptr.color) = ltk_color_create(renderdata, e->defaultval.color)))
       +        if (handler->finished)
       +                return 0;
       +        handler->get_parseinfo(&parseinfo, &len);
       +        ltk_theme_parseinfo *parent_parseinfo = NULL;
       +        size_t parent_len = 0;
       +        if (handler->parent) {
       +                struct theme_handlerinfo *parent_handler = themehandler_get_entry(
       +                        theme_handlers, LENGTH(theme_handlers), handler->parent, strlen(handler->parent)
       +                );
       +                /* Yes, this will cause an infinite loop if there's a cycle in the parent
       +                   relationship. However, there is absolutely no reason to construct such a cycle. */
       +                /* FIXME: warning if not found */
       +                if (parent_handler) {
       +                        if (fill_single_theme_defaults(renderdata, parent_handler))
       +                                return 1;
       +                        parent_handler->get_parseinfo(&parent_parseinfo, &parent_len);
       +                }
       +        }
       +        for (size_t i = 0; i < len; i++) {
       +                ltk_theme_parseinfo *e = &parseinfo[i];
       +                if (e->initialized)
       +                        continue;
       +                ltk_theme_parseinfo *ep = parent_parseinfo ?
       +                        theme_get_entry(parent_parseinfo, parent_len, e->key, strlen(e->key)) : NULL;
       +                switch (e->type) {
       +                case THEME_INT:
       +                        *(e->ptr.i) = ep ? *(ep->ptr.i) : e->defaultval.i;
       +                        e->initialized = 1;
       +                        break;
       +                case THEME_UINT:
       +                        *(e->ptr.u) = ep ? *(ep->ptr.u) : e->defaultval.u;
       +                        e->initialized = 1;
       +                        break;
       +                case THEME_DOUBLE:
       +                        *(e->ptr.d) = ep ? *(ep->ptr.d) : e->defaultval.d;
       +                        e->initialized = 1;
       +                        break;
       +                case THEME_SIZE:
       +                        *(e->ptr.size) = ep ? *(ep->ptr.size) : e->defaultval.size;
       +                        e->initialized = 1;
       +                        break;
       +                case THEME_STRING:
       +                        if (ep)
       +                                *(e->ptr.str) = *(ep->ptr.str) ? ltk_strdup(*(ep->ptr.str)) : NULL;
       +                        else if (e->defaultval.str)
       +                                *(e->ptr.str) = ltk_strdup(e->defaultval.str);
       +                        else
       +                                *(e->ptr.str) = NULL;
       +                        e->initialized = 1;
       +                        break;
       +                case THEME_COLOR:
       +                        if (ep) {
       +                                if (!(*(e->ptr.color) = ltk_color_copy(renderdata, *(ep->ptr.color))))
                                                return 1;
       -                                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");
       +                        } else if (!(*(e->ptr.color) = ltk_color_create(renderdata, e->defaultval.color))) {
       +                                return 1;
                                }
       +                        e->initialized = 1;
       +                        break;
       +                case THEME_BOOL:
       +                        *(e->ptr.b) = ep ? *(ep->ptr.b) : e->defaultval.b;
       +                        e->initialized = 1;
       +                        break;
       +                case THEME_BORDERSIDES:
       +                        *(e->ptr.border) = ep ? *(ep->ptr.border) : e->defaultval.border;
       +                        e->initialized = 1;
       +                        break;
       +                default:
       +                        ltk_fatal("Invalid theme setting type. This should not happen.\n");
                        }
                }
       +        handler->finished = 1;
       +        return 0;
       +}
       +
       +static int
       +fill_theme_defaults(ltk_renderdata *renderdata) {
       +        for (size_t j = 0; j < LENGTH(theme_handlers); j++) {
       +                if (fill_single_theme_defaults(renderdata, &theme_handlers[j]))
       +                        return 1;
       +        }
                return 0;
        }
        
       @@ -266,6 +300,7 @@ uninitialize_theme(ltk_renderdata *renderdata) {
                size_t len;
                for (size_t j = 0; j < LENGTH(theme_handlers); j++) {
                        theme_handlers[j].get_parseinfo(&parseinfo, &len);
       +                theme_handlers[j].finished = 0;
                        for (size_t i = 0; i < len; i++) {
                                ltk_theme_parseinfo *e = &parseinfo[i];
                                if (!e->initialized)
   DIR diff --git a/src/ltk/entry.c b/src/ltk/entry.c
       @@ -168,8 +168,10 @@ static struct {
                ltk_color *border_disabled;
                ltk_color *fill_disabled;
        
       +        char *font;
                ltk_size border_width;
                ltk_size pad;
       +        ltk_size font_size;
        } theme;
        
        /* FIXME:
       @@ -191,6 +193,8 @@ static ltk_theme_parseinfo parseinfo[] = {
                {"pad", THEME_SIZE, {.size = &theme.pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_ENTRY_PADDING, 0},
                {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"selection-color", THEME_COLOR, {.color = &theme.selection_color}, {.color = "#000000"}, 0, 0, 0},
       +        {"font-size", THEME_SIZE, {.size = &theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0},
       +        {"font", THEME_STRING, {.str = &theme.font}, {.str = "Monospace"}, 0, 0, 0},
        };
        
        void
       @@ -747,18 +751,19 @@ recalc_ideal_size(ltk_entry *entry) {
        static void
        ltk_entry_recalc_ideal_size(ltk_widget *self) {
                ltk_entry *entry = LTK_CAST_ENTRY(self);
       -        int font_size = ltk_size_to_pixel(self->window->theme->font_size, self->last_dpi);
       +        int font_size = ltk_size_to_pixel(theme.font_size, self->last_dpi);
                ltk_text_line_set_font_size(entry->tl, font_size);
                recalc_ideal_size(entry);
        }
        
        ltk_entry *
       -ltk_entry_create(ltk_window *window, char *text) {
       +ltk_entry_create(ltk_window *window, const char *text) {
                ltk_entry *entry = ltk_malloc(sizeof(ltk_entry));
                ltk_fill_widget_defaults(LTK_CAST_WIDGET(entry), window, &vtable, 0, 0);
        
       -        ltk_size font_size = window->theme->font_size;
       -        entry->tl = ltk_text_line_create_default(ltk_size_to_pixel(font_size, entry->widget.last_dpi), text, 0, -1);
       +        entry->tl = ltk_text_line_create_const_text_default(
       +                theme.font, ltk_size_to_pixel(theme.font_size, entry->widget.last_dpi), text, -1
       +        );
                recalc_ideal_size(entry);
        
                entry->cur_offset = 0;
   DIR diff --git a/src/ltk/entry.h b/src/ltk/entry.h
       @@ -38,6 +38,6 @@ typedef struct {
                char selecting;
        } ltk_entry;
        
       -ltk_entry *ltk_entry_create(ltk_window *window, char *text);
       +ltk_entry *ltk_entry_create(ltk_window *window, const char *text);
        
        #endif /* LTK_ENTRY_H */
   DIR diff --git a/src/ltk/label.c b/src/ltk/label.c
       @@ -59,7 +59,9 @@ static struct {
                ltk_color *text_color;
                ltk_color *bg_color;
                ltk_color *bg_color_active;
       +        char *font;
                ltk_size pad;
       +        ltk_size font_size;
        } theme;
        
        static ltk_theme_parseinfo parseinfo[] = {
       @@ -67,6 +69,8 @@ static ltk_theme_parseinfo parseinfo[] = {
                {"bg-color-active", THEME_COLOR, {.color = &theme.bg_color_active}, {.color = "#222222"}, 0, 0, 0},
                {"pad", THEME_SIZE, {.size = &theme.pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_LABEL_PADDING, 0},
                {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
       +        {"font-size", THEME_SIZE, {.size = &theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0},
       +        {"font", THEME_STRING, {.str = &theme.font}, {.str = "Monospace"}, 0, 0, 0},
        };
        
        void
       @@ -104,18 +108,19 @@ recalc_ideal_size(ltk_label *label) {
        static void
        ltk_label_recalc_ideal_size(ltk_widget *self) {
                ltk_label *label = LTK_CAST_LABEL(self);
       -        int font_size = ltk_size_to_pixel(self->window->theme->font_size, self->last_dpi);
       +        int font_size = ltk_size_to_pixel(theme.font_size, self->last_dpi);
                ltk_text_line_set_font_size(label->tl, font_size);
                recalc_ideal_size(label);
        }
        
        ltk_label *
       -ltk_label_create(ltk_window *window, char *text) {
       +ltk_label_create(ltk_window *window, const char *text) {
                ltk_label *label = ltk_malloc(sizeof(ltk_label));
        
                ltk_fill_widget_defaults(LTK_CAST_WIDGET(label), window, &vtable, 0, 0);
       -        ltk_size font_size = window->theme->font_size;
       -        label->tl = ltk_text_line_create_default(ltk_size_to_pixel(font_size, label->widget.last_dpi), text, 0, -1);
       +        label->tl = ltk_text_line_create_const_text_default(
       +                theme.font, ltk_size_to_pixel(theme.font_size, label->widget.last_dpi), text, -1
       +        );
                recalc_ideal_size(label);
        
                return label;
   DIR diff --git a/src/ltk/label.h b/src/ltk/label.h
       @@ -29,6 +29,6 @@ typedef struct {
                ltk_text_line *tl;
        } ltk_label;
        
       -ltk_label *ltk_label_create(ltk_window *window, char *text);
       +ltk_label *ltk_label_create(ltk_window *window, const char *text);
        
        #endif /* LTK_LABEL_H */
   DIR diff --git a/src/ltk/ltk.c b/src/ltk/ltk.c
       @@ -196,8 +196,7 @@ ltk_init(void) {
                ltk_free0(config_path);
        
                ltk_events_init(shared_data.renderdata);
       -        ltk_window_theme *window_theme = ltk_window_get_theme();
       -        shared_data.text_context = ltk_text_context_create(shared_data.renderdata, window_theme->font);
       +        shared_data.text_context = ltk_text_context_create(shared_data.renderdata);
                shared_data.clipboard = ltk_clipboard_create(shared_data.renderdata);
                /* FIXME: configure cache size; check for overflow */
                ltk_image_init(shared_data.renderdata, 1024 * 1024 * 4);
       @@ -514,6 +513,11 @@ ltk_handle_event(ltk_event *event) {
        }
        
        ltk_text_line *
       -ltk_text_line_create_default(uint16_t font_size, char *text, int take_over_text, int width) {
       -        return ltk_text_line_create(shared_data.text_context, font_size, text, take_over_text, width);
       +ltk_text_line_create_default(const char *font, int font_size, char *text, int take_over_text, int width) {
       +        return ltk_text_line_create(shared_data.text_context, font, font_size, text, take_over_text, width);
       +}
       +
       +ltk_text_line *
       +ltk_text_line_create_const_text_default(const char *font, int font_size, const char *text, int width) {
       +        return ltk_text_line_create_const_text(shared_data.text_context, font, font_size, text, width);
        }
   DIR diff --git a/src/ltk/ltk.h b/src/ltk/ltk.h
       @@ -50,7 +50,8 @@ void ltk_window_destroy(ltk_widget *self, int shallow);
        int ltk_call_cmd(ltk_widget *caller, const char *cmd, size_t cmdlen, const char *text, size_t textlen);
        
        /* convenience function to use the default text context */
       -ltk_text_line *ltk_text_line_create_default(uint16_t font_size, char *text, int take_over_text, int width);
       +ltk_text_line *ltk_text_line_create_default(const char *font, int font_size, char *text, int take_over_text, int width);
       +ltk_text_line *ltk_text_line_create_const_text_default(const char *font, int font_size, const char *text, int width);
        
        ltk_clipboard *ltk_get_clipboard(void);
        ltk_renderdata *ltk_get_renderer(void);
   DIR diff --git a/src/ltk/menu.c b/src/ltk/menu.c
       @@ -79,10 +79,12 @@ static struct entry_theme {
                ltk_color *border_disabled;
                ltk_color *fill_disabled;
        
       +        char *font;
                ltk_size text_pad;
                ltk_size arrow_pad;
                ltk_size arrow_size;
                ltk_size border_width;
       +        ltk_size font_size;
        } menu_entry_theme, submenu_entry_theme;
        
        static void ltk_menu_ensure_rect_shown(ltk_widget *self, ltk_rect r);
       @@ -130,6 +132,7 @@ static ltk_widget *ltk_menuentry_get_child(ltk_widget *self);
        /* FIXME: these functions are named really badly */
        static void recalc_ideal_menu_size_with_notification(ltk_widget *self, ltk_widget *widget);
        static void recalc_ideal_menu_size(ltk_menu *menu);
       +static void ltk_menuentry_set_font(ltk_menuentry *entry);
        static void ltk_menu_recalc_ideal_size(ltk_widget *self);
        static void ltk_menuentry_recalc_ideal_size(ltk_widget *self);
        static void ltk_menuentry_recalc_ideal_size_with_notification(ltk_menuentry *entry);
       @@ -227,6 +230,8 @@ static ltk_theme_parseinfo menu_entry_parseinfo[] = {
                {"text-disabled", THEME_COLOR, {.color = &menu_entry_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"border-disabled", THEME_COLOR, {.color = &menu_entry_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"fill-disabled", THEME_COLOR, {.color = &menu_entry_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
       +        {"font-size", THEME_SIZE, {.size = &menu_entry_theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0},
       +        {"font", THEME_STRING, {.str = &menu_entry_theme.font}, {.str = "Monospace"}, 0, 0, 0},
        };
        
        static ltk_theme_parseinfo submenu_parseinfo[] = {
       @@ -260,6 +265,8 @@ static ltk_theme_parseinfo submenu_entry_parseinfo[] = {
                {"text-disabled", THEME_COLOR, {.color = &submenu_entry_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"border-disabled", THEME_COLOR, {.color = &submenu_entry_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"fill-disabled", THEME_COLOR, {.color = &submenu_entry_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
       +        {"font-size", THEME_SIZE, {.size = &submenu_entry_theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0},
       +        {"font", THEME_STRING, {.str = &submenu_entry_theme.font}, {.str = "Monospace"}, 0, 0, 0},
        };
        void
        ltk_menu_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
       @@ -1003,12 +1010,20 @@ recalc_ideal_menuentry_size(ltk_menuentry *entry) {
        static void
        ltk_menuentry_recalc_ideal_size(ltk_widget *self) {
                ltk_menuentry *entry = LTK_CAST_MENUENTRY(self);
       -        int font_size = ltk_size_to_pixel(self->window->theme->font_size, self->last_dpi);
       +        struct entry_theme *t = IN_SUBMENU(entry) ? &submenu_entry_theme : &menu_entry_theme;
       +        int font_size = ltk_size_to_pixel(t->font_size, self->last_dpi);
                ltk_text_line_set_font_size(entry->text_line, font_size);
                recalc_ideal_menuentry_size(entry);
        }
        
        static void
       +ltk_menuentry_set_font(ltk_menuentry *entry) {
       +        struct entry_theme *t = IN_SUBMENU(entry) ? &submenu_entry_theme : &menu_entry_theme;
       +        int font_size = ltk_size_to_pixel(t->font_size, LTK_CAST_WIDGET(entry)->last_dpi);
       +        ltk_text_line_set_font(entry->text_line, t->font, font_size);
       +}
       +
       +static void
        ltk_menuentry_recalc_ideal_size_with_notification(ltk_menuentry *entry) {
                recalc_ideal_menuentry_size(entry);
                /* FIXME: only call if something changed */
       @@ -1021,9 +1036,10 @@ ltk_menuentry *
        ltk_menuentry_create(ltk_window *window, const char *text) {
                ltk_menuentry *e = ltk_malloc(sizeof(ltk_menuentry));
                ltk_fill_widget_defaults(&e->widget, window, &entry_vtable, 0, 0);
       -        /* FIXME: this should be split up into two versions with const or not const (one for taking over text) */
       -        e->text_line = ltk_text_line_create_default(
       -                ltk_size_to_pixel(window->theme->font_size, e->widget.last_dpi), (char *)text, 0, -1
       +        e->text_line = ltk_text_line_create_const_text_default(
       +                menu_entry_theme.font,
       +                ltk_size_to_pixel(menu_entry_theme.font_size, e->widget.last_dpi),
       +                text, -1
                );
                e->submenu = NULL;
                /* Note: This is only set as a dummy value! The actual ideal size can't
       @@ -1037,7 +1053,7 @@ ltk_menuentry_create(ltk_window *window, const char *text) {
        static int
        ltk_menuentry_remove_child(ltk_widget *self, ltk_widget *widget) {
                ltk_menuentry *e = LTK_CAST_MENUENTRY(self);
       -        if (widget != &e->submenu->widget)
       +        if (widget != LTK_CAST_WIDGET(e->submenu))
                        return 1;
                widget->parent = NULL;
                e->submenu = NULL;
       @@ -1067,6 +1083,8 @@ ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx) {
                if (insert_entry(menu, entry, idx))
                        return 2; /* invalid index */
                entry->widget.parent = &menu->widget;
       +        /* the theme may have changed if the entry switched between menu and submenu */
       +        ltk_menuentry_set_font(entry);
                ltk_menuentry_recalc_ideal_size_with_notification(entry);
                recalc_ideal_menu_size_with_notification(LTK_CAST_WIDGET(menu), NULL);
                menu->widget.dirty = 1;
       @@ -1113,7 +1131,10 @@ ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx) {
                if (idx >= menu->num_entries)
                        return 1; /* invalid index */
                menu->entries[idx]->widget.parent = NULL;
       -        ltk_menuentry_recalc_ideal_size_with_notification(menu->entries[idx]);
       +        /* I don't think this is needed because the entry isn't shown
       +           anywhere. Its size will be recalculated once it is added
       +           to a menu again. */
       +        /* ltk_menuentry_recalc_ideal_size_with_notification(menu->entries[idx]); */
                memmove(
                    menu->entries + idx,
                    menu->entries + idx + 1,
       @@ -1151,11 +1172,15 @@ ltk_menu_remove_child(ltk_widget *self, ltk_widget *child) {
                return ltk_menu_remove_entry(LTK_CAST_MENU(self), LTK_CAST_MENUENTRY(child));
        }
        
       +/* TODO: add function to also destroy the entries when removing them */
        void
        ltk_menu_remove_all_entries(ltk_menu *menu) {
                for (size_t i = 0; i < menu->num_entries; i++) {
                        menu->entries[i]->widget.parent = NULL;
       -                ltk_menuentry_recalc_ideal_size_with_notification(menu->entries[i]);
       +                /* I don't think this is needed because the entry isn't shown
       +                   anywhere. Its size will be recalculated once it is added
       +                   to a menu again. */
       +                /* ltk_menuentry_recalc_ideal_size_with_notification(menu->entries[i]); */
                }
                menu->num_entries = menu->num_alloc = 0;
                ltk_free0(menu->entries);
   DIR diff --git a/src/ltk/text.h b/src/ltk/text.h
       @@ -26,13 +26,15 @@
        typedef struct ltk_text_line ltk_text_line;
        typedef struct ltk_text_context ltk_text_context;
        
       -ltk_text_context *ltk_text_context_create(ltk_renderdata *data, char *default_font);
       +ltk_text_context *ltk_text_context_create(ltk_renderdata *data);
        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_font_size(ltk_text_line *tl, uint16_t font_size);
       +/* FIXME: font string format is currently defined by implementation, maybe standardize that? */
       +ltk_text_line *ltk_text_line_create(ltk_text_context *ctx, const char *font, int font_size, char *text, int take_over_text, int width);
       +ltk_text_line *ltk_text_line_create_const_text(ltk_text_context *ctx, const char *font, int font_size, const char *text, int width);
       +void ltk_text_line_set_font(ltk_text_line *tl, const char *font, int font_size);
       +void ltk_text_line_set_font_size(ltk_text_line *tl, int font_size);
        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);
        void ltk_text_line_destroy(ltk_text_line *tl);
   DIR diff --git a/src/ltk/text_pango.c b/src/ltk/text_pango.c
       @@ -39,7 +39,8 @@ struct ltk_text_line {
                char *text;
                size_t len;
                PangoLayout *layout;
       -        uint16_t font_size;
       +        PangoFontDescription *font_desc;
       +        int font_size;
                PangoAttrList *attrs;
        };
        
       @@ -47,16 +48,14 @@ struct ltk_text_context {
                ltk_renderdata *data;
                PangoFontMap *fontmap;
                PangoContext *context;
       -        char *default_font;
        };
        
        ltk_text_context *
       -ltk_text_context_create(ltk_renderdata *data, char *default_font) {
       +ltk_text_context_create(ltk_renderdata *data) {
                ltk_text_context *ctx = ltk_malloc(sizeof(ltk_text_context));
                ctx->data = data;
                ctx->fontmap = NULL;
                ctx->context = NULL;
       -        ctx->default_font = ltk_strdup(default_font);
                return ctx;
        }
        
       @@ -74,7 +73,6 @@ ltk_text_context_init(ltk_text_context *ctx) {
        
        void
        ltk_text_context_destroy(ltk_text_context *ctx) {
       -        ltk_free(ctx->default_font);
                /* FIXME: if both are unref'd, there is a segfault - what is
                   the normal thing to do here? */
                if (ctx->fontmap)
       @@ -104,15 +102,29 @@ ltk_text_line_set_text(ltk_text_line *tl, char *text, int take_over_text) {
        }
        
        void
       -ltk_text_line_set_font_size(ltk_text_line *tl, uint16_t font_size) {
       -        PangoFontDescription *desc = pango_font_description_from_string(tl->ctx->default_font);
       +ltk_text_line_set_font_size(ltk_text_line *tl, int font_size) {
       +        if (font_size == tl->font_size)
       +                return;
       +        pango_font_description_set_absolute_size(tl->font_desc, font_size * PANGO_SCALE);
       +        pango_layout_set_font_description(tl->layout, tl->font_desc);
       +        tl->font_size = font_size;
       +}
       +
       +void
       +ltk_text_line_set_font(ltk_text_line *tl, const char *font, int font_size) {
       +        /* it doesn't seem as if there's a way to parse a font string without
       +           createing a new font description (but I might have missed something) */
       +        PangoFontDescription *desc = pango_font_description_from_string(font);
                pango_font_description_set_absolute_size(desc, font_size * PANGO_SCALE);
                pango_layout_set_font_description(tl->layout, desc);
       -        pango_font_description_free(desc);
       +        if (tl->font_desc)
       +                pango_font_description_free(tl->font_desc);
       +        tl->font_desc = desc;
       +        tl->font_size = font_size;
        }
        
        ltk_text_line *
       -ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, char *text, int take_over_text, int width) {
       +ltk_text_line_create(ltk_text_context *ctx, const char *font, int font_size, char *text, int take_over_text, int width) {
                ltk_text_context_init(ctx);
                ltk_text_line *tl = ltk_malloc(sizeof(ltk_text_line));
                if (take_over_text)
       @@ -131,11 +143,17 @@ ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, char *text, int 
                        ltk_text_line_set_width(tl, width * PANGO_SCALE);
                tl->attrs = NULL;
                ltk_text_line_clear_attrs(tl);
       -        ltk_text_line_set_font_size(tl, font_size);
       +        tl->font_desc = NULL;
       +        ltk_text_line_set_font(tl, font, font_size);
        
                return tl;
        }
        
       +ltk_text_line *
       +ltk_text_line_create_const_text(ltk_text_context *ctx, const char *font, int font_size, const char *text, int width) {
       +        return ltk_text_line_create(ctx, font, font_size, ltk_strdup(text), 1, width);
       +}
       +
        void
        ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y) {
                XftDraw *d = ltk_surface_get_xft_draw(s);
       @@ -375,6 +393,7 @@ ltk_text_line_move_cursor_visually(ltk_text_line *tl, size_t pos, int movement, 
        
        void
        ltk_text_line_destroy(ltk_text_line *tl) {
       +        pango_font_description_free(tl->font_desc);
                if (tl->attrs)
                        pango_attr_list_unref(tl->attrs);
                g_object_unref(tl->layout);
   DIR diff --git a/src/ltk/window.c b/src/ltk/window.c
       @@ -118,7 +118,14 @@ static ltk_widget **widget_stack = NULL;
        static size_t widget_stack_alloc = 0;
        static size_t widget_stack_len = 0;
        
       -static ltk_window_theme theme;
       +static struct {
       +        int border_width;
       +        ltk_size font_size;
       +        char *font;
       +        ltk_color *fg;
       +        ltk_color *bg;
       +} theme;
       +
        static ltk_theme_parseinfo theme_parseinfo[] = {
                {"bg", THEME_COLOR, {.color = &theme.bg}, {.color = "#000000"}, 0, 0, 0},
                {"fg", THEME_COLOR, {.color = &theme.fg}, {.color = "#FFFFFF"}, 0, 0, 0},
       @@ -132,12 +139,6 @@ ltk_window_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
                *len = LENGTH(theme_parseinfo);
        }
        
       -/* FIXME: maybe ltk_fatal if ltk not initialized? */
       -ltk_window_theme *
       -ltk_window_get_theme(void) {
       -        return &theme;
       -}
       -
        void
        ltk_window_cleanup(void) {
                ltk_keypress_bindings_destroy(keypresses);
       @@ -489,7 +490,7 @@ ltk_window_redraw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_re
                        window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
                /* FIXME: this should use window->dirty_rect, but that doesn't work
                   properly with double buffering */
       -        ltk_surface_fill_rect(window->surface, window->theme->bg, (ltk_rect){0, 0, window->rect.w, window->rect.h});
       +        ltk_surface_fill_rect(window->surface, theme.bg, (ltk_rect){0, 0, window->rect.w, window->rect.h});
                if (window->root_widget) {
                        ptr = window->root_widget;
                        ltk_widget_draw(ptr, window->surface, 0, 0, window->rect);
       @@ -624,7 +625,6 @@ ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, 
                unsigned int dpi = (unsigned int)round(config->dpi_scale * config->fixed_dpi * 5);
                window->renderwindow = ltk_renderer_create_window(data, title, x, y, w, h, dpi);
                ltk_renderer_set_window_properties(window->renderwindow, theme.bg);
       -        window->theme = &theme;
        
                window->root_widget = NULL;
                window->hover_widget = NULL;
   DIR diff --git a/src/ltk/window.h b/src/ltk/window.h
       @@ -29,14 +29,6 @@
        #define LTK_WINDOW_SIGNAL_CLOSE -1
        #define LTK_WINDOW_SIGNAL_INVALID -2
        
       -typedef struct {
       -        int border_width;
       -        ltk_size font_size;
       -        char *font;
       -        ltk_color *fg;
       -        ltk_color *bg;
       -} ltk_window_theme;
       -
        typedef struct ltk_window {
                ltk_widget widget;
                ltk_renderwindow *renderwindow;
       @@ -49,7 +41,6 @@ typedef struct ltk_window {
                ltk_widget *pressed_widget;
        
                ltk_rect rect;
       -        ltk_window_theme *theme;
                ltk_rect dirty_rect;
                /* FIXME: generic array */
                ltk_widget **popups;
       @@ -81,6 +72,4 @@ void ltk_window_register_popup(ltk_window *window, ltk_widget *popup);
        void ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup);
        void ltk_window_unregister_all_popups(ltk_window *window);
        
       -ltk_window_theme *ltk_window_get_theme(void);
       -
        #endif  /* LTK_WINDOW_H */