/* * 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 #include #include #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; } UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) { return gtk_bin_get_child(GTK_BIN(textarea)); } 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 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; var->from = NULL; var->from_ctx = NULL; } 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; } }