diff -r 000000000000 -r 2483f517c562 ui/motif/text.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/text.c Sun Jan 21 16:30:18 2024 +0100 @@ -0,0 +1,507 @@ +/* + * 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 +#include + +#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 = cxMalloc( + obj->ctx->allocator, + 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->end = NULL; + mgr->cur = NULL; + mgr->length = 0; + mgr->event = 1; + return mgr; +} + +void ui_destroy_undomgr(UiUndoMgr *mgr) { + UiTextBufOp *op = mgr->begin; + while(op) { + UiTextBufOp *nextOp = op->next; + if(op->text) { + free(op->text); + } + free(op); + op = nextOp; + } + free(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) { + UiTextBufOp *elm = mgr->cur->next; + if(elm) { + mgr->cur->next = NULL; + mgr->end = mgr->cur; + while(elm) { + elm->prev = NULL; + UiTextBufOp *next = elm->next; + ui_free_textbuf_op(elm); + elm = next; + } + } + + UiTextBufOp *last_op = mgr->cur; + 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->prev = NULL; + op->next = NULL; + op->type = type; + op->start = txv->startPos; + op->end = txv->endPos + 1; + op->len = length; + op->text = str; + + cx_linked_list_add( + (void**)&mgr->begin, + (void**)&mgr->end, + offsetof(UiTextBufOp, prev), + offsetof(UiTextBufOp, next), + op); + + mgr->cur = 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 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; + 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; + + UiTextBufOp *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; + 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; +} +