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 } |