Mercurial > pidgin
comparison src/gtkrequest.c @ 13360:2e6dda9f9159
[gaim-migrate @ 15733]
SF Patch #1440729 from Sadrul
Closes SF Feature Request #1414706
"This moves some stuff from gtkrequest.c to gtkutils.c
-- this allows to setup autocomplete for entries that
deal with screen-names of the buddies.
I have used this function in the pounce dialog -- so
now it has auto-complete."
committer: Tailor Script <tailor@pidgin.im>
author | Richard Laager <rlaager@wiktel.com> |
---|---|
date | Wed, 01 Mar 2006 06:10:41 +0000 |
parents | d278dac585a5 |
children | 9b4a80566fd5 |
comparison
equal
deleted
inserted
replaced
13359:ca250092a23a | 13360:2e6dda9f9159 |
---|---|
34 #include "gtkutils.h" | 34 #include "gtkutils.h" |
35 #include "gtkstock.h" | 35 #include "gtkstock.h" |
36 | 36 |
37 #include <gdk/gdkkeysyms.h> | 37 #include <gdk/gdkkeysyms.h> |
38 | 38 |
39 #if GTK_CHECK_VERSION(2,3,0) | 39 static GtkWidget * create_account_field(GaimRequestField *field); |
40 # define NEW_STYLE_COMPLETION | |
41 #endif | |
42 | 40 |
43 typedef struct | 41 typedef struct |
44 { | 42 { |
45 GaimRequestType type; | 43 GaimRequestType type; |
46 | 44 |
77 } file; | 75 } file; |
78 | 76 |
79 } u; | 77 } u; |
80 | 78 |
81 } GaimGtkRequestData; | 79 } GaimGtkRequestData; |
82 | |
83 #ifndef NEW_STYLE_COMPLETION | |
84 typedef struct | |
85 { | |
86 GCompletion *completion; | |
87 | |
88 gboolean completion_started; | |
89 | |
90 } GaimGtkCompletionData; | |
91 #endif | |
92 | 80 |
93 static void | 81 static void |
94 generic_response_start(GaimGtkRequestData *data) | 82 generic_response_start(GaimGtkRequestData *data) |
95 { | 83 { |
96 GdkWindow *window = GTK_WIDGET(data->dialog)->window; | 84 GdkWindow *window = GTK_WIDGET(data->dialog)->window; |
675 | 663 |
676 gtk_widget_set_sensitive(req_data->ok_button, | 664 gtk_widget_set_sensitive(req_data->ok_button, |
677 gaim_request_fields_all_required_filled(field->group->fields_list)); | 665 gaim_request_fields_all_required_filled(field->group->fields_list)); |
678 } | 666 } |
679 | 667 |
680 #ifndef NEW_STYLE_COMPLETION | |
681 static gboolean | |
682 completion_entry_event(GtkEditable *entry, GdkEventKey *event, | |
683 GaimGtkCompletionData *data) | |
684 { | |
685 int pos, end_pos; | |
686 | |
687 if (event->type == GDK_KEY_PRESS && event->keyval == GDK_Tab) | |
688 { | |
689 gtk_editable_get_selection_bounds(entry, &pos, &end_pos); | |
690 | |
691 if (data->completion_started && | |
692 pos != end_pos && pos > 1 && | |
693 end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry)))) | |
694 { | |
695 gtk_editable_select_region(entry, 0, 0); | |
696 gtk_editable_set_position(entry, -1); | |
697 | |
698 return TRUE; | |
699 } | |
700 } | |
701 else if (event->type == GDK_KEY_PRESS && event->length > 0) | |
702 { | |
703 char *prefix, *nprefix; | |
704 | |
705 gtk_editable_get_selection_bounds(entry, &pos, &end_pos); | |
706 | |
707 if (data->completion_started && | |
708 pos != end_pos && pos > 1 && | |
709 end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry)))) | |
710 { | |
711 char *temp; | |
712 | |
713 temp = gtk_editable_get_chars(entry, 0, pos); | |
714 prefix = g_strconcat(temp, event->string, NULL); | |
715 g_free(temp); | |
716 } | |
717 else if (pos == end_pos && pos > 1 && | |
718 end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry)))) | |
719 { | |
720 prefix = g_strconcat(gtk_entry_get_text(GTK_ENTRY(entry)), | |
721 event->string, NULL); | |
722 } | |
723 else | |
724 return FALSE; | |
725 | |
726 pos = strlen(prefix); | |
727 nprefix = NULL; | |
728 | |
729 g_completion_complete(data->completion, prefix, &nprefix); | |
730 | |
731 if (nprefix != NULL) | |
732 { | |
733 gtk_entry_set_text(GTK_ENTRY(entry), nprefix); | |
734 gtk_editable_set_position(entry, pos); | |
735 gtk_editable_select_region(entry, pos, -1); | |
736 | |
737 data->completion_started = TRUE; | |
738 | |
739 g_free(nprefix); | |
740 g_free(prefix); | |
741 | |
742 return TRUE; | |
743 } | |
744 | |
745 g_free(prefix); | |
746 } | |
747 | |
748 return FALSE; | |
749 } | |
750 | |
751 static void | |
752 destroy_completion_data(GtkWidget *w, GaimGtkCompletionData *data) | |
753 { | |
754 g_list_foreach(data->completion->items, (GFunc)g_free, NULL); | |
755 g_completion_free(data->completion); | |
756 | |
757 g_free(data); | |
758 } | |
759 #endif /* !NEW_STYLE_COMPLETION */ | |
760 | |
761 #ifdef NEW_STYLE_COMPLETION | |
762 static gboolean screenname_completion_match_func(GtkEntryCompletion *completion, | |
763 const gchar *key, GtkTreeIter *iter, gpointer user_data) | |
764 { | |
765 GtkTreeModel *model; | |
766 GValue val1; | |
767 GValue val2; | |
768 const char *tmp; | |
769 | |
770 model = gtk_entry_completion_get_model (completion); | |
771 | |
772 val1.g_type = 0; | |
773 gtk_tree_model_get_value(model, iter, 2, &val1); | |
774 tmp = g_value_get_string(&val1); | |
775 if (tmp != NULL && gaim_str_has_prefix(tmp, key)) | |
776 { | |
777 g_value_unset(&val1); | |
778 return TRUE; | |
779 } | |
780 g_value_unset(&val1); | |
781 | |
782 val2.g_type = 0; | |
783 gtk_tree_model_get_value(model, iter, 3, &val2); | |
784 tmp = g_value_get_string(&val2); | |
785 if (tmp != NULL && gaim_str_has_prefix(tmp, key)) | |
786 { | |
787 g_value_unset(&val2); | |
788 return TRUE; | |
789 } | |
790 g_value_unset(&val2); | |
791 | |
792 return FALSE; | |
793 } | |
794 | |
795 static gboolean screenname_completion_match_selected_cb(GtkEntryCompletion *completion, | |
796 GtkTreeModel *model, GtkTreeIter *iter, gpointer *user_data) | |
797 { | |
798 GValue val; | |
799 GaimRequestField *screen_field = user_data[1]; | |
800 GList *fields = screen_field->group->fields; | |
801 GaimAccount *account; | |
802 | |
803 val.g_type = 0; | |
804 gtk_tree_model_get_value(model, iter, 1, &val); | |
805 gtk_entry_set_text(GTK_ENTRY(user_data[0]), g_value_get_string(&val)); | |
806 g_value_unset(&val); | |
807 | |
808 gtk_tree_model_get_value(model, iter, 4, &val); | |
809 account = g_value_get_pointer(&val); | |
810 g_value_unset(&val); | |
811 | |
812 if (account == NULL) | |
813 return TRUE; | |
814 | |
815 do { | |
816 GaimRequestField *field = fields->data; | |
817 | |
818 if (gaim_request_field_get_type(field) == GAIM_REQUEST_FIELD_ACCOUNT) { | |
819 const char *type_hint = gaim_request_field_get_type_hint(field); | |
820 | |
821 if (type_hint != NULL && !strcmp(type_hint, "account")) { | |
822 /* We found the corresponding account field. */ | |
823 GtkOptionMenu *optmenu = GTK_OPTION_MENU(field->ui_data); | |
824 | |
825 /* Set the account in the request API. */ | |
826 gaim_request_field_account_set_value(field, account); | |
827 | |
828 if (optmenu != NULL) { | |
829 GList *items = GTK_MENU_SHELL(gtk_option_menu_get_menu(optmenu))->children; | |
830 guint index = 0; | |
831 | |
832 do { | |
833 if (account == g_object_get_data(G_OBJECT(items->data), "account")) { | |
834 /* Set the account in the GUI. */ | |
835 gtk_option_menu_set_history(GTK_OPTION_MENU(field->ui_data), index); | |
836 return TRUE; | |
837 } | |
838 index++; | |
839 } while ((items = items->next) != NULL); | |
840 } | |
841 | |
842 return TRUE; | |
843 } | |
844 } | |
845 | |
846 } while ((fields = fields->next) != NULL); | |
847 | |
848 return TRUE; | |
849 } | |
850 | |
851 static void | |
852 add_screenname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias, | |
853 const GaimAccount *account, const char *screenname) | |
854 { | |
855 GtkTreeIter iter; | |
856 gboolean completion_added = FALSE; | |
857 gchar *normalized_screenname; | |
858 gchar *tmp; | |
859 | |
860 tmp = g_utf8_normalize(screenname, -1, G_NORMALIZE_DEFAULT); | |
861 normalized_screenname = g_utf8_casefold(tmp, -1); | |
862 g_free(tmp); | |
863 | |
864 /* There's no sense listing things like: 'xxx "xxx"' | |
865 when the screenname and buddy alias match. */ | |
866 if (buddy_alias && strcmp(buddy_alias, screenname)) { | |
867 char *completion_entry = g_strdup_printf("%s \"%s\"", screenname, buddy_alias); | |
868 char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT); | |
869 | |
870 tmp = g_utf8_casefold(tmp2, -1); | |
871 g_free(tmp2); | |
872 | |
873 gtk_list_store_append(store, &iter); | |
874 gtk_list_store_set(store, &iter, | |
875 0, completion_entry, | |
876 1, screenname, | |
877 2, normalized_screenname, | |
878 3, tmp, | |
879 4, account, | |
880 -1); | |
881 g_free(completion_entry); | |
882 g_free(tmp); | |
883 completion_added = TRUE; | |
884 } | |
885 | |
886 /* There's no sense listing things like: 'xxx "xxx"' | |
887 when the screenname and contact alias match. */ | |
888 if (contact_alias && strcmp(contact_alias, screenname)) { | |
889 /* We don't want duplicates when the contact and buddy alias match. */ | |
890 if (!buddy_alias || strcmp(contact_alias, buddy_alias)) { | |
891 char *completion_entry = g_strdup_printf("%s \"%s\"", | |
892 screenname, contact_alias); | |
893 char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT); | |
894 | |
895 tmp = g_utf8_casefold(tmp2, -1); | |
896 g_free(tmp2); | |
897 | |
898 gtk_list_store_append(store, &iter); | |
899 gtk_list_store_set(store, &iter, | |
900 0, completion_entry, | |
901 1, screenname, | |
902 2, normalized_screenname, | |
903 3, tmp, | |
904 4, account, | |
905 -1); | |
906 g_free(completion_entry); | |
907 g_free(tmp); | |
908 completion_added = TRUE; | |
909 } | |
910 } | |
911 | |
912 if (completion_added == FALSE) { | |
913 /* Add the buddy's screenname. */ | |
914 gtk_list_store_append(store, &iter); | |
915 gtk_list_store_set(store, &iter, | |
916 0, screenname, | |
917 1, screenname, | |
918 2, normalized_screenname, | |
919 3, NULL, | |
920 4, account, | |
921 -1); | |
922 } | |
923 | |
924 g_free(normalized_screenname); | |
925 } | |
926 #endif /* NEW_STYLE_COMPLETION */ | |
927 | |
928 static void get_log_set_name(GaimLogSet *set, gpointer value, gpointer **set_hash_data) | |
929 { | |
930 /* 1. Don't show buddies because we will have gotten them already. | |
931 * 2. Only show those with non-NULL accounts that are currently connected. | |
932 * 3. The boxes that use this autocomplete code handle only IMs. */ | |
933 if (!set->buddy && | |
934 (GPOINTER_TO_INT(set_hash_data[1]) || | |
935 (set->account != NULL && gaim_account_is_connected(set->account))) && | |
936 set->type == GAIM_LOG_IM) { | |
937 #ifdef NEW_STYLE_COMPLETION | |
938 add_screenname_autocomplete_entry((GtkListStore *)set_hash_data[0], | |
939 NULL, NULL, set->account, set->name); | |
940 #else | |
941 GList **items = ((GList **)set_hash_data[0]); | |
942 /* Steal the name for the GCompletion. */ | |
943 *items = g_list_append(*items, set->name); | |
944 set->name = set->normalized_name = NULL; | |
945 #endif /* NEW_STYLE_COMPLETION */ | |
946 } | |
947 } | |
948 | |
949 static void | |
950 setup_screenname_autocomplete(GtkWidget *entry, GaimRequestField *field, gboolean all) | |
951 { | |
952 #ifdef NEW_STYLE_COMPLETION | |
953 /* Store the displayed completion value, the screenname, the UTF-8 normalized & casefolded screenname, | |
954 * the UTF-8 normalized & casefolded value for comparison, and the account. */ | |
955 GtkListStore *store = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER); | |
956 | |
957 GaimBlistNode *gnode, *cnode, *bnode; | |
958 GHashTable *sets; | |
959 gpointer set_hash_data[] = {store, GINT_TO_POINTER(all)}; | |
960 GtkEntryCompletion *completion; | |
961 gpointer *data; | |
962 | |
963 for (gnode = gaim_get_blist()->root; gnode != NULL; gnode = gnode->next) | |
964 { | |
965 if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) | |
966 continue; | |
967 | |
968 for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) | |
969 { | |
970 if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) | |
971 continue; | |
972 | |
973 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) | |
974 { | |
975 GaimBuddy *buddy = (GaimBuddy *)bnode; | |
976 | |
977 if (!all && !gaim_account_is_connected(buddy->account)) | |
978 continue; | |
979 | |
980 add_screenname_autocomplete_entry(store, | |
981 ((GaimContact *)cnode)->alias, | |
982 gaim_buddy_get_contact_alias(buddy), | |
983 buddy->account, | |
984 buddy->name | |
985 ); | |
986 } | |
987 } | |
988 } | |
989 | |
990 sets = gaim_log_get_log_sets(); | |
991 g_hash_table_foreach(sets, (GHFunc)get_log_set_name, &set_hash_data); | |
992 g_hash_table_destroy(sets); | |
993 | |
994 | |
995 /* Sort the completion list by screenname. */ | |
996 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), | |
997 1, GTK_SORT_ASCENDING); | |
998 | |
999 completion = gtk_entry_completion_new(); | |
1000 gtk_entry_completion_set_match_func(completion, screenname_completion_match_func, NULL, NULL); | |
1001 | |
1002 data = g_new0(gpointer, 2); | |
1003 data[0] = entry; | |
1004 data[1] = field; | |
1005 g_signal_connect(G_OBJECT(completion), "match-selected", | |
1006 G_CALLBACK(screenname_completion_match_selected_cb), data); | |
1007 | |
1008 gtk_entry_set_completion(GTK_ENTRY(entry), completion); | |
1009 g_object_unref(completion); | |
1010 | |
1011 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store)); | |
1012 g_object_unref(store); | |
1013 | |
1014 gtk_entry_completion_set_text_column(completion, 0); | |
1015 | |
1016 #else /* !NEW_STYLE_COMPLETION */ | |
1017 GaimGtkCompletionData *data; | |
1018 GaimBlistNode *gnode, *cnode, *bnode; | |
1019 GList *item = g_list_append(NULL, NULL); | |
1020 GHashTable *sets; | |
1021 gpointer set_hash_data[2]; | |
1022 | |
1023 data = g_new0(GaimGtkCompletionData, 1); | |
1024 | |
1025 data->completion = g_completion_new(NULL); | |
1026 | |
1027 g_completion_set_compare(data->completion, g_ascii_strncasecmp); | |
1028 | |
1029 for (gnode = gaim_get_blist()->root; gnode != NULL; gnode = gnode->next) | |
1030 { | |
1031 if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) | |
1032 continue; | |
1033 | |
1034 for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) | |
1035 { | |
1036 if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) | |
1037 continue; | |
1038 | |
1039 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) | |
1040 { | |
1041 GaimBuddy *buddy = (GaimBuddy *)bnode; | |
1042 | |
1043 if (!all && !gaim_account_is_connected(buddy->account)) | |
1044 continue; | |
1045 | |
1046 item->data = g_strdup(buddy->name); | |
1047 g_completion_add_items(data->completion, item); | |
1048 } | |
1049 } | |
1050 } | |
1051 g_list_free(item); | |
1052 | |
1053 sets = gaim_log_get_log_sets(); | |
1054 item = NULL; | |
1055 set_hash_data[0] = &item; | |
1056 set_hash_data[1] = GINT_TO_POINTER(all); | |
1057 g_hash_table_foreach(sets, (GHFunc)get_log_set_name, &set_hash_data); | |
1058 g_hash_table_destroy(sets); | |
1059 g_completion_add_items(data->completion, item); | |
1060 g_list_free(item); | |
1061 | |
1062 g_signal_connect(G_OBJECT(entry), "event", | |
1063 G_CALLBACK(completion_entry_event), data); | |
1064 g_signal_connect(G_OBJECT(entry), "destroy", | |
1065 G_CALLBACK(destroy_completion_data), data); | |
1066 | |
1067 #endif /* !NEW_STYLE_COMPLETION */ | |
1068 } | |
1069 | |
1070 static void | 668 static void |
1071 setup_entry_field(GtkWidget *entry, GaimRequestField *field) | 669 setup_entry_field(GtkWidget *entry, GaimRequestField *field) |
1072 { | 670 { |
1073 const char *type_hint; | 671 const char *type_hint; |
1074 | 672 |
1080 G_CALLBACK(req_entry_field_changed_cb), field); | 678 G_CALLBACK(req_entry_field_changed_cb), field); |
1081 } | 679 } |
1082 | 680 |
1083 if ((type_hint = gaim_request_field_get_type_hint(field)) != NULL) | 681 if ((type_hint = gaim_request_field_get_type_hint(field)) != NULL) |
1084 { | 682 { |
1085 if (!strncmp(type_hint, "screenname", sizeof("screenname") - 1)) | 683 if (gaim_str_has_prefix(type_hint, "screenname")) |
1086 { | 684 { |
1087 setup_screenname_autocomplete(entry, field, !strcmp(type_hint, "screenname-all")); | 685 GtkWidget *optmenu = NULL; |
686 GList *fields = field->group->fields; | |
687 while (fields) | |
688 { | |
689 GaimRequestField *fld = fields->data; | |
690 fields = fields->next; | |
691 | |
692 if (gaim_request_field_get_type(fld) == GAIM_REQUEST_FIELD_ACCOUNT) | |
693 { | |
694 const char *type_hint = gaim_request_field_get_type_hint(fld); | |
695 if (type_hint != NULL && strcmp(type_hint, "account") == 0) | |
696 { | |
697 if (fld->ui_data == NULL) | |
698 fld->ui_data - create_account_field(fld); | |
699 optmenu = GTK_WIDGET(fld->ui_data); | |
700 break; | |
701 } | |
702 } | |
703 } | |
704 gaim_gtk_setup_screenname_autocomplete(entry, optmenu, !strcmp(type_hint, "screenname-all")); | |
1088 } | 705 } |
1089 } | 706 } |
1090 } | 707 } |
1091 | 708 |
1092 static GtkWidget * | 709 static GtkWidget * |
1659 } | 1276 } |
1660 | 1277 |
1661 gtk_widget_show(label); | 1278 gtk_widget_show(label); |
1662 } | 1279 } |
1663 | 1280 |
1664 if (type == GAIM_REQUEST_FIELD_STRING) | 1281 if (field->ui_data != NULL) |
1282 widget = GTK_WIDGET(field->ui_data); | |
1283 else if (type == GAIM_REQUEST_FIELD_STRING) | |
1665 widget = create_string_field(field); | 1284 widget = create_string_field(field); |
1666 else if (type == GAIM_REQUEST_FIELD_INTEGER) | 1285 else if (type == GAIM_REQUEST_FIELD_INTEGER) |
1667 widget = create_int_field(field); | 1286 widget = create_int_field(field); |
1668 else if (type == GAIM_REQUEST_FIELD_BOOLEAN) | 1287 else if (type == GAIM_REQUEST_FIELD_BOOLEAN) |
1669 widget = create_bool_field(field); | 1288 widget = create_bool_field(field); |