]> uap-core.de Git - note.git/commitdiff
add markdown parser and minimal styling
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 9 Mar 2025 17:13:18 +0000 (18:13 +0100)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 9 Mar 2025 17:13:18 +0000 (18:13 +0100)
16 files changed:
application/Makefile
application/application.h
application/editor.c [new file with mode: 0644]
application/editor.h [new file with mode: 0644]
application/gtk-text.c [new file with mode: 0644]
application/gtk-text.h [new file with mode: 0644]
application/note.c
application/notebook.c
application/tests/test-editor.c [new file with mode: 0644]
application/tests/test-editor.h [new file with mode: 0644]
application/tests/testmain.c [new file with mode: 0644]
application/window.c
application/window.h
configure
make/Makefile.mk
make/project.xml

index 0a5d8ec23bcb45d7e1515153fe5ee0a44f6b8ea9..0a94c71b9e896de55982eb4ca4ab09b4e60f0f98 100644 (file)
@@ -31,8 +31,7 @@ include ../config.mk
 
 CFLAGS += -I../ui/ -I../ucx -I..
 
-SRC  = main.c
-SRC += application.c
+SRC  = application.c
 SRC += menu.c
 SRC += window.c
 SRC += types.c
@@ -40,16 +39,31 @@ SRC += store.c
 SRC += store_sqlite.c
 SRC += notebook.c
 SRC += note.c
+SRC += editor.c
+SRC += $(APP_PLATFORM_SRC)
 
-OBJ = $(SRC:%.c=../build/application/%.$(OBJ_EXT))
+OBJ = $(SRC:%.c=../build/application/%$(OBJ_EXT))
+MAIN_OBJ = ../build/application/main$(OBJ_EXT)
+       
+TEST_SRC  = tests/testmain.c
+TEST_SRC += tests/test-editor.c
+       
+TEST_OBJ = $(TEST_SRC:%.c=../build/application/%$(OBJ_EXT))
        
 APP_BIN = ../build/bin/note$(APP_EXT)
+TEST_BIN = ../build/bin/notetest$(APP_EXT)
+
+all: $(APP_BIN) $(TEST_BIN)
 
-all: $(APP_BIN)
+$(APP_BIN): $(MAIN_OBJ) $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a 
+       $(CC) -o $(APP_BIN) $(MAIN_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx -lidav -ldbutils -lmd4c $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS)
 
-$(APP_BIN): $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a 
-       $(CC) -o $(APP_BIN) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx -lidav -ldbutils -lmd4c $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS)
+$(TEST_BIN): $(OBJ) $(TEST_OBJ) $(BUILD_ROOT)/build/lib/libuitk.a 
+       $(CC) -o $(TEST_BIN) $(TEST_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx -lidav -ldbutils -lmd4c $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS)
 
-../build/application/%.$(OBJ_EXT): %.c
+../build/application/%$(OBJ_EXT): %.c
+       $(CC) $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
+       
+../build/application/tests/%$(OBJ_EXT): %.c
        $(CC) $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
 
