1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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);
99 SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area);
100
101
102
103
104
105
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
111 UI_APPLY_LAYOUT1(current, args);
112 current->container->add(current->container, scroll_area,
TRUE);
113
114
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
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
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
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
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
565 #if GTK_MAJOR_VERSION <=
3
566 gtk_entry_set_width_chars(
GTK_ENTRY(textfield), args.width);
567 #endif
568 }
569 if(frameless) {
570
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
682
683
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);
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
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
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
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
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);
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
1045
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
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
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
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