ui/gtk/text.c

changeset 32
e5f4d8af567e
parent 29
3fc287f06305
child 35
834d9c15a69f
equal deleted inserted replaced
31:bf810176ddb8 32:e5f4d8af567e
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
36 40
37 #include "../common/types.h" 41 #include "../common/types.h"
38 42
39 static void selection_handler( 43 static void selection_handler(
40 GtkTextBuffer *buf, 44 GtkTextBuffer *buf,
532 mgr->cur = elm; 536 mgr->cur = elm;
533 } 537 }
534 } 538 }
535 539
536 540
537 static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) { 541
542
543 static UIWIDGET create_textfield(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs args) {
538 GtkWidget *textfield = gtk_entry_new(); 544 GtkWidget *textfield = gtk_entry_new();
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);
539 548
540 UiTextField *uitext = malloc(sizeof(UiTextField)); 549 UiTextField *uitext = malloc(sizeof(UiTextField));
541 uitext->ctx = obj->ctx; 550 uitext->ctx = obj->ctx;
542 uitext->var = var; 551 uitext->var = var;
543 552
545 textfield, 554 textfield,
546 "destroy", 555 "destroy",
547 G_CALLBACK(ui_textfield_destroy), 556 G_CALLBACK(ui_textfield_destroy),
548 uitext); 557 uitext);
549 558
550 if(width > 0) { 559 if(args.width > 0) {
551 gtk_entry_set_width_chars(GTK_ENTRY(textfield), width); 560 gtk_entry_set_width_chars(GTK_ENTRY(textfield), args.width);
552 } 561 }
553 if(frameless) { 562 if(frameless) {
554 // TODO: gtk2legacy workaroud 563 // TODO: gtk2legacy workaroud
555 gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE); 564 gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE);
556 } 565 }
584 } 593 }
585 594
586 return textfield; 595 return textfield;
587 } 596 }
588 597
589 static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) { 598 UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) {
590 UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING); 599 return create_textfield(obj, FALSE, FALSE, args);
591 if(var) { 600 }
592 return create_textfield_var(obj, width, frameless, password, var); 601
593 } else { 602 UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
594 // TODO: error 603 return create_textfield(obj, TRUE, FALSE, args);
595 } 604 }
596 return NULL; 605
597 } 606 UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
598 607 return create_textfield(obj, FALSE, TRUE, args);
599 static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) { 608 }
600 UiVar *var = NULL; 609
601 if(value) {
602 var = malloc(sizeof(UiVar));
603 var->value = value;
604 var->type = UI_VAR_SPECIAL;
605 var->from = NULL;
606 var->from_ctx = NULL;
607 }
608 return create_textfield_var(obj, width, frameless, password, var);
609 }
610 610
611 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) { 611 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
612 if(textfield->var) {
613 UiText *text = textfield->var->value;
614 if(text->undomgr) {
615 ui_destroy_undomgr(text->undomgr);
616 }
617 ui_destroy_boundvar(textfield->ctx, textfield->var);
618 }
619 free(textfield); 612 free(textfield);
620 } 613 }
621 614
622 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) { 615 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
616 // changed event is only registered, if the textfield->var != NULL
623 UiString *value = textfield->var->value; 617 UiString *value = textfield->var->value;
624 if(value->observers) { 618 if(value->observers) {
625 UiEvent e; 619 UiEvent e;
626 e.obj = textfield->ctx->obj; 620 e.obj = textfield->ctx->obj;
627 e.window = e.obj->window; 621 e.window = e.obj->window;
630 e.intval = 0; 624 e.intval = 0;
631 ui_notify_evt(value->observers, &e); 625 ui_notify_evt(value->observers, &e);
632 } 626 }
633 } 627 }
634 628
635 UIWIDGET ui_textfield_deprecated(UiObject *obj, UiString *value) {
636 return create_textfield(obj, 0, FALSE, FALSE, value);
637 }
638
639 UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
640 return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
641 }
642
643 UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
644 return create_textfield(obj, width, FALSE, FALSE, value);
645 }
646
647 UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
648 return create_textfield_nv(obj, width, FALSE, FALSE, varname);
649 }
650
651 UIWIDGET ui_frameless_textfield_deprecated(UiObject *obj, UiString *value) {
652 return create_textfield(obj, 0, TRUE, FALSE, value);
653 }
654
655 UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
656 return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
657 }
658
659 UIWIDGET ui_passwordfield_deprecated(UiObject *obj, UiString *value) {
660 return create_textfield(obj, 0, FALSE, TRUE, value);
661 }
662
663 UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
664 return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
665 }
666
667 UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
668 return create_textfield(obj, width, FALSE, TRUE, value);
669 }
670
671 UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
672 return create_textfield_nv(obj, width, FALSE, TRUE, varname);
673 }
674 629
675 char* ui_textfield_get(UiString *str) { 630 char* ui_textfield_get(UiString *str) {
676 if(str->value.ptr) { 631 if(str->value.ptr) {
677 str->value.free(str->value.ptr); 632 str->value.free(str->value.ptr);
678 } 633 }
687 str->value.free(str->value.ptr); 642 str->value.free(str->value.ptr);
688 str->value.ptr = NULL; 643 str->value.ptr = NULL;
689 str->value.free = NULL; 644 str->value.free = NULL;
690 } 645 }
691 } 646 }
647
648 // ----------------------- path textfield -----------------------
649
650 // TODO: move to common
651 static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) {
652 cxstring *pathelms;
653 size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms);
654
655 if (nelm == 0) {
656 *ret_nelm = 0;
657 return NULL;
658 }
659
660 UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm));
661 size_t n = nelm;
662 int j = 0;
663 for (int i = 0; i < nelm; i++) {
664 cxstring c = pathelms[i];
665 if (c.length == 0) {
666 if (i == 0) {
667 c.length = 1;
668 }
669 else {
670 n--;
671 continue;
672 }
673 }
674
675 cxmutstr m = cx_strdup(c);
676 elms[j].name = m.ptr;
677 elms[j].name_len = m.length;
678
679 size_t elm_path_len = c.ptr + c.length - full_path;
680 cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len));
681 elms[j].path = elm_path.ptr;
682 elms[j].path_len = elm_path.length;
683
684 j++;
685 }
686 *ret_nelm = n;
687
688 return elms;
689 }
690
691 static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) {
692 gtk_box_pack_start(GTK_BOX(pathtf->hbox), pathtf->entry, TRUE, TRUE, 0);
693 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox);
694
695 gtk_widget_show(pathtf->entry);
696 gtk_widget_grab_focus(pathtf->entry);
697
698 return TRUE;
699 }
700
701 static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) {
702 for(int i=0;i<nelm;i++) {
703 free(elms[i].name);
704 free(elms[i].path);
705 }
706 free(elms);
707 }
708
709 static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) {
710 free(pathtf->current_path);
711 g_object_unref(pathtf->entry);
712 free(pathtf);
713 }
714
715 static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) {
716 const gchar *text = gtk_entry_get_text(GTK_ENTRY(pathtf->entry));
717 if(strlen(text) == 0) {
718 return;
719 }
720
721 UiObject *obj = pathtf->obj;
722
723 if(ui_pathtextfield_update(pathtf, text)) {
724 return;
725 }
726
727 if(pathtf->onactivate) {
728 UiEvent evt;
729 evt.obj = obj;
730 evt.window = obj->window;
731 evt.document = obj->ctx->document;
732 evt.eventdata = (char*)text;
733 evt.intval = -1;
734 pathtf->onactivate(&evt, pathtf->onactivatedata);
735 }
736 }
737
738 static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) {
739 if (event->keyval == GDK_KEY_Escape) {
740 // reset GtkEntry value
741 gtk_entry_set_text(GTK_ENTRY(self), pathtf->current_path);
742 const gchar *text = gtk_entry_get_text(GTK_ENTRY(self));
743 ui_pathtextfield_update(pathtf, text);
744 return TRUE;
745 }
746 return FALSE;
747 }
748
749 static GtkWidget* create_path_button_box() {
750 GtkWidget *bb = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
751 gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_EXPAND); // linked style
752 gtk_box_set_homogeneous(GTK_BOX(bb), FALSE);
753 gtk_box_set_spacing(GTK_BOX(bb), 0);
754 return bb;
755 }
756
757 UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
758 UiObject* current = uic_current_obj(obj);
759
760 UiPathTextField *pathtf = malloc(sizeof(UiPathTextField));
761 memset(pathtf, 0, sizeof(UiPathTextField));
762 pathtf->obj = obj;
763 pathtf->getpathelm = args.getpathelm;
764 pathtf->getpathelmdata = args.getpathelmdata;
765 pathtf->onactivate = args.onactivate;
766 pathtf->onactivatedata = args.onactivatedata;
767 pathtf->ondragcomplete = args.ondragcomplete;
768 pathtf->ondragcompletedata = args.ondragcompletedata;
769 pathtf->ondragstart = args.ondragstart;
770 pathtf->ondragstartdata = args.ondragstartdata;
771 pathtf->ondrop = args.ondrop;
772 pathtf->ondropdata = args.ondropsdata;
773
774 if(!pathtf->getpathelm) {
775 pathtf->getpathelm = default_pathelm_func;
776 pathtf->getpathelmdata = NULL;
777 }
778
779 // top level container for the path textfield is a GtkEventBox
780 // the event box is needed to handle background button presses
781 GtkWidget *eventbox = gtk_event_box_new();
782 g_signal_connect(
783 eventbox,
784 "button-press-event",
785 G_CALLBACK(path_textfield_btn_pressed),
786 pathtf);
787 g_signal_connect(
788 eventbox,
789 "destroy",
790 G_CALLBACK(ui_path_textfield_destroy),
791 pathtf);
792
793 UI_APPLY_LAYOUT1(current, args);
794 current->container->add(current->container, eventbox, FALSE);
795
796 // hbox as parent for the GtkEntry and GtkButtonBox
797 GtkWidget *hbox = ui_gtk_hbox_new(0);
798 pathtf->hbox = hbox;
799 gtk_container_add(GTK_CONTAINER(eventbox), hbox);
800 gtk_widget_set_name(hbox, "path-textfield-box");
801
802 // create GtkEntry, that is also visible by default (with input yet)
803 pathtf->entry = gtk_entry_new();
804 g_object_ref(G_OBJECT(pathtf->entry));
805 gtk_box_pack_start(GTK_BOX(hbox), pathtf->entry, TRUE, TRUE, 0);
806
807 g_signal_connect(
808 pathtf->entry,
809 "activate",
810 G_CALLBACK(ui_path_textfield_activate),
811 pathtf);
812 g_signal_connect(
813 pathtf->entry,
814 "key-press-event",
815 G_CALLBACK(ui_path_textfield_key_press),
816 pathtf);
817
818 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
819 if (var) {
820 UiString* value = (UiString*)var->value;
821 value->obj = pathtf;
822 value->get = ui_path_textfield_get;
823 value->set = ui_path_textfield_set;
824
825 if(value->value.ptr) {
826 char *str = strdup(value->value.ptr);
827 ui_string_set(value, str);
828 free(str);
829 }
830 }
831
832 return hbox;
833 }
834
835 void ui_path_button_clicked(GtkWidget *widget, UiEventData *event) {
836 UiPathElm *elm = event->customdata;
837 cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len));
838 UiEvent evt;
839 evt.obj = event->obj;
840 evt.window = evt.obj->window;
841 evt.document = evt.obj->ctx->document;
842 evt.eventdata = elm->path;
843 evt.intval = event->value;
844 event->callback(&evt, event->userdata);
845 free(path.ptr);
846 }
847
848 int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path) {
849 size_t full_path_len = strlen(full_path);
850
851 size_t nelm = 0;
852 UiPathElm* path_elm = pathtf->getpathelm(full_path, full_path_len, &nelm, pathtf->getpathelmdata);
853 if (!path_elm) {
854 return 1;
855 }
856
857 free(pathtf->current_path);
858 pathtf->current_path = strdup(full_path);
859
860 ui_pathelm_destroy(pathtf->current_pathelms, pathtf->current_nelm);
861 pathtf->current_pathelms = path_elm;
862 pathtf->current_nelm = nelm;
863
864 GtkWidget *buttonbox = create_path_button_box();
865 pathtf->buttonbox = buttonbox;
866
867 // switch from entry to buttonbox
868 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->entry);
869 gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0);
870
871 for (int i=0;i<nelm;i++) {
872 UiPathElm *elm = &path_elm[i];
873
874 cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len));
875 GtkWidget *button = gtk_button_new_with_label(name.ptr);
876 free(name.ptr);
877
878 if(pathtf->onactivate) {
879 UiEventData *eventdata = malloc(sizeof(UiEventData));
880 eventdata->callback = pathtf->onactivate;
881 eventdata->userdata = pathtf->onactivatedata;
882 eventdata->obj = pathtf->obj;
883 eventdata->customdata = elm;
884 eventdata->value = i;
885
886 g_signal_connect(
887 button,
888 "clicked",
889 G_CALLBACK(ui_path_button_clicked),
890 eventdata);
891
892 g_signal_connect(
893 button,
894 "destroy",
895 G_CALLBACK(ui_destroy_userdata),
896 eventdata);
897 }
898
899 gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0);
900 }
901
902 gtk_widget_show_all(buttonbox);
903
904 return 0;
905 }
906
907 char* ui_path_textfield_get(UiString *str) {
908 if(str->value.ptr) {
909 str->value.free(str->value.ptr);
910 }
911 UiPathTextField *tf = str->obj;
912 str->value.ptr = g_strdup(gtk_entry_get_text(GTK_ENTRY(tf->entry)));
913 str->value.free = (ui_freefunc)g_free;
914 return str->value.ptr;
915 }
916
917 void ui_path_textfield_set(UiString *str, const char *value) {
918 UiPathTextField *tf = str->obj;
919 gtk_entry_set_text(GTK_ENTRY(tf->entry), value);
920 ui_pathtextfield_update(tf, value);
921 if(str->value.ptr) {
922 str->value.free(str->value.ptr);
923 str->value.ptr = NULL;
924 str->value.free = NULL;
925 }
926 }

mercurial