ui/motif/text.c

changeset 0
804d8803eade
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/motif/text.c	Wed Dec 09 11:32:01 2020 +0100
@@ -0,0 +1,488 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 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 "text.h"
+#include "container.h"
+
+
+UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
+    UiContainer *ct = uic_get_current_container(obj);
+    int n = 0;
+    Arg args[16];
+    
+    //XtSetArg(args[n], XmNeditable, TRUE);
+    //n++;
+    XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT);
+    n++;
+    
+    Widget parent = ct->prepare(ct, args, &n, TRUE);
+    Widget text_area = XmCreateScrolledText(parent, "text_area", args, n);
+    ct->add(ct, XtParent(text_area));
+    XtManageChild(text_area);
+    
+    UiTextArea *uitext = ucx_mempool_malloc(
+            obj->ctx->mempool,
+            sizeof(UiTextArea));
+    uitext->ctx = obj->ctx;
+    uitext->last_selection_state = 0;
+    XtAddCallback(
+                text_area,
+                XmNmotionVerifyCallback,
+                (XtCallbackProc)ui_text_selection_callback,
+                uitext);
+    
+    // bind value
+    if(var->value) {
+        UiText *value = var->value;
+        if(value->value.ptr) {
+            XmTextSetString(text_area, value->value.ptr);
+            value->value.free(value->value.ptr);
+        }
+        
+        value->set = ui_textarea_set;
+        value->get = ui_textarea_get;
+        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->value.ptr = NULL;
+        value->obj = text_area;
+        
+        if(!value->undomgr) {
+            value->undomgr = ui_create_undomgr();
+        }
+        
+        XtAddCallback(
+                text_area,
+                XmNmodifyVerifyCallback,
+                (XtCallbackProc)ui_text_modify_callback,
+                var);
+    }
+    
+    return text_area;
+}
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
+    UiVar *var = malloc(sizeof(UiVar));
+    var->value = value;
+    var->type = UI_VAR_SPECIAL;
+    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);
+    }
+    char *str = XmTextGetString(text->obj);
+    text->value.ptr = str;
+    text->value.free = (ui_freefunc)XtFree;
+    return str;
+}
+
+void ui_textarea_set(UiText *text, char *str) {
+    XmTextSetString(text->obj, str);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+    int length = end - begin;
+    char *str = XtMalloc(length + 1);
+    XmTextGetSubstring(text->obj, begin, length, length + 1, str);
+    text->value.ptr = str;
+    text->value.free = (ui_freefunc)XtFree;
+    return str;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+    text->value.ptr = NULL;
+    XmTextInsert(text->obj, pos, str);
+    if(text->value.ptr) {
+        text->value.free(text->value.ptr);
+    }
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+    XmTextSetInsertionPosition(text->obj, pos);
+}
+
+int ui_textarea_position(UiText *text) {
+    long begin;
+    long end;
+    XmTextGetSelectionPosition(text->obj, &begin, &end);
+    text->pos = begin;
+    return text->pos;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+    XmTextGetSelectionPosition(text->obj, (long*)begin, (long*)end);
+}
+
+int ui_textarea_length(UiText *text) {
+    return (int)XmTextGetLastPosition(text->obj);
+}
+
+
+void ui_text_set(UiText *text, char *str) {
+    if(text->set) {
+        text->set(text, str);
+    } else {
+        if(text->value.ptr) {
+            text->value.free(text->value.ptr);
+        }
+        text->value.ptr = XtNewString(str);
+        text->value.free = (ui_freefunc)XtFree;
+    }
+}
+
+char* ui_text_get(UiText *text) {
+    if(text->get) {
+        return text->get(text);
+    } else {
+        return text->value.ptr;
+    }
+}
+
+
+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_text_selection_callback(
+        Widget widget,
+        UiTextArea *textarea,
+        XtPointer data)
+{
+    long left = 0;
+    long right = 0;
+    XmTextGetSelectionPosition(widget, &left, &right);
+    int sel = left < right ? 1 : 0;
+    if(sel != textarea->last_selection_state) {
+        if(sel) {
+            ui_set_group(textarea->ctx, UI_GROUP_SELECTION);
+        } else {
+            ui_unset_group(textarea->ctx, UI_GROUP_SELECTION);
+        }
+    }
+    textarea->last_selection_state = sel;
+}
+
+void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data) {
+    UiText *value = var->value;
+    if(!value->obj) {
+        // TODO: bug, fix
+        return;
+    }
+    if(!value->undomgr) {
+        value->undomgr = ui_create_undomgr();
+    }
+    
+    XmTextVerifyCallbackStruct *txv = (XmTextVerifyCallbackStruct*)data;
+    int type = txv->text->length > 0 ? UI_TEXTBUF_INSERT : UI_TEXTBUF_DELETE;
+    UiUndoMgr *mgr = value->undomgr;
+    if(!mgr->event) {
+        return;
+    }
+    
+    char *text = txv->text->ptr;
+    int length = txv->text->length;
+    
+    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;
+            }
+        }
+        
+        if(type == UI_TEXTBUF_INSERT) {
+            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 *str;
+    if(type == UI_TEXTBUF_INSERT) {
+        str = malloc(length + 1);
+        memcpy(str, text, length);
+        str[length] = 0;
+    } else {
+        length = txv->endPos - txv->startPos;
+        str = malloc(length + 1);
+        XmTextGetSubstring(value->obj, txv->startPos, length, length+1, str);
+    }
+    
+    UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->type = type;
+    op->start = txv->startPos;
+    op->end = txv->endPos + 1;
+    op->len = length;
+    op->text = str;
+    
+    UcxList *elm = ucx_list_append(NULL, op);
+    mgr->cur = elm;
+    mgr->begin = ucx_list_concat(mgr->begin, elm);
+}
+
+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_free_textbuf_op(UiTextBufOp *op) {
+    if(op->text) {
+        free(op->text);
+    }
+    free(op);
+}
+
+
+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: {
+                XmTextReplace(value->obj, op->start, op->end, "");
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                XmTextInsert(value->obj, op->start, op->text);
+                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: {
+                XmTextInsert(value->obj, op->start, op->text);
+                break;
+            }
+            case UI_TEXTBUF_DELETE: {
+                XmTextReplace(value->obj, op->start, op->end, "");
+                break;
+            }
+        }
+        mgr->event = 1;
+        mgr->cur = elm;
+    }
+}
+
+
+/* ------------------------- textfield ------------------------- */
+
+static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
+    UiContainer *ct = uic_get_current_container(obj);
+    int n = 0;
+    Arg args[16];
+    XtSetArg(args[n], XmNeditMode, XmSINGLE_LINE_EDIT);
+    n++;
+    if(width > 0) {
+        XtSetArg(args[n], XmNcolumns, width / 2 + 1);
+        n++;
+    }
+    if(frameless) {
+        XtSetArg(args[n], XmNshadowThickness, 0);
+        n++;
+    }
+    if(password) {
+        // TODO
+    }
+    
+    Widget parent = ct->prepare(ct, args, &n, FALSE);
+    Widget textfield = XmCreateText(parent, "text_field", args, n);
+    ct->add(ct, textfield);
+    XtManageChild(textfield);
+    
+    // bind value
+    if(value) {
+        if(value->value.ptr) {
+            XmTextSetString(textfield, value->value.ptr);
+            value->value.free(value->value.ptr);
+        }
+        
+        value->set = ui_textfield_set;
+        value->get = ui_textfield_get;
+        value->value.ptr = NULL;
+        value->obj = textfield;
+    }
+    
+    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) {
+        UiString *value = var->value;
+        return ui_textfield(obj, value);
+    } else {
+        // TODO: error
+    }
+    return NULL;
+}
+
+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);
+    }
+    char *value = XmTextGetString(str->obj);
+    str->value.ptr = value;
+    str->value.free = (ui_freefunc)XtFree;
+    return value;
+}
+
+void ui_textfield_set(UiString *str, char *value) {
+    XmTextSetString(str->obj, value);
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    str->value.ptr = NULL;
+}
+

mercurial