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 #include <cx/printf.h> 37 38 #include <gdk/gdkkeysyms.h> 39 40 41 #include "../common/types.h" 42 43 static void selection_handler( 44 GtkTextBuffer *buf, 45 GtkTextIter *location, 46 GtkTextMark *mark, 47 UiTextArea *textview) 48 { 49 const char *mname = gtk_text_mark_get_name(mark); 50 if(mname) { 51 GtkTextIter begin; 52 GtkTextIter end; 53 int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end); 54 if(sel != textview->last_selection_state) { 55 if(sel) { 56 ui_set_group(textview->ctx, UI_GROUP_SELECTION); 57 } else { 58 ui_unset_group(textview->ctx, UI_GROUP_SELECTION); 59 } 60 } 61 textview->last_selection_state = sel; 62 } 63 } 64 65 UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) { 66 UiObject* current = uic_current_obj(obj); 67 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_TEXT); 68 69 GtkWidget *text_area = gtk_text_view_new(); 70 ui_set_name_and_style(text_area, args.name, args.style_class); 71 ui_set_widget_groups(obj->ctx, text_area, args.groups); 72 73 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR); 74 g_signal_connect( 75 text_area, 76 "realize", 77 G_CALLBACK(ui_textarea_realize_event), 78 NULL); 79 80 UiTextArea *uitext = malloc(sizeof(UiTextArea)); 81 uitext->obj = obj; 82 uitext->ctx = obj->ctx; 83 uitext->var = var; 84 uitext->last_selection_state = 0; 85 uitext->onchange = args.onchange; 86 uitext->onchangedata = args.onchangedata; 87 88 g_signal_connect( 89 text_area, 90 "destroy", 91 G_CALLBACK(ui_textarea_destroy), 92 uitext); 93 94 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); 95 gtk_scrolled_window_set_policy( 96 GTK_SCROLLED_WINDOW(scroll_area), 97 GTK_POLICY_AUTOMATIC, 98 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 99 SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area); 100 101 // font and padding 102 //PangoFontDescription *font; 103 //font = pango_font_description_from_string("Monospace"); 104 //gtk_widget_modify_font(text_area, font); // TODO 105 //pango_font_description_free(font); 106 107 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2); 108 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2); 109 110 // add 111 UI_APPLY_LAYOUT1(current, args); 112 current->container->add(current->container, scroll_area, TRUE); 113 114 // bind value 115 if(var) { 116 UiText *value = var->value; 117 GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area)); 118 119 if(value->value.ptr) { 120 gtk_text_buffer_set_text(buf, value->value.ptr, -1); 121 value->value.free(value->value.ptr); 122 } 123 124 value->get = ui_textarea_get; 125 value->set = ui_textarea_set; 126 value->getsubstr = ui_textarea_getsubstr; 127 value->insert = ui_textarea_insert; 128 value->setposition = ui_textarea_setposition; 129 value->position = ui_textarea_position; 130 value->selection = ui_textarea_selection; 131 value->length = ui_textarea_length; 132 value->remove = ui_textarea_remove; 133 value->value.ptr = NULL; 134 value->value.free = NULL; 135 value->obj = buf; 136 if(!value->undomgr) { 137 value->undomgr = ui_create_undomgr(); 138 } 139 140 g_signal_connect( 141 buf, 142 "changed", 143 G_CALLBACK(ui_textbuf_changed), 144 uitext); 145 146 // register undo manager 147 g_signal_connect( 148 buf, 149 "insert-text", 150 G_CALLBACK(ui_textbuf_insert), 151 var); 152 g_signal_connect( 153 buf, 154 "delete-range", 155 G_CALLBACK(ui_textbuf_delete), 156 var); 157 g_signal_connect( 158 buf, 159 "mark-set", 160 G_CALLBACK(selection_handler), 161 uitext); 162 } 163 164 return scroll_area; 165 } 166 167 void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) { 168 if(textarea->var) { 169 ui_destroy_boundvar(textarea->ctx, textarea->var); 170 } 171 free(textarea); 172 } 173 174 UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) { 175 return SCROLLEDWINDOW_GET_CHILD(textarea); 176 } 177 178 char* ui_textarea_get(UiText *text) { 179 if(text->value.ptr) { 180 text->value.free(text->value.ptr); 181 } 182 GtkTextBuffer *buf = text->obj; 183 GtkTextIter start; 184 GtkTextIter end; 185 gtk_text_buffer_get_bounds(buf, &start, &end); 186 char *str = gtk_text_buffer_get_text(buf, &start, &end, FALSE); 187 text->value.ptr = g_strdup(str); 188 text->value.free = (ui_freefunc)g_free; 189 return str; 190 } 191 192 void ui_textarea_set(UiText *text, const char *str) { 193 gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1); 194 if(text->value.ptr) { 195 text->value.free(text->value.ptr); 196 } 197 text->value.ptr = NULL; 198 text->value.free = NULL; 199 } 200 201 char* ui_textarea_getsubstr(UiText *text, int begin, int end) { 202 if(text->value.ptr) { 203 text->value.free(text->value.ptr); 204 } 205 GtkTextBuffer *buf = text->obj; 206 GtkTextIter ib; 207 GtkTextIter ie; 208 gtk_text_buffer_get_iter_at_offset(text->obj, &ib, begin); 209 gtk_text_buffer_get_iter_at_offset(text->obj, &ie, end); 210 char *str = gtk_text_buffer_get_text(buf, &ib, &ie, FALSE); 211 text->value.ptr = g_strdup(str); 212 text->value.free = (ui_freefunc)g_free; 213 return str; 214 } 215 216 void ui_textarea_insert(UiText *text, int pos, char *str) { 217 GtkTextIter offset; 218 gtk_text_buffer_get_iter_at_offset(text->obj, &offset, pos); 219 gtk_text_buffer_insert(text->obj, &offset, str, -1); 220 if(text->value.ptr) { 221 text->value.free(text->value.ptr); 222 } 223 text->value.ptr = NULL; 224 text->value.free = NULL; 225 } 226 227 void ui_textarea_setposition(UiText *text, int pos) { 228 GtkTextIter iter; 229 gtk_text_buffer_get_iter_at_offset(text->obj, &iter, pos); 230 gtk_text_buffer_place_cursor(text->obj, &iter); 231 } 232 233 int ui_textarea_position(UiText *text) { 234 GtkTextIter begin; 235 GtkTextIter end; 236 gtk_text_buffer_get_selection_bounds(text->obj, &begin, &end); 237 text->pos = gtk_text_iter_get_offset(&begin); 238 return text->pos; 239 } 240 241 void ui_textarea_selection(UiText *text, int *begin, int *end) { 242 GtkTextIter b; 243 GtkTextIter e; 244 gtk_text_buffer_get_selection_bounds(text->obj, &b, &e); 245 *begin = gtk_text_iter_get_offset(&b); 246 *end = gtk_text_iter_get_offset(&e); 247 } 248 249 int ui_textarea_length(UiText *text) { 250 GtkTextBuffer *buf = text->obj; 251 GtkTextIter start; 252 GtkTextIter end; 253 gtk_text_buffer_get_bounds(buf, &start, &end); 254 return gtk_text_iter_get_offset(&end); 255 } 256 257 void ui_textarea_remove(UiText *text, int begin, int end) { 258 GtkTextBuffer *buf = text->obj; 259 GtkTextIter ib; 260 GtkTextIter ie; 261 gtk_text_buffer_get_iter_at_offset(buf, &ib, begin); 262 gtk_text_buffer_get_iter_at_offset(buf, &ie, end); 263 gtk_text_buffer_delete(buf, &ib, &ie); 264 } 265 266 void ui_textarea_realize_event(GtkWidget *widget, gpointer data) { 267 gtk_widget_grab_focus(widget); 268 } 269 270 271 272 void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) { 273 UiText *value = textarea->var->value; 274 275 UiEvent e; 276 e.obj = textarea->obj; 277 e.window = e.obj->window; 278 e.document = textarea->ctx->document; 279 e.eventdata = value; 280 e.intval = 0; 281 282 if(textarea->onchange) { 283 textarea->onchange(&e, textarea->onchangedata); 284 } 285 286 if(value->observers) { 287 ui_notify_evt(value->observers, &e); 288 } 289 } 290 291 // undo manager functions 292 293 void ui_textbuf_insert( 294 GtkTextBuffer *textbuffer, 295 GtkTextIter *location, 296 char *text, 297 int length, 298 void *data) 299 { 300 UiVar *var = data; 301 UiText *value = var->value; 302 if(!value->undomgr) { 303 value->undomgr = ui_create_undomgr(); 304 } 305 UiUndoMgr *mgr = value->undomgr; 306 if(!mgr->event) { 307 return; 308 } 309 310 if(mgr->cur) { 311 UiTextBufOp *elm = mgr->cur->next; 312 if(elm) { 313 mgr->cur->next = NULL; 314 mgr->end = mgr->cur; 315 while(elm) { 316 elm->prev = NULL; 317 UiTextBufOp *next = elm->next; 318 ui_free_textbuf_op(elm); 319 elm = next; 320 } 321 } 322 323 UiTextBufOp *last_op = mgr->cur; 324 if( 325 last_op->type == UI_TEXTBUF_INSERT && 326 ui_check_insertstr(last_op->text, last_op->len, text, length) == 0) 327 { 328 // append text to last op 329 int ln = last_op->len; 330 char *newtext = malloc(ln + length + 1); 331 memcpy(newtext, last_op->text, ln); 332 memcpy(newtext+ln, text, length); 333 newtext[ln+length] = '\0'; 334 335 last_op->text = newtext; 336 last_op->len = ln + length; 337 last_op->end += length; 338 339 return; 340 } 341 } 342 343 char *dpstr = malloc(length + 1); 344 memcpy(dpstr, text, length); 345 dpstr[length] = 0; 346 347 UiTextBufOp *op = malloc(sizeof(UiTextBufOp)); 348 op->prev = NULL; 349 op->next = NULL; 350 op->type = UI_TEXTBUF_INSERT; 351 op->start = gtk_text_iter_get_offset(location); 352 op->end = op->start+length; 353 op->len = length; 354 op->text = dpstr; 355 356 cx_linked_list_add( 357 (void**)&mgr->begin, 358 (void**)&mgr->end, 359 offsetof(UiTextBufOp, prev), 360 offsetof(UiTextBufOp, next), 361 op); 362 363 mgr->cur = op; 364 } 365 366 void ui_textbuf_delete( 367 GtkTextBuffer *textbuffer, 368 GtkTextIter *start, 369 GtkTextIter *end, 370 void *data) 371 { 372 UiVar *var = data; 373 UiText *value = var->value; 374 if(!value->undomgr) { 375 value->undomgr = ui_create_undomgr(); 376 } 377 UiUndoMgr *mgr = value->undomgr; 378 if(!mgr->event) { 379 return; 380 } 381 382 if(mgr->cur) { 383 UiTextBufOp *elm = mgr->cur->next; 384 if(elm) { 385 mgr->cur->next = NULL; 386 mgr->end = mgr->cur; 387 while(elm) { 388 elm->prev = NULL; 389 UiTextBufOp *next = elm->next; 390 ui_free_textbuf_op(elm); 391 elm = next; 392 } 393 } 394 } 395 396 char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE); 397 398 UiTextBufOp *op = malloc(sizeof(UiTextBufOp)); 399 op->prev = NULL; 400 op->next = NULL; 401 op->type = UI_TEXTBUF_DELETE; 402 op->start = gtk_text_iter_get_offset(start); 403 op->end = gtk_text_iter_get_offset(end); 404 op->len = op->end - op->start; 405 406 char *dpstr = malloc(op->len + 1); 407 memcpy(dpstr, text, op->len); 408 dpstr[op->len] = 0; 409 op->text = dpstr; 410 411 cx_linked_list_add( 412 (void**)&mgr->begin, 413 (void**)&mgr->end, 414 offsetof(UiTextBufOp, prev), 415 offsetof(UiTextBufOp, next), 416 op); 417 418 mgr->cur = op; 419 } 420 421 UiUndoMgr* ui_create_undomgr() { 422 UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr)); 423 mgr->begin = NULL; 424 mgr->end = NULL; 425 mgr->cur = NULL; 426 mgr->length = 0; 427 mgr->event = 1; 428 return mgr; 429 } 430 431 void ui_destroy_undomgr(UiUndoMgr *mgr) { 432 UiTextBufOp *op = mgr->begin; 433 while(op) { 434 UiTextBufOp *nextOp = op->next; 435 if(op->text) { 436 free(op->text); 437 } 438 free(op); 439 op = nextOp; 440 } 441 free(mgr); 442 } 443 444 void ui_free_textbuf_op(UiTextBufOp *op) { 445 if(op->text) { 446 free(op->text); 447 } 448 free(op); 449 } 450 451 int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) { 452 // return 1 if oldstr + newstr are one word 453 454 int has_space = 0; 455 for(int i=0;i<oldlen;i++) { 456 if(oldstr[i] < 33) { 457 has_space = 1; 458 break; 459 } 460 } 461 462 for(int i=0;i<newlen;i++) { 463 if(has_space && newstr[i] > 32) { 464 return 1; 465 } 466 } 467 468 return 0; 469 } 470 471 void ui_text_undo(UiText *value) { 472 UiUndoMgr *mgr = value->undomgr; 473 474 if(mgr->cur) { 475 UiTextBufOp *op = mgr->cur; 476 mgr->event = 0; 477 switch(op->type) { 478 case UI_TEXTBUF_INSERT: { 479 GtkTextIter begin; 480 GtkTextIter end; 481 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); 482 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); 483 gtk_text_buffer_delete(value->obj, &begin, &end); 484 break; 485 } 486 case UI_TEXTBUF_DELETE: { 487 GtkTextIter begin; 488 GtkTextIter end; 489 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); 490 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); 491 gtk_text_buffer_insert(value->obj, &begin, op->text, op->len); 492 break; 493 } 494 } 495 mgr->event = 1; 496 mgr->cur = mgr->cur->prev; 497 } 498 } 499 500 void ui_text_redo(UiText *value) { 501 UiUndoMgr *mgr = value->undomgr; 502 503 UiTextBufOp *elm = NULL; 504 if(mgr->cur) { 505 if(mgr->cur->next) { 506 elm = mgr->cur->next; 507 } 508 } else if(mgr->begin) { 509 elm = mgr->begin; 510 } 511 512 if(elm) { 513 UiTextBufOp *op = elm; 514 mgr->event = 0; 515 switch(op->type) { 516 case UI_TEXTBUF_INSERT: { 517 GtkTextIter begin; 518 GtkTextIter end; 519 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); 520 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); 521 gtk_text_buffer_insert(value->obj, &begin, op->text, op->len); 522 break; 523 } 524 case UI_TEXTBUF_DELETE: { 525 GtkTextIter begin; 526 GtkTextIter end; 527 gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start); 528 gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end); 529 gtk_text_buffer_delete(value->obj, &begin, &end); 530 break; 531 } 532 } 533 mgr->event = 1; 534 mgr->cur = elm; 535 } 536 } 537 538 539 540 541 static UIWIDGET create_textfield(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs args) { 542 GtkWidget *textfield = gtk_entry_new(); 543 ui_set_name_and_style(textfield, args.name, args.style_class); 544 ui_set_widget_groups(obj->ctx, textfield, args.groups); 545 546 UiObject* current = uic_current_obj(obj); 547 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING); 548 549 UiTextField *uitext = malloc(sizeof(UiTextField)); 550 uitext->obj = obj; 551 uitext->var = var; 552 uitext->onchange = args.onchange; 553 uitext->onchangedata = args.onchangedata; 554 uitext->onactivate = args.onactivate; 555 uitext->onactivatedata = args.onactivatedata; 556 557 g_signal_connect( 558 textfield, 559 "destroy", 560 G_CALLBACK(ui_textfield_destroy), 561 uitext); 562 563 if(args.width > 0) { 564 // TODO: gtk4 565 #if GTK_MAJOR_VERSION <= 3 566 gtk_entry_set_width_chars(GTK_ENTRY(textfield), args.width); 567 #endif 568 } 569 if(frameless) { 570 // TODO: gtk2legacy workaroud 571 gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE); 572 } 573 if(password) { 574 gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE); 575 } 576 577 UI_APPLY_LAYOUT1(current, args); 578 current->container->add(current->container, textfield, FALSE); 579 580 if(var) { 581 UiString *value = var->value; 582 if(value->value.ptr) { 583 ENTRY_SET_TEXT(textfield, value->value.ptr); 584 value->value.free(value->value.ptr); 585 value->value.ptr = NULL; 586 value->value.free = NULL; 587 } 588 589 value->get = ui_textfield_get; 590 value->set = ui_textfield_set; 591 value->value.ptr = NULL; 592 value->value.free = NULL; 593 value->obj = GTK_ENTRY(textfield); 594 } 595 596 if(args.onchange || var) { 597 g_signal_connect( 598 textfield, 599 "changed", 600 G_CALLBACK(ui_textfield_changed), 601 uitext); 602 } 603 604 if(args.onactivate) { 605 g_signal_connect( 606 textfield, 607 "activate", 608 G_CALLBACK(ui_textfield_activate), 609 uitext); 610 } 611 612 return textfield; 613 } 614 615 UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) { 616 return create_textfield(obj, FALSE, FALSE, args); 617 } 618 619 UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) { 620 return create_textfield(obj, TRUE, FALSE, args); 621 } 622 623 UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) { 624 return create_textfield(obj, FALSE, TRUE, args); 625 } 626 627 628 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) { 629 free(textfield); 630 } 631 632 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) { 633 UiString *value = textfield->var->value; 634 635 UiEvent e; 636 e.obj = textfield->obj; 637 e.window = e.obj->window; 638 e.document = textfield->obj->ctx->document; 639 e.eventdata = value; 640 e.intval = 0; 641 642 if(textfield->onchange) { 643 textfield->onchange(&e, textfield->onchangedata); 644 } 645 646 if(textfield->var) { 647 ui_notify_evt(value->observers, &e); 648 } 649 } 650 651 void ui_textfield_activate(GtkEntry* self, UiTextField *textfield) { 652 if(textfield->onactivate) { 653 UiEvent e; 654 e.obj = textfield->obj; 655 e.window = e.obj->window; 656 e.document = textfield->obj->ctx->document; 657 e.eventdata = NULL; 658 e.intval = 0; 659 textfield->onactivate(&e, textfield->onactivatedata); 660 } 661 } 662 663 char* ui_textfield_get(UiString *str) { 664 if(str->value.ptr) { 665 str->value.free(str->value.ptr); 666 } 667 str->value.ptr = g_strdup(ENTRY_GET_TEXT(str->obj)); 668 str->value.free = (ui_freefunc)g_free; 669 return str->value.ptr; 670 } 671 672 void ui_textfield_set(UiString *str, const char *value) { 673 ENTRY_SET_TEXT(str->obj, value); 674 if(str->value.ptr) { 675 str->value.free(str->value.ptr); 676 str->value.ptr = NULL; 677 str->value.free = NULL; 678 } 679 } 680 681 // ----------------------- path textfield ----------------------- 682 683 // TODO: move to common 684 static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) { 685 cxstring *pathelms; 686 size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms); 687 688 if (nelm == 0) { 689 *ret_nelm = 0; 690 return NULL; 691 } 692 693 UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm)); 694 size_t n = nelm; 695 int j = 0; 696 for (int i = 0; i < nelm; i++) { 697 cxstring c = pathelms[i]; 698 if (c.length == 0) { 699 if (i == 0) { 700 c.length = 1; 701 } 702 else { 703 n--; 704 continue; 705 } 706 } 707 708 cxmutstr m = cx_strdup(c); 709 elms[j].name = m.ptr; 710 elms[j].name_len = m.length; 711 712 size_t elm_path_len = c.ptr + c.length - full_path; 713 cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len)); 714 elms[j].path = elm_path.ptr; 715 elms[j].path_len = elm_path.length; 716 717 j++; 718 } 719 *ret_nelm = n; 720 721 return elms; 722 } 723 724 static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { 725 for(int i=0;i<nelm;i++) { 726 free(elms[i].name); 727 free(elms[i].path); 728 } 729 free(elms); 730 } 731 732 static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) { 733 g_object_unref(pathtf->entry); 734 free(pathtf); 735 } 736 737 void ui_path_button_clicked(GtkWidget *widget, UiEventDataExt *event) { 738 UiPathTextField *pathtf = event->customdata1; 739 for(int i=0;i<event->value1;i++) { 740 if(i <= event->value0) { 741 WIDGET_REMOVE_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive"); 742 } else { 743 WIDGET_ADD_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive"); 744 } 745 } 746 747 UiPathElm *elm = event->customdata0; 748 cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len)); 749 UiEvent evt; 750 evt.obj = event->obj; 751 evt.window = evt.obj->window; 752 evt.document = evt.obj->ctx->document; 753 evt.eventdata = elm->path; 754 evt.intval = event->value0; 755 event->callback(&evt, event->userdata); 756 free(path.ptr); 757 } 758 759 int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path) { 760 size_t full_path_len = strlen(full_path); 761 if(full_path_len == 0) { 762 return 1; 763 } 764 765 size_t nelm = 0; 766 UiPathElm* path_elm = pathtf->getpathelm(full_path, full_path_len, &nelm, pathtf->getpathelmdata); 767 if (!path_elm) { 768 return 1; 769 } 770 771 free(pathtf->current_path); 772 pathtf->current_path = strdup(full_path); 773 774 ui_pathelm_destroy(pathtf->current_pathelms, pathtf->current_nelm); 775 free(pathtf->current_path_buttons); 776 pathtf->current_path_buttons = calloc(nelm, sizeof(GtkWidget*)); 777 pathtf->current_pathelms = path_elm; 778 pathtf->current_nelm = nelm; 779 780 return ui_pathtextfield_update_widget(pathtf); 781 } 782 783 static GtkWidget* ui_path_elm_button(UiPathTextField *pathtf, UiPathElm *elm, int i) { 784 cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len)); 785 GtkWidget *button = gtk_button_new_with_label(name.ptr); 786 pathtf->current_path_buttons[i] = button; 787 free(name.ptr); 788 789 if(pathtf->onactivate) { 790 UiEventDataExt *eventdata = malloc(sizeof(UiEventDataExt)); 791 memset(eventdata, 0, sizeof(UiEventDataExt)); 792 eventdata->callback = pathtf->onactivate; 793 eventdata->userdata = pathtf->onactivatedata; 794 eventdata->obj = pathtf->obj; 795 eventdata->customdata0 = elm; 796 eventdata->customdata1 = pathtf; 797 eventdata->value0 = i; 798 eventdata->value1 = pathtf->current_nelm; 799 800 g_signal_connect( 801 button, 802 "clicked", 803 G_CALLBACK(ui_path_button_clicked), 804 eventdata); 805 806 g_signal_connect( 807 button, 808 "destroy", 809 G_CALLBACK(ui_destroy_userdata), 810 eventdata); 811 } 812 813 return button; 814 } 815 816 static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) { 817 const gchar *text = ENTRY_GET_TEXT(pathtf->entry); 818 if(strlen(text) == 0) { 819 return; 820 } 821 822 UiObject *obj = pathtf->obj; 823 824 if(ui_pathtextfield_update(pathtf, text)) { 825 return; 826 } 827 828 if(pathtf->onactivate) { 829 UiEvent evt; 830 evt.obj = obj; 831 evt.window = obj->window; 832 evt.document = obj->ctx->document; 833 evt.eventdata = (char*)text; 834 evt.intval = -1; 835 pathtf->onactivate(&evt, pathtf->onactivatedata); 836 } 837 } 838 839 #if GTK_MAJOR_VERSION >= 4 840 841 static void pathbar_show_hbox(GtkWidget *widget, UiPathTextField *pathtf) { 842 if(pathtf->current_path) { 843 gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox); 844 ENTRY_SET_TEXT(pathtf->entry, pathtf->current_path); 845 } 846 } 847 848 static gboolean ui_path_textfield_key_controller( 849 GtkEventControllerKey* self, 850 guint keyval, 851 guint keycode, 852 GdkModifierType state, 853 UiPathTextField *pathtf) 854 { 855 if(keyval == GDK_KEY_Escape) { 856 pathbar_show_hbox(NULL, pathtf); 857 } 858 return FALSE; 859 } 860 861 UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) { 862 UiObject* current = uic_current_obj(obj); 863 864 UiPathTextField *pathtf = malloc(sizeof(UiPathTextField)); 865 memset(pathtf, 0, sizeof(UiPathTextField)); 866 pathtf->obj = obj; 867 pathtf->getpathelm = args.getpathelm; 868 pathtf->getpathelmdata = args.getpathelmdata; 869 pathtf->onactivate = args.onactivate; 870 pathtf->onactivatedata = args.onactivatedata; 871 pathtf->ondragcomplete = args.ondragcomplete; 872 pathtf->ondragcompletedata = args.ondragcompletedata; 873 pathtf->ondragstart = args.ondragstart; 874 pathtf->ondragstartdata = args.ondragstartdata; 875 pathtf->ondrop = args.ondrop; 876 pathtf->ondropdata = args.ondropsdata; 877 878 if(!pathtf->getpathelm) { 879 pathtf->getpathelm = default_pathelm_func; 880 pathtf->getpathelmdata = NULL; 881 } 882 883 pathtf->stack = gtk_stack_new(); 884 gtk_widget_set_name(pathtf->stack, "path-textfield-box"); 885 886 UI_APPLY_LAYOUT1(current, args); 887 current->container->add(current->container, pathtf->stack, FALSE); 888 889 pathtf->entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 890 pathtf->entry = gtk_entry_new(); 891 gtk_box_append(GTK_BOX(pathtf->entry_box), pathtf->entry); 892 gtk_widget_set_hexpand(pathtf->entry, TRUE); 893 894 GtkWidget *cancel_button = gtk_button_new_from_icon_name("window-close-symbolic"); 895 gtk_widget_add_css_class(cancel_button, "flat"); 896 gtk_widget_add_css_class(cancel_button, "pathbar-extra-button"); 897 gtk_box_append(GTK_BOX(pathtf->entry_box), cancel_button); 898 g_signal_connect( 899 cancel_button, 900 "clicked", 901 G_CALLBACK(pathbar_show_hbox), 902 pathtf); 903 904 gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->entry_box); 905 g_object_ref(pathtf->entry); // for compatibility with older pathbar version 906 g_signal_connect( 907 pathtf->entry, 908 "activate", 909 G_CALLBACK(ui_path_textfield_activate), 910 pathtf); 911 912 GtkEventController *entry_cancel = gtk_event_controller_key_new(); 913 gtk_widget_add_controller(pathtf->entry, entry_cancel); 914 g_signal_connect(entry_cancel, "key-pressed", G_CALLBACK(ui_path_textfield_key_controller), pathtf); 915 916 gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box); 917 918 919 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING); 920 if (var) { 921 UiString* value = (UiString*)var->value; 922 value->obj = pathtf; 923 value->get = ui_path_textfield_get; 924 value->set = ui_path_textfield_set; 925 926 if(value->value.ptr) { 927 char *str = strdup(value->value.ptr); 928 ui_string_set(value, str); 929 free(str); 930 } 931 } 932 933 return pathtf->stack; 934 } 935 936 static void pathbar_pressed( 937 GtkGestureClick* self, 938 gint n_press, 939 gdouble x, 940 gdouble y, 941 UiPathTextField *pathtf) 942 { 943 gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box); 944 gtk_widget_grab_focus(pathtf->entry); 945 } 946 947 int ui_pathtextfield_update_widget(UiPathTextField* pathtf) { 948 // recreate button hbox 949 if(pathtf->hbox) { 950 gtk_stack_remove(GTK_STACK(pathtf->stack), pathtf->hbox); 951 } 952 pathtf->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 953 gtk_box_set_homogeneous(GTK_BOX(pathtf->hbox), FALSE); 954 gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->hbox); 955 gtk_widget_set_name(pathtf->hbox, "pathbar"); 956 957 // add buttons for path elements 958 for (int i=0;i<pathtf->current_nelm;i++) { 959 UiPathElm *elm = &pathtf->current_pathelms[i]; 960 961 GtkWidget *button = ui_path_elm_button(pathtf, elm, i); 962 gtk_widget_add_css_class(button, "flat"); 963 964 gtk_box_append(GTK_BOX(pathtf->hbox), button); 965 966 if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) { 967 GtkWidget *path_separator = gtk_label_new("/"); 968 gtk_widget_add_css_class(path_separator, "pathbar-button-inactive"); 969 gtk_box_append(GTK_BOX(pathtf->hbox), path_separator); 970 } 971 } 972 gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox); 973 974 // create a widget for receiving button press events 975 GtkWidget *event_area = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 976 GtkGesture *handler = gtk_gesture_click_new(); 977 gtk_widget_add_controller(event_area, GTK_EVENT_CONTROLLER(handler)); 978 g_signal_connect( 979 handler, 980 "pressed", 981 G_CALLBACK(pathbar_pressed), 982 pathtf); 983 gtk_widget_set_hexpand(event_area, TRUE); 984 gtk_widget_set_vexpand(event_area, TRUE); 985 gtk_box_append(GTK_BOX(pathtf->hbox), event_area); 986 987 return 0; 988 } 989 990 #else 991 992 static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) { 993 gtk_box_pack_start(GTK_BOX(pathtf->hbox), pathtf->entry, TRUE, TRUE, 0); 994 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox); 995 pathtf->buttonbox = NULL; 996 997 gtk_widget_show(pathtf->entry); 998 gtk_widget_grab_focus(pathtf->entry); 999 1000 return TRUE; 1001 } 1002 1003 static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) { 1004 if (event->keyval == GDK_KEY_Escape) { 1005 // reset GtkEntry value 1006 gtk_entry_set_text(GTK_ENTRY(self), pathtf->current_path); 1007 const gchar *text = gtk_entry_get_text(GTK_ENTRY(self)); 1008 ui_pathtextfield_update(pathtf, text); 1009 return TRUE; 1010 } 1011 return FALSE; 1012 } 1013 1014 static GtkWidget* create_path_button_box() { 1015 GtkWidget *bb = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL); 1016 gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_EXPAND); // linked style 1017 gtk_box_set_homogeneous(GTK_BOX(bb), FALSE); 1018 gtk_box_set_spacing(GTK_BOX(bb), 0); 1019 return bb; 1020 } 1021 1022 UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) { 1023 UiObject* current = uic_current_obj(obj); 1024 1025 UiPathTextField *pathtf = malloc(sizeof(UiPathTextField)); 1026 memset(pathtf, 0, sizeof(UiPathTextField)); 1027 pathtf->obj = obj; 1028 pathtf->getpathelm = args.getpathelm; 1029 pathtf->getpathelmdata = args.getpathelmdata; 1030 pathtf->onactivate = args.onactivate; 1031 pathtf->onactivatedata = args.onactivatedata; 1032 pathtf->ondragcomplete = args.ondragcomplete; 1033 pathtf->ondragcompletedata = args.ondragcompletedata; 1034 pathtf->ondragstart = args.ondragstart; 1035 pathtf->ondragstartdata = args.ondragstartdata; 1036 pathtf->ondrop = args.ondrop; 1037 pathtf->ondropdata = args.ondropsdata; 1038 1039 if(!pathtf->getpathelm) { 1040 pathtf->getpathelm = default_pathelm_func; 1041 pathtf->getpathelmdata = NULL; 1042 } 1043 1044 // top level container for the path textfield is a GtkEventBox 1045 // the event box is needed to handle background button presses 1046 GtkWidget *eventbox = gtk_event_box_new(); 1047 g_signal_connect( 1048 eventbox, 1049 "button-press-event", 1050 G_CALLBACK(path_textfield_btn_pressed), 1051 pathtf); 1052 g_signal_connect( 1053 eventbox, 1054 "destroy", 1055 G_CALLBACK(ui_path_textfield_destroy), 1056 pathtf); 1057 1058 UI_APPLY_LAYOUT1(current, args); 1059 current->container->add(current->container, eventbox, FALSE); 1060 1061 // hbox as parent for the GtkEntry and GtkButtonBox 1062 GtkWidget *hbox = ui_gtk_hbox_new(0); 1063 pathtf->hbox = hbox; 1064 gtk_container_add(GTK_CONTAINER(eventbox), hbox); 1065 gtk_widget_set_name(hbox, "path-textfield-box"); 1066 1067 // create GtkEntry, that is also visible by default (with input yet) 1068 pathtf->entry = gtk_entry_new(); 1069 g_object_ref(G_OBJECT(pathtf->entry)); 1070 gtk_box_pack_start(GTK_BOX(hbox), pathtf->entry, TRUE, TRUE, 0); 1071 1072 g_signal_connect( 1073 pathtf->entry, 1074 "activate", 1075 G_CALLBACK(ui_path_textfield_activate), 1076 pathtf); 1077 g_signal_connect( 1078 pathtf->entry, 1079 "key-press-event", 1080 G_CALLBACK(ui_path_textfield_key_press), 1081 pathtf); 1082 1083 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING); 1084 if (var) { 1085 UiString* value = (UiString*)var->value; 1086 value->obj = pathtf; 1087 value->get = ui_path_textfield_get; 1088 value->set = ui_path_textfield_set; 1089 1090 if(value->value.ptr) { 1091 char *str = strdup(value->value.ptr); 1092 ui_string_set(value, str); 1093 free(str); 1094 } 1095 } 1096 1097 return hbox; 1098 } 1099 1100 int ui_pathtextfield_update_widget(UiPathTextField* pathtf) { 1101 GtkWidget *buttonbox = create_path_button_box(); 1102 1103 // switch from entry to buttonbox or remove current buttonbox 1104 if(pathtf->buttonbox) { 1105 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox); 1106 } else { 1107 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->entry); 1108 } 1109 gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0); 1110 pathtf->buttonbox = buttonbox; 1111 1112 for (int i=0;i<pathtf->current_nelm;i++) { 1113 UiPathElm *elm = &pathtf->current_pathelms[i]; 1114 GtkWidget *button = ui_path_elm_button(pathtf, elm, i); 1115 gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0); 1116 } 1117 1118 gtk_widget_show_all(buttonbox); 1119 1120 return 0; 1121 } 1122 1123 #endif 1124 1125 char* ui_path_textfield_get(UiString *str) { 1126 if(str->value.ptr) { 1127 str->value.free(str->value.ptr); 1128 } 1129 UiPathTextField *tf = str->obj; 1130 str->value.ptr = g_strdup(ENTRY_GET_TEXT(tf->entry)); 1131 str->value.free = (ui_freefunc)g_free; 1132 return str->value.ptr; 1133 } 1134 1135 void ui_path_textfield_set(UiString *str, const char *value) { 1136 UiPathTextField *tf = str->obj; 1137 ENTRY_SET_TEXT(tf->entry, value); 1138 ui_pathtextfield_update(tf, value); 1139 if(str->value.ptr) { 1140 str->value.free(str->value.ptr); 1141 str->value.ptr = NULL; 1142 str->value.free = NULL; 1143 } 1144 } 1145