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:
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 = <k_entry_key_press,
+ .key_release = <k_entry_key_release,
+ .mouse_press = <k_entry_mouse_press,
+ .mouse_release = <k_entry_mouse_release,
+ .release = NULL,
+ .motion_notify = <k_entry_motion_notify,
+ .mouse_leave = <k_entry_mouse_leave,
+ .mouse_enter = <k_entry_mouse_enter,
+ .change_state = NULL,
+ .get_child_at_pos = NULL,
+ .resize = NULL,
+ .hide = NULL,
+ .draw = <k_entry_draw,
+ .destroy = <k_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 = <k_box_cmd
+ },
+ {
+ .name = "button",
+ .ini_handler = <k_button_ini_handler,
+ .fill_theme_defaults = <k_button_fill_theme_defaults,
+ .uninitialize_theme = <k_button_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ .cmd = <k_button_cmd
+ },
+ {
+ .name = "entry",
+ .ini_handler = <k_entry_ini_handler,
+ .fill_theme_defaults = <k_entry_fill_theme_defaults,
+ .uninitialize_theme = <k_entry_uninitialize_theme,
+ .register_keypress = <k_entry_register_keypress,
+ .register_keyrelease = <k_entry_register_keyrelease,
+ .cleanup = <k_entry_cleanup,
+ .cmd = <k_entry_cmd
+ },
+ {
+ .name = "grid",
+ .ini_handler = NULL,
+ .fill_theme_defaults = NULL,
+ .uninitialize_theme = NULL,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ .cmd = <k_grid_cmd
+ },
+ {
+ .name = "label",
+ .ini_handler = <k_label_ini_handler,
+ .fill_theme_defaults = <k_label_fill_theme_defaults,
+ .uninitialize_theme = <k_label_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ .cmd = <k_label_cmd
+ },
+ {
+ .name = "menu",
+ .ini_handler = <k_menu_ini_handler,
+ .fill_theme_defaults = <k_menu_fill_theme_defaults,
+ .uninitialize_theme = <k_menu_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ .cmd = <k_menu_cmd
+ },
+ {
+ .name = "menuentry",
+ .ini_handler = <k_menuentry_ini_handler,
+ .fill_theme_defaults = <k_menuentry_fill_theme_defaults,
+ .uninitialize_theme = <k_menuentry_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ .cmd = <k_menuentry_cmd
+ },
+ {
+ .name = "submenu",
+ .ini_handler = <k_submenu_ini_handler,
+ .fill_theme_defaults = <k_submenu_fill_theme_defaults,
+ .uninitialize_theme = <k_submenu_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ .cmd = <k_menu_cmd
+ },
+ {
+ .name = "submenuentry",
+ .ini_handler = <k_submenuentry_ini_handler,
+ .fill_theme_defaults = <k_submenuentry_fill_theme_defaults,
+ .uninitialize_theme = <k_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 = <k_scrollbar_ini_handler,
+ .fill_theme_defaults = <k_scrollbar_fill_theme_defaults,
+ .uninitialize_theme = <k_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 = <k_widget_register_keypress,
+ .register_keyrelease = <k_widget_register_keyrelease,
+ .cleanup = <k_widget_cleanup,
+ .cmd = NULL
+ },
+ {
+ /* Handler for window theme. */
+ .name = "window",
+ .ini_handler = <k_window_ini_handler,
+ .fill_theme_defaults = <k_window_fill_theme_defaults,
+ .uninitialize_theme = <k_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 = <k_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 = <k_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