ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit 3a21eba1783e1d29204d54e006fe9f5efb016fc3
parent 185426535af1a1f3ba649607d5346f59fa687803
Author: lumidify <nobody@lumidify.org>
Date:   Thu,  2 Jun 2022 08:48:16 +0200

Improve theme handling

Diffstat:
M.ltk/theme.ini | 30+++++++++++++++---------------
MMakefile | 2++
Msrc/button.c | 83++++++++++++++++++++++++++-----------------------------------------------------
Msrc/button.h | 5+++--
Msrc/color.c | 17++++++++++++++---
Msrc/color.h | 3++-
Msrc/label.c | 42++++++++++++++++++++----------------------
Msrc/label.h | 7++++---
Msrc/ltk.h | 14++++++++------
Msrc/ltkd.c | 167++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/menu.c | 253++++++++++++++++++++++++-------------------------------------------------------
Msrc/menu.h | 9++++++---
Msrc/scrollbar.c | 60++++++++++++++++++++++--------------------------------------
Msrc/scrollbar.h | 6++++--
Msrc/text.h | 1+
Asrc/theme.c | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/theme.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/util.h | 7++++++-
18 files changed, 509 insertions(+), 406 deletions(-)

diff --git a/.ltk/theme.ini b/.ltk/theme.ini @@ -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 diff --git a/Makefile b/Makefile @@ -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) @@ -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 diff --git a/src/button.c b/src/button.c @@ -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 @@ -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 @@ -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); diff --git a/src/button.h b/src/button.h @@ -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, diff --git a/src/color.c b/src/color.c @@ -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 +} diff --git a/src/color.h b/src/color.h @@ -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_ */ diff --git a/src/label.c b/src/label.c @@ -33,6 +33,7 @@ #include "label.h" #include "graphics.h" #include "surface_cache.h" +#include "theme.h" #define MAX_LABEL_PADDING 500 @@ -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 @@ -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); diff --git a/src/label.h b/src/label.h @@ -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 @@ -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, diff --git a/src/ltk.h b/src/ltk.h @@ -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, @@ -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; @@ -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; @@ -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; @@ -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); diff --git a/src/ltkd.c b/src/ltkd.c @@ -42,6 +42,7 @@ #include "ini.h" #include "khash.h" +#include "theme.h" #include "memory.h" #include "color.h" #include "rect.h" @@ -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 @@ -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); @@ -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; } @@ -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( @@ -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; @@ -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; @@ -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 ); @@ -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 @@ -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); @@ -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; diff --git a/src/menu.c b/src/menu.c @@ -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 @@ -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); @@ -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 @@ -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; diff --git a/src/menu.h b/src/menu.h @@ -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, diff --git a/src/scrollbar.c b/src/scrollbar.c @@ -31,6 +31,7 @@ #include "ltk.h" #include "util.h" #include "scrollbar.h" +#include "theme.h" #define MAX_SCROLLBAR_WIDTH 100 /* completely arbitrary */ @@ -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 diff --git a/src/scrollbar.h b/src/scrollbar.h @@ -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_ */ diff --git a/src/text.h b/src/text.h @@ -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); diff --git a/src/theme.c b/src/theme.c @@ -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); */ + } + } +} diff --git a/src/theme.h b/src/theme.h @@ -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_ */ diff --git a/src/util.h b/src/util.h @@ -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, @@ -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_ */