index d95216cd98a0a3993ea412c8d44bb87f3e3e34d6..1c6d1e288479458a591445eb440f6ff59190699b 100644 (file)
@@ -60,6 +60,7 @@ typedef struct MainWindow {
     
     UIWIDGET splitpane;
     UIWIDGET document_tabview;
+    UIWIDGET textview;
     
     /*
      * is the note list visible (splitpane child 0)
diff --git a/application/editor.c b/application/editor.c
new file mode 100644 (file)
index 0000000..690cb59
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "editor.h"
+
+#include <cx/array_list.h>
+#include <cx/linked_list.h>
+#include <cx/buffer.h>
+#include <cx/tree.h>
+
+
+
+void editor_load_markdown(UiText *text, cxmutstr markdown) {
+    // make sure the textbuf is initialized
+    editor_init_textbuf(text);
+    
+    MDDoc *doc = parse_markdown(cx_strcast(markdown));
+    if(!doc) {
+        ui_set(text, markdown.ptr); // fallback
+        return;
+    }
+    MDDocLinear lin = mddoc_linearization(doc);
+    ui_set(text, lin.content.ptr);
+    editor_apply_styles(text, lin.styles);
+    
+    free(lin.content.ptr);
+    cxListFree(lin.styles);
+    mddoc_free(doc);
+}
+
+
+typedef struct MDParserData {
+    MDDoc *doc;
+    MDPara *p_current;
+    CxList *text_stack;
+    const CxAllocator *a;
+} MDParserData;
+
+/*
+ * get the last element from the parserdata text_stack
+ */
+static MDNode* get_current_textnode(MDParserData *p) {
+    size_t stack_size = cxListSize(p->text_stack);
+    if(stack_size > 0) {
+        return cxListAt(p->text_stack, stack_size-1);
+    }
+    return NULL;
+}
+
+
+static int md_enter_block(MD_BLOCKTYPE type, void* detail, void* userdata) {
+    MDParserData *data = userdata;
+    if(type == MD_BLOCK_DOC) {
+        return 0;
+    }
+    
+    // TODO: li
+    
+    // create empty paragraph node and add it to the document
+    MDPara *p = cxMalloc(data->a, sizeof(MDPara));
+    p->content = NULL;
+    p->next = NULL;
+    p->type = type;
+    
+    if(!data->doc->content) {
+        data->doc->content = p;
+    } else {
+        data->p_current->next = p;
+    }
+    data->p_current = p;
+    
+    // each paragraph starts with an empty text_stack
+    cxListClear(data->text_stack);
+    
+    return 0;
+}
+
+static int md_leave_block(MD_BLOCKTYPE type, void* detail, void* userdata) {
+    
+    return 0;
+}
+
+static void textstack_update_last_element(CxList *stack_list, MDNode *t) {
+    size_t len = cxListSize(stack_list);
+    if(len > 0) {
+        // replace this, when something like cxListSet is available
+        cxListRemove(stack_list, len-1);
+        cxListAdd(stack_list, t);
+    }
+}
+
+static void md_node_add_child(MDNode *node, MDNode *child) {
+    cx_linked_list_add((void**)&node->children_begin, (void**)&node->children_end, -1, offsetof(MDNode, next), child);
+}
+
+// create a node and add it to the current paragraph node tree
+static MDNode* md_node_create(MDParserData *data) {
+    MDNode *current = get_current_textnode(data);
+    size_t len = cxListSize(data->text_stack);
+    
+    MDNode *node = cxCalloc(data->a, 1, sizeof(MDNode));
+    if(current) {
+        if(current->closed) {
+            current->next = node;
+            if(current->parent) {
+                current->parent->children_end = node;
+            }
+            textstack_update_last_element(data->text_stack, node);
+        } else {
+            md_node_add_child(current, node);
+        }
+    } else {
+        data->p_current->content = node;
+    }
+    cxListAdd(data->text_stack, node);
+    return node;
+}
+
+static int md_enter_span(MD_SPANTYPE type, void* detail, void* userdata) {
+    MDParserData *data = userdata;
+    
+    MDNode *node = md_node_create(data);
+    node->type = type;
+    
+    return 0;
+}
+
+static int md_leave_span(MD_SPANTYPE type, void* detail, void* userdata) {
+    MDParserData *data = userdata;
+    size_t len = cxListSize(data->text_stack);
+    MDNode *elm = NULL;
+    if(len > 1) {
+        cxListRemoveAndGet(data->text_stack, len-1, &elm);
+    } else {
+        // don't remove the last element
+        elm = cxListAt(data->text_stack, len-1);
+    }
+    if(elm) {
+        elm->closed = TRUE;
+    }
+    
+    return 0;
+}
+
+static int md_text(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) {
+    MDParserData *data = userdata;
+    MDNode *current = get_current_textnode(data);
+    
+    // if the previous node is a text node, we can merge the nodes
+    // there are 2 cases: the root node is a text node (current->text.ptr)
+    // or the current node is not a text node, but has text children
+    if(current) {
+        MDNode *prev_text_node = NULL;
+        if(current->text.ptr) {
+            prev_text_node = current;
+        } else if(!current->closed && current->children_end && current->children_end->text.ptr) {
+            prev_text_node = current->children_end;
+        }
+        
+        if(prev_text_node) {
+            cxmutstr content = cx_strcat_a(data->a, 2, prev_text_node->text, cx_strn(text, size));
+            cxFree(data->a, prev_text_node->text.ptr);
+            prev_text_node->text = content;
+            return 0;
+        }
+    }
+    
+    MDNode *textnode = cxCalloc(data->a, 1, sizeof(MDNode));
+    textnode->text = cx_strdup_a(data->a, cx_strn(text, size));
+    textnode->closed = TRUE;
+    
+    if(current) {
+        if(current->closed) {
+            current->next = textnode;
+            if(current->parent) {
+                current->parent->children_end = textnode;
+            }
+        } else {
+            md_node_add_child(current, textnode);
+        }
+    } else {
+        // root node
+        data->p_current->content = textnode;
+        cxListAdd(data->text_stack, textnode);
+    }
+    
+    return 0;
+}
+
+MDDoc* parse_markdown(cxstring markdown) {
+    CxMempool *mp = cxMempoolCreateSimple(1024);
+    const CxAllocator *a = mp->allocator;
+    MDDoc *doc = cxMalloc(a, sizeof(MDDoc));
+    doc->mp = mp;
+    doc->content = NULL;
+    doc->size = markdown.length;
+    
+    MDParserData data;
+    data.doc = doc;
+    data.p_current = NULL;
+    data.text_stack = cxArrayListCreateSimple(CX_STORE_POINTERS, 16);
+    data.a = a;
+    
+    MD_PARSER parser = {0};
+    parser.enter_block = md_enter_block;
+    parser.leave_block = md_leave_block;
+    parser.enter_span = md_enter_span;
+    parser.leave_span = md_leave_span;
+    parser.text = md_text;
+    
+    if(md_parse(markdown.ptr, markdown.length, &parser, &data)) {
+        cxMempoolFree(mp);
+        doc = NULL;
+    }
+    cxListFree(data.text_stack);
+    
+    return doc;
+}
+
+void mddoc_free(MDDoc *doc) {
+    cxMempoolFree(doc->mp);
+}
+
+cxstring mdnode_get_text(MDNode *node) {
+    if(node->text.ptr) {
+        return cx_strcast(node->text);
+    }
+    if(node->children_begin && node->children_begin->text.ptr) {
+        return cx_strcast(node->children_begin->text);
+    }
+    
+    return (cxstring){NULL, 0};
+}
+
+static const char* node_style(MDNode *n) {
+    switch(n->type) {
+        case MD_SPAN_EM: return EDITOR_STYLE_EMPHASIS;
+        case MD_SPAN_STRONG: return EDITOR_STYLE_STRONG;
+    }
+    return NULL;
+}
+
+static void linearize_mdnodes(CxBuffer *buf, CxList *sections, MDNode *n, int depth) {
+    if(depth >= MD_MAX_DEPTH) {
+        return;
+    }
+    
+    if(n->text.ptr) {
+        cxBufferWrite(n->text.ptr, 1, n->text.length, buf);
+    } else {
+        size_t start_pos = buf->pos;
+        
+        MDNode *c = n->children_begin;
+        depth++;
+        while(c) {
+            linearize_mdnodes(buf, sections, c, depth);
+            c = c->next;
+        }
+        
+        MDDocStyleSection sec;
+        sec.pos = start_pos;
+        sec.length = buf->pos - start_pos;
+        sec.style = node_style(n);
+        cxListAdd(sections, &sec);
+    }
+}
+
+static const char* paragraph_style(MDPara *p) {
+    // TODO: implement all styles
+    switch(p->type) {
+        case MD_BLOCK_H: return EDITOR_STYLE_HEADING1;
+        default: return EDITOR_STYLE_PARAGRAPH;
+    }
+}
+
+static void linearize_paragraph(CxBuffer *buf, CxList *sections, MDPara *p) {
+    size_t start_pos = buf->pos;
+    
+    MDNode *n = p->content;
+    while(n) {
+        linearize_mdnodes(buf, sections, n, 0);
+        n = n->next;
+    }
+    cxBufferPut(buf, '\n');
+    
+    
+    MDDocStyleSection sec;
+    sec.pos = start_pos;
+    sec.length = buf->pos - start_pos;
+    sec.style = paragraph_style(p);
+    cxListAdd(sections, &sec);
+    
+    cxBufferPut(buf, '\n');
+ }
+
+MDDocLinear mddoc_linearization(MDDoc *doc) {
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, doc->size, NULL, CX_BUFFER_AUTO_EXTEND);
+    CxList *sections = cxArrayListCreateSimple(sizeof(MDDocStyleSection), 64);
+    
+    MDPara *p = doc->content;
+    while(p) {
+        linearize_paragraph(&buf, sections, p);
+        p = p->next;
+    }
+    cxBufferTerminate(&buf);
+    
+    MDDocLinear l;
+    l.content = cx_mutstrn(buf.space, buf.size);
+    l.styles = sections;
+    return l;
+}
diff --git a/application/editor.h b/application/editor.h
new file mode 100644 (file)
index 0000000..246ba1b
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef EDITOR_H
+#define EDITOR_H
+
+#include "application.h"
+#include "../md4c/md4c.h"
+
+#include <cx/mempool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define EDITOR_STYLE_PARAGRAPH "para"
+#define EDITOR_STYLE_HEADING1  "heading1"
+#define EDITOR_STYLE_HEADING2  "heading2"
+#define EDITOR_STYLE_HEADING3  "heading3"
+#define EDITOR_STYLE_HEADING4  "heading4"
+#define EDITOR_STYLE_HEADING5  "heading5"
+#define EDITOR_STYLE_HEADING6  "heading6"
+#define EDITOR_STYLE_QUOTE     "quote"
+#define EDITOR_STYLE_CODE      "code"
+#define EDITOR_STYLE_EMPHASIS  "emphasis"
+#define EDITOR_STYLE_STRONG    "strong"
+    
+    
+#define MD_MAX_DEPTH 50
+    
+typedef struct MDDoc MDDoc;
+typedef struct MDPara MDPara;
+typedef struct MDNode MDNode;
+typedef struct MDDocLinear MDDocLinear;
+typedef struct MDDocStyleSection MDDocStyleSection;
+
+struct MDPara {
+    MD_BLOCKTYPE type;
+    MDNode *content;
+    MDPara *next;
+};
+
+struct MDNode {
+    MDNode *parent;
+    cxmutstr text;
+    cxmutstr link;
+    MD_SPANTYPE type;
+    MDNode *children_begin;
+    MDNode *children_end;
+    MDNode *next;
+    int closed;
+};
+    
+struct MDDoc {
+    CxMempool *mp;
+    MDPara *content;
+    size_t size;
+};
+
+struct MDDocLinear {
+    cxmutstr content;
+    CxList *styles;
+};
+
+struct MDDocStyleSection {
+    int pos;
+    int length;
+    const char *style;
+};
+    
+void editor_init(UiText *text);
+    
+void editor_load_markdown(UiText *text, cxmutstr markdown);
+
+MDDoc* parse_markdown(cxstring markdown);
+void mddoc_free(MDDoc *doc);
+
+cxstring mdnode_get_text(MDNode *node);
+
+MDDocLinear mddoc_linearization(MDDoc *doc);
+    
+    
+// platform specific implementation
+// (gtk-text.c)
+void editor_init_textview(UIWIDGET textview);
+void editor_init_textbuf(UiText *text);
+void editor_apply_styles(UiText *text, CxList /* MDDocStyleSection */ *styles);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EDITOR_H */
+
diff --git a/application/gtk-text.c b/application/gtk-text.c
new file mode 100644 (file)
index 0000000..9d9aa9d
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "gtk-text.h"
+#include "editor.h"
+
+void editor_init_textview(UIWIDGET textview) {
+    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR);
+    
+}
+
+void editor_init_textbuf(UiText *text) {
+    // toolkit internals: data1 is a GtkTextBuffer, but it is only
+    // initialized after UiText is bound to the textview
+    if(!text->data1) {
+        text->data1 = gtk_text_buffer_new(NULL);
+        text->datatype = UI_TEXT_TYPE_BUFFER;
+    }
+    
+    GtkTextBuffer *buf = text->data1;
+    
+    void *initialized = g_object_get_data(G_OBJECT(buf), "md");
+    if(!initialized) {
+        init_textbuf(buf);
+        g_object_set_data(G_OBJECT(buf), "md", text);
+    }
+}
+
+void init_textbuf(GtkTextBuffer *buf) {
+    GtkTextTagTable *tagtable = gtk_text_buffer_get_tag_table(buf);
+    init_tagtable(tagtable);
+    
+    
+}
+
+void init_tagtable(GtkTextTagTable *table) {
+    printf("init_tagtable\n");
+    GtkTextTag *tag;
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_PARAGRAPH);
+    gtk_text_tag_table_add(table, tag);
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_HEADING1);
+    g_object_set(tag, "scale", 1.5, "weight", PANGO_WEIGHT_BOLD, NULL);
+    gtk_text_tag_table_add(table, tag);
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_HEADING2);
+    g_object_set(tag, "scale", 1.4, "weight", PANGO_WEIGHT_BOLD, NULL);
+    gtk_text_tag_table_add(table, tag);
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_HEADING3);
+    g_object_set(tag, "scale", 1.3, "weight", PANGO_WEIGHT_BOLD, NULL);
+    gtk_text_tag_table_add(table, tag);
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_HEADING4);
+    g_object_set(tag, "scale", 1.2, "weight", PANGO_WEIGHT_BOLD, NULL);
+    gtk_text_tag_table_add(table, tag);
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_HEADING5);
+    g_object_set(tag, "scale", 1.1, "weight", PANGO_WEIGHT_BOLD, NULL);
+    gtk_text_tag_table_add(table, tag);
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_HEADING6);
+    g_object_set(tag, "scale", 1.1, "weight", PANGO_WEIGHT_BOLD, NULL);
+    gtk_text_tag_table_add(table, tag);
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_QUOTE);
+    g_object_set(tag, "left-margin", 20, NULL);
+    gtk_text_tag_table_add(table, tag);
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_CODE);
+    g_object_set(tag, "family", "Monospace", "paragraph-background", "#080808", NULL);
+    gtk_text_tag_table_add(table, tag);
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_EMPHASIS);
+    g_object_set(tag, "style", PANGO_STYLE_ITALIC, NULL);
+    gtk_text_tag_table_add(table, tag);
+    
+    tag = gtk_text_tag_new(EDITOR_STYLE_STRONG);
+    g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, NULL);
+    gtk_text_tag_table_add(table, tag);
+}
+
+void editor_apply_styles(UiText *text, CxList /* MDDocStyleSection */ *styles) {
+    GtkTextBuffer *buffer = text->data1;
+    
+    CxIterator i = cxListIterator(styles);
+    cx_foreach(MDDocStyleSection*, sec, i) {
+        GtkTextIter begin, end;
+        gtk_text_buffer_get_iter_at_offset(buffer, &begin, sec->pos);
+        gtk_text_buffer_get_iter_at_offset(buffer, &end, sec->pos + sec->length);
+        if(sec->style) {
+            gtk_text_buffer_apply_tag_by_name(buffer, sec->style, &begin, &end);
+        }
+    }
+}
diff --git a/application/gtk-text.h b/application/gtk-text.h
new file mode 100644 (file)
index 0000000..55591b3
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GTK_TEXT_H
+#define GTK_TEXT_H
+
+#include "editor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void init_textbuf(GtkTextBuffer *buf);
+void init_tagtable(GtkTextTagTable *table);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GTK_TEXT_H */
+
index 1d8fdb3134bf4bd3e2f73731ab4a97f1af9568d1..9b3cbc4a1d8a8935a4b9d59be0dd274a1b9fed14 100644 (file)
@@ -29,6 +29,7 @@
 #include "note.h"
 
 #include "store.h"
+#include "editor.h"
 
 NoteModel* notemodel_create(const CxAllocator *note_allocator) {
     NoteModel *model = ui_document_new(sizeof(NoteModel));
@@ -79,7 +80,7 @@ static void note_content_loaded(UiEvent *event, cxmutstr result, void *userdata)
     note->content = result;
     printf("note content: %s\n", result.ptr);
     if(note->model) {
-        ui_set(note->model->text, result.ptr);
+        editor_load_markdown(note->model->text, result);
     }
 }
 
index 698b62a751dfe695839c54e58db44a641a0e383b..55cdad2aaa7043e72122f3fdb93a13d6e7a6b23e 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "store.h"
 #include "note.h"
+#include "editor.h"
 
 NotebookModel* notebookmodel_create() {
     NotebookModel *model = ui_document_new(sizeof(NotebookModel));
@@ -189,6 +190,9 @@ void notebookmodel_new_note(NotebookModel *model) {
     new_note->parent_id = model->collection->collection_id;
     notebookmodel_attach_note(model, new_note);
     new_note->model->modified = TRUE;
+    // initialize note content
+    // possible to implement something like note templates here
+    editor_load_markdown(new_note->model->text, cx_mutstrn("", 0));
 }
 
 /*
diff --git a/application/tests/test-editor.c b/application/tests/test-editor.c
new file mode 100644 (file)
index 0000000..c9e2a63
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "test-editor.h"
+
+#include "../editor.h"
+
+#include <cx/compare.h>
+
+
+CX_TEST(test_parse_markdown_para) {
+    MDDoc *doc;
+            
+    CX_TEST_DO {
+        doc = parse_markdown(CX_STR("simple paragraph"));
+        CX_TEST_ASSERT(doc);
+        CX_TEST_ASSERT(doc->content);
+        CX_TEST_ASSERT(doc->content->type == MD_BLOCK_P);
+        CX_TEST_ASSERT(doc->content->next == NULL);
+        CX_TEST_ASSERT(doc->content->content);
+        CX_TEST_ASSERT(doc->content->content->text.ptr != NULL);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(doc->content->content->text), CX_STR("simple paragraph")));
+        mddoc_free(doc);
+        
+        doc = parse_markdown(CX_STR("paragraph 0\n\nparagraph 1"));
+        CX_TEST_ASSERT(doc);
+        MDPara *p0 = doc->content;
+        CX_TEST_ASSERT(p0);
+        CX_TEST_ASSERT(p0->type == MD_BLOCK_P);
+        CX_TEST_ASSERT(p0->content);
+        CX_TEST_ASSERT(p0->content->text.ptr != NULL);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(p0->content->text), CX_STR("paragraph 0")));
+        MDPara *p1 = p0->next;
+        CX_TEST_ASSERT(p1);
+        CX_TEST_ASSERT(p1->type == MD_BLOCK_P);
+        CX_TEST_ASSERT(p1->content);
+        CX_TEST_ASSERT(p1->content->text.ptr != NULL);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(p1->content->text), CX_STR("paragraph 1")));
+        mddoc_free(doc);
+        
+        doc = parse_markdown(CX_STR("# heading\n\n    code1\n    code2\n"));
+        CX_TEST_ASSERT(doc);
+        p0 = doc->content;
+        CX_TEST_ASSERT(p0);
+        CX_TEST_ASSERT(p0->type == MD_BLOCK_H);
+        CX_TEST_ASSERT(p0->content);
+        CX_TEST_ASSERT(p0->content->text.ptr != NULL);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(p0->content->text), CX_STR("heading")));
+        p1 = p0->next;
+        CX_TEST_ASSERT(p1);
+        CX_TEST_ASSERT(p1->type == MD_BLOCK_CODE);
+        CX_TEST_ASSERT(p1->content);
+        CX_TEST_ASSERT(p1->content->text.ptr != NULL);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(p1->content->text), CX_STR("code1\ncode2\n")));
+        mddoc_free(doc);
+    }
+}
+
+CX_TEST(test_parse_markdown_formatting_simple) {
+    // Simple linear test of MDText nodes
+            
+    MDDoc *doc;
+    MDPara *p0;
+    MDNode *t0;
+    MDNode *t1;
+    MDNode *t2;
+    
+    CX_TEST_DO {
+        doc = parse_markdown(CX_STR("test **bold** end"));
+        CX_TEST_ASSERT(doc);
+        CX_TEST_ASSERT(doc->content);
+        p0 = doc->content;
+        CX_TEST_ASSERT(p0->type == MD_BLOCK_P);
+        CX_TEST_ASSERT(doc->content->next == NULL);
+        t0 = p0->content;
+        CX_TEST_ASSERT(t0);
+        CX_TEST_ASSERT(t0->text.ptr != NULL);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(t0->text), CX_STR("test ")));
+        t1 = t0->next;
+        CX_TEST_ASSERT(t1);
+        CX_TEST_ASSERT(t1->type == MD_SPAN_STRONG);
+        CX_TEST_ASSERT(!cx_strcmp(mdnode_get_text(t1), CX_STR("bold")));
+        t2 = t1->next;
+        CX_TEST_ASSERT(t2);
+        CX_TEST_ASSERT(t2->text.ptr != NULL);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(t2->text), CX_STR(" end")));
+        mddoc_free(doc);
+        
+        doc = parse_markdown(CX_STR("**bold start** end"));
+        CX_TEST_ASSERT(doc);
+        CX_TEST_ASSERT(doc->content);
+        p0 = doc->content;
+        CX_TEST_ASSERT(p0->type == MD_BLOCK_P);
+        CX_TEST_ASSERT(doc->content->next == NULL);
+        t0 = p0->content;
+        CX_TEST_ASSERT(t0);
+        CX_TEST_ASSERT(t0->type == MD_SPAN_STRONG);
+        CX_TEST_ASSERT(!cx_strcmp(mdnode_get_text(t0), CX_STR("bold start")));
+        t1 = t0->next;
+        CX_TEST_ASSERT(t1);
+        CX_TEST_ASSERT(t1->text.ptr != NULL);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(t1->text), CX_STR(" end")));
+        mddoc_free(doc);
+        
+        doc = parse_markdown(CX_STR("start **bold end**"));
+        CX_TEST_ASSERT(doc);
+        CX_TEST_ASSERT(doc->content);
+        p0 = doc->content;
+        CX_TEST_ASSERT(p0->type == MD_BLOCK_P);
+        CX_TEST_ASSERT(doc->content->next == NULL);
+        t0 = p0->content;
+        CX_TEST_ASSERT(t0);
+        CX_TEST_ASSERT(t0->text.ptr != NULL);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(t0->text), CX_STR("start ")));
+        t1 = t0->next;
+        CX_TEST_ASSERT(t1);
+        CX_TEST_ASSERT(t1->type == MD_SPAN_STRONG);
+        CX_TEST_ASSERT(!cx_strcmp(mdnode_get_text(t1), CX_STR("bold end")));
+        mddoc_free(doc);
+    }
+}
+
+CX_TEST(test_parse_markdown_formatting_nested) {
+    // nested MDText nodes
+    
+    MDDoc *doc;
+    MDPara *p0;
+    MDNode *t0;
+    MDNode *t1;
+    MDNode *t2;
+    MDNode *t3;
+    MDNode *t4;
+    
+    CX_TEST_DO {
+        doc = parse_markdown(CX_STR("test *begin __bold__ end*"));
+        CX_TEST_ASSERT(doc);
+        CX_TEST_ASSERT(doc->content);
+        p0 = doc->content;
+        CX_TEST_ASSERT(p0->type == MD_BLOCK_P);
+        CX_TEST_ASSERT(doc->content->next == NULL);
+        t0 = p0->content;
+        
+        CX_TEST_ASSERT(t0);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(t0->text), CX_STR("test ")));
+        t1 = t0->next;
+        CX_TEST_ASSERT(t1);
+        CX_TEST_ASSERT(t1->text.ptr == NULL);
+        CX_TEST_ASSERT(t1->type == MD_SPAN_EM);
+        CX_TEST_ASSERT(t1->next == NULL);
+        t2 = t1->children_begin;
+        CX_TEST_ASSERT(t2);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(t2->text), CX_STR("begin ")));
+        t3 = t2->next;
+        CX_TEST_ASSERT(t3);
+        CX_TEST_ASSERT(t3->text.ptr == NULL);
+        CX_TEST_ASSERT(t3->type == MD_SPAN_STRONG);
+        CX_TEST_ASSERT(!cx_strcmp(mdnode_get_text(t3), CX_STR("bold")));
+        t4 = t3->next;
+        CX_TEST_ASSERT(t4);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strcast(t4->text), CX_STR(" end")));
+        mddoc_free(doc);
+        
+    }
+}
+
+static int str_eq(const char *s1, const char *s2) {
+    if(!s1) {
+        return s1 == s2;
+    }
+    if(!s2) {
+        return s1 == s2;
+    }
+    return !strcmp(s1, s2);
+}
+
+static int section_style_sort(MDDocStyleSection *s1, MDDocStyleSection *s2) {
+    return cx_cmp_int(&s1->pos, &s2->pos);
+}
+
+CX_TEST(test_mddoc_linearization) {
+    MDDoc *doc = parse_markdown(CX_STR("# heading 1\n\ntest *begin __bold__ text* end"));
+    
+    CX_TEST_DO {
+        MDDocLinear md = mddoc_linearization(doc);
+        // don't compare strings with cx_strcmp because currently the last paragraph
+        // is terminated with 2 line breaks, but this behavior can change in the future
+        CX_TEST_ASSERT(cx_strprefix(cx_strcast(md.content), CX_STR("heading 1\n\ntest begin bold text end")));
+        
+        CX_TEST_ASSERT(md.styles);
+        CX_TEST_ASSERT(cxListSize(md.styles) == 4);
+        md.styles->collection.cmpfunc = (cx_compare_func)section_style_sort;
+        cxListSort(md.styles); // sort list, because there is no defined order for the style sections list
+        MDDocStyleSection *styles = cxListAt(md.styles, 0);
+        CX_TEST_ASSERT(str_eq(styles[0].style, EDITOR_STYLE_HEADING1));
+        CX_TEST_ASSERT(styles[0].pos == 0);
+        CX_TEST_ASSERT(styles[0].length == 10);
+        CX_TEST_ASSERT(str_eq(styles[1].style, EDITOR_STYLE_PARAGRAPH));
+        CX_TEST_ASSERT(styles[1].pos == 11);
+        CX_TEST_ASSERT(styles[1].length > 24);
+        CX_TEST_ASSERT(str_eq(styles[2].style, EDITOR_STYLE_EMPHASIS));
+        CX_TEST_ASSERT(styles[2].pos == 16);
+        CX_TEST_ASSERT(styles[2].length == 15);
+        CX_TEST_ASSERT(str_eq(styles[3].style, EDITOR_STYLE_STRONG));
+        CX_TEST_ASSERT(styles[3].pos == 22);
+        CX_TEST_ASSERT(styles[3].length == 4);
+        
+        free(md.content.ptr);
+        cxListFree(md.styles);
+    }
+}
diff --git a/application/tests/test-editor.h b/application/tests/test-editor.h
new file mode 100644 (file)
index 0000000..6894146
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEST_EDITOR_H
+#define TEST_EDITOR_H
+
+#include <cx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+CX_TEST(test_parse_markdown_para);
+CX_TEST(test_parse_markdown_formatting_simple);
+CX_TEST(test_parse_markdown_formatting_nested);
+CX_TEST(test_mddoc_linearization);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_EDITOR_H */
+
diff --git a/application/tests/testmain.c b/application/tests/testmain.c
new file mode 100644 (file)
index 0000000..67a05c2
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <cx/test.h>
+
+#include "test-editor.h"
+
+int main(int argc, char **argv) {
+    CxTestSuite *suite = cx_test_suite_new("note");
+    
+    cx_test_register(suite, test_parse_markdown_para);
+    cx_test_register(suite, test_parse_markdown_formatting_simple);
+    cx_test_register(suite, test_parse_markdown_formatting_nested);
+    cx_test_register(suite, test_mddoc_linearization);
+    
+    cx_test_run_stdout(suite);
+    int err = suite->failure > 0 ? 1 : 0;
+    cx_test_suite_free(suite);  
+    return err;
+}
index 10a1d480ce7d88d5e45b9590e0ba472b3def174b..64ca1188ac316019eeb80afb04e6a184286c11a8 100644 (file)
@@ -82,7 +82,7 @@ void window_create() {
                         ui_button(obj, .icon = "insert-image");
                         ui_button(obj, .icon = "insert-link");
                     }
-                    ui_textarea(obj, .varname = "note_text", .vfill = TRUE, .hfill = TRUE, .hexpand = TRUE, .vexpand = TRUE, .colspan = 2, .groups = UI_GROUPS(APP_STATE_NOTE_SELECTED), .fill = UI_ON);
+                    wdata->textview = ui_textarea(obj, .varname = "note_text", .vfill = TRUE, .hfill = TRUE, .hexpand = TRUE, .vexpand = TRUE, .colspan = 2, .groups = UI_GROUPS(APP_STATE_NOTE_SELECTED), .fill = UI_ON);
                 } 
             }
         }
index 1df1709893c86ad8d7663e759b8d4de31188c9ad..2f8f42f52cee5cad7f0baf884037e28ad25000c3 100644 (file)
@@ -57,6 +57,10 @@ void action_note_selected(UiEvent *event, void *userdata);
 void action_note_activated(UiEvent *event, void *userdata);
 
 
+
+
+
+
 #ifdef __cplusplus
 }
 #endif
index 27c5b605a9577f31eeeeab12e5ad322f576d21c9..7dd94355ba4fd72e9c65a9f144dbb63066077c2b 100755 (executable)
--- a/configure
+++ b/configure
@@ -901,6 +901,7 @@ checkopt_toolkit_libadwaita()
     cat >> "$TEMP_DIR/make.mk" << __EOF__
 TOOLKIT = gtk
 GTKOBJ = draw_cairo.o
+APP_PLATFORM_SRC = gtk-text.c
 __EOF__
     return 0
 }
@@ -919,6 +920,7 @@ checkopt_toolkit_gtk4()
     cat >> "$TEMP_DIR/make.mk" << __EOF__
 TOOLKIT = gtk
 GTKOBJ = draw_cairo.o
+APP_PLATFORM_SRC = gtk-text.c
 __EOF__
     return 0
 }
@@ -937,6 +939,8 @@ checkopt_toolkit_gtk3()
     cat >> "$TEMP_DIR/make.mk" << __EOF__
 TOOLKIT = gtk
 GTKOBJ = draw_cairo.o
+GTKOBJ = draw_cairo.o
+APP_PLATFORM_SRC = gtk-text.c
 __EOF__
     return 0
 }
@@ -951,6 +955,7 @@ checkopt_toolkit_cocoa()
     fi
     cat >> "$TEMP_DIR/make.mk" << __EOF__
 TOOLKIT = cocoa
+APP_PLATFORM_SRC = cocoa-text.m
 __EOF__
     return 0
 }
index 8690d35babbabb2f1a8b25d44549fd9b0100f967..a870e959c44c5f285428d5b5ef099e1b14895a57 100644 (file)
@@ -32,7 +32,7 @@ BUILD_ROOT = ./
 include config.mk
 
 BUILD_DIRS = build/bin build/lib
-BUILD_DIRS += build/application build/ucx build/libidav build/dbutils build/md4c
+BUILD_DIRS += build/application build/application/tests build/ucx build/libidav build/dbutils build/md4c
 BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT)
 
 all: $(BUILD_DIRS) ucx dbutils ui libidav application
index 24ca8443b92f6cb5bcc81110cc179e63ed187e3f..1123a0dd23ce1a37ae4ff5d1a04ad03647518422 100644 (file)
                                <dependencies>libadwaita,webkitgtk6</dependencies>
                                <make>TOOLKIT = gtk</make>
                                <make>GTKOBJ = draw_cairo.o</make>
+                               <make>APP_PLATFORM_SRC = gtk-text.c</make>
                        </value>
                        <value str="gtk4">
                                <dependencies>gtk4,webkitgtk6</dependencies>
                                <make>TOOLKIT = gtk</make>
                                <make>GTKOBJ = draw_cairo.o</make>
+                               <make>APP_PLATFORM_SRC = gtk-text.c</make>
                        </value>
                        <value str="gtk3">
                                <dependencies>gtk3,webkit2gtk4</dependencies>
                                <make>TOOLKIT = gtk</make>
                                <make>GTKOBJ = draw_cairo.o</make>
+                               <make>GTKOBJ = draw_cairo.o</make>
+                               <make>APP_PLATFORM_SRC = gtk-text.c</make>
                        </value>
                        <value str="cocoa">
                                <dependencies>cocoa</dependencies>
                                <make>TOOLKIT = cocoa</make>
+                               <make>APP_PLATFORM_SRC = cocoa-text.m</make>
                        </value>
                        <!--
                        <value str="gtk2">