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);