ui/gtk/text.c

changeset 431
bb7da585debc
parent 402
96a055be7f0b
equal deleted inserted replaced
169:fe49cff3c571 431:bb7da585debc
31 #include <string.h> 31 #include <string.h>
32 32
33 #include "text.h" 33 #include "text.h"
34 #include "container.h" 34 #include "container.h"
35 35
36 #include <cx/printf.h>
37
38 #include <gdk/gdkkeysyms.h>
39
40
41 #include "../common/types.h"
36 42
37 static void selection_handler( 43 static void selection_handler(
38 GtkTextBuffer *buf, 44 GtkTextBuffer *buf,
39 GtkTextIter *location, 45 GtkTextIter *location,
40 GtkTextMark *mark, 46 GtkTextMark *mark,
54 } 60 }
55 textview->last_selection_state = sel; 61 textview->last_selection_state = sel;
56 } 62 }
57 } 63 }
58 64
59 UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) { 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
60 GtkWidget *text_area = gtk_text_view_new(); 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
61 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR); 73 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR);
62 g_signal_connect( 74 g_signal_connect(
63 text_area, 75 text_area,
64 "realize", 76 "realize",
65 G_CALLBACK(ui_textarea_realize_event), 77 G_CALLBACK(ui_textarea_realize_event),
66 NULL); 78 NULL);
67 79
68 UiTextArea *uitext = malloc(sizeof(UiTextArea)); 80 UiTextArea *uitext = malloc(sizeof(UiTextArea));
81 uitext->obj = obj;
69 uitext->ctx = obj->ctx; 82 uitext->ctx = obj->ctx;
70 uitext->var = var; 83 uitext->var = var;
71 uitext->last_selection_state = 0; 84 uitext->last_selection_state = 0;
85 uitext->onchange = args.onchange;
86 uitext->onchangedata = args.onchangedata;
72 87
73 g_signal_connect( 88 g_signal_connect(
74 text_area, 89 text_area,
75 "destroy", 90 "destroy",
76 G_CALLBACK(ui_textarea_destroy), 91 G_CALLBACK(ui_textarea_destroy),
77 uitext); 92 uitext);
78 93
79 GtkWidget *scroll_area = gtk_scrolled_window_new (NULL, NULL); 94 GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
80 gtk_scrolled_window_set_policy( 95 gtk_scrolled_window_set_policy(
81 GTK_SCROLLED_WINDOW(scroll_area), 96 GTK_SCROLLED_WINDOW(scroll_area),
82 GTK_POLICY_AUTOMATIC, 97 GTK_POLICY_AUTOMATIC,
83 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS 98 GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS
84 gtk_container_add(GTK_CONTAINER(scroll_area), text_area); 99 SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area);
85 100
86 // font and padding 101 // font and padding
87 PangoFontDescription *font; 102 //PangoFontDescription *font;
88 font = pango_font_description_from_string("Monospace"); 103 //font = pango_font_description_from_string("Monospace");
89 gtk_widget_modify_font(text_area, font); 104 //gtk_widget_modify_font(text_area, font); // TODO
90 pango_font_description_free(font); 105 //pango_font_description_free(font);
91 106
92 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2); 107 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); 108 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2);
94 109
95 // add 110 // add
96 UiContainer *ct = uic_get_current_container(obj); 111 UI_APPLY_LAYOUT1(current, args);
97 ct->add(ct, scroll_area, TRUE); 112 current->container->add(current->container, scroll_area, TRUE);
98 113
99 // bind value 114 // bind value
100 UiText *value = var->value; 115 if(var) {
101 if(value) { 116 UiText *value = var->value;
102 GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area)); 117 GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area));
103 118
104 if(value->value.ptr) { 119 if(value->value.ptr) {
105 gtk_text_buffer_set_text(buf, value->value.ptr, -1); 120 gtk_text_buffer_set_text(buf, value->value.ptr, -1);
106 value->value.free(value->value.ptr); 121 value->value.free(value->value.ptr);
148 163
149 return scroll_area; 164 return scroll_area;
150 } 165 }
151 166
152 void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) { 167 void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) {
153 ui_destroy_boundvar(textarea->ctx, textarea->var); 168 if(textarea->var) {
169 ui_destroy_boundvar(textarea->ctx, textarea->var);
170 }
154 free(textarea); 171 free(textarea);
155 } 172 }
156 173
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) { 174 UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {
177 return gtk_bin_get_child(GTK_BIN(textarea)); 175 return SCROLLEDWINDOW_GET_CHILD(textarea);
178 } 176 }
179 177
180 char* ui_textarea_get(UiText *text) { 178 char* ui_textarea_get(UiText *text) {
181 if(text->value.ptr) { 179 if(text->value.ptr) {
182 text->value.free(text->value.ptr); 180 text->value.free(text->value.ptr);
189 text->value.ptr = g_strdup(str); 187 text->value.ptr = g_strdup(str);
190 text->value.free = (ui_freefunc)g_free; 188 text->value.free = (ui_freefunc)g_free;
191 return str; 189 return str;
192 } 190 }
193 191
194 void ui_textarea_set(UiText *text, char *str) { 192 void ui_textarea_set(UiText *text, const char *str) {
195 gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1); 193 gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1);
196 if(text->value.ptr) { 194 if(text->value.ptr) {
197 text->value.free(text->value.ptr); 195 text->value.free(text->value.ptr);
198 } 196 }
199 text->value.ptr = NULL; 197 text->value.ptr = NULL;
271 269
272 270
273 271
274 void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) { 272 void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) {
275 UiText *value = textarea->var->value; 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
276 if(value->observers) { 286 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); 287 ui_notify_evt(value->observers, &e);
284 } 288 }
285 } 289 }
286 290
287 // undo manager functions 291 // undo manager functions
302 if(!mgr->event) { 306 if(!mgr->event) {
303 return; 307 return;
304 } 308 }
305 309
306 if(mgr->cur) { 310 if(mgr->cur) {
307 UcxList *elm = mgr->cur->next; 311 UiTextBufOp *elm = mgr->cur->next;
308 if(elm) { 312 if(elm) {
309 mgr->cur->next = NULL; 313 mgr->cur->next = NULL;
314 mgr->end = mgr->cur;
310 while(elm) { 315 while(elm) {
311 elm->prev = NULL; 316 elm->prev = NULL;
312 UcxList *next = elm->next; 317 UiTextBufOp *next = elm->next;
313 ui_free_textbuf_op(elm->data); 318 ui_free_textbuf_op(elm);
314 free(elm);
315 elm = next; 319 elm = next;
316 } 320 }
317 } 321 }
318 322
319 UiTextBufOp *last_op = mgr->cur->data; 323 UiTextBufOp *last_op = mgr->cur;
320 if( 324 if(
321 last_op->type == UI_TEXTBUF_INSERT && 325 last_op->type == UI_TEXTBUF_INSERT &&
322 ui_check_insertstr(last_op->text, last_op->len, text, length) == 0) 326 ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
323 { 327 {
324 // append text to last op 328 // append text to last op
339 char *dpstr = malloc(length + 1); 343 char *dpstr = malloc(length + 1);
340 memcpy(dpstr, text, length); 344 memcpy(dpstr, text, length);
341 dpstr[length] = 0; 345 dpstr[length] = 0;
342 346
343 UiTextBufOp *op = malloc(sizeof(UiTextBufOp)); 347 UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
348 op->prev = NULL;
349 op->next = NULL;
344 op->type = UI_TEXTBUF_INSERT; 350 op->type = UI_TEXTBUF_INSERT;
345 op->start = gtk_text_iter_get_offset(location); 351 op->start = gtk_text_iter_get_offset(location);
346 op->end = op->start+length; 352 op->end = op->start+length;
347 op->len = length; 353 op->len = length;
348 op->text = dpstr; 354 op->text = dpstr;
349 355
350 UcxList *elm = ucx_list_append(NULL, op); 356 cx_linked_list_add(
351 mgr->cur = elm; 357 (void**)&mgr->begin,
352 mgr->begin = ucx_list_concat(mgr->begin, elm); 358 (void**)&mgr->end,
359 offsetof(UiTextBufOp, prev),
360 offsetof(UiTextBufOp, next),
361 op);
362
363 mgr->cur = op;
353 } 364 }
354 365
355 void ui_textbuf_delete( 366 void ui_textbuf_delete(
356 GtkTextBuffer *textbuffer, 367 GtkTextBuffer *textbuffer,
357 GtkTextIter *start, 368 GtkTextIter *start,
367 if(!mgr->event) { 378 if(!mgr->event) {
368 return; 379 return;
369 } 380 }
370 381
371 if(mgr->cur) { 382 if(mgr->cur) {
372 UcxList *elm = mgr->cur->next; 383 UiTextBufOp *elm = mgr->cur->next;
373 if(elm) { 384 if(elm) {
374 mgr->cur->next = NULL; 385 mgr->cur->next = NULL;
386 mgr->end = mgr->cur;
375 while(elm) { 387 while(elm) {
376 elm->prev = NULL; 388 elm->prev = NULL;
377 UcxList *next = elm->next; 389 UiTextBufOp *next = elm->next;
378 ui_free_textbuf_op(elm->data); 390 ui_free_textbuf_op(elm);
379 free(elm);
380 elm = next; 391 elm = next;
381 } 392 }
382 } 393 }
383 } 394 }
384 395
385 char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE); 396 char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE);
386 397
387 UiTextBufOp *op = malloc(sizeof(UiTextBufOp)); 398 UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
399 op->prev = NULL;
400 op->next = NULL;
388 op->type = UI_TEXTBUF_DELETE; 401 op->type = UI_TEXTBUF_DELETE;
389 op->start = gtk_text_iter_get_offset(start); 402 op->start = gtk_text_iter_get_offset(start);
390 op->end = gtk_text_iter_get_offset(end); 403 op->end = gtk_text_iter_get_offset(end);
391 op->len = op->end - op->start; 404 op->len = op->end - op->start;
392 405
393 char *dpstr = malloc(op->len + 1); 406 char *dpstr = malloc(op->len + 1);
394 memcpy(dpstr, text, op->len); 407 memcpy(dpstr, text, op->len);
395 dpstr[op->len] = 0; 408 dpstr[op->len] = 0;
396 op->text = dpstr; 409 op->text = dpstr;
397 410
398 UcxList *elm = ucx_list_append(NULL, op); 411 cx_linked_list_add(
399 mgr->cur = elm; 412 (void**)&mgr->begin,
400 mgr->begin = ucx_list_concat(mgr->begin, elm); 413 (void**)&mgr->end,
414 offsetof(UiTextBufOp, prev),
415 offsetof(UiTextBufOp, next),
416 op);
417
418 mgr->cur = op;
401 } 419 }
402 420
403 UiUndoMgr* ui_create_undomgr() { 421 UiUndoMgr* ui_create_undomgr() {
404 UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr)); 422 UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
405 mgr->begin = NULL; 423 mgr->begin = NULL;
424 mgr->end = NULL;
406 mgr->cur = NULL; 425 mgr->cur = NULL;
407 mgr->length = 0; 426 mgr->length = 0;
408 mgr->event = 1; 427 mgr->event = 1;
409 return mgr; 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);
410 } 442 }
411 443
412 void ui_free_textbuf_op(UiTextBufOp *op) { 444 void ui_free_textbuf_op(UiTextBufOp *op) {
413 if(op->text) { 445 if(op->text) {
414 free(op->text); 446 free(op->text);
438 470
439 void ui_text_undo(UiText *value) { 471 void ui_text_undo(UiText *value) {
440 UiUndoMgr *mgr = value->undomgr; 472 UiUndoMgr *mgr = value->undomgr;
441 473
442 if(mgr->cur) { 474 if(mgr->cur) {
443 UiTextBufOp *op = mgr->cur->data; 475 UiTextBufOp *op = mgr->cur;
444 mgr->event = 0; 476 mgr->event = 0;
445 switch(op->type) { 477 switch(op->type) {
446 case UI_TEXTBUF_INSERT: { 478 case UI_TEXTBUF_INSERT: {
447 GtkTextIter begin; 479 GtkTextIter begin;
448 GtkTextIter end; 480 GtkTextIter end;
466 } 498 }
467 499
468 void ui_text_redo(UiText *value) { 500 void ui_text_redo(UiText *value) {
469 UiUndoMgr *mgr = value->undomgr; 501 UiUndoMgr *mgr = value->undomgr;
470 502
471 UcxList *elm = NULL; 503 UiTextBufOp *elm = NULL;
472 if(mgr->cur) { 504 if(mgr->cur) {
473 if(mgr->cur->next) { 505 if(mgr->cur->next) {
474 elm = mgr->cur->next; 506 elm = mgr->cur->next;
475 } 507 }
476 } else if(mgr->begin) { 508 } else if(mgr->begin) {
477 elm = mgr->begin; 509 elm = mgr->begin;
478 } 510 }
479 511
480 if(elm) { 512 if(elm) {
481 UiTextBufOp *op = elm->data; 513 UiTextBufOp *op = elm;
482 mgr->event = 0; 514 mgr->event = 0;
483 switch(op->type) { 515 switch(op->type) {
484 case UI_TEXTBUF_INSERT: { 516 case UI_TEXTBUF_INSERT: {
485 GtkTextIter begin; 517 GtkTextIter begin;
486 GtkTextIter end; 518 GtkTextIter end;
502 mgr->cur = elm; 534 mgr->cur = elm;
503 } 535 }
504 } 536 }
505 537
506 538
507 static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) { 539
540
541 static UIWIDGET create_textfield(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs args) {
508 GtkWidget *textfield = gtk_entry_new(); 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);
509 548
510 UiTextField *uitext = malloc(sizeof(UiTextField)); 549 UiTextField *uitext = malloc(sizeof(UiTextField));
511 uitext->ctx = obj->ctx; 550 uitext->obj = obj;
512 uitext->var = var; 551 uitext->var = var;
552 uitext->onchange = args.onchange;
553 uitext->onchangedata = args.onchangedata;
554 uitext->onactivate = args.onactivate;
555 uitext->onactivatedata = args.onactivatedata;
513 556
514 g_signal_connect( 557 g_signal_connect(
515 textfield, 558 textfield,
516 "destroy", 559 "destroy",
517 G_CALLBACK(ui_textfield_destroy), 560 G_CALLBACK(ui_textfield_destroy),
518 uitext); 561 uitext);
519 562
520 if(width > 0) { 563 if(args.width > 0) {
521 gtk_entry_set_width_chars(GTK_ENTRY(textfield), width); 564 // TODO: gtk4
565 #if GTK_MAJOR_VERSION <= 3
566 gtk_entry_set_width_chars(GTK_ENTRY(textfield), args.width);
567 #endif
522 } 568 }
523 if(frameless) { 569 if(frameless) {
524 // TODO: gtk2legacy workaroud 570 // TODO: gtk2legacy workaroud
525 gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE); 571 gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE);
526 } 572 }
527 if(password) { 573 if(password) {
528 gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE); 574 gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
529 } 575 }
530 576
531 UiContainer *ct = uic_get_current_container(obj); 577 UI_APPLY_LAYOUT1(current, args);
532 ct->add(ct, textfield, FALSE); 578 current->container->add(current->container, textfield, FALSE);
533 579
534 if(var) { 580 if(var) {
535 UiString *value = var->value; 581 UiString *value = var->value;
536 if(value->value.ptr) { 582 if(value->value.ptr) {
537 gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr); 583 ENTRY_SET_TEXT(textfield, value->value.ptr);
538 value->value.free(value->value.ptr); 584 value->value.free(value->value.ptr);
539 value->value.ptr = NULL; 585 value->value.ptr = NULL;
540 value->value.free = NULL; 586 value->value.free = NULL;
541 } 587 }
542 588
543 value->get = ui_textfield_get; 589 value->get = ui_textfield_get;
544 value->set = ui_textfield_set; 590 value->set = ui_textfield_set;
545 value->value.ptr = NULL; 591 value->value.ptr = NULL;
546 value->value.free = NULL; 592 value->value.free = NULL;
547 value->obj = GTK_ENTRY(textfield); 593 value->obj = GTK_ENTRY(textfield);
548 594 }
595
596 if(args.onchange || var) {
549 g_signal_connect( 597 g_signal_connect(
550 textfield, 598 textfield,
551 "changed", 599 "changed",
552 G_CALLBACK(ui_textfield_changed), 600 G_CALLBACK(ui_textfield_changed),
553 uitext); 601 uitext);
554 } 602 }
555 603
604 if(args.onactivate) {
605 g_signal_connect(
606 textfield,
607 "activate",
608 G_CALLBACK(ui_textfield_activate),
609 uitext);
610 }
611
556 return textfield; 612 return textfield;
557 } 613 }
558 614
559 static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) { 615 UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) {
560 UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING); 616 return create_textfield(obj, FALSE, FALSE, args);
561 if(var) { 617 }
562 return create_textfield_var(obj, width, frameless, password, var); 618
563 } else { 619 UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
564 // TODO: error 620 return create_textfield(obj, TRUE, FALSE, args);
565 } 621 }
566 return NULL; 622
567 } 623 UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
568 624 return create_textfield(obj, FALSE, TRUE, args);
569 static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) { 625 }
570 UiVar *var = NULL; 626
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 627
581 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) { 628 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
582 if(textfield->var) {
583 ui_destroy_boundvar(textfield->ctx, textfield->var);
584 }
585 free(textfield); 629 free(textfield);
586 } 630 }
587 631
588 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) { 632 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
589 UiString *value = textfield->var->value; 633 UiString *value = textfield->var->value;
590 if(value->observers) { 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) {
591 UiEvent e; 653 UiEvent e;
592 e.obj = textfield->ctx->obj; 654 e.obj = textfield->obj;
593 e.window = e.obj->window; 655 e.window = e.obj->window;
594 e.document = textfield->ctx->document; 656 e.document = textfield->obj->ctx->document;
595 e.eventdata = value; 657 e.eventdata = NULL;
596 e.intval = 0; 658 e.intval = 0;
597 ui_notify_evt(value->observers, &e); 659 textfield->onactivate(&e, textfield->onactivatedata);
598 } 660 }
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 } 661 }
640 662
641 char* ui_textfield_get(UiString *str) { 663 char* ui_textfield_get(UiString *str) {
642 if(str->value.ptr) { 664 if(str->value.ptr) {
643 str->value.free(str->value.ptr); 665 str->value.free(str->value.ptr);
644 } 666 }
645 str->value.ptr = g_strdup(gtk_entry_get_text(str->obj)); 667 str->value.ptr = g_strdup(ENTRY_GET_TEXT(str->obj));
646 str->value.free = (ui_freefunc)g_free; 668 str->value.free = (ui_freefunc)g_free;
647 return str->value.ptr; 669 return str->value.ptr;
648 } 670 }
649 671
650 void ui_textfield_set(UiString *str, char *value) { 672 void ui_textfield_set(UiString *str, const char *value) {
651 gtk_entry_set_text(str->obj, value); 673 ENTRY_SET_TEXT(str->obj, value);
652 if(str->value.ptr) { 674 if(str->value.ptr) {
653 str->value.free(str->value.ptr); 675 str->value.free(str->value.ptr);
654 str->value.ptr = NULL; 676 str->value.ptr = NULL;
655 str->value.free = NULL; 677 str->value.free = NULL;
656 } 678 }
657 } 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 }

mercurial