ui/gtk/text.c

changeset 0
804d8803eade
child 2
ea89bbb0c4c8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/text.c	Wed Dec 09 11:32:01 2020 +0100
@@ -0,0 +1,651 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "text.h"
+#include "container.h"
+
+
+static void selection_handler(
+        GtkTextBuffer *buf,
+        GtkTextIter *location,
+        GtkTextMark *mark,
+        UiTextArea *textview)
+{
+    const char *mname = gtk_text_mark_get_name(mark);
+    if(mname) {
+        GtkTextIter begin;
+        GtkTextIter end;
+        int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end);
+        if(sel != textview->last_selection_state) {
+            if(sel) {
+                ui_set_group(textview->ctx, UI_GROUP_SELECTION);
+            } else {
+                ui_unset_group(textview->ctx, UI_GROUP_SELECTION);
+            }
+        }
+        textview->last_selection_state = sel;
+    }
+}
+
+UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
+    GtkWidget *text_area = gtk_text_view_new();
+    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR);
+    g_signal_connect(
+            text_area,
+            "realize",
+            G_CALLBACK(ui_textarea_realize_event),
+            NULL);
+    
+    UiTextArea *uitext = malloc(sizeof(UiTextArea));
+    uitext->ctx = obj->ctx;
+    uitext->var = var;
+    uitext->last_selection_state = 0;
+    
+    g_signal_connect(
+                text_area,
+                "destroy",
+                G_CALLBACK(ui_textarea_destroy),
+                uitext);
+    
+    GtkWidget *scroll_area = gtk_scrolled_window_new (NULL, NULL);
+    gtk_scrolled_window_set_policy(
+            GTK_SCROLLED_WINDOW(scroll_area),
+            GTK_POLICY_AUTOMATIC,
+            GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
+    gtk_container_add(GTK_CONTAINER(scroll_area), text_area);
+    
+    // font and padding
+    PangoFontDescription *font;
+    font = pango_font_description_from_string("Monospace");
+    gtk_widget_modify_font(text_area, font);
+    pango_font_description_free(font);
+    
+    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2);
+    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2);
+    
+    // add
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, scroll_area, TRUE);
+    
+    // bind value
+    UiText *value = var->value;
+    if(value) {
+        GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area));
+        
+        if(value->value.ptr) {
+            gtk_text_buffer_set_text(buf, value->value.ptr, -1);
+            value->value.free(value->value.ptr);
+        }
+        
+        value->get = ui_textarea_get;
+        value->set = ui_textarea_set;
+        value->getsubstr = ui_textarea_getsubstr;
+        value->insert = ui_textarea_insert;
+        value->setposition = ui_textarea_setposition;
+        value->position = ui_textarea_position;
+        value->selection = ui_textarea_selection;
+        value->length = ui_textarea_length;
+        value->remove = ui_textarea_remove;
+        value->value.ptr = NULL;
+        value->value.free = NULL;
+        value->obj = buf;
+        if(!value->undomgr) {
+            value->undomgr = ui_create_undomgr();
+        }
+        
+        g_signal_connect(
+                buf,
+                "changed",
+                G_CALLBACK(ui_textbuf_changed),
+                uitext);
+        
+        // register undo manager
+        g_signal_connect(
+                buf,
+                "insert-text",
+                G_CALLBACK(ui_textbuf_insert),
+                var);
+        g_signal_connect(
+                buf,
+                "delete-range",
+                G_CALLBACK(ui_textbuf_delete),
+                var); 
+        g_signal_connect(
+                buf,
+                "mark-set",
+                G_CALLBACK(selection_handler),
+                uitext);
+    }
+    
+    return scroll_area;
+}
+
+void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) {
+    ui_destroy_boundvar(textarea->ctx, textarea->var);
+    free(textarea);
+}
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = value;
+    var->type = UI_VAR_SPECIAL;
+    var->from = NULL;
+    var->from_ctx = NULL;
+    return ui_textarea_var(obj, var);
+}
+
+UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
+    if(var) {
+        return ui_textarea_var(obj, var);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+char* ui_textarea_get(UiText *text) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    GtkTextBuffer *buf = text->obj;
+    GtkTextIter start;
+    GtkTextIter end;
+    gtk_text_buffer_get_bounds(buf, &start, &end);
+    char *str = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
+    text->value.ptr = g_strdup(str);
+    text->value.free = (ui_freefunc)g_free;
+    return str;
+}
+
+void ui_textarea_set(UiText *text, char *str) {
+    gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+    text->value.free = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    GtkTextBuffer *buf = text->obj;
+    GtkTextIter ib;
+    GtkTextIter ie;
+    gtk_text_buffer_get_iter_at_offset(text->obj, &ib, begin);
+    gtk_text_buffer_get_iter_at_offset(text->obj, &ie, end);
+    char *str = gtk_text_buffer_get_text(buf, &ib, &ie, FALSE);
+    text->value.ptr = g_strdup(str);
+    text->value.free = (ui_freefunc)g_free;
+    return str;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+    GtkTextIter offset;
+    gtk_text_buffer_get_iter_at_offset(text->obj, &offset, pos);
+    gtk_text_buffer_insert(text->obj, &offset, str, -1);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+    text->value.free = NULL;
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+    GtkTextIter iter;
+    gtk_text_buffer_get_iter_at_offset(text->obj, &iter, pos);
+    gtk_text_buffer_place_cursor(text->obj, &iter);
+}
+
+int ui_textarea_position(UiText *text) {
+    GtkTextIter begin;
+    GtkTextIter end;
+    gtk_text_buffer_get_selection_bounds(text->obj, &begin, &end);
+    text->pos = gtk_text_iter_get_offset(&begin);
+    return text->pos;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+    GtkTextIter b;
+    GtkTextIter e;
+    gtk_text_buffer_get_selection_bounds(text->obj, &b, &e);
+    *begin = gtk_text_iter_get_offset(&b);
+    *end = gtk_text_iter_get_offset(&e);
+}
+
+int ui_textarea_length(UiText *text) {
+    GtkTextBuffer *buf = text->obj;
+    GtkTextIter start;
+    GtkTextIter end;
+    gtk_text_buffer_get_bounds(buf, &start, &end);
+    return gtk_text_iter_get_offset(&end);
+}
+
+void ui_textarea_remove(UiText *text, int begin, int end) {
+    GtkTextBuffer *buf = text->obj;
+    GtkTextIter ib;
+    GtkTextIter ie;
+    gtk_text_buffer_get_iter_at_offset(buf, &ib, begin);
+    gtk_text_buffer_get_iter_at_offset(buf, &ie, end);
+    gtk_text_buffer_delete(buf, &ib, &ie);
+}
+
+void ui_textarea_realize_event(GtkWidget *widget, gpointer data) {
+    gtk_widget_grab_focus(widget);
+}
+
+
+
+void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) {
+    UiText *value = textarea->var->value;
+    if(value->observers) {
+        UiEvent e;
+        e.obj = textarea->ctx->obj;
+        e.window = e.obj->window;
+        e.document = textarea->ctx->document;
+        e.eventdata = value;
+        e.intval = 0;
+        ui_notify_evt(value->observers, &e);
+    }
+}
+
+// undo manager functions
+
+void ui_textbuf_insert(
+        GtkTextBuffer *textbuffer,
+        GtkTextIter *location,
+        char *text,
+        int length,
+        void *data)
+{
+    UiVar *var = data;
+    UiText *value = var->value;
+    if(!value->undomgr) {
+        value->undomgr = ui_create_undomgr();
+    }
+    UiUndoMgr *mgr = value->undomgr;
+    if(!mgr->event) {
+        return;
+    }
+    
+    if(mgr->cur) {
+        UcxList *elm = mgr->cur->next;
+        if(elm) {
+            mgr->cur->next = NULL;
+            while(elm) {
+                elm->prev = NULL;   
+                UcxList *next = elm->next;
+                ui_free_textbuf_op(elm->data);
+                free(elm);
+                elm = next;
+            }
+        }
+        
+        UiTextBufOp *last_op = mgr->cur->data;
+        if(
+            last_op->type == UI_TEXTBUF_INSERT &&
+            ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
+        {
+            // append text to last op       
+            int ln = last_op->len;
+            char *newtext = malloc(ln + length + 1);
+            memcpy(newtext, last_op->text, ln);
+            memcpy(newtext+ln, text, length);
+            newtext[ln+length] = '\0';
+            
+            last_op->text = newtext;
+            last_op->len = ln + length;
+            last_op->end += length;
+            
+            return;
+        }
+    }
+    
+    char *dpstr = malloc(length + 1);
+    memcpy(dpstr, text, length);
+    dpstr[length] = 0;
+    
+    UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->type = UI_TEXTBUF_INSERT;
+    op->start = gtk_text_iter_get_offset(location);
+    op->end = op->start+length;
+    op->len = length;
+    op->text = dpstr;
+    
+    UcxList *elm = ucx_list_append(NULL, op);
+    mgr->cur = elm;
+    mgr->begin = ucx_list_concat(mgr->begin, elm);
+}
+
+void ui_textbuf_delete(
+        GtkTextBuffer *textbuffer,
+        GtkTextIter *start,
+        GtkTextIter *end,
+        void *data)
+{
+    UiVar *var = data;
+    UiText *value = var->value;
+    if(!value->undomgr) {
+        value->undomgr = ui_create_undomgr();
+    }
+    UiUndoMgr *mgr = value->undomgr;
+    if(!mgr->event) {
+        return;
+    }
+    
+    if(mgr->cur) {
+        UcxList *elm = mgr->cur->next;
+        if(elm) {
+            mgr->cur->next = NULL;
+            while(elm) {
+                elm->prev = NULL;   
+                UcxList *next = elm->next;
+                ui_free_textbuf_op(elm->data);
+                free(elm);
+                elm = next;
+            }
+        }
+    }
+    
+    char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE);
+    
+    UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->type = UI_TEXTBUF_DELETE;
+    op->start = gtk_text_iter_get_offset(start);
+    op->end = gtk_text_iter_get_offset(end);
+    op->len = op->end - op->start;
+    
+    char *dpstr = malloc(op->len + 1);
+    memcpy(dpstr, text, op->len);
+    dpstr[op->len] = 0;
+    op->text = dpstr;
+    
+    UcxList *elm = ucx_list_append(NULL, op);
+    mgr->cur = elm;
+    mgr->begin = ucx_list_concat(mgr->begin, elm);
+}
+
+UiUndoMgr* ui_create_undomgr() {
+    UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
+    mgr->begin = NULL;
+    mgr->cur = NULL;
+    mgr->length = 0;
+    mgr->event = 1;
+    return mgr;
+}
+
+void ui_free_textbuf_op(UiTextBufOp *op) {
+    if(op->text) {
+        free(op->text);
+    }
+    free(op);
+}
+
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
+    // return 1 if oldstr + newstr are one word
+    
+    int has_space = 0;
+    for(int i=0;i<oldlen;i++) {
+        if(oldstr[i] < 33) {
+            has_space = 1;
+            break;
+        }
+    }
+    
+    for(int i=0;i<newlen;i++) {
+        if(has_space && newstr[i] > 32) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+void ui_text_undo(UiText *value) {
+    UiUndoMgr *mgr = value->undomgr;
+    
+    if(mgr->cur) {
+        UiTextBufOp *op = mgr->cur->data;
+        mgr->event = 0;
+        switch(op->type) {
+            case UI_TEXTBUF_INSERT: {
+                GtkTextIter begin;
+                GtkTextIter end;
+                gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+                gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+                gtk_text_buffer_delete(value->obj, &begin, &end);
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                GtkTextIter begin;
+                GtkTextIter end;
+                gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+                gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+                gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
+                break;
+            }
+        }
+        mgr->event = 1;
+        mgr->cur = mgr->cur->prev;
+    }
+}
+
+void ui_text_redo(UiText *value) {
+    UiUndoMgr *mgr = value->undomgr;
+    
+    UcxList *elm = NULL;
+    if(mgr->cur) {
+        if(mgr->cur->next) {
+            elm = mgr->cur->next;
+        }
+    } else if(mgr->begin) {
+        elm = mgr->begin;
+    }
+    
+    if(elm) {
+        UiTextBufOp *op = elm->data;
+        mgr->event = 0;
+        switch(op->type) {
+            case UI_TEXTBUF_INSERT: {
+                GtkTextIter begin;
+                GtkTextIter end;
+                gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+                gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+                gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                GtkTextIter begin;
+                GtkTextIter end;
+                gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+                gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+                gtk_text_buffer_delete(value->obj, &begin, &end);
+                break;
+            }
+        }
+        mgr->event = 1;
+        mgr->cur = elm;
+    }
+}
+
+
+static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) {
+    GtkWidget *textfield = gtk_entry_new();
+    
+    UiTextField *uitext = malloc(sizeof(UiTextField));
+    uitext->ctx = obj->ctx;
+    uitext->var = var;
+    
+    g_signal_connect(
+                textfield,
+                "destroy",
+                G_CALLBACK(ui_textfield_destroy),
+                uitext);
+    
+    if(width > 0) {
+        gtk_entry_set_width_chars(GTK_ENTRY(textfield), width);
+    }
+    if(frameless) {
+        // TODO: gtk2legacy workaroud
+        gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE);
+    }
+    if(password) {
+        gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
+    }
+    
+    UiContainer *ct = uic_get_current_container(obj);
+    ct->add(ct, textfield, FALSE);
+    
+    if(var) {
+        UiString *value = var->value;
+        if(value->value.ptr) {
+            gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr);
+            value->value.free(value->value.ptr);
+            value->value.ptr = NULL;
+            value->value.free = NULL;
+        }
+        
+        value->get = ui_textfield_get;
+        value->set = ui_textfield_set;
+        value->value.ptr = NULL;
+        value->value.free = NULL;
+        value->obj = GTK_ENTRY(textfield);
+        
+        g_signal_connect(
+                textfield,
+                "changed",
+                G_CALLBACK(ui_textfield_changed),
+                uitext);
+    }
+    
+    return textfield;
+}
+
+static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
+    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
+    if(var) {
+        return create_textfield_var(obj, width, frameless, password, var);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
+    UiVar *var = NULL;
+    if(value) {
+        var = malloc(sizeof(UiVar));
+        var->value = value;
+        var->type = UI_VAR_SPECIAL;
+    }
+    return create_textfield_var(obj, width, frameless, password, var);
+}
+
+void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
+    if(textfield->var) {
+        ui_destroy_boundvar(textfield->ctx, textfield->var);
+    }
+    free(textfield);
+}
+
+void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
+    UiString *value = textfield->var->value;
+    if(value->observers) {
+        UiEvent e;
+        e.obj = textfield->ctx->obj;
+        e.window = e.obj->window;
+        e.document = textfield->ctx->document;
+        e.eventdata = value;
+        e.intval = 0;
+        ui_notify_evt(value->observers, &e);
+    }
+}
+
+UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
+    return create_textfield(obj, width, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
+    return create_textfield_nv(obj, width, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, TRUE, FALSE, value);
+}
+
+UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
+}
+
+UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
+    return create_textfield(obj, 0, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
+    return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
+}
+
+UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
+    return create_textfield(obj, width, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
+    return create_textfield_nv(obj, width, FALSE, TRUE, varname);
+}
+
+char* ui_textfield_get(UiString *str) {
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    str->value.ptr = g_strdup(gtk_entry_get_text(str->obj));
+    str->value.free = (ui_freefunc)g_free;
+    return str->value.ptr;
+}
+
+void ui_textfield_set(UiString *str, char *value) {
+    gtk_entry_set_text(str->obj, value);
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+        str->value.ptr = NULL;
+        str->value.free = NULL;
+    }
+}

mercurial