689 *ret_nelm = n; |
689 *ret_nelm = n; |
690 |
690 |
691 return elms; |
691 return elms; |
692 } |
692 } |
693 |
693 |
694 |
|
695 #if GTK_MAJOR_VERSION >= 4 |
|
696 |
|
697 UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) { |
|
698 // TODO |
|
699 return NULL; |
|
700 } |
|
701 |
|
702 #else |
|
703 |
|
704 static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) { |
|
705 gtk_box_pack_start(GTK_BOX(pathtf->hbox), pathtf->entry, TRUE, TRUE, 0); |
|
706 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox); |
|
707 pathtf->buttonbox = NULL; |
|
708 |
|
709 gtk_widget_show(pathtf->entry); |
|
710 gtk_widget_grab_focus(pathtf->entry); |
|
711 |
|
712 return TRUE; |
|
713 } |
|
714 |
|
715 static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { |
694 static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { |
716 for(int i=0;i<nelm;i++) { |
695 for(int i=0;i<nelm;i++) { |
717 free(elms[i].name); |
696 free(elms[i].name); |
718 free(elms[i].path); |
697 free(elms[i].path); |
719 } |
698 } |
720 free(elms); |
699 free(elms); |
721 } |
700 } |
722 |
701 |
723 static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) { |
702 static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) { |
724 free(pathtf->current_path); |
703 free(pathtf->hbox); |
725 g_object_unref(pathtf->entry); |
704 g_object_unref(pathtf->entry); |
726 free(pathtf); |
705 free(pathtf); |
727 } |
706 } |
728 |
707 |
|
708 void ui_path_button_clicked(GtkWidget *widget, UiEventData *event) { |
|
709 UiPathElm *elm = event->customdata; |
|
710 cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len)); |
|
711 UiEvent evt; |
|
712 evt.obj = event->obj; |
|
713 evt.window = evt.obj->window; |
|
714 evt.document = evt.obj->ctx->document; |
|
715 evt.eventdata = elm->path; |
|
716 evt.intval = event->value; |
|
717 event->callback(&evt, event->userdata); |
|
718 free(path.ptr); |
|
719 } |
|
720 |
|
721 int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path) { |
|
722 size_t full_path_len = strlen(full_path); |
|
723 if(full_path_len == 0) { |
|
724 return 1; |
|
725 } |
|
726 |
|
727 size_t nelm = 0; |
|
728 UiPathElm* path_elm = pathtf->getpathelm(full_path, full_path_len, &nelm, pathtf->getpathelmdata); |
|
729 if (!path_elm) { |
|
730 return 1; |
|
731 } |
|
732 |
|
733 free(pathtf->current_path); |
|
734 pathtf->current_path = strdup(full_path); |
|
735 |
|
736 ui_pathelm_destroy(pathtf->current_pathelms, pathtf->current_nelm); |
|
737 pathtf->current_pathelms = path_elm; |
|
738 pathtf->current_nelm = nelm; |
|
739 |
|
740 return ui_pathtextfield_update_widget(pathtf); |
|
741 } |
|
742 |
|
743 static GtkWidget* ui_path_elm_button(UiPathTextField *pathtf, UiPathElm *elm, int i) { |
|
744 cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len)); |
|
745 GtkWidget *button = gtk_button_new_with_label(name.ptr); |
|
746 free(name.ptr); |
|
747 |
|
748 if(pathtf->onactivate) { |
|
749 UiEventData *eventdata = malloc(sizeof(UiEventData)); |
|
750 eventdata->callback = pathtf->onactivate; |
|
751 eventdata->userdata = pathtf->onactivatedata; |
|
752 eventdata->obj = pathtf->obj; |
|
753 eventdata->customdata = elm; |
|
754 eventdata->value = i; |
|
755 |
|
756 g_signal_connect( |
|
757 button, |
|
758 "clicked", |
|
759 G_CALLBACK(ui_path_button_clicked), |
|
760 eventdata); |
|
761 |
|
762 g_signal_connect( |
|
763 button, |
|
764 "destroy", |
|
765 G_CALLBACK(ui_destroy_userdata), |
|
766 eventdata); |
|
767 } |
|
768 |
|
769 return button; |
|
770 } |
|
771 |
729 static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) { |
772 static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) { |
730 const gchar *text = gtk_entry_get_text(GTK_ENTRY(pathtf->entry)); |
773 const gchar *text = ENTRY_GET_TEXT(pathtf->entry); |
731 if(strlen(text) == 0) { |
774 if(strlen(text) == 0) { |
732 return; |
775 return; |
733 } |
776 } |
734 |
777 |
735 UiObject *obj = pathtf->obj; |
778 UiObject *obj = pathtf->obj; |
747 evt.intval = -1; |
790 evt.intval = -1; |
748 pathtf->onactivate(&evt, pathtf->onactivatedata); |
791 pathtf->onactivate(&evt, pathtf->onactivatedata); |
749 } |
792 } |
750 } |
793 } |
751 |
794 |
752 static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) { |
795 #if GTK_MAJOR_VERSION >= 4 |
753 if (event->keyval == GDK_KEY_Escape) { |
796 |
754 // reset GtkEntry value |
797 static void pathbar_show_hbox(GtkWidget *widget, UiPathTextField *pathtf) { |
755 gtk_entry_set_text(GTK_ENTRY(self), pathtf->current_path); |
798 if(pathtf->current_path) { |
756 const gchar *text = gtk_entry_get_text(GTK_ENTRY(self)); |
799 gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox); |
757 ui_pathtextfield_update(pathtf, text); |
800 ENTRY_SET_TEXT(pathtf->entry, pathtf->current_path); |
758 return TRUE; |
801 } |
|
802 } |
|
803 |
|
804 static gboolean ui_path_textfield_key_controller( |
|
805 GtkEventControllerKey* self, |
|
806 guint keyval, |
|
807 guint keycode, |
|
808 GdkModifierType state, |
|
809 UiPathTextField *pathtf) |
|
810 { |
|
811 if(keyval == GDK_KEY_Escape) { |
|
812 pathbar_show_hbox(NULL, pathtf); |
759 } |
813 } |
760 return FALSE; |
814 return FALSE; |
761 } |
|
762 |
|
763 static GtkWidget* create_path_button_box() { |
|
764 GtkWidget *bb = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL); |
|
765 gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_EXPAND); // linked style |
|
766 gtk_box_set_homogeneous(GTK_BOX(bb), FALSE); |
|
767 gtk_box_set_spacing(GTK_BOX(bb), 0); |
|
768 return bb; |
|
769 } |
815 } |
770 |
816 |
771 UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) { |
817 UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) { |
772 UiObject* current = uic_current_obj(obj); |
818 UiObject* current = uic_current_obj(obj); |
773 |
819 |
788 if(!pathtf->getpathelm) { |
834 if(!pathtf->getpathelm) { |
789 pathtf->getpathelm = default_pathelm_func; |
835 pathtf->getpathelm = default_pathelm_func; |
790 pathtf->getpathelmdata = NULL; |
836 pathtf->getpathelmdata = NULL; |
791 } |
837 } |
792 |
838 |
|
839 pathtf->stack = gtk_stack_new(); |
|
840 gtk_widget_set_name(pathtf->stack, "path-textfield-box"); |
|
841 |
|
842 UI_APPLY_LAYOUT1(current, args); |
|
843 current->container->add(current->container, pathtf->stack, FALSE); |
|
844 |
|
845 pathtf->entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); |
|
846 pathtf->entry = gtk_entry_new(); |
|
847 gtk_box_append(GTK_BOX(pathtf->entry_box), pathtf->entry); |
|
848 gtk_widget_set_hexpand(pathtf->entry, TRUE); |
|
849 |
|
850 GtkWidget *cancel_button = gtk_button_new_from_icon_name("window-close-symbolic"); |
|
851 gtk_widget_add_css_class(cancel_button, "flat"); |
|
852 gtk_widget_add_css_class(cancel_button, "pathbar-extra-button"); |
|
853 gtk_box_append(GTK_BOX(pathtf->entry_box), cancel_button); |
|
854 g_signal_connect( |
|
855 cancel_button, |
|
856 "clicked", |
|
857 G_CALLBACK(pathbar_show_hbox), |
|
858 pathtf); |
|
859 |
|
860 gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->entry_box); |
|
861 g_object_ref(pathtf->entry); // for compatibility with older pathbar version |
|
862 g_signal_connect( |
|
863 pathtf->entry, |
|
864 "activate", |
|
865 G_CALLBACK(ui_path_textfield_activate), |
|
866 pathtf); |
|
867 |
|
868 GtkEventController *entry_cancel = gtk_event_controller_key_new(); |
|
869 gtk_widget_add_controller(pathtf->entry, entry_cancel); |
|
870 g_signal_connect(entry_cancel, "key-pressed", G_CALLBACK(ui_path_textfield_key_controller), pathtf); |
|
871 |
|
872 gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box); |
|
873 |
|
874 |
|
875 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING); |
|
876 if (var) { |
|
877 UiString* value = (UiString*)var->value; |
|
878 value->obj = pathtf; |
|
879 value->get = ui_path_textfield_get; |
|
880 value->set = ui_path_textfield_set; |
|
881 |
|
882 if(value->value.ptr) { |
|
883 char *str = strdup(value->value.ptr); |
|
884 ui_string_set(value, str); |
|
885 free(str); |
|
886 } |
|
887 } |
|
888 |
|
889 return pathtf->stack; |
|
890 } |
|
891 |
|
892 static void pathbar_pressed( |
|
893 GtkGestureClick* self, |
|
894 gint n_press, |
|
895 gdouble x, |
|
896 gdouble y, |
|
897 UiPathTextField *pathtf) |
|
898 { |
|
899 gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box); |
|
900 gtk_widget_grab_focus(pathtf->entry); |
|
901 } |
|
902 |
|
903 int ui_pathtextfield_update_widget(UiPathTextField* pathtf) { |
|
904 // recreate button hbox |
|
905 if(pathtf->hbox) { |
|
906 gtk_stack_remove(GTK_STACK(pathtf->stack), pathtf->hbox); |
|
907 } |
|
908 pathtf->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); |
|
909 gtk_box_set_homogeneous(GTK_BOX(pathtf->hbox), FALSE); |
|
910 gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->hbox); |
|
911 gtk_widget_set_name(pathtf->hbox, "pathbar"); |
|
912 |
|
913 // add buttons for path elements |
|
914 for (int i=0;i<pathtf->current_nelm;i++) { |
|
915 UiPathElm *elm = &pathtf->current_pathelms[i]; |
|
916 |
|
917 GtkWidget *button = ui_path_elm_button(pathtf, elm, i); |
|
918 gtk_widget_add_css_class(button, "flat"); |
|
919 |
|
920 gtk_box_append(GTK_BOX(pathtf->hbox), button); |
|
921 |
|
922 if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) { |
|
923 gtk_box_append(GTK_BOX(pathtf->hbox), gtk_label_new("/")); |
|
924 } |
|
925 } |
|
926 gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox); |
|
927 |
|
928 // create a widget for receiving button press events |
|
929 GtkWidget *event_area = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); |
|
930 GtkGesture *handler = gtk_gesture_click_new(); |
|
931 gtk_widget_add_controller(event_area, GTK_EVENT_CONTROLLER(handler)); |
|
932 g_signal_connect( |
|
933 handler, |
|
934 "pressed", |
|
935 G_CALLBACK(pathbar_pressed), |
|
936 pathtf); |
|
937 gtk_widget_set_hexpand(event_area, TRUE); |
|
938 gtk_widget_set_vexpand(event_area, TRUE); |
|
939 gtk_box_append(GTK_BOX(pathtf->hbox), event_area); |
|
940 |
|
941 return 0; |
|
942 } |
|
943 |
|
944 #else |
|
945 |
|
946 static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) { |
|
947 gtk_box_pack_start(GTK_BOX(pathtf->hbox), pathtf->entry, TRUE, TRUE, 0); |
|
948 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox); |
|
949 pathtf->buttonbox = NULL; |
|
950 |
|
951 gtk_widget_show(pathtf->entry); |
|
952 gtk_widget_grab_focus(pathtf->entry); |
|
953 |
|
954 return TRUE; |
|
955 } |
|
956 |
|
957 static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) { |
|
958 if (event->keyval == GDK_KEY_Escape) { |
|
959 // reset GtkEntry value |
|
960 gtk_entry_set_text(GTK_ENTRY(self), pathtf->current_path); |
|
961 const gchar *text = gtk_entry_get_text(GTK_ENTRY(self)); |
|
962 ui_pathtextfield_update(pathtf, text); |
|
963 return TRUE; |
|
964 } |
|
965 return FALSE; |
|
966 } |
|
967 |
|
968 static GtkWidget* create_path_button_box() { |
|
969 GtkWidget *bb = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL); |
|
970 gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_EXPAND); // linked style |
|
971 gtk_box_set_homogeneous(GTK_BOX(bb), FALSE); |
|
972 gtk_box_set_spacing(GTK_BOX(bb), 0); |
|
973 return bb; |
|
974 } |
|
975 |
|
976 UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) { |
|
977 UiObject* current = uic_current_obj(obj); |
|
978 |
|
979 UiPathTextField *pathtf = malloc(sizeof(UiPathTextField)); |
|
980 memset(pathtf, 0, sizeof(UiPathTextField)); |
|
981 pathtf->obj = obj; |
|
982 pathtf->getpathelm = args.getpathelm; |
|
983 pathtf->getpathelmdata = args.getpathelmdata; |
|
984 pathtf->onactivate = args.onactivate; |
|
985 pathtf->onactivatedata = args.onactivatedata; |
|
986 pathtf->ondragcomplete = args.ondragcomplete; |
|
987 pathtf->ondragcompletedata = args.ondragcompletedata; |
|
988 pathtf->ondragstart = args.ondragstart; |
|
989 pathtf->ondragstartdata = args.ondragstartdata; |
|
990 pathtf->ondrop = args.ondrop; |
|
991 pathtf->ondropdata = args.ondropsdata; |
|
992 |
|
993 if(!pathtf->getpathelm) { |
|
994 pathtf->getpathelm = default_pathelm_func; |
|
995 pathtf->getpathelmdata = NULL; |
|
996 } |
|
997 |
793 // top level container for the path textfield is a GtkEventBox |
998 // top level container for the path textfield is a GtkEventBox |
794 // the event box is needed to handle background button presses |
999 // the event box is needed to handle background button presses |
795 GtkWidget *eventbox = gtk_event_box_new(); |
1000 GtkWidget *eventbox = gtk_event_box_new(); |
796 g_signal_connect( |
1001 g_signal_connect( |
797 eventbox, |
1002 eventbox, |
844 } |
1049 } |
845 |
1050 |
846 return hbox; |
1051 return hbox; |
847 } |
1052 } |
848 |
1053 |
849 void ui_path_button_clicked(GtkWidget *widget, UiEventData *event) { |
1054 int ui_pathtextfield_update_widget(UiPathTextField* pathtf) { |
850 UiPathElm *elm = event->customdata; |
|
851 cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len)); |
|
852 UiEvent evt; |
|
853 evt.obj = event->obj; |
|
854 evt.window = evt.obj->window; |
|
855 evt.document = evt.obj->ctx->document; |
|
856 evt.eventdata = elm->path; |
|
857 evt.intval = event->value; |
|
858 event->callback(&evt, event->userdata); |
|
859 free(path.ptr); |
|
860 } |
|
861 |
|
862 int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path) { |
|
863 size_t full_path_len = strlen(full_path); |
|
864 if(full_path_len == 0) { |
|
865 return 1; |
|
866 } |
|
867 |
|
868 size_t nelm = 0; |
|
869 UiPathElm* path_elm = pathtf->getpathelm(full_path, full_path_len, &nelm, pathtf->getpathelmdata); |
|
870 if (!path_elm) { |
|
871 return 1; |
|
872 } |
|
873 |
|
874 free(pathtf->current_path); |
|
875 pathtf->current_path = strdup(full_path); |
|
876 |
|
877 ui_pathelm_destroy(pathtf->current_pathelms, pathtf->current_nelm); |
|
878 pathtf->current_pathelms = path_elm; |
|
879 pathtf->current_nelm = nelm; |
|
880 |
|
881 GtkWidget *buttonbox = create_path_button_box(); |
1055 GtkWidget *buttonbox = create_path_button_box(); |
882 |
1056 |
883 // switch from entry to buttonbox or remove current buttonbox |
1057 // switch from entry to buttonbox or remove current buttonbox |
884 if(pathtf->buttonbox) { |
1058 if(pathtf->buttonbox) { |
885 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox); |
1059 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox); |
887 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->entry); |
1061 gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->entry); |
888 } |
1062 } |
889 gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0); |
1063 gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0); |
890 pathtf->buttonbox = buttonbox; |
1064 pathtf->buttonbox = buttonbox; |
891 |
1065 |
892 for (int i=0;i<nelm;i++) { |
1066 for (int i=0;i<pathtf->current_nelm;i++) { |
893 UiPathElm *elm = &path_elm[i]; |
1067 UiPathElm *elm = &pathtf->current_pathelms[i]; |
894 |
1068 GtkWidget *button = ui_path_elm_button(pathtf, elm, i); |
895 cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len)); |
|
896 GtkWidget *button = gtk_button_new_with_label(name.ptr); |
|
897 free(name.ptr); |
|
898 |
|
899 if(pathtf->onactivate) { |
|
900 UiEventData *eventdata = malloc(sizeof(UiEventData)); |
|
901 eventdata->callback = pathtf->onactivate; |
|
902 eventdata->userdata = pathtf->onactivatedata; |
|
903 eventdata->obj = pathtf->obj; |
|
904 eventdata->customdata = elm; |
|
905 eventdata->value = i; |
|
906 |
|
907 g_signal_connect( |
|
908 button, |
|
909 "clicked", |
|
910 G_CALLBACK(ui_path_button_clicked), |
|
911 eventdata); |
|
912 |
|
913 g_signal_connect( |
|
914 button, |
|
915 "destroy", |
|
916 G_CALLBACK(ui_destroy_userdata), |
|
917 eventdata); |
|
918 } |
|
919 |
|
920 gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0); |
1069 gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0); |
921 } |
1070 } |
922 |
1071 |
923 gtk_widget_show_all(buttonbox); |
1072 gtk_widget_show_all(buttonbox); |
924 |
1073 |
925 return 0; |
1074 return 0; |
926 } |
1075 } |
|
1076 |
|
1077 #endif |
927 |
1078 |
928 char* ui_path_textfield_get(UiString *str) { |
1079 char* ui_path_textfield_get(UiString *str) { |
929 if(str->value.ptr) { |
1080 if(str->value.ptr) { |
930 str->value.free(str->value.ptr); |
1081 str->value.free(str->value.ptr); |
931 } |
1082 } |
932 UiPathTextField *tf = str->obj; |
1083 UiPathTextField *tf = str->obj; |
933 str->value.ptr = g_strdup(gtk_entry_get_text(GTK_ENTRY(tf->entry))); |
1084 str->value.ptr = g_strdup(ENTRY_GET_TEXT(tf->entry)); |
934 str->value.free = (ui_freefunc)g_free; |
1085 str->value.free = (ui_freefunc)g_free; |
935 return str->value.ptr; |
1086 return str->value.ptr; |
936 } |
1087 } |
937 |
1088 |
938 void ui_path_textfield_set(UiString *str, const char *value) { |
1089 void ui_path_textfield_set(UiString *str, const char *value) { |
939 UiPathTextField *tf = str->obj; |
1090 UiPathTextField *tf = str->obj; |
940 gtk_entry_set_text(GTK_ENTRY(tf->entry), value); |
1091 ENTRY_SET_TEXT(tf->entry, value); |
941 ui_pathtextfield_update(tf, value); |
1092 ui_pathtextfield_update(tf, value); |
942 if(str->value.ptr) { |
1093 if(str->value.ptr) { |
943 str->value.free(str->value.ptr); |
1094 str->value.free(str->value.ptr); |
944 str->value.ptr = NULL; |
1095 str->value.ptr = NULL; |
945 str->value.free = NULL; |
1096 str->value.free = NULL; |
946 } |
1097 } |
947 } |
1098 } |
948 |
|
949 #endif |
|