UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2017 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 33 #include "text.h" 34 #include "container.h" 35 36 37 static void selection_handler( 38 GtkTextBuffer *buf, 39 GtkTextIter *location, 40 GtkTextMark *mark, 41 UiTextArea *textview) 42 { 43 const char *mname = gtk_text_mark_get_name(mark); 44 if(mname) { 45 GtkTextIter begin; 46 GtkTextIter end; 47 int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end); 48 if(sel != textview->last_selection_state) { 49 if(sel) { 50 ui_set_group(textview->ctx, UI_GROUP_SELECTION); 51 } else { 52 ui_unset_group(textview->ctx, UI_GROUP_SELECTION); 53 } 54 } 55 textview->last_selection_state = sel; 56 } 57 } 58 59 UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) { 60 GtkWidget *text_area = gtk_text_view_new(); 61 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR); 62 g_signal_connect( 63 text_area, 64 "realize", 65 G_CALLBACK(ui_textarea_realize_event), 66 NULL); 67 68 UiTextArea *uitext = malloc(sizeof(UiTextArea)); 69 uitext->ctx = obj->ctx; 70 uitext->var = var; 71 uitext->last_selection_state = 0; 72 73 g_signal_connect( 74 text_area, 75 "destroy", 76 G_CALLBACK(ui_textarea_destroy), 77 uitext); 78 79 GtkWidget *scroll_area = gtk_scrolled_window_new (NULL, NULL); 80 gtk_scrolled_window_set_policy( 81 GTK_SCROLLED_WINDOW(scroll_area), 82 GTK_POLICY_AUTOMATIC, 83 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 84 gtk_container_add(GTK_CONTAINER(scroll_area), text_area); 85 86 // font and padding 87 PangoFontDescription *font; 88 font = pango_font_description_from_string("Monospace"); 89 gtk_widget_modify_font(text_area, font); 90 pango_font_description_free(font); 91 92 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2); 93 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2); 94 95 // add 96 UiContainer *ct = uic_get_current_container(obj); 97 ct->add(ct, scroll_area, TRUE); 98 99 // bind value 100 UiText *value = var->value; 101 if(value) { 102 GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area)); 103 104 if(value->value.ptr) { 105 gtk_text_buffer_set_text(buf, value->value.ptr, -1); 106 value->value.free(value->value.ptr); 107 } 108 109 value->get = ui_textarea_get; 110 value->set = ui_textarea_set; 111 value->getsubstr = ui_textarea_getsubstr; 112 value->insert = ui_textarea_insert; 113 value->setposition = ui_textarea_setposition; 114 value->position = ui_textarea_position; 115 value->selection = ui_textarea_selection; 116 value->length = ui_textarea_length; 117 value->remove = ui_textarea_remove; 118 value->value.ptr = NULL; 119 value->value.free = NULL; 120 value->obj = buf; 121 if(!value->undomgr) { 122 value->undomgr = ui_create_undomgr(); 123 } 124 125 g_signal_connect( 126 buf, 127 "changed", 128 G_CALLBACK(ui_textbuf_changed), 129 uitext); 130 131 // register undo manager 132 g_signal_connect( 133 buf, 134 "insert-text", 135 G_CALLBACK(ui_textbuf_insert), 136 var); 137 g_signal_connect( 138 buf, 139 "delete-range", 140 G_CALLBACK(ui_textbuf_delete), 141 var); 142 g_signal_connect( 143 buf, 144 "mark-set", 145 G_CALLBACK(selection_handler), 146 uitext); 147 } 148 149 return scroll_area; 150 } 151 152 void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) { 153 ui_destroy_boundvar(textarea->ctx, textarea->var); 154 free(textarea); 155 } 156 157 UIWIDGET ui_textarea(UiObject *obj, UiText *value) { 158 UiVar *var = malloc(sizeof(UiVar)); 159 var->value = value; 160 var->type = UI_VAR_SPECIAL; 161 var->from = NULL; 162 var->from_ctx = NULL; 163 return ui_textarea_var(obj, var); 164 } 165 166 UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) { 167 UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT); 168 if(var) { 169 return ui_textarea_var(obj, var); 170 } else { 171 // TODO: error 172 } 173 return NULL; 174 } 175 176 UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) { 177 return gtk_bin_get_child(GTK_BIN(textarea)); 178 } 179 180 char* ui_textarea_get(UiText *text) { 181 if(text->value.ptr) { 182 text->value.free(text->value.ptr); 183 } 184 GtkTextBuffer *buf = text->obj; 185 GtkTextIter start; 186 GtkTextIter end; 187 gtk_text_buffer_get_bounds(buf, &start, &end); 188 char *str = gtk_text_buffer_get_text(buf, &start, &end, FALSE); 189 text->value.ptr = g_strdup(str); 190 text->value.free = (ui_freefunc)g_free; 191 return str; 192 } 193 194 void ui_textarea_set(UiText *text, char *str) { 195 gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1); 196 if(text->value.ptr) { 197 text->value.free(text->value.ptr); 198 } 199 text->value.ptr = NULL; 200 text->value.free = NULL; 201 } 202 203 char* ui_textarea_getsubstr(UiText *text, int begin, int end) { 204 if(text->value.ptr) { 205 text->value.free(text->value.ptr); 206 } 207 GtkTextBuffer *buf = text->obj; 208 GtkTextIter ib; 209 GtkTextIter ie; 210 gtk_text_buffer_get_iter_at_offset(text->obj, &ib, begin); 211 gtk_text_buffer_get_iter_at_offset(text->obj, &ie, end); 212 char *str = gtk_text_buffer_get_text(buf, &ib, &ie, FALSE); 213 text->value.ptr = g_strdup(str); 214 text->value.free = (ui_freefunc)g_free; 215 return str; 216 } 217 218 void ui_textarea_insert(UiText *text, int pos, char *str) { 219 GtkTextIter offset; 220 gtk_text_buffer_get_iter_at_offset(text->obj, &offset, pos); 221 gtk_text_buffer_insert(text->obj, &offset, str, -1); 222 if(text->value.ptr) { 223 text->value.free(text->value.ptr); 224 } 225 text->value.ptr = NULL; 226 text->value.free = NULL; 227 } 228 229 void ui_textarea_setposition(UiText *text, int pos) { 230 GtkTextIter iter; 231 gtk_text_buffer_get_iter_at_offset(text->obj, &iter, pos); 232 gtk_text_buffer_place_cursor(text->obj, &iter); 233 } 234 235 int ui_textarea_position(UiText *text) { 236 GtkTextIter begin; 237 GtkTextIter end; 238 gtk_text_buffer_get_selection_bounds(text->obj, &begin, &end); 239 text->pos = gtk_text_iter_get_offset(&begin); 240 return text->pos; 241 } 242 243 void ui_textarea_selection(UiText *text, int *begin, int *end) { 244 GtkTextIter b; 245 GtkTextIter e; 246 gtk_text_buffer_get_selection_bounds(text->obj, &b, &e); 247 *begin = gtk_text_iter_get_offset(&b); 248 *end = gtk_text_iter_get_offset(&e); 249 } 250 251 int ui_textarea_length(UiText *text) { 252 GtkTextBuffer *buf = text->obj; 253 GtkTextIter start; 254 GtkTextIter end; 255 gtk_text_buffer_get_bounds(buf, &start, &end); 256 return gtk_text_iter_get_offset(&end); 257 } 258 259 void ui_textarea_remove(UiText *text, int begin, int end) { 260 GtkTextBuffer *buf = text->obj; 261 GtkTextIter ib; 262 GtkTextIter ie; 263 gtk_text_buffer_get_iter_at_offset(buf, &ib, begin); 264 gtk_text_buffer_get_iter_at_offset(buf, &ie, end); 265 gtk_text_buffer_delete(buf, &ib, &ie); 266 } 267 268 void ui_textarea_realize_event(GtkWidget *widget, gpointer data) { 269 gtk_widget_grab_focus(widget); 270 } 271 272 273 274 void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) { 275 UiText *value = textarea->var->value; 276 if(value->observers) { 277 UiEvent e; 278 e.obj = textarea->ctx->obj; 279 e.window = e.obj->window; 280 e.document = textarea->ctx->document; 281 e.eventdata = value; 282 e.intval = 0; 283 ui_notify_evt(value->observers, &e); 284 } 285 } 286 287 // undo manager functions 288 289 void ui_textbuf_insert( 290 GtkTextBuffer *textbuffer, 291 GtkTextIter *location, 292 char *text, 293 int length, 294 void *data) 295 { 296 UiVar *var = data; 297 UiText *value = var->value; 298 if(!value->undomgr) { 299 value->undomgr = ui_create_undomgr(); 300 } 301 UiUndoMgr *mgr = value->undomgr; 302 if(!mgr->event) { 303 return; 304 } 305 306 if(mgr->cur) { 307 UcxList *elm = mgr->cur->next; 308 if(elm) { 309 mgr->cur->next = NULL; 310 while(elm) { 311 elm->prev = NULL; 312 UcxList *next = elm->next; 313 ui_free_textbuf_op(elm->data); 314 free(elm); 315 elm = next; 316 } 317 } 318 319 UiTextBufOp *last_op = mgr->cur->data; 320 if( 321 last_op->type == UI_TEXTBUF_INSERT && 322 ui_check_insertstr(last_op->text, last_op->len, text, length) == 0) 323 { 324 // append text to last op 325 int ln = last_op->len; 326 char *newtext = malloc(ln + length + 1); 327 memcpy(newtext, last_op->text, ln); 328 memcpy(newtext+ln, text, length); 329 newtext[ln+length] = '\0'; 330 331 last_op->text = newtext; 332 last_op->len = ln + length; 333 last_op->end += length; 334 335 return; 336 } 337 } 338 339 char *dpstr = malloc(length + 1); 340 memcpy(dpstr, text, length); 341 dpstr[length] = 0; 342 343 UiTextBufOp *op = malloc(sizeof(UiTextBufOp)); 344 op->type = UI_TEXTBUF_INSERT; 345 op->start = gtk_text_iter_get_offset(location); 346 op->end = op->start+length; 347 op->len = length; 348 op->text = dpstr; 349 350 UcxList *elm = ucx_list_append(NULL, op); 351 mgr->cur = elm; 352 mgr->begin = ucx_list_concat(mgr->begin, elm); 353 } 354 355 void ui_textbuf_delete( 356 GtkTextBuffer *textbuffer, 357 GtkTextIter *start, 358 GtkTextIter *end, 359 void *data) 360 { 361 UiVar *var = data; 362 UiText *value = var->value; 363 if(!value->undomgr) { 364 value->undomgr = ui_create_undomgr(); 365 } 366 UiUndoMgr *mgr = value->undomgr; 367 if(!mgr->event) { 368 return; 369 } 370 371 if(mgr->cur) { 372 UcxList *elm = mgr->cur->next; 373 if(elm) { 374 mgr->cur->next = NULL; 375 while(elm) { 376 elm->prev = NULL; 377 UcxList *next = elm->next; 378 ui_free_textbuf_op(elm->data); 379 free(elm); 380 elm = next; 381 } 382 } 383 } 384 385 char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE); 386 387 UiTextBufOp *op = malloc(sizeof(UiTextBufOp)); 388 op->type = UI_TEXTBUF_DELETE; 389 op->start = gtk_text_iter_get_offset(start); 390 op->end = gtk_text_iter_get_offset(end); 391 op->len = op->end - op->start; 392 393 char *dpstr = malloc(op->len + 1); 394 memcpy(dpstr, text, op->len); 395 dpstr[op->len] = 0; 396 op->text = dpstr; 397 398 UcxList *elm = ucx_list_append(NULL, op); 399 mgr->cur = elm; 400 mgr->begin = ucx_list_concat(mgr->begin, elm); 401 } 402 403 UiUndoMgr* ui_create_undomgr() { 404 UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr)); 405 mgr->begin = NULL; 406 mgr->cur = NULL; 407 mgr->length = 0; 408 mgr->event = 1; 409 return mgr; 410 } 411 412 void ui_free_textbuf_op(UiTextBufOp *op) { 413 if(op->text) { 414 free(op->text); 415 } 416 free(op); 417 } 418 419 int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) { 420 // return 1 if oldstr + newstr are one word 421 422 int has_space = 0; 423 for(int i=0;i<oldlen;i++) { 424 if(oldstr[i] < 33) { 425 has_space = 1; 426 break; 427 } 428 } 429 430 for(int i=0;i<newlen;i++) { 431 if(has_space && newstr[i] > 32) { 432 return 1; 433 } 434 } 435 436 return 0; 437 } 438 439 void ui_text_undo(UiText *value) { 440 UiUndoMgr *mgr = value->undomgr; 441 442 if(mgr->cur) { 443 UiTextBufOp *op = mgr->cur->data; 444 mgr->event = 0; 445 switch(op->type) { 446 case UI_TEXTBUF_INSERT: { 447 GtkTextIter begin; 448 GtkTextIter end; 449 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); 450 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); 451 gtk_text_buffer_delete(value->obj, &begin, &end); 452 break; 453 } 454 case UI_TEXTBUF_DELETE: { 455 GtkTextIter begin; 456 GtkTextIter end; 457 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); 458 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); 459 gtk_text_buffer_insert(value->obj, &begin, op->text, op->len); 460 break; 461 } 462 } 463 mgr->event = 1; 464 mgr->cur = mgr->cur->prev; 465 } 466 } 467 468 void ui_text_redo(UiText *value) { 469 UiUndoMgr *mgr = value->undomgr; 470 471 UcxList *elm = NULL; 472 if(mgr->cur) { 473 if(mgr->cur->next) { 474 elm = mgr->cur->next; 475 } 476 } else if(mgr->begin) { 477 elm = mgr->begin; 478 } 479 480 if(elm) { 481 UiTextBufOp *op = elm->data; 482 mgr->event = 0; 483 switch(op->type) { 484 case UI_TEXTBUF_INSERT: { 485 GtkTextIter begin; 486 GtkTextIter end; 487 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); 488 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); 489 gtk_text_buffer_insert(value->obj, &begin, op->text, op->len); 490 break; 491 } 492 case UI_TEXTBUF_DELETE: { 493 GtkTextIter begin; 494 GtkTextIter end; 495 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); 496 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); 497 gtk_text_buffer_delete(value->obj, &begin, &end); 498 break; 499 } 500 } 501 mgr->event = 1; 502 mgr->cur = elm; 503 } 504 } 505 506 507 static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) { 508 GtkWidget *textfield = gtk_entry_new(); 509 510 UiTextField *uitext = malloc(sizeof(UiTextField)); 511 uitext->ctx = obj->ctx; 512 uitext->var = var; 513 514 g_signal_connect( 515 textfield, 516 "destroy", 517 G_CALLBACK(ui_textfield_destroy), 518 uitext); 519 520 if(width > 0) { 521 gtk_entry_set_width_chars(GTK_ENTRY(textfield), width); 522 } 523 if(frameless) { 524 // TODO: gtk2legacy workaroud 525 gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE); 526 } 527 if(password) { 528 gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE); 529 } 530 531 UiContainer *ct = uic_get_current_container(obj); 532 ct->add(ct, textfield, FALSE); 533 534 if(var) { 535 UiString *value = var->value; 536 if(value->value.ptr) { 537 gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr); 538 value->value.free(value->value.ptr); 539 value->value.ptr = NULL; 540 value->value.free = NULL; 541 } 542 543 value->get = ui_textfield_get; 544 value->set = ui_textfield_set; 545 value->value.ptr = NULL; 546 value->value.free = NULL; 547 value->obj = GTK_ENTRY(textfield); 548 549 g_signal_connect( 550 textfield, 551 "changed", 552 G_CALLBACK(ui_textfield_changed), 553 uitext); 554 } 555 556 return textfield; 557 } 558 559 static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) { 560 UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING); 561 if(var) { 562 return create_textfield_var(obj, width, frameless, password, var); 563 } else { 564 // TODO: error 565 } 566 return NULL; 567 } 568 569 static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) { 570 UiVar *var = NULL; 571 if(value) { 572 var = malloc(sizeof(UiVar)); 573 var->value = value; 574 var->type = UI_VAR_SPECIAL; 575 var->from = NULL; 576 var->from_ctx = NULL; 577 } 578 return create_textfield_var(obj, width, frameless, password, var); 579 } 580 581 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) { 582 if(textfield->var) { 583 ui_destroy_boundvar(textfield->ctx, textfield->var); 584 } 585 free(textfield); 586 } 587 588 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) { 589 UiString *value = textfield->var->value; 590 if(value->observers) { 591 UiEvent e; 592 e.obj = textfield->ctx->obj; 593 e.window = e.obj->window; 594 e.document = textfield->ctx->document; 595 e.eventdata = value; 596 e.intval = 0; 597 ui_notify_evt(value->observers, &e); 598 } 599 } 600 601 UIWIDGET ui_textfield(UiObject *obj, UiString *value) { 602 return create_textfield(obj, 0, FALSE, FALSE, value); 603 } 604 605 UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) { 606 return create_textfield_nv(obj, 0, FALSE, FALSE, varname); 607 } 608 609 UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) { 610 return create_textfield(obj, width, FALSE, FALSE, value); 611 } 612 613 UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) { 614 return create_textfield_nv(obj, width, FALSE, FALSE, varname); 615 } 616 617 UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) { 618 return create_textfield(obj, 0, TRUE, FALSE, value); 619 } 620 621 UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) { 622 return create_textfield_nv(obj, 0, TRUE, FALSE, varname); 623 } 624 625 UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) { 626 return create_textfield(obj, 0, FALSE, TRUE, value); 627 } 628 629 UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) { 630 return create_textfield_nv(obj, 0, FALSE, TRUE, varname); 631 } 632 633 UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) { 634 return create_textfield(obj, width, FALSE, TRUE, value); 635 } 636 637 UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) { 638 return create_textfield_nv(obj, width, FALSE, TRUE, varname); 639 } 640 641 char* ui_textfield_get(UiString *str) { 642 if(str->value.ptr) { 643 str->value.free(str->value.ptr); 644 } 645 str->value.ptr = g_strdup(gtk_entry_get_text(str->obj)); 646 str->value.free = (ui_freefunc)g_free; 647 return str->value.ptr; 648 } 649 650 void ui_textfield_set(UiString *str, char *value) { 651 gtk_entry_set_text(str->obj, value); 652 if(str->value.ptr) { 653 str->value.free(str->value.ptr); 654 str->value.ptr = NULL; 655 str->value.free = NULL; 656 } 657 } 658