638 str->value.free(str->value.ptr); |
642 str->value.free(str->value.ptr); |
639 str->value.ptr = NULL; |
643 str->value.ptr = NULL; |
640 str->value.free = NULL; |
644 str->value.free = NULL; |
641 } |
645 } |
642 } |
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 } |