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 99773bbc2bcaea288c5d8b657bdff74ae69e216a
parent 6b058581e1cb02dd6d4775d8ab3f008bcc87829c
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 30 Jul 2023 20:50:21 +0200

Add initial incomplete implementation of text entry widget

It's currently unusable, just a basic framework is there.
Also, the basic text backend is currently broken.

Diffstat:
M.ltk/ltk.cfg | 15++++++++++++++-
MMakefile | 10+++++++---
MREADME.md | 1+
Asrc/array.h | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/config.c | 167++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/config.h | 34+++++++++++++++++-----------------
Asrc/entry.c | 476+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/entry.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/event_xlib.c | 2++
Msrc/eventdefs.h | 2++
Asrc/keys.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ltkd.c | 272++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/memory.c | 1+
Msrc/proto_types.h | 4+++-
Msrc/text.h | 62+++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/text_pango.c | 287++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/text_stb.c | 12+++++++-----
Msrc/util.c | 2+-
Msrc/util.h | 2+-
Msrc/widget.c | 143++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/widget.h | 14++++++++++----
Dsrc/widget_config.h | 7-------
Mtest3.gui | 5++++-
Mtest3.sh | 8++++----
24 files changed, 1549 insertions(+), 254 deletions(-)

diff --git a/.ltk/ltk.cfg b/.ltk/ltk.cfg @@ -5,7 +5,7 @@ all-activatable = true # text-editor = ... # line-editor = ... -[key-binding] +[key-binding:widget] # In future: # bind edit-text-external ... # bind edit-line-external ... @@ -31,6 +31,19 @@ bind-keypress set-pressed sym return #flags run-always bind-keyrelease unset-pressed sym return #flags run-always # alternative: rawtext instead of text to ignore mapping +[key-binding:entry] +bind-keypress cursor-to-beginning sym home +bind-keypress cursor-to-end sym end +bind-keypress cursor-left sym left +bind-keypress cursor-right sym right +#bind-keypress delete-backwards sym backspace +#bind-keypress delete-forwards sym delete +#bind-keypress cursor-left sym left +#bind-keypress cursor-right sym right +#bind-keypress selection-left sym left mods shift +#bind-keypress selection-right sym right mods shift +#bind-keypress select-all text a mods ctrl + # default mapping (just to silence warnings) [key-mapping] language = "English (US)" diff --git a/Makefile b/Makefile @@ -4,13 +4,14 @@ NAME = ltk VERSION = -999-prealpha0 +# NOTE/FIXME: stb backend is currently broken # Note: The stb backend should not be used with untrusted font files. # FIXME: Using DEBUG here doesn't work because it somehow # interferes with a predefined macro, at least on OpenBSD. -DEV = 0 +DEV = 1 MEMDEBUG = 0 SANITIZE = 0 -USE_PANGO = 0 +USE_PANGO = 1 # Note: this macro magic for debugging and pango rendering seems ugly; it should probably be changed @@ -50,6 +51,7 @@ OBJ = \ src/box.o \ src/scrollbar.o \ src/button.o \ + src/entry.o \ src/label.o \ src/menu.o \ src/theme.o \ @@ -66,6 +68,7 @@ OBJ = \ HDR = \ src/box.h \ src/button.h \ + src/entry.h \ src/color.h \ src/grid.h \ src/ini.h \ @@ -90,7 +93,8 @@ HDR = \ src/err.h \ src/proto_types.h \ src/config.h \ - src/widget_config.h + src/array.h \ + src/keys.h all: src/ltkd src/ltkc diff --git a/README.md b/README.md @@ -6,6 +6,7 @@ WILDEST FANTASIES, NOT ACTUAL WORKING CODE. To build with or without pango: Follow instructions in config.mk. Note: The basic (non-pango) text doesn't work properly on all systems. +Note: The basic (non-pango) text is currently completely broken. To test: diff --git a/src/array.h b/src/array.h @@ -0,0 +1,167 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2020, 2023 lumidify <nobody@lumidify.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LTK_ARRAY_H_ +#define _LTK_ARRAY_H_ + +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" +#include "memory.h" + +#define LTK_ARRAY_INIT_DECL_BASE(name, type, storage) \ +typedef struct { \ + type *buf; \ + size_t buf_size; \ + size_t len; \ +} ltk_array_##name; \ + \ +storage ltk_array_##name *ltk_array_create_##name(size_t initial_len); \ +storage type ltk_array_pop_##name(ltk_array_##name *ar); \ +storage void ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len); \ +storage void ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len); \ +storage void ltk_array_resize_##name(ltk_array_##name *ar, size_t size); \ +storage void ltk_array_destroy_##name(ltk_array_##name *ar); \ +storage void ltk_array_clear_##name(ltk_array_##name *ar); \ +storage void ltk_array_append_##name(ltk_array_##name *ar, type elem); \ +storage void ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type)); \ +storage type ltk_array_get_safe_##name(ltk_array_##name *ar, size_t index); \ +storage void ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e); + +#define LTK_ARRAY_INIT_IMPL_BASE(name, type, storage) \ +storage ltk_array_##name * \ +ltk_array_create_##name(size_t initial_len) { \ + if (initial_len == 0) \ + ltk_fatal("Array length is zero\n"); \ + ltk_array_##name *ar = ltk_malloc(sizeof(ltk_array_##name)); \ + ar->buf = ltk_reallocarray(NULL, initial_len, sizeof(type)); \ + ar->buf_size = initial_len; \ + ar->len = 0; \ + return ar; \ +} \ + \ +storage type \ +ltk_array_pop_##name(ltk_array_##name *ar) { \ + if (ar->len == 0) \ + ltk_fatal("Array empty; cannot pop.\n"); \ + ar->len--; \ + return ar->buf[ar->len]; \ +} \ + \ +/* FIXME: having this function in the public interface is ugly */ \ +storage void \ +ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len) { \ + if (index > ar->len) \ + ltk_fatal("Array index out of bounds\n"); \ + ltk_array_resize_##name(ar, ar->len + len); \ + ar->len += len; \ + if (ar->len - len == index) \ + return; \ + memmove(ar->buf + index + len, ar->buf + index, \ + (ar->len - len - index) * sizeof(type)); \ +} \ + \ +storage void \ +ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len) { \ + ltk_array_prepare_gap_##name(ar, index, len); \ + for (size_t i = 0; i < len; i++) { \ + ar->buf[index + i] = elem[i]; \ + } \ +} \ + \ +storage void \ +ltk_array_append_##name(ltk_array_##name *ar, type elem) { \ + if (ar->len == ar->buf_size) \ + ltk_array_resize_##name(ar, ar->len + 1); \ + ar->buf[ar->len++] = elem; \ +} \ + \ +storage void \ +ltk_array_clear_##name(ltk_array_##name *ar) { \ + ar->len = 0; \ + ltk_array_resize_##name(ar, 1); \ +} \ + \ +storage void \ +ltk_array_resize_##name(ltk_array_##name *ar, size_t len) { \ + size_t new_size = ideal_array_size(ar->buf_size, len); \ + if (new_size != ar->buf_size) { \ + ar->buf = ltk_reallocarray(ar->buf, new_size, sizeof(type)); \ + ar->buf_size = new_size; \ + ar->len = ar->len < new_size ? ar->len : new_size; \ + } \ +} \ + \ +storage void \ +ltk_array_destroy_##name(ltk_array_##name *ar) { \ + if (!ar) \ + return; \ + ltk_free(ar->buf); \ + ltk_free(ar); \ +} \ + \ +storage void \ +ltk_array_destroy_deep_##name(ltk_array_##name *ar, void (*destroy_func)(type)) { \ + if (!ar) \ + return; \ + for (size_t i = 0; i < ar->len; i++) { \ + destroy_func(ar->buf[i]); \ + } \ + ltk_array_destroy_##name(ar); \ +} \ + \ +storage type \ +ltk_array_get_safe_##name(ltk_array_##name *ar, size_t index) { \ + if (index >= ar->len) \ + ltk_fatal("Index out of bounds.\n"); \ + return ar->buf[index]; \ +} \ + \ +storage void \ +ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e) { \ + if (index >= ar->len) \ + ltk_fatal("Index out of bounds.\n"); \ + ar->buf[index] = e; \ +} + +#define ltk_array(name) ltk_array_##name +#define ltk_array_create(name, initial_len) ltk_array_create_##name(initial_len) +#define ltk_array_pop(name, ar) ltk_array_pop_##name(ar) +#define ltk_array_insert(name, ar, index, elem, len) ltk_array_insert_##name(ar, index, elem, len) +#define ltk_array_resize(name, ar, size) ltk_array_resize_##name(ar, size) +#define ltk_array_destroy(name, ar) ltk_array_destroy_##name(ar) +#define ltk_array_clear(name, ar) ltk_array_clear_##name(ar) +#define ltk_array_append(name, ar, elem) ltk_array_append_##name(ar, elem) +#define ltk_array_destroy_deep(name, ar, destroy_func) ltk_array_destroy_deep_##name(ar, destroy_func) +#define ltk_array_length(ar) ((ar)->len) +#define ltk_array_get(ar, index) ((ar)->buf[index]) +#define ltk_array_get_safe(name, ar, index) ltk_array_get_safe_##name(ar, index) +#define ltk_array_set_safe(name, ar, index, e) ltk_array_set_safe_##name(ar, index, e) + +#define LTK_ARRAY_INIT_DECL(name, type) LTK_ARRAY_INIT_DECL_BASE(name, type,) +#define LTK_ARRAY_INIT_IMPL(name, type) LTK_ARRAY_INIT_IMPL_BASE(name, type,) +#define LTK_ARRAY_INIT_DECL_STATIC(name, type) LTK_ARRAY_INIT_DECL_BASE(name, type, static) +#define LTK_ARRAY_INIT_IMPL_STATIC(name, type) LTK_ARRAY_INIT_IMPL_BASE(name, type, static) + +#endif /* _LTK_ARRAY_H_ */ diff --git a/src/config.c b/src/config.c @@ -203,6 +203,10 @@ parse_keysym(char *text, size_t len, ltk_keysym *sym_ret) { *sym_ret = LTK_KEY_TAB; else if (str_array_equal("escape", text, len)) *sym_ret = LTK_KEY_ESCAPE; + else if (str_array_equal("end", text, len)) + *sym_ret = LTK_KEY_END; + else if (str_array_equal("home", text, len)) + *sym_ret = LTK_KEY_HOME; else return 1; return 0; @@ -252,19 +256,20 @@ parse_flags(char *text, size_t len, ltk_key_binding_flags *flags_ret) { } static int -parse_keypress_binding(struct lexstate *s, struct token *tok, ltk_keypress_binding *binding_ret, char **errstr) { - ltk_keypress_binding b = {NULL, NULL, NULL, LTK_KEY_NONE, LTK_MOD_NONE, LTK_KEY_BINDING_NOFLAGS}; +parse_keypress_binding( + struct lexstate *s, struct token *tok, + ltk_keypress_binding *binding_ret, + char **func_name_ret, size_t *func_len_ret, + char **errstr) { + ltk_keypress_binding b = {NULL, NULL, LTK_KEY_NONE, LTK_MOD_NONE, LTK_KEY_BINDING_NOFLAGS}; *tok = next_token(s); char *msg = NULL; if (tok->type != STRING) { msg = "Invalid token type"; goto error; } - b.callback = ltk_get_key_func(tok->text, tok->len); - if (!b.callback) { - msg = "Invalid function specification"; - goto error; - } + *func_name_ret = tok->text; + *func_len_ret = tok->len; struct token prevtok; int text_init = 0, rawtext_init = 0, sym_init = 0, mods_init = 0, flags_init = 0; while (1) { @@ -364,28 +369,15 @@ error: return 1; } -static void -push_keypress(ltk_config *c, ltk_keypress_binding b) { - if (c->keys.press_alloc == c->keys.press_len) { - c->keys.press_alloc = ideal_array_size(c->keys.press_alloc, c->keys.press_len + 1); - c->keys.press_bindings = ltk_reallocarray(c->keys.press_bindings, c->keys.press_alloc, sizeof(ltk_keypress_binding)); - } - c->keys.press_bindings[c->keys.press_len] = b; - c->keys.press_len++; -} - -static void -push_keyrelease(ltk_config *c, ltk_keyrelease_binding b) { - if (c->keys.release_alloc == c->keys.release_len) { - c->keys.release_alloc = ideal_array_size(c->keys.release_alloc, c->keys.release_len + 1); - c->keys.release_bindings = ltk_reallocarray(c->keys.release_bindings, c->keys.release_alloc, sizeof(ltk_keyrelease_binding)); - } - c->keys.release_bindings[c->keys.release_len] = b; - c->keys.release_len++; -} - static int -parse_keybinding(struct lexstate *s, ltk_config *c, struct token *tok, char **errstr) { +parse_keybinding( + struct lexstate *s, + struct token *tok, + char *widget, + size_t len, + keypress_binding_handler press_handler, + keyrelease_binding_handler release_handler, + char **errstr) { char *msg = NULL; *tok = next_token(s); if (tok->type == SECTION || tok->type == END) { @@ -397,12 +389,19 @@ parse_keybinding(struct lexstate *s, ltk_config *c, struct token *tok, char **er goto error; } else if (str_array_equal("bind-keypress", tok->text, tok->len)) { ltk_keypress_binding b; - if (parse_keypress_binding(s, tok, &b, errstr)) + char *name; + size_t nlen; + if (parse_keypress_binding(s, tok, &b, &name, &nlen, errstr)) return 1; - push_keypress(c, b); + if (press_handler(widget, len, name, nlen, b)) { + msg = "Invalid key binding"; + goto error; + } } else if (str_array_equal("bind-keyrelease", tok->text, tok->len)) { ltk_keypress_binding b; - if (parse_keypress_binding(s, tok, &b, errstr)) + char *name; + size_t nlen; + if (parse_keypress_binding(s, tok, &b, &name, &nlen, errstr)) return 1; if (b.text || b.rawtext) { free(b.text); @@ -410,7 +409,10 @@ parse_keybinding(struct lexstate *s, ltk_config *c, struct token *tok, char **er msg = "Text and rawtext may only be specified for keypress bindings"; goto error; } - push_keyrelease(c, (ltk_keyrelease_binding){b.callback, b.sym, b.mods, b.flags}); + if (release_handler(widget, len, name, nlen, (ltk_keyrelease_binding){b.sym, b.mods, b.flags})) { + msg = "Invalid key binding"; + goto error; + } } else { msg = "Invalid statement"; goto error; @@ -427,22 +429,22 @@ error: static void push_lang_mapping(ltk_config *c) { - if (c->keys.mappings_alloc == c->keys.mappings_len) { - c->keys.mappings_alloc = ideal_array_size(c->keys.mappings_alloc, c->keys.mappings_len + 1); - c->keys.mappings = ltk_reallocarray(c->keys.mappings, c->keys.mappings_alloc, sizeof(ltk_language_mapping)); + if (c->mappings_alloc == c->mappings_len) { + c->mappings_alloc = ideal_array_size(c->mappings_alloc, c->mappings_len + 1); + c->mappings = ltk_reallocarray(c->mappings, c->mappings_alloc, sizeof(ltk_language_mapping)); } - c->keys.mappings[c->keys.mappings_len].lang = NULL; - c->keys.mappings[c->keys.mappings_len].mappings = NULL; - c->keys.mappings[c->keys.mappings_len].mappings_alloc = 0; - c->keys.mappings[c->keys.mappings_len].mappings_len = 0; - c->keys.mappings_len++; + c->mappings[c->mappings_len].lang = NULL; + c->mappings[c->mappings_len].mappings = NULL; + c->mappings[c->mappings_len].mappings_alloc = 0; + c->mappings[c->mappings_len].mappings_len = 0; + c->mappings_len++; } static void push_text_mapping(ltk_config *c, char *text1, size_t len1, char *text2, size_t len2) { - if (c->keys.mappings_len == 0) + if (c->mappings_len == 0) return; /* I guess just fail silently... */ - ltk_language_mapping *m = &c->keys.mappings[c->keys.mappings_len - 1]; + ltk_language_mapping *m = &c->mappings[c->mappings_len - 1]; if (m->mappings_alloc == m->mappings_len) { m->mappings_alloc = ideal_array_size(m->mappings_alloc, m->mappings_len + 1); m->mappings = ltk_reallocarray(m->mappings, m->mappings_alloc, sizeof(ltk_keytext_mapping)); @@ -454,21 +456,15 @@ push_text_mapping(ltk_config *c, char *text1, size_t len1, char *text2, size_t l static void destroy_config(ltk_config *c) { - for (size_t i = 0; i < c->keys.press_len; i++) { - ltk_free(c->keys.press_bindings[i].text); - ltk_free(c->keys.press_bindings[i].rawtext); - } - ltk_free(c->keys.press_bindings); - ltk_free(c->keys.release_bindings); - for (size_t i = 0; i < c->keys.mappings_len; i++) { - ltk_free(c->keys.mappings[i].lang); - for (size_t j = 0; j < c->keys.mappings[i].mappings_len; j++) { - ltk_free(c->keys.mappings[i].mappings[j].from); - ltk_free(c->keys.mappings[i].mappings[j].to); + for (size_t i = 0; i < c->mappings_len; i++) { + ltk_free(c->mappings[i].lang); + for (size_t j = 0; j < c->mappings[i].mappings_len; j++) { + ltk_free(c->mappings[i].mappings[j].from); + ltk_free(c->mappings[i].mappings[j].to); } - ltk_free(c->keys.mappings[i].mappings); + ltk_free(c->mappings[i].mappings); } - ltk_free(c->keys.mappings); + ltk_free(c->mappings); ltk_free(c); } @@ -488,8 +484,8 @@ int ltk_config_get_language_index(char *lang, size_t *idx_ret) { if (!global_config) return 1; - for (size_t i = 0; i < global_config->keys.mappings_len; i++) { - if (!strcmp(lang, global_config->keys.mappings[i].lang)) { + for (size_t i = 0; i < global_config->mappings_len; i++) { + if (!strcmp(lang, global_config->mappings[i].lang)) { *idx_ret = i; return 0; } @@ -499,22 +495,32 @@ ltk_config_get_language_index(char *lang, size_t *idx_ret) { ltk_language_mapping * ltk_config_get_language_mapping(size_t idx) { - if (!global_config || idx >= global_config->keys.mappings_len) + if (!global_config || idx >= global_config->mappings_len) return NULL; - return &global_config->keys.mappings[idx]; + return &global_config->mappings[idx]; +} + +int +str_array_prefix(const char *str, const char *ar, size_t len) { + size_t slen = strlen(str); + if (len < slen) + return 0; + return !strncmp(str, ar, slen); } /* WARNING: errstr must be freed! */ /* FIXME: make ltk_load_file give size_t; handle errors there (copy from ledit) */ static int -load_from_text(const char *filename, char *file_contents, size_t len, char **errstr) { +load_from_text( + const char *filename, + char *file_contents, + size_t len, + keypress_binding_handler press_handler, + keyrelease_binding_handler release_handler, + char **errstr) { ltk_config *config = ltk_malloc(sizeof(ltk_config)); - config->keys.press_bindings = NULL; - config->keys.release_bindings = NULL; - config->keys.mappings = NULL; - config->keys.press_alloc = config->keys.press_len = 0; - config->keys.release_alloc = config->keys.release_len = 0; - config->keys.mappings_alloc = config->keys.mappings_len = 0; + config->mappings = NULL; + config->mappings_alloc = config->mappings_len = 0; config->general.explicit_focus = 0; config->general.all_activatable = 0; @@ -584,10 +590,12 @@ load_from_text(const char *filename, char *file_contents, size_t len, char **err } start_of_line = 1; } - } else if (str_array_equal("key-binding", secttok.text, secttok.len)) { + } else if (str_array_prefix("key-binding:", secttok.text, secttok.len)) { int ret = 0; + char *widget = secttok.text + strlen("key-binding:"); + size_t len = secttok.len - strlen("key-binding:"); while (1) { - if ((ret = parse_keybinding(&s, config, &tok, errstr)) > 0) { + if ((ret = parse_keybinding(&s, &tok, widget, len, press_handler, release_handler, errstr)) > 0) { goto errornomsg; } else if (ret < 0) { start_of_line = 1; @@ -620,7 +628,7 @@ load_from_text(const char *filename, char *file_contents, size_t len, char **err msg = "Language already set"; goto error; } - config->keys.mappings[config->keys.mappings_len - 1].lang = ltk_strndup(tok.text, tok.len); + config->mappings[config->mappings_len - 1].lang = ltk_strndup(tok.text, tok.len); lang_init = 1; } else if (str_array_equal("map", prev2tok.text, prev2tok.len)) { if (prev1tok.type != STRING || tok.type != STRING) { @@ -673,14 +681,18 @@ errornomsg: } int -ltk_config_parsefile(const char *filename, char **errstr) { +ltk_config_parsefile( + const char *filename, + keypress_binding_handler press_handler, + keyrelease_binding_handler release_handler, + char **errstr) { unsigned long len = 0; char *file_contents = ltk_read_file(filename, &len); if (!file_contents) { *errstr = ltk_print_fmt("Unable to open file \"%s\"", filename); return 1; } - int ret = load_from_text(filename, file_contents, len, errstr); + int ret = load_from_text(filename, file_contents, len, press_handler, release_handler, errstr); ltk_free(file_contents); return ret; } @@ -702,9 +714,18 @@ const char *default_config = "[general]\n" /* FIXME: improve this configuration */ int -ltk_config_load_default(char **errstr) { +ltk_config_load_default( + keypress_binding_handler press_handler, + keyrelease_binding_handler release_handler, + char **errstr) { char *config_copied = ltk_strdup(default_config); - int ret = load_from_text("<default config>", config_copied, strlen(config_copied), errstr); + int ret = load_from_text("<default config>", config_copied, strlen(config_copied), press_handler, release_handler, errstr); free(config_copied); return ret; } + +void +ltk_keypress_binding_destroy(ltk_keypress_binding b) { + ltk_free(b.text); + ltk_free(b.rawtext); +} diff --git a/src/config.h b/src/config.h @@ -4,7 +4,6 @@ #include <stddef.h> #include "eventdefs.h" -#include "widget_config.h" typedef enum{ LTK_KEY_BINDING_NOFLAGS = 0, @@ -14,15 +13,12 @@ typedef enum{ typedef struct { char *text; char *rawtext; - /* FIXME: forward declaration to avoid having to pull everything in for these definitions */ - ltk_key_callback *callback; ltk_keysym sym; ltk_mod_type mods; ltk_key_binding_flags flags; } ltk_keypress_binding; typedef struct { - ltk_key_callback *callback; ltk_keysym sym; ltk_mod_type mods; ltk_key_binding_flags flags; @@ -39,31 +35,35 @@ typedef struct { size_t mappings_alloc, mappings_len; } ltk_language_mapping; -/* FIXME: generic array */ -typedef struct { - ltk_keypress_binding *press_bindings; - ltk_keyrelease_binding *release_bindings; - ltk_language_mapping *mappings; - size_t press_alloc, press_len; - size_t release_alloc, release_len; - size_t mappings_alloc, mappings_len; -} ltk_keys_config; - typedef struct { char explicit_focus; char all_activatable; } ltk_general_config; typedef struct { - ltk_keys_config keys; + ltk_language_mapping *mappings; + size_t mappings_alloc, mappings_len; ltk_general_config general; } ltk_config; +typedef int (*keypress_binding_handler)(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b); +typedef int (*keyrelease_binding_handler)(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b); + void ltk_config_cleanup(void); ltk_config *ltk_config_get(void); int ltk_config_get_language_index(char *lang, size_t *idx_ret); ltk_language_mapping *ltk_config_get_language_mapping(size_t idx); -int ltk_config_parsefile(const char *filename, char **errstr); -int ltk_config_load_default(char **errstr); +int ltk_config_parsefile( + const char *filename, + keypress_binding_handler press_handler, + keyrelease_binding_handler release_handler, + char **errstr +); +int ltk_config_load_default( + keypress_binding_handler press_handler, + keyrelease_binding_handler release_handler, + char **errstr +); +void ltk_keypress_binding_destroy(ltk_keypress_binding b); #endif /* LTK_CONFIG_H */ diff --git a/src/entry.c b/src/entry.c @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2022-2023 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 + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <stdarg.h> + +#include "proto_types.h" +#include "event.h" +#include "memory.h" +#include "color.h" +#include "rect.h" +#include "widget.h" +#include "ltk.h" +#include "util.h" +#include "text.h" +#include "entry.h" +#include "graphics.h" +#include "surface_cache.h" +#include "theme.h" +#include "array.h" +#include "keys.h" + +#define MAX_ENTRY_BORDER_WIDTH 100 +#define MAX_ENTRY_PADDING 500 + +static void ltk_entry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip); +static ltk_entry *ltk_entry_create(ltk_window *window, + const char *id, char *text); +static void ltk_entry_destroy(ltk_widget *self, int shallow); +static void ltk_entry_redraw_surface(ltk_entry *entry, ltk_surface *s); + +static int ltk_entry_key_press(ltk_widget *self, ltk_key_event *event); +static int ltk_entry_key_release(ltk_widget *self, ltk_key_event *event); +static int ltk_entry_mouse_press(ltk_widget *self, ltk_button_event *event); +static int ltk_entry_mouse_release(ltk_widget *self, ltk_button_event *event); +static int ltk_entry_motion_notify(ltk_widget *self, ltk_motion_event *event); +static int ltk_entry_mouse_enter(ltk_widget *self, ltk_motion_event *event); +static int ltk_entry_mouse_leave(ltk_widget *self, ltk_motion_event *event); + +typedef void (*cb_func)(ltk_entry *, char *, size_t); + +/* FIXME: configure mouse actions, e.g. select-word-under-pointer, move-cursor-to-pointer */ + +static void cursor_to_beginning(ltk_entry *entry, char *text, size_t len); +static void cursor_to_end(ltk_entry *entry, char *text, size_t len); +static void cursor_left(ltk_entry *entry, char *text, size_t len); +static void cursor_right(ltk_entry *entry, char *text, size_t len); + +struct key_cb { + char *text; + cb_func func; +}; + +static struct key_cb cb_map[] = { + {"cursor-left", &cursor_left}, + {"cursor-right", &cursor_right}, + {"cursor-to-beginning", &cursor_to_beginning}, + {"cursor-to-end", &cursor_to_end}, +}; + +struct keypress_cfg { + ltk_keypress_binding b; + struct key_cb cb; +}; + +struct keyrelease_cfg { + ltk_keyrelease_binding b; + struct key_cb cb; +}; + +LTK_ARRAY_INIT_DECL_STATIC(keypress, struct keypress_cfg) +LTK_ARRAY_INIT_IMPL_STATIC(keypress, struct keypress_cfg) +LTK_ARRAY_INIT_DECL_STATIC(keyrelease, struct keyrelease_cfg) +LTK_ARRAY_INIT_IMPL_STATIC(keyrelease, struct keyrelease_cfg) + +static ltk_array(keypress) *keypresses = NULL; +static ltk_array(keyrelease) *keyreleases = NULL; + +GEN_CB_MAP_HELPERS(cb_map, struct key_cb, text) + +int +ltk_entry_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b) { + if (!keypresses) + keypresses = ltk_array_create(keypress, 1); + struct key_cb *cb = cb_map_get_entry(func_name, func_len); + if (!cb) + return 1; + struct keypress_cfg cfg = {b, *cb}; + ltk_array_append(keypress, keypresses, cfg); + return 0; +} + +int +ltk_entry_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b) { + if (!keyreleases) + keyreleases = ltk_array_create(keyrelease, 1); + struct key_cb *cb = cb_map_get_entry(func_name, func_len); + if (!cb) + return 1; + struct keyrelease_cfg cfg = {b, *cb}; + ltk_array_append(keyrelease, keyreleases, cfg); + return 0; +} + +static void +destroy_keypress_cfg(struct keypress_cfg cfg) { + ltk_keypress_binding_destroy(cfg.b); +} + +void +ltk_entry_cleanup(void) { + ltk_array_destroy_deep(keypress, keypresses, &destroy_keypress_cfg); + ltk_array_destroy(keyrelease, keyreleases); + keypresses = NULL; + keyreleases = NULL; +} + +static struct ltk_widget_vtable vtable = { + .key_press = &ltk_entry_key_press, + .key_release = &ltk_entry_key_release, + .mouse_press = &ltk_entry_mouse_press, + .mouse_release = &ltk_entry_mouse_release, + .release = NULL, + .motion_notify = &ltk_entry_motion_notify, + .mouse_leave = &ltk_entry_mouse_leave, + .mouse_enter = &ltk_entry_mouse_enter, + .change_state = NULL, + .get_child_at_pos = NULL, + .resize = NULL, + .hide = NULL, + .draw = &ltk_entry_draw, + .destroy = &ltk_entry_destroy, + .child_size_change = NULL, + .remove_child = NULL, + .type = LTK_WIDGET_ENTRY, + .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS | LTK_NEEDS_KEYBOARD, +}; + +static struct { + int border_width; + ltk_color text_color; + int pad; + + ltk_color border; + ltk_color fill; + + ltk_color border_pressed; + ltk_color fill_pressed; + + ltk_color border_hover; + ltk_color fill_hover; + + ltk_color border_active; + ltk_color fill_active; + + ltk_color border_disabled; + ltk_color fill_disabled; +} theme; + +/* FIXME: +need to distinguish between active and focused keybindings - entry binding for opening +in external text editor should work no matter if active or focused */ +/* FIXME: mouse press also needs to set focused */ +static ltk_theme_parseinfo parseinfo[] = { + {"border", THEME_COLOR, {.color = &theme.border}, {.color = "#339999"}, 0, 0, 0}, + {"border-hover", THEME_COLOR, {.color = &theme.border_hover}, {.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 = "#339999"}, 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_ENTRY_BORDER_WIDTH, 0}, + {"fill", THEME_COLOR, {.color = &theme.fill}, {.color = "#113355"}, 0, 0, 0}, + {"fill-hover", THEME_COLOR, {.color = &theme.fill_hover}, {.color = "#113355"}, 0, 0, 0}, + {"fill-active", THEME_COLOR, {.color = &theme.fill_active}, {.color = "#113355"}, 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_ENTRY_PADDING, 0}, + {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0}, +}; +static int parseinfo_sorted = 0; + +int +ltk_entry_ini_handler(ltk_window *window, const char *prop, const char *value) { + return ltk_theme_handle_value(window, "entry", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted); +} + +int +ltk_entry_fill_theme_defaults(ltk_window *window) { + return ltk_theme_fill_defaults(window, "entry", parseinfo, LENGTH(parseinfo)); +} + +void +ltk_entry_uninitialize_theme(ltk_window *window) { + ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo)); +} + +/* FIXME: only keep text in surface to avoid large surface */ +/* -> or maybe not even that? */ +static void +ltk_entry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) { + ltk_entry *entry = (ltk_entry *)self; + ltk_rect lrect = self->lrect; + ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); + if (clip_final.w <= 0 || clip_final.h <= 0) + return; + ltk_surface *s; + ltk_surface_cache_request_surface_size(entry->key, lrect.w, lrect.h); + if (!ltk_surface_cache_get_surface(entry->key, &s) || self->dirty) + ltk_entry_redraw_surface(entry, s); + ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y); +} + +static void +ltk_entry_redraw_surface(ltk_entry *entry, ltk_surface *s) { + ltk_rect rect = entry->widget.lrect; + int bw = theme.border_width; + ltk_color *border = NULL, *fill = NULL; + /* FIXME: HOVERACTIVE STATE */ + if (entry->widget.state & LTK_DISABLED) { + border = &theme.border_disabled; + fill = &theme.fill_disabled; + } else if (entry->widget.state & LTK_PRESSED) { + border = &theme.border_pressed; + fill = &theme.fill_pressed; + } else if (entry->widget.state & LTK_ACTIVE) { + border = &theme.border_active; + fill = &theme.fill_active; + } else if (entry->widget.state & LTK_HOVER) { + border = &theme.border_hover; + fill = &theme.fill_hover; + } else { + border = &theme.border; + fill = &theme.fill; + } + rect.x = 0; + rect.y = 0; + ltk_surface_fill_rect(s, fill, rect); + if (bw > 0) + ltk_surface_draw_rect(s, border, (ltk_rect){bw / 2, bw / 2, rect.w - bw, rect.h - bw}, bw); + + int text_w, text_h; + ltk_text_line_get_size(entry->tl, &text_w, &text_h); + /* FIXME: what if text_h > rect.h? */ + int text_x = bw + theme.pad; + int text_y = (rect.h - text_h) / 2; + ltk_rect clip_rect = (ltk_rect){text_x, text_y, rect.w - 2 * bw - 2 * theme.pad, text_h}; + ltk_text_line_draw_clipped(entry->tl, s, &theme.text_color, text_x - entry->cur_offset, text_y, clip_rect); + if (entry->widget.state & LTK_FOCUSED) { + int x, y, w, h; + ltk_text_line_pos_to_rect(entry->tl, entry->pos, &x, &y, &w, &h); + /* FIXME: configure line width */ + ltk_surface_draw_rect(s, &theme.text_color, (ltk_rect){x - entry->cur_offset + text_x, y + text_y, 1, h}, 1); + } + entry->widget.dirty = 0; +} + +/* FIXME: these don't need len, but they do need rawtext as well */ +static void +cursor_to_beginning(ltk_entry *entry, char *text, size_t len) { + (void)text; + (void)len; + entry->pos = 0; +} + +static void +cursor_to_end(ltk_entry *entry, char *text, size_t len) { + (void)text; + (void)len; + entry->pos = entry->len; +} + +/* FIXME: actually move shown text */ +static void +cursor_left(ltk_entry *entry, char *text, size_t len) { + (void)text; + (void)len; + entry->pos = ltk_text_line_move_cursor_visually(entry->tl, entry->pos, -1, NULL); +} + +static void +cursor_right(ltk_entry *entry, char *text, size_t len) { + (void)text; + (void)len; + entry->pos = ltk_text_line_move_cursor_visually(entry->tl, entry->pos, 1, NULL); +} + +static int +ltk_entry_key_press(ltk_widget *self, ltk_key_event *event) { + ltk_entry *entry = (ltk_entry *)self; + ltk_keypress_binding b; + for (size_t i = 0; i < ltk_array_length(keypresses); i++) { + b = ltk_array_get(keypresses, i).b; + /* FIXME: do this properly */ + if (b.sym == event->sym) { + ltk_array_get(keypresses, i).cb.func(entry, event->text, 0); + entry->widget.dirty = 1; + ltk_window_invalidate_widget_rect(self->window, self); + return 1; + } + } + /* FIXME: rawtext? */ + if (event->text) { + /* FIXME: properly handle everything */ + if (event->text[0] == '\n' || event->text[0] == '\r' || event->text[0] == 0x1b) + return 0; + size_t len = strlen(event->text); + if (entry->alloc < entry->len + len + 1) { + size_t new_alloc = ideal_array_size(entry->alloc, entry->len + len + 1); + entry->text = ltk_realloc(entry->text, new_alloc); + entry->alloc = new_alloc; + } + memmove(entry->text + entry->pos + len, entry->text + entry->pos, entry->len - entry->pos); + memmove(entry->text + entry->pos, event->text, len); + entry->len += len; + entry->pos += len; + entry->text[entry->len] = '\0'; + ltk_text_line_set_text(entry->tl, entry->text, 0); + /* FIXME: need to react to resize and adjust cur_offset */ + int x, y, w, h; + ltk_text_line_get_size(entry->tl, &w, &h); + unsigned int ideal_h = h + 2 * theme.border_width + 2 * theme.pad; + unsigned int ideal_w = w + 2 * theme.border_width + 2 * theme.pad; + if (ideal_w != self->ideal_w || ideal_h != self->ideal_h) { + self->ideal_w = ideal_w; + self->ideal_h = ideal_h; + if (self->parent && self->parent->vtable->child_size_change) + self->parent->vtable->child_size_change(self->parent, self); + } + ltk_text_line_pos_to_rect(entry->tl, entry->pos, &x, &y, &w, &h); + /* FIXME: test if anything weird can happen since resize is called by parent->child_size_change, + and then the stuff on the next few lines is done afterwards */ + /* FIXME: adjustable cursor width */ + int text_w = entry->widget.lrect.w - 2 * theme.border_width - 2 * theme.pad; + if (x - entry->cur_offset + 1 > text_w) + entry->cur_offset = x - text_w + 1; + entry->widget.dirty = 1; + ltk_window_invalidate_widget_rect(self->window, self); + return 1; + } + return 0; +} + +static int +ltk_entry_key_release(ltk_widget *self, ltk_key_event *event) { + (void)self; (void)event; + return 0; +} + +static int +ltk_entry_mouse_press(ltk_widget *self, ltk_button_event *event) { + (void)self; (void)event; + return 0; +} + +static int +ltk_entry_mouse_release(ltk_widget *self, ltk_button_event *event) { + (void)self; (void)event; + return 0; +} + +static int +ltk_entry_motion_notify(ltk_widget *self, ltk_motion_event *event) { + (void)self; (void)event; + return 0; +} + +static int +ltk_entry_mouse_enter(ltk_widget *self, ltk_motion_event *event) { + (void)self; (void)event; + return 0; +} + +static int +ltk_entry_mouse_leave(ltk_widget *self, ltk_motion_event *event) { + (void)self; (void)event; + return 0; +} + +static ltk_entry * +ltk_entry_create(ltk_window *window, const char *id, char *text) { + ltk_entry *entry = ltk_malloc(sizeof(ltk_entry)); + + uint16_t font_size = window->theme->font_size; + entry->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1); + int text_w, text_h; + ltk_text_line_get_size(entry->tl, &text_w, &text_h); + ltk_fill_widget_defaults(&entry->widget, id, window, &vtable, entry->widget.ideal_w, entry->widget.ideal_h); + entry->widget.ideal_w = text_w + theme.border_width * 2 + theme.pad * 2; + entry->widget.ideal_h = text_h + theme.border_width * 2 + theme.pad * 2; + entry->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, entry->widget.ideal_w, entry->widget.ideal_h); + entry->cur_offset = 0; + entry->text = ltk_strdup(text); + entry->len = strlen(text); + entry->alloc = entry->len + 1; + entry->pos = entry->sel_start = entry->sel_end = 0; + entry->widget.dirty = 1; + + return entry; +} + +static void +ltk_entry_destroy(ltk_widget *self, int shallow) { + (void)shallow; + ltk_entry *entry = (ltk_entry *)self; + if (!entry) { + ltk_warn("Tried to destroy NULL entry.\n"); + return; + } + ltk_surface_cache_release_key(entry->key); + ltk_text_line_destroy(entry->tl); + ltk_free(entry); +} + +/* FIXME: make text optional, command set-text */ +/* entry <entry id> create <text> */ +static int +ltk_entry_cmd_create( + ltk_window *window, + char **tokens, + size_t num_tokens, + ltk_error *err) { + ltk_entry *entry; + /* FIXME: factor out these repeated pieces */ + if (num_tokens != 4) { + err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS; + err->arg = -1; + return 1; + } + if (!ltk_widget_id_free(tokens[1])) { + err->type = ERR_WIDGET_ID_IN_USE; + err->arg = 1; + return 1; + } + entry = ltk_entry_create(window, tokens[1], tokens[3]); + ltk_set_widget((ltk_widget *)entry, tokens[1]); + + return 0; +} + +/* entry <entry id> <command> ... */ +int +ltk_entry_cmd( + ltk_window *window, + char **tokens, + size_t num_tokens, + ltk_error *err) { + if (num_tokens < 3) { + err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS; + err->arg = -1; + return 1; + } + if (strcmp(tokens[2], "create") == 0) { + return ltk_entry_cmd_create(window, tokens, num_tokens, err); + } else { + err->type = ERR_INVALID_COMMAND; + err->arg = -1; + return 1; + } + + return 0; +} diff --git a/src/entry.h b/src/entry.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 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 + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LTK_ENTRY_H +#define LTK_ENTRY_H + +/* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */ + +#include "err.h" +#include "config.h" + +typedef struct { + ltk_widget widget; + ltk_text_line *tl; + ltk_surface_cache_key *key; + char *text; + size_t len, alloc, pos, sel_start, sel_end; + int cur_offset; +} ltk_entry; + +int ltk_entry_ini_handler(ltk_window *window, const char *prop, const char *value); +int ltk_entry_fill_theme_defaults(ltk_window *window); +void ltk_entry_uninitialize_theme(ltk_window *window); + +/* FIXME: document that pointers inside binding are taken over! */ +int ltk_entry_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b); +int ltk_entry_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b); +void ltk_entry_cleanup(void); + +int ltk_entry_cmd( + ltk_window *window, + char **tokens, + size_t num_tokens, + ltk_error * +); + +#endif /* LTK_ENTRY_H */ diff --git a/src/event_xlib.c b/src/event_xlib.c @@ -60,6 +60,8 @@ get_keysym(KeySym sym) { and I'm not sure how standardized it is */ case XK_ISO_Left_Tab: case XK_Tab: return LTK_KEY_TAB; case XK_Escape: return LTK_KEY_ESCAPE; + case XK_End: return LTK_KEY_END; + case XK_Home: return LTK_KEY_HOME; default: return LTK_KEY_NONE; } } diff --git a/src/eventdefs.h b/src/eventdefs.h @@ -40,6 +40,8 @@ typedef enum { LTK_KEY_RETURN, LTK_KEY_TAB, LTK_KEY_ESCAPE, + LTK_KEY_HOME, + LTK_KEY_END } ltk_keysym; typedef enum { diff --git a/src/keys.h b/src/keys.h @@ -0,0 +1,60 @@ +#ifndef _KEYS_H_ +#define _KEYS_H_ + +#include "util.h" + +/* FIXME: replace with proper string type */ +struct ltk_search_cmp_helper { + const char *text; + size_t len; +}; + +/* FIXME: documentation */ +#define GEN_CB_MAP_HELPERS(name, typename, cmp_entry) \ + \ +static int name##_sorted = 0; \ + \ +/* \ + * IMPORTANT: The text passed to *_get_entry may not be nul-terminated, \ + * so ltk_search_cmp_helper has to be used for the bsearch comparison \ + * helper. \ + */ \ + \ +static int \ +name##_search_helper(const void *keyv, const void *entryv) { \ + struct ltk_search_cmp_helper *key = (struct ltk_search_cmp_helper *)keyv; \ + typename *entry = (typename *)entryv; \ + int ret = strncmp(key->text, entry->cmp_entry, key->len); \ + if (ret == 0) { \ + if (entry->cmp_entry[key->len] == '\0') \ + return 0; \ + else \ + return -1; \ + } \ + return ret; \ +} \ + \ +static int \ +name##_sort_helper(const void *entry1v, const void *entry2v) { \ + typename *entry1 = (typename *)entry1v; \ + typename *entry2 = (typename *)entry2v; \ + return strcmp(entry1->cmp_entry, entry2->cmp_entry); \ +} \ + \ +static typename * \ +name##_get_entry(const char *text, size_t len) { \ + /* just in case */ \ + if (!name##_sorted) { \ + qsort( \ + name, LENGTH(name), \ + sizeof(name[0]), &name##_sort_helper); \ + name##_sorted = 1; \ + } \ + struct ltk_search_cmp_helper tmp = {.len = len, .text = text}; \ + return bsearch( \ + &tmp, name, LENGTH(name), \ + sizeof(name[0]), &name##_search_helper \ + ); \ +} + +#endif diff --git a/src/ltkd.c b/src/ltkd.c @@ -53,6 +53,7 @@ #include "grid.h" /* #include "draw.h" */ #include "button.h" +#include "entry.h" #include "label.h" #include "scrollbar.h" #include "box.h" @@ -122,6 +123,7 @@ 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 void ltk_window_uninitialize_theme(ltk_window *window); static int read_sock(struct ltk_sock_info *sock); static int push_token(struct token_list *tl, char *token); @@ -144,6 +146,151 @@ static char *sock_path = NULL; global originally, but that's just the way it is. */ static ltk_window *main_window = NULL; +typedef struct { + char *name; + int (*ini_handler)(ltk_window *, const char *, const char *); + int (*fill_theme_defaults)(ltk_window *); + void (*uninitialize_theme)(ltk_window *); + int (*register_keypress)(const char *, size_t, ltk_keypress_binding); + int (*register_keyrelease)(const char *, size_t, ltk_keyrelease_binding); + void (*cleanup)(void); + int (*cmd)(ltk_window *, char **, size_t, ltk_error *); +} ltk_widget_funcs; + +/* FIXME: use binary search when searching for the widget */ +ltk_widget_funcs widget_funcs[] = { + { + .name = "box", + .ini_handler = NULL, + .fill_theme_defaults = NULL, + .uninitialize_theme = NULL, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + .cmd = &ltk_box_cmd + }, + { + .name = "button", + .ini_handler = &ltk_button_ini_handler, + .fill_theme_defaults = &ltk_button_fill_theme_defaults, + .uninitialize_theme = &ltk_button_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + .cmd = &ltk_button_cmd + }, + { + .name = "entry", + .ini_handler = &ltk_entry_ini_handler, + .fill_theme_defaults = &ltk_entry_fill_theme_defaults, + .uninitialize_theme = &ltk_entry_uninitialize_theme, + .register_keypress = &ltk_entry_register_keypress, + .register_keyrelease = &ltk_entry_register_keyrelease, + .cleanup = &ltk_entry_cleanup, + .cmd = &ltk_entry_cmd + }, + { + .name = "grid", + .ini_handler = NULL, + .fill_theme_defaults = NULL, + .uninitialize_theme = NULL, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + .cmd = &ltk_grid_cmd + }, + { + .name = "label", + .ini_handler = &ltk_label_ini_handler, + .fill_theme_defaults = &ltk_label_fill_theme_defaults, + .uninitialize_theme = &ltk_label_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + .cmd = &ltk_label_cmd + }, + { + .name = "menu", + .ini_handler = &ltk_menu_ini_handler, + .fill_theme_defaults = &ltk_menu_fill_theme_defaults, + .uninitialize_theme = &ltk_menu_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + .cmd = &ltk_menu_cmd + }, + { + .name = "menuentry", + .ini_handler = &ltk_menuentry_ini_handler, + .fill_theme_defaults = &ltk_menuentry_fill_theme_defaults, + .uninitialize_theme = &ltk_menuentry_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + .cmd = &ltk_menuentry_cmd + }, + { + .name = "submenu", + .ini_handler = &ltk_submenu_ini_handler, + .fill_theme_defaults = &ltk_submenu_fill_theme_defaults, + .uninitialize_theme = &ltk_submenu_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + .cmd = &ltk_menu_cmd + }, + { + .name = "submenuentry", + .ini_handler = &ltk_submenuentry_ini_handler, + .fill_theme_defaults = &ltk_submenuentry_fill_theme_defaults, + .uninitialize_theme = &ltk_submenuentry_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + /* This "widget" is only needed to have separate styles for regular + menu entries and submenu entries. "submenu" is just an alias for + "menu" in most cases - it's just needed when creating a menu to + decide if it's a submenu or not. + FIXME: is that even necessary? Why can't it just decide if it's + a submenu based on whether it has a parent or not? + -> I guess right-click menus are also just submenus, so they + need to set it explicitly, but wasn't there another reaseon? */ + .cmd = NULL + }, + { + .name = "scrollbar", + .ini_handler = &ltk_scrollbar_ini_handler, + .fill_theme_defaults = &ltk_scrollbar_fill_theme_defaults, + .uninitialize_theme = &ltk_scrollbar_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + .cmd = NULL + }, + { + /* Handler for general widget key bindings. */ + .name = "widget", + .ini_handler = NULL, + .fill_theme_defaults = NULL, + .uninitialize_theme = NULL, + .register_keypress = &ltk_widget_register_keypress, + .register_keyrelease = &ltk_widget_register_keyrelease, + .cleanup = &ltk_widget_cleanup, + .cmd = NULL + }, + { + /* Handler for window theme. */ + .name = "window", + .ini_handler = &ltk_window_ini_handler, + .fill_theme_defaults = &ltk_window_fill_theme_defaults, + .uninitialize_theme = &ltk_window_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + .cmd = NULL + } +}; + int main(int argc, char *argv[]) { setlocale(LC_CTYPE, ""); @@ -168,6 +315,7 @@ main(int argc, char *argv[]) { ltk_logfile = open_log(ltk_dir); if (!ltk_logfile) ltk_fatal_errno("Unable to open log file.\n"); + /* FIXME: move to widget_funcs? */ ltk_widgets_init(); /* FIXME: set window size properly - I only run it in a tiling WM @@ -490,7 +638,10 @@ ltk_cleanup(void) { } ltk_config_cleanup(); - ltk_widgets_cleanup(); + for (size_t i = 0; i < LENGTH(widget_funcs); i++) { + if (widget_funcs[i].cleanup) + widget_funcs[i].cleanup(); + } ltk_events_cleanup(); if (main_window) { ltk_uninitialize_theme(main_window); @@ -813,30 +964,23 @@ 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); } +static void +ltk_window_uninitialize_theme(ltk_window *window) { + ltk_theme_uninitialize(window, theme_parseinfo, LENGTH(theme_parseinfo)); +} + /* FIXME: standardize return codes - usually, 0 is returned on success, but ini.h uses 1 on success, so this is all a bit confusing */ +/* FIXME: switch away from ini.h */ 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 if (strcmp(widget, "menuentry") == 0) { - ltk_menuentry_ini_handler(window, prop, value); - } else if (strcmp(widget, "submenuentry") == 0) { - ltk_menuentry_ini_handler(window, prop, value); - } else { - return 0; + for (size_t i = 0; i < LENGTH(widget_funcs); i++) { + if (widget_funcs[i].ini_handler && !strcmp(widget, widget_funcs[i].name)) { + widget_funcs[i].ini_handler(window, prop, value); + return 1; + } } - return 1; + return 0; } static void @@ -845,29 +989,46 @@ ltk_load_theme(ltk_window *window, const char *path) { 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_menuentry_fill_theme_defaults(window) || - ltk_submenuentry_fill_theme_defaults(window)) { - ltk_uninitialize_theme(window); - ltk_fatal("Unable to load theme defaults.\n"); + for (size_t i = 0; i < LENGTH(widget_funcs); i++) { + if (widget_funcs[i].fill_theme_defaults) { + if (widget_funcs[i].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); - ltk_menuentry_uninitialize_theme(window); - ltk_submenuentry_uninitialize_theme(window); + for (size_t i = 0; i < LENGTH(widget_funcs); i++) { + if (widget_funcs[i].uninitialize_theme) + widget_funcs[i].uninitialize_theme(window); + } +} + +static int +handle_keypress_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b) { + for (size_t i = 0; i < LENGTH(widget_funcs); i++) { + if (str_array_equal(widget_funcs[i].name, widget_name, wlen)) { + if (!widget_funcs[i].register_keypress) + return 1; + return widget_funcs[i].register_keypress(name, nlen, b); + } + } + return 1; +} + +static int +handle_keyrelease_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b) { + for (size_t i = 0; i < LENGTH(widget_funcs); i++) { + if (str_array_equal(widget_funcs[i].name, widget_name, wlen)) { + if (!widget_funcs[i].register_keyrelease) + return 1; + return widget_funcs[i].register_keyrelease(name, nlen, b); + } + } + return 1; } static ltk_window * @@ -886,12 +1047,12 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int /* FIXME: search different directories for config */ char *config_path = ltk_strcat_useful(ltk_dir, "/ltk.cfg"); char *errstr = NULL; - if (ltk_config_parsefile(config_path, &errstr)) { + if (ltk_config_parsefile(config_path, &handle_keypress_binding, &handle_keyrelease_binding, &errstr)) { if (errstr) { ltk_warn("Unable to load config: %s\n", errstr); ltk_free(errstr); } - if (ltk_config_load_default(&errstr)) { + if (ltk_config_load_default(&handle_keypress_binding, &handle_keyrelease_binding, &errstr)) { /* FIXME: I guess errstr isn't freed here, but whatever */ ltk_fatal("Unable to load default config: %s\n", errstr); } @@ -925,7 +1086,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->renderdata, w, h); - window->text_context = ltk_text_context_create(window, window->theme->font); + window->text_context = ltk_text_context_create(window->renderdata, window->theme->font); return window; } @@ -1497,20 +1658,6 @@ process_commands(ltk_window *window, int client) { errdetail.arg = -1; err = 1; seq = sock->last_seq; - } else if (strcmp(tokens[0], "grid") == 0) { - err = ltk_grid_cmd(window, tokens, num_tokens, &errdetail); - } else if (strcmp(tokens[0], "box") == 0) { - err = ltk_box_cmd(window, tokens, num_tokens, &errdetail); - } else if (strcmp(tokens[0], "button") == 0) { - err = ltk_button_cmd(window, tokens, num_tokens, &errdetail); - } else if (strcmp(tokens[0], "label") == 0) { - err = ltk_label_cmd(window, tokens, num_tokens, &errdetail); - } else if (strcmp(tokens[0], "menu") == 0) { - err = ltk_menu_cmd(window, tokens, num_tokens, &errdetail); - } else if (strcmp(tokens[0], "submenu") == 0) { - err = ltk_menu_cmd(window, tokens, num_tokens, &errdetail); - } else if (strcmp(tokens[0], "menuentry") == 0) { - err = ltk_menuentry_cmd(window, tokens, num_tokens, &errdetail); } else if (strcmp(tokens[0], "set-root-widget") == 0) { err = ltk_set_root_widget_cmd(window, tokens, num_tokens, &errdetail); } else if (strcmp(tokens[0], "quit") == 0) { @@ -1537,9 +1684,18 @@ process_commands(ltk_window *window, int client) { } last = 1; } else { - errdetail.type = ERR_INVALID_COMMAND; - errdetail.arg = -1; - err = 1; + int found = 0; + for (size_t i = 0; i < LENGTH(widget_funcs); i++) { + if (widget_funcs[i].cmd && !strcmp(tokens[0], widget_funcs[i].name)) { + err = widget_funcs[i].cmd(window, tokens, num_tokens, &errdetail); + found = 1; + } + } + if (!found) { + errdetail.type = ERR_INVALID_COMMAND; + errdetail.arg = -1; + err = 1; + } } sock->tokens.num_tokens = 0; sock->last_seq = seq; diff --git a/src/memory.c b/src/memory.c @@ -147,6 +147,7 @@ ideal_array_size(size_t old, size_t needed) { /* FIXME: the shrinking here only makes sense if not many elements are removed at once - what would be more sensible here? */ + /* FIXME: overflow */ if (old < needed) ret = old * 2 > needed ? old * 2 : needed; else if (needed * 4 < old) diff --git a/src/proto_types.h b/src/proto_types.h @@ -9,7 +9,8 @@ #define LTK_WIDGET_BOX 5 #define LTK_WIDGET_MENU 6 #define LTK_WIDGET_MENUENTRY 7 -#define LTK_NUM_WIDGETS 8 +#define LTK_WIDGET_ENTRY 8 +#define LTK_NUM_WIDGETS 9 #define LTK_WIDGETMASK_UNKNOWN (UINT32_C(1) << LTK_WIDGET_UNKNOWN) #define LTK_WIDGETMASK_ANY (UINT32_C(0xFFFF)) @@ -19,6 +20,7 @@ #define LTK_WIDGETMASK_BOX (UINT32_C(1) << LTK_WIDGET_BOX) #define LTK_WIDGETMASK_MENU (UINT32_C(1) << LTK_WIDGET_MENU) #define LTK_WIDGETMASK_MENUENTRY (UINT32_C(1) << LTK_WIDGET_MENUENTRY) +#define LTK_WIDGETMASK_ENTRY (UINT32_C(1) << LTK_WIDGET_ENTRY) /* P == protocol; W == widget */ diff --git a/src/text.h b/src/text.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2023 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 @@ -14,8 +14,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef _LTK_TEXT_H_ -#define _LTK_TEXT_H_ +#ifndef LTK_TEXT_H +#define LTK_TEXT_H #include "color.h" #include "graphics.h" @@ -24,10 +24,7 @@ typedef struct ltk_text_line ltk_text_line; /* typedef struct ltk_text_context ltk_text_context; */ -/* FIXME: something to hold display, etc. to avoid circular reference between window and text context */ -/* FIXME: this is done now (ltk_renderdata), just need to use it here... */ - -ltk_text_context *ltk_text_context_create(ltk_window *window, char *default_font); +ltk_text_context *ltk_text_context_create(ltk_renderdata *data, char *default_font); void ltk_text_context_destroy(ltk_text_context *ctx); /* FIXME: allow to give length of text */ @@ -36,29 +33,48 @@ ltk_text_line *ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, c 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); +/* FIXME: length of text */ +void ltk_text_line_set_text(ltk_text_line *line, char *text, int take_over_text); /* Draw the entire line to a surface. */ /* FIXME: Some widgets rely on this to not fail when negative coordinates are given or the text goes outside of the surface boundaries - in the stb backend, this is taken into account and the pango-xft backend doesn't *seem* to have any problems with it, - but I don't know if that's guaranteed. Proper clipping would be better, but Pango - can't do that. */ + but I don't know if that's guaranteed. */ void ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y); -/* Get the smallest rectangle of the line that can be drawn while covering 'clip'. - * The Pango backend will currently always return the full rectangle for the line - * because it doesn't support clipping. Other backends may just return a slightly - * larger rectangle so they can draw full glyphs. */ -ltk_rect ltk_text_line_get_minimal_clip_rect(ltk_text_line *tl, ltk_rect clip); - -/* Draw a line onto a surface at position x,y and clipped to 'clip'. Note that - * clipping isn't supported properly, so the drawn part of the line may be - * larger than 'clip'. In order to find out the exact size of the drawn section, - * use ltk_rect_line_get_minimal_clip_rect. */ +/* Draw a line onto a surface at position x,y and clipped to 'clip'. + Note that 'clip' is relative to the origin of the given surface, not 'x' and 'y'. */ void ltk_text_line_draw_clipped(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y, ltk_rect clip); -/* FIXME: Any way to implement clipping properly? The text would need to be drawn - to an intermediate surface, but then we lose alpha blending with the background - (unless another library like cairo is used, which I want to avoid). */ +void ltk_text_line_clear_attrs(ltk_text_line *tl); +void ltk_text_line_add_attr_bg(ltk_text_line *tl, size_t start, size_t end, ltk_color *color); +void ltk_text_line_add_attr_fg(ltk_text_line *tl, size_t start, size_t end, ltk_color *color); + +typedef enum { + LTK_TEXT_LTR, + LTK_TEXT_RTL, +} ltk_text_direction; + +/* FIXME: support for strong and weak cursor */ +/* FIXME: better interface that doesn't just return ltr on error (e.g. invalid index) */ +ltk_text_direction ltk_text_line_get_byte_direction(ltk_text_line *tl, size_t byte); +ltk_text_direction ltk_text_line_get_softline_direction(ltk_text_line *tl, size_t line); +size_t ltk_text_line_get_num_softlines(ltk_text_line *tl); +size_t ltk_text_line_xy_to_pos(ltk_text_line *tl, int x, int y, int snap_nearest); +void ltk_text_line_pos_to_rect(ltk_text_line *tl, size_t pos, int *x_ret, int *y_ret, int *w_ret, int *h_ret); +size_t ltk_text_line_x_softline_to_pos(ltk_text_line *tl, int x, size_t softline, int snap_nearest); +void ltk_text_line_pos_to_x_softline(ltk_text_line *tl, size_t pos, int middle, int *x_ret, size_t *softline_ret); +size_t ltk_text_line_move_cursor_visually(ltk_text_line *tl, size_t pos, int movement, size_t *prev_index_ret); + +/* FIXME: two versions: bigline and normal line with text stored in memory but no bidi etc. */ +/* need to set endline separator so it can return when end of line reached! */ +/* FIXME: does this even make sense? */ +void ltk_text_bline_create( + size_t (*iter_cur)(void *data), + void (*iter_next)(void *data, char **text_ret, size_t *len_ret), + void (*iter_prev)(void *data, size_t maxlen, char **text_ret, size_t *len_ret), + void *data +); -#endif /* _LTK_TEXT_H_ */ +#endif /* LTK_TEXT_H */ diff --git a/src/text_pango.c b/src/text_pango.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2023 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 @@ -25,6 +25,7 @@ #include <X11/Xutil.h> #include <pango/pangoxft.h> +#include <pango/pango-utils.h> /* for PANGO_VERSION_CHECK */ #include "xlib_shared.h" #include "memory.h" @@ -38,22 +39,45 @@ struct ltk_text_line { ltk_text_context *ctx; char *text; + size_t len; PangoLayout *layout; uint16_t font_size; + PangoAttrList *attrs; }; struct ltk_text_context { - ltk_window *window; + ltk_renderdata *data; PangoFontMap *fontmap; PangoContext *context; char *default_font; }; +size_t +prev_utf8(char *text, size_t index) { + if (index == 0) + return 0; + size_t i = index - 1; + /* find valid utf8 char - this probably needs to be improved */ + while (i > 0 && ((text[i] & 0xC0) == 0x80)) + i--; + return i; +} + +size_t +next_utf8(char *text, size_t len, size_t index) { + if (index >= len) + return len; + size_t i = index + 1; + while (i < len && ((text[i] & 0xC0) == 0x80)) + i++; + return i; +} + ltk_text_context * -ltk_text_context_create(ltk_window *window, char *default_font) { +ltk_text_context_create(ltk_renderdata *data, char *default_font) { ltk_text_context *ctx = ltk_malloc(sizeof(ltk_text_context)); - ctx->window = window; - ctx->fontmap = pango_xft_get_font_map(window->renderdata->dpy, window->renderdata->screen); + ctx->data = data; + ctx->fontmap = pango_xft_get_font_map(data->dpy, data->screen); ctx->context = pango_font_map_create_context(ctx->fontmap); ctx->default_font = ltk_strdup(default_font); return ctx; @@ -71,7 +95,22 @@ ltk_text_context_destroy(ltk_text_context *ctx) { void ltk_text_line_set_width(ltk_text_line *tl, int width) { - pango_layout_set_width(tl->layout, width * PANGO_SCALE); + if (width <= 0) + pango_layout_set_width(tl->layout, -1); + else + pango_layout_set_width(tl->layout, width * PANGO_SCALE); +} + +void +ltk_text_line_set_text(ltk_text_line *tl, char *text, int take_over_text) { + if (tl->text) + free(tl->text); + if (take_over_text) + tl->text = text; + else + tl->text = ltk_strdup(text); + tl->len = strlen(tl->text); + pango_layout_set_text(tl->layout, tl->text, tl->len); } ltk_text_line * @@ -81,6 +120,7 @@ ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, char *text, int tl->text = text; else tl->text = ltk_strdup(text); + tl->len = strlen(tl->text); tl->font_size = font_size; tl->layout = pango_layout_new(ctx->context); @@ -91,7 +131,10 @@ ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, char *text, int tl->ctx = ctx; pango_layout_set_wrap(tl->layout, PANGO_WRAP_WORD_CHAR); pango_layout_set_text(tl->layout, text, -1); - ltk_text_line_set_width(tl, width * PANGO_SCALE); + if (width > 0) + ltk_text_line_set_width(tl, width * PANGO_SCALE); + tl->attrs = NULL; + ltk_text_line_clear_attrs(tl); return tl; } @@ -102,19 +145,22 @@ ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, i pango_xft_render_layout(d, &color->xftcolor, tl->layout, x * PANGO_SCALE, y * PANGO_SCALE); } -/* FIXME: any way to actually implement clipping with pango? */ void ltk_text_line_draw_clipped(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y, ltk_rect clip) { - (void)clip; + XftDraw *d = ltk_surface_get_xft_draw(s); + /* FIXME: check for integer overflow */ + XPoint points[] = { + {clip.x, clip.y}, + {clip.x + clip.w, clip.y}, + {clip.x + clip.w, clip.y + clip.h}, + {clip.x, clip.y + clip.h} + }; + Region r = XPolygonRegion(points, 4, EvenOddRule); + /* FIXME: error checking */ + XftDrawSetClip(d, r); ltk_text_line_draw(tl, s, color, x, y); -} - -ltk_rect -ltk_text_line_get_minimal_clip_rect(ltk_text_line *tl, ltk_rect clip) { - (void)clip; - int w, h; - ltk_text_line_get_size(tl, &w, &h); - return (ltk_rect){0, 0, w, h}; + XDestroyRegion(r); + XftDrawSetClip(d, NULL); } void @@ -123,7 +169,214 @@ ltk_text_line_get_size(ltk_text_line *tl, int *w, int *h) { } void +ltk_text_line_clear_attrs(ltk_text_line *tl) { + PangoAttrList *attrs = pango_attr_list_new(); + #if PANGO_VERSION_CHECK(1, 44, 0) + PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE); + pango_attr_list_insert(attrs, no_hyphens); + #endif + pango_layout_set_attributes(tl->layout, attrs); + if (tl->attrs) + pango_attr_list_unref(tl->attrs); + tl->attrs = attrs; +} + +void +ltk_text_line_add_attr_bg(ltk_text_line *tl, size_t start, size_t end, ltk_color *color) { + XRenderColor c = color->xftcolor.color; + PangoAttribute *attr = pango_attr_background_new(c.red, c.green, c.blue); + attr->start_index = start; + attr->end_index = end; + pango_attr_list_insert(tl->attrs, attr); + pango_layout_set_attributes(tl->layout, tl->attrs); +} + +void +ltk_text_line_add_attr_fg(ltk_text_line *tl, size_t start, size_t end, ltk_color *color) { + XRenderColor c = color->xftcolor.color; + PangoAttribute *attr = pango_attr_foreground_new(c.red, c.green, c.blue); + attr->start_index = start; + attr->end_index = end; + pango_attr_list_insert(tl->attrs, attr); + pango_layout_set_attributes(tl->layout, tl->attrs); +} + +ltk_text_direction +ltk_text_line_get_softline_direction(ltk_text_line *tl, size_t line) { + int num_softlines = pango_layout_get_line_count(tl->layout); + if ((int)line >= num_softlines) + return LTK_TEXT_LTR; + PangoLayoutLine *sl = pango_layout_get_line_readonly(tl->layout, (int)line); + if (!sl) + return LTK_TEXT_LTR; + return sl->resolved_dir == PANGO_DIRECTION_RTL || sl->resolved_dir == PANGO_DIRECTION_WEAK_RTL ? LTK_TEXT_RTL : LTK_TEXT_LTR; +} + +ltk_text_direction +ltk_text_line_get_byte_direction(ltk_text_line *tl, size_t byte) { + /* FIXME: check if index out of range first? */ + PangoDirection dir = pango_layout_get_direction(tl->layout, (int)byte); + return dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL ? LTK_TEXT_RTL : LTK_TEXT_LTR; +} + +size_t +ltk_text_line_get_num_softlines(ltk_text_line *tl) { + return (size_t)pango_layout_get_line_count(tl->layout); +} + +size_t +ltk_text_line_xy_to_pos(ltk_text_line *tl, int x, int y, int snap_nearest) { + int index, trailing; + pango_layout_xy_to_index( + tl->layout, + x * PANGO_SCALE, y * PANGO_SCALE, + &index, & trailing + ); + if (snap_nearest) { + while (trailing > 0) { + trailing--; + /* FIXME: proper string type with length */ + // FIXME: next_utf8 should be size_t + index = next_utf8(tl->text, tl->len, index); + } + } + return (size_t)index; +} + +/* FIXME: get_nearest_legal_pos */ +/* FIXME: factor out common text code from ltk and ledit */ + +/* WARNING: width can be negative - https://docs.gtk.org/Pango/method.Layout.index_to_pos.html */ +void +ltk_text_line_pos_to_rect(ltk_text_line *tl, size_t pos, int *x_ret, int *y_ret, int *w_ret, int *h_ret) { + PangoRectangle rect; + pango_layout_index_to_pos(tl->layout, (int)pos, &rect); + *x_ret = rect.x / PANGO_SCALE; + *y_ret = rect.y / PANGO_SCALE; + *w_ret = rect.width / PANGO_SCALE; + *h_ret = rect.height / PANGO_SCALE; +} + +/* FIXME: a lot more error checking, including integer overflows */ +size_t +ltk_text_line_x_softline_to_pos(ltk_text_line *tl, int x, size_t softline, int snap_nearest) { + int trailing = 0; + int x_relative = x * PANGO_SCALE; + PangoLayoutLine *pango_line = pango_layout_get_line_readonly(tl->layout, softline); + int tlw, tlh; + pango_layout_get_size(tl->layout, &tlw, &tlh); + /* x is absolute, so the margin at the left needs to be subtracted */ + if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) { + PangoRectangle rect; + pango_layout_line_get_extents(pango_line, NULL, &rect); + x_relative -= (tlw - rect.width); + } + int tmp_pos; + pango_layout_line_x_to_index( + pango_line, x_relative, &tmp_pos, &trailing + ); + size_t pos = (size_t)tmp_pos; + /* snap to the nearest border between graphemes */ + if (snap_nearest) { + while (trailing > 0) { + trailing--; + pos = next_utf8(tl->text, tl->len, pos); + } + } + return pos; +} + +void +ltk_text_line_pos_to_x_softline(ltk_text_line *tl, size_t pos, int middle, int *x_ret, size_t *softline_ret) { + /* + if (pos > INT_MAX) + err_overflow(); + */ + int sl_tmp; + pango_layout_index_to_line_x(tl->layout, (int)pos, 0, &sl_tmp, x_ret); + *softline_ret = sl_tmp; + PangoLayoutLine *pango_line = pango_layout_get_line_readonly(tl->layout, *softline_ret); + int tlw, tlh; + pango_layout_get_size(tl->layout, &tlw, &tlh); + /* FIXME: wouldn't it be easier to just use pango_layout_index_to_pos for everything here? */ + /* add left margin to x position if line is aligned right */ + if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) { + PangoRectangle rect; + pango_layout_line_get_extents(pango_line, NULL, &rect); + *x_ret += (tlw - rect.width); + } + if (middle) { + PangoRectangle rect; + pango_layout_index_to_pos(tl->layout, (int)pos, &rect); + *x_ret += rect.width / 2; + } + *x_ret /= PANGO_SCALE; /* FIXME: PANGO_PIXELS, etc. */ +} + +/* prev_index_ret is used instead of just calling get_legal_normal_pos + because weird things happen otherwise + -> in certain cases, this is still weird because prev_index_ret sometimes + is not at the end of the line, but this is the best I could come up + with for now */ +size_t +ltk_text_line_move_cursor_visually(ltk_text_line *tl, size_t pos, int movement, size_t *prev_index_ret) { + /* FIXME + if (pos > INT_MAX) + err_overflow(); + */ + /* FIXME: trailing */ + int trailing = 0; + int tmp_index; + int new_index = (int)pos, last_index = (int)pos; + int dir = 1; + int num = movement; + if (movement < 0) { + dir = -1; + num = -movement; + } + /* FIXME: This is stupid. Anything outside the range of int won't work + anyways because of pango (and because everything else would break + anyways with such long lines), so it's stupid to do all this weird + casting. */ + /* + if (cur_line->len > INT_MAX) + err_overflow(); + */ + while (num > 0) { + tmp_index = new_index; + pango_layout_move_cursor_visually( + tl->layout, TRUE, + new_index, trailing, dir, + &new_index, &trailing + ); + /* for some reason, this is necessary */ + if (new_index < 0) + new_index = 0; + else if (new_index > (int)tl->len) + new_index = (int)tl->len; + num--; + if (tmp_index != new_index) + last_index = tmp_index; + } + /* FIXME: Allow cursor to be at end of soft line */ + /* we don't currently support a difference between the cursor being at + the end of a soft line and the beginning of the next line */ + /* FIXME: spaces at end of softlines are weird in normal mode */ + while (trailing > 0) { + trailing--; + new_index = next_utf8(tl->text, tl->len, new_index); + } + if (new_index < 0) + new_index = 0; + if (prev_index_ret) + *prev_index_ret = (size_t)last_index; + return (size_t)new_index; +} + +void ltk_text_line_destroy(ltk_text_line *tl) { + if (tl->attrs) + g_object_unref(tl->attrs); g_object_unref(tl->layout); ltk_free(tl->text); ltk_free(tl); diff --git a/src/text_stb.c b/src/text_stb.c @@ -96,7 +96,7 @@ KHASH_MAP_INIT_INT(glyphinfo, ltk_glyph_info*) KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*) struct ltk_text_context { - ltk_window *window; + ltk_renderdata *data; khash_t(glyphcache) *glyph_cache; ltk_font **fonts; int num_fonts; @@ -202,9 +202,9 @@ static size_t u8_wc_toutf8(char *dest, uint32_t ch) { */ ltk_text_context * -ltk_text_context_create(ltk_window *window, char *default_font) { +ltk_text_context_create(ltk_renderdata *data, char *default_font) { ltk_text_context *ctx = ltk_malloc(sizeof(ltk_text_context)); - ctx->window = window; + ctx->data = data; ctx->glyph_cache = kh_init(glyphcache); ctx->fonts = ltk_malloc(sizeof(ltk_font *)); ctx->num_fonts = 0; @@ -228,6 +228,8 @@ ltk_text_context_destroy(ltk_text_context *ctx) { } kh_destroy(glyphcache, ctx->glyph_cache); ltk_free(ctx); + /* FIXME: figure out where this should go */ + FcFini(); } static ltk_glyph_info * @@ -592,7 +594,7 @@ ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, i if (w <= 0 || h <= 0) return; Drawable d = ltk_surface_get_drawable(s); - XImage *img = XGetImage(tl->ctx->window->renderdata->dpy, d, x, y, w, h, 0xFFFFFF, ZPixmap); + XImage *img = XGetImage(tl->ctx->data->dpy, d, x, y, w, h, 0xFFFFFF, ZPixmap); int last_break = 0; for (int i = 0; i < tl->lines; i++) { @@ -608,7 +610,7 @@ ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, i } last_break = next_break; } - XPutImage(tl->ctx->window->renderdata->dpy, d, tl->ctx->window->renderdata->gc, img, 0, 0, x, y, w, h); + XPutImage(tl->ctx->data->dpy, d, tl->ctx->data->gc, img, 0, 0, x, y, w, h); XDestroyImage(img); } diff --git a/src/util.c b/src/util.c @@ -154,7 +154,7 @@ ltk_fatal_errno(const char *format, ...) { } int -str_array_equal(char *terminated, char *array, size_t len) { +str_array_equal(const char *terminated, const char *array, size_t len) { if (!strncmp(terminated, array, len)) { /* this is kind of inefficient, but there's no way to know otherwise if strncmp just stopped comparing after a '\0' */ diff --git a/src/util.h b/src/util.h @@ -46,7 +46,7 @@ void ltk_warn(const char *format, ...); * Returns non-zero if they are equal, 0 otherwise. */ /* Note: this doesn't work if array contains '\0'. */ -int str_array_equal(char *terminated, char *array, size_t len); +int str_array_equal(const char *terminated, const char *array, size_t len); #define LENGTH(X) (sizeof(X) / sizeof(X[0])) diff --git a/src/widget.c b/src/widget.c @@ -26,14 +26,104 @@ #include "util.h" #include "khash.h" #include "surface_cache.h" -#include "widget_config.h" #include "config.h" - -struct ltk_key_callback { +#include "array.h" +#include "keys.h" + +static int cb_focus_active(ltk_window *window, ltk_key_event *event, int handled); +static int cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled); +static int cb_move_prev(ltk_window *window, ltk_key_event *event, int handled); +static int cb_move_next(ltk_window *window, ltk_key_event *event, int handled); +static int cb_move_left(ltk_window *window, ltk_key_event *event, int handled); +static int cb_move_right(ltk_window *window, ltk_key_event *event, int handled); +static int cb_move_up(ltk_window *window, ltk_key_event *event, int handled); +static int cb_move_down(ltk_window *window, ltk_key_event *event, int handled); +static int cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled); +static int cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled); +static int cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled); + +struct key_cb { char *func_name; int (*callback)(ltk_window *, ltk_key_event *, int handled); }; +static struct key_cb cb_map[] = { + {"focus-active", &cb_focus_active}, + {"move-down", &cb_move_down}, + {"move-left", &cb_move_left}, + {"move-next", &cb_move_next}, + {"move-prev", &cb_move_prev}, + {"move-right", &cb_move_right}, + {"move-up", &cb_move_up}, + {"remove-popups", &cb_remove_popups}, + {"set-pressed", &cb_set_pressed}, + {"unfocus-active", &cb_unfocus_active}, + {"unset-pressed", &cb_unset_pressed}, +}; + + +struct keypress_cfg { + ltk_keypress_binding b; + struct key_cb cb; +}; + +struct keyrelease_cfg { + ltk_keyrelease_binding b; + struct key_cb cb; +}; + +LTK_ARRAY_INIT_DECL_STATIC(keypress, struct keypress_cfg) +LTK_ARRAY_INIT_IMPL_STATIC(keypress, struct keypress_cfg) +LTK_ARRAY_INIT_DECL_STATIC(keyrelease, struct keyrelease_cfg) +LTK_ARRAY_INIT_IMPL_STATIC(keyrelease, struct keyrelease_cfg) + +static ltk_array(keypress) *keypresses = NULL; +static ltk_array(keyrelease) *keyreleases = NULL; + +GEN_CB_MAP_HELPERS(cb_map, struct key_cb, func_name) + +/* FIXME: most of this is duplicated code */ +/* FIXME: document that pointers inside binding are taken over! */ +int ltk_widget_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b); +int ltk_widget_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b); + +int +ltk_widget_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b) { + if (!keypresses) + keypresses = ltk_array_create(keypress, 1); + struct key_cb *cb = cb_map_get_entry(func_name, func_len); + if (!cb) + return 1; + struct keypress_cfg cfg = {b, *cb}; + ltk_array_append(keypress, keypresses, cfg); + return 0; +} + +int +ltk_widget_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b) { + if (!keyreleases) + keyreleases = ltk_array_create(keyrelease, 1); + struct key_cb *cb = cb_map_get_entry(func_name, func_len); + if (!cb) + return 1; + struct keyrelease_cfg cfg = {b, *cb}; + ltk_array_append(keyrelease, keyreleases, cfg); + return 0; +} + +static void +destroy_keypress_cfg(struct keypress_cfg cfg) { + ltk_keypress_binding_destroy(cfg.b); +} + +void +ltk_widget_cleanup(void) { + ltk_array_destroy_deep(keypress, keypresses, &destroy_keypress_cfg); + ltk_array_destroy(keyrelease, keyreleases); + keypresses = NULL; + keyreleases = NULL; +} + static void ltk_destroy_widget_hash(void); KHASH_MAP_INIT_STR(widget, ltk_widget *) @@ -826,30 +916,6 @@ cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled) { return 0; } -static ltk_key_callback key_callbacks[] = { - {"move-next", &cb_move_next}, - {"move-prev", &cb_move_prev}, - {"move-up", &cb_move_up}, - {"move-down", &cb_move_down}, - {"move-left", &cb_move_left}, - {"move-right", &cb_move_right}, - {"focus-active", &cb_focus_active}, - {"unfocus-active", &cb_unfocus_active}, - {"set-pressed", &cb_set_pressed}, - {"unset-pressed", &cb_unset_pressed}, - {"remove-popups", &cb_remove_popups}, -}; - -/* FIXME: binary search (copy from ledit) */ -ltk_key_callback * -ltk_get_key_func(char *name, size_t len) { - for (size_t i = 0; i < LENGTH(key_callbacks); i++) { - if (str_array_equal(key_callbacks[i].func_name, name, len)) - return &key_callbacks[i]; - } - return NULL; -} - /* FIXME: should keyrelease events be ignored if the corresponding keypress event was consumed for movement? */ /* FIXME: check if there's any weirdness when combining return and mouse press */ @@ -857,8 +923,6 @@ ltk_get_key_func(char *name, size_t len) { /* FIXME: implement key binding flag to run before widget handler is called */ void ltk_window_key_press_event(ltk_window *window, ltk_key_event *event) { - /* FIXME: how to handle config being NULL? */ - ltk_config *config = ltk_config_get(); int handled = 0; if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) { gen_widget_stack(window->active_widget); @@ -870,20 +934,22 @@ ltk_window_key_press_event(ltk_window *window, ltk_key_event *event) { } } } + if (!keypresses) + return; ltk_keypress_binding *b = NULL; - for (size_t i = 0; i < config->keys.press_len; i++) { - b = &config->keys.press_bindings[i]; + for (size_t i = 0; i < ltk_array_length(keypresses); i++) { + b = &ltk_array_get(keypresses, i).b; if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) { continue; } else if (b->text) { if (event->mapped && !strcmp(b->text, event->mapped)) - handled |= b->callback->callback(window, event, handled); + handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled); } else if (b->rawtext) { if (event->text && !strcmp(b->text, event->text)) - handled |= b->callback->callback(window, event, handled); + handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled); } else if (b->sym != LTK_KEY_NONE) { if (event->sym == b->sym) - handled |= b->callback->callback(window, event, handled); + handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled); } } @@ -893,7 +959,6 @@ ltk_window_key_press_event(ltk_window *window, ltk_key_event *event) { void ltk_window_key_release_event(ltk_window *window, ltk_key_event *event) { /* FIXME: emit event */ - ltk_config *config = ltk_config_get(); int handled = 0; if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) { gen_widget_stack(window->active_widget); @@ -904,13 +969,15 @@ ltk_window_key_release_event(ltk_window *window, ltk_key_event *event) { } } } + if (!keyreleases) + return; ltk_keyrelease_binding *b = NULL; - for (size_t i = 0; i < config->keys.release_len; i++) { - b = &config->keys.release_bindings[i]; + for (size_t i = 0; i < ltk_array_length(keyreleases); i++) { + b = &ltk_array_get(keyreleases, i).b; if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) { continue; } else if (b->sym != LTK_KEY_NONE && event->sym == b->sym) { - handled |= b->callback->callback(window, event, handled); + handled |= ltk_array_get(keyreleases, i).cb.callback(window, event, handled); } } } diff --git a/src/widget.h b/src/widget.h @@ -20,10 +20,10 @@ #include "err.h" #include "rect.h" #include "event.h" +#include "config.h" /* FIXME: SORT OUT INCLUDES PROPERLY! */ - typedef struct ltk_widget ltk_widget; typedef uint32_t ltk_widget_type; @@ -31,6 +31,9 @@ typedef enum { LTK_ACTIVATABLE_NORMAL = 1, LTK_ACTIVATABLE_SPECIAL = 2, LTK_ACTIVATABLE_ALWAYS = 1|2, + /* FIXME: redundant or needs better name - is implied by entries in vtable + - if there are widgets that have keyboard functions in the vtable but + shouldn't have this set, then it's a bad name */ LTK_NEEDS_KEYBOARD = 4, LTK_NEEDS_REDRAW = 8, LTK_HOVER_IS_ACTIVE = 16, @@ -113,8 +116,6 @@ struct ltk_widget { char hidden; }; -/* FIXME: just give the structs for the actual event type here instead - of the generic ltk_event */ struct ltk_widget_vtable { int (*key_press)(struct ltk_widget *, ltk_key_event *); int (*key_release)(struct ltk_widget *, ltk_key_event *); @@ -174,7 +175,6 @@ int ltk_widget_id_free(const char *id); ltk_widget *ltk_get_widget(const char *id, ltk_widget_type type, ltk_error *err); void ltk_set_widget(ltk_widget *widget, const char *id); void ltk_remove_widget(const char *id); -void ltk_widgets_cleanup(); void ltk_widgets_init(); void ltk_widget_resize(ltk_widget *widget); void ltk_widget_remove_client(int client); @@ -191,4 +191,10 @@ void ltk_widget_remove_from_event_lmask(ltk_widget *widget, int client, uint32_t void ltk_widget_remove_from_event_wmask(ltk_widget *widget, int client, uint32_t mask); void ltk_widget_remove_from_event_lwmask(ltk_widget *widget, int client, uint32_t mask); + +/* FIXME: document that pointers inside binding are taken over! */ +int ltk_widget_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b); +int ltk_widget_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b); +void ltk_widget_cleanup(void); + #endif /* LTK_WIDGET_H */ diff --git a/src/widget_config.h b/src/widget_config.h @@ -1,7 +0,0 @@ -#ifndef LTK_WIDGET_CONFIG_H -#define LTK_WIDGET_CONFIG_H - -typedef struct ltk_key_callback ltk_key_callback; -ltk_key_callback *ltk_get_key_func(char *name, size_t len); - -#endif /* LTK_WIDGET_CONFIG_H */ diff --git a/test3.gui b/test3.gui @@ -1,7 +1,8 @@ -grid grd1 create 3 1 +grid grd1 create 4 1 grid grd1 set-row-weight 0 1 grid grd1 set-row-weight 1 1 grid grd1 set-row-weight 2 1 +grid grd1 set-row-weight 3 1 grid grd1 set-column-weight 0 1 set-root-widget grd1 button btn1 create "I'm a button!" @@ -11,3 +12,5 @@ grid grd1 add btn1 0 0 1 1 grid grd1 add btn2 1 0 1 1 grid grd1 add btn3 2 0 1 1 mask-add btn1 button press +entry entry1 create "Hi" +grid grd1 add entry1 3 0 1 1 ew diff --git a/test3.sh b/test3.sh @@ -2,10 +2,10 @@ export LTKDIR="`pwd`/.ltk" ltk_id=`./src/ltkd -t "Cool Window"` -if [ $? -ne 0 ]; then - echo "Unable to start ltkd." >&2 - exit 1 -fi +#if [ $? -ne 0 ]; then +# echo "Unable to start ltkd." >&2 +# exit 1 +#fi cat test3.gui | ./src/ltkc $ltk_id | while read cmd do