2009-01-06 [colin] 3.7.0cvs15
[claws.git] / src / plugins / pgpcore / select-keys.c
1 /* select-keys.c - GTK+ based key selection
2  *      Copyright (C) 2001-2007 Werner Koch (dd9jn) and the Claws Mail team
3  *
4  * This program is free software; you can redistribute it and/or modify        
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  * 
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #ifdef USE_GPGME
24 #include <stdio.h>
25 #include <stdlib.h>
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtk.h>
31 #include "select-keys.h"
32 #include "utils.h"
33 #include "gtkutils.h"
34 #include "inputdialog.h"
35 #include "manage_window.h"
36 #include "alertpanel.h"
37
38 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
39 #define DIMof(type,member)   DIM(((type *)0)->member)
40
41
42 enum col_titles { 
43     COL_ALGO,
44     COL_KEYID,
45     COL_NAME,
46     COL_EMAIL,
47     COL_VALIDITY,
48
49     N_COL_TITLES
50 };
51
52 struct select_keys_s {
53     int okay;
54     GtkWidget *window;
55     GtkLabel *toplabel;
56     GtkCMCList *clist;
57     const char *pattern;
58     unsigned int num_keys;
59     gpgme_key_t *kset;
60     gpgme_ctx_t select_ctx;
61     gpgme_protocol_t proto;
62     GtkSortType sort_type;
63     enum col_titles sort_column;
64     SelectionResult result;
65 };
66
67
68 static void set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto);
69 static gpgme_key_t fill_clist (struct select_keys_s *sk, const char *pattern,
70                         gpgme_protocol_t proto);
71 static void create_dialog (struct select_keys_s *sk);
72 static void open_dialog (struct select_keys_s *sk);
73 static void close_dialog (struct select_keys_s *sk);
74 static gint delete_event_cb (GtkWidget *widget,
75                              GdkEventAny *event, gpointer data);
76 static gboolean key_pressed_cb (GtkWidget *widget,
77                                 GdkEventKey *event, gpointer data);
78 static void select_btn_cb (GtkWidget *widget, gpointer data);
79 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
80 static void dont_encrypt_btn_cb (GtkWidget *widget, gpointer data);
81 static void other_btn_cb (GtkWidget *widget, gpointer data);
82 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
83 static void sort_keys_name (GtkWidget *widget, gpointer data);
84 static void sort_keys_email (GtkWidget *widget, gpointer data);
85
86 static gboolean use_untrusted (gpgme_key_t, gpgme_protocol_t proto);
87
88 static void
89 update_progress (struct select_keys_s *sk, int running, const char *pattern)
90 {
91     static int windmill[] = { '-', '\\', '|', '/' };
92     char *buf;
93
94     if (!running)
95         buf = g_strdup_printf (_("No exact match for '%s'; please select the key."),
96                                pattern);
97     else 
98         buf = g_strdup_printf (_("Collecting info for '%s' ... %c"),
99                                pattern,
100                                windmill[running%DIM(windmill)]);
101     gtk_label_set_text (sk->toplabel, buf);
102     g_free (buf);
103 }
104
105
106 /**
107  * gpgmegtk_recipient_selection:
108  * @recp_names: A list of email addresses
109  * 
110  * Select a list of recipients from a given list of email addresses.
111  * This may pop up a window to present the user a choice, it will also
112  * check that the recipients key are all valid.
113  * 
114  * Return value: NULL on error or a list of list of recipients.
115  **/
116 gpgme_key_t *
117 gpgmegtk_recipient_selection (GSList *recp_names, SelectionResult *result,
118                                 gpgme_protocol_t proto)
119 {
120     struct select_keys_s sk;
121     gpgme_key_t key = NULL;
122     memset (&sk, 0, sizeof sk);
123
124     open_dialog (&sk);
125
126     do {
127         sk.pattern = recp_names? recp_names->data:NULL;
128         sk.proto = proto;
129         gtk_cmclist_clear (sk.clist);
130         key = fill_clist (&sk, sk.pattern, proto);
131         update_progress (&sk, 0, sk.pattern);
132         if (!key) {
133                 gtk_widget_show_all (sk.window);
134                 gtk_main ();
135         } else {
136                 gtk_widget_hide (sk.window);
137                 sk.kset = g_realloc(sk.kset,
138                         sizeof(gpgme_key_t) * (sk.num_keys + 1));
139                 gpgme_key_ref(key);
140                 sk.kset[sk.num_keys] = key;
141                 sk.num_keys++;
142                 sk.okay = 1;
143                 sk.result = KEY_SELECTION_OK;
144                 gpgme_release (sk.select_ctx);
145                 sk.select_ctx = NULL;
146                 debug_print("used %s\n", key->uids->email);
147         }
148         key = NULL;
149         if (recp_names)
150             recp_names = recp_names->next;
151     } while (sk.okay && recp_names);
152
153     close_dialog (&sk);
154
155     if (!sk.okay) {
156         g_free(sk.kset);
157         sk.kset = NULL;
158     } else {
159         sk.kset = g_realloc(sk.kset, sizeof(gpgme_key_t) * (sk.num_keys + 1));
160         sk.kset[sk.num_keys] = NULL;
161     }
162     if (result)
163             *result = sk.result;
164     return sk.kset;
165
166
167 static void
168 destroy_key (gpointer data)
169 {
170     gpgme_key_t key = data;
171     gpgme_key_release (key);
172 }
173
174 static void
175 set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto)
176 {
177     const char *s;
178     const char *text[N_COL_TITLES];
179     char *algo_buf;
180     int row;
181     gsize by_read = 0, by_written = 0;
182     gchar *ret_str = NULL;
183
184     /* first check whether the key is capable of encryption which is not
185      * the case for revoked, expired or sign-only keys */
186     if (!key->can_encrypt || key->revoked || key->expired)
187         return;
188
189     algo_buf = g_strdup_printf ("%du/%s", 
190          key->subkeys->length,
191          gpgme_pubkey_algo_name(key->subkeys->pubkey_algo) );
192     text[COL_ALGO] = algo_buf;
193
194     s = key->subkeys->keyid;
195     if (strlen (s) == 16)
196         s += 8; /* show only the short keyID */
197     text[COL_KEYID] = s;
198
199
200     s = key->uids->name;
201     if (!s || !*s)
202         s = key->uids->uid;
203     if (proto == GPGME_PROTOCOL_CMS) {
204         if (strstr(s, ",CN="))
205                 s = strstr(s, ",CN=")+4;
206         else if (strstr(s, "CN="))
207                 s = strstr(s, "CN=")+3;
208     } 
209     
210     ret_str = NULL;
211     if (!g_utf8_validate(s, -1, NULL))
212             ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
213     if (ret_str && by_written) {
214         s = ret_str;
215     }
216     text[COL_NAME] = s;
217
218     if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
219         gpgme_user_id_t uid = key->uids->next;
220         if (uid)
221                 s = uid->email;
222         else
223                 s = key->uids->email;
224     } else {
225         s = key->uids->email;
226     }
227     
228     ret_str = NULL;
229     if (!g_utf8_validate(s, -1, NULL))
230             ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
231     if (ret_str && by_written) {
232         s = ret_str;
233     }
234     text[COL_EMAIL] = s;
235
236     switch (key->uids->validity)
237       {
238       case GPGME_VALIDITY_UNDEFINED:
239         s = _("Undefined");
240         break;
241       case GPGME_VALIDITY_NEVER:
242         s = _("Never");
243         break;
244       case GPGME_VALIDITY_MARGINAL:
245         s = _("Marginal");
246         break;
247       case GPGME_VALIDITY_FULL:
248         s = _("Full");
249         break;
250       case GPGME_VALIDITY_ULTIMATE:
251         s = _("Ultimate");
252         break;
253       case GPGME_VALIDITY_UNKNOWN:
254       default:
255         s = _("Unknown");
256         break;
257       }
258     text[COL_VALIDITY] = s;
259
260     row = gtk_cmclist_append (clist, (gchar**)text);
261     g_free (algo_buf);
262
263     gtk_cmclist_set_row_data_full (clist, row, key, destroy_key);
264 }
265
266 static gpgme_key_t 
267 fill_clist (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
268 {
269     GtkCMCList *clist;
270     gpgme_ctx_t ctx;
271     gpgme_error_t err;
272     gpgme_key_t key;
273     int running=0;
274     int num_results = 0;
275     gboolean exact_match = FALSE;
276     gpgme_key_t last_key = NULL;
277     g_return_val_if_fail (sk, NULL);
278     clist = sk->clist;
279     g_return_val_if_fail (clist, NULL);
280
281     debug_print ("select_keys:fill_clist:  pattern '%s' proto %d\n", pattern, proto);
282
283     /*gtk_cmclist_freeze (select_keys.clist);*/
284     err = gpgme_new (&ctx);
285     g_assert (!err);
286
287     gpgme_set_protocol(ctx, proto);
288     sk->select_ctx = ctx;
289
290     update_progress (sk, ++running, pattern);
291     while (gtk_events_pending ())
292         gtk_main_iteration ();
293
294     err = gpgme_op_keylist_start (ctx, pattern, 0);
295     if (err) {
296         debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
297                      pattern, gpgme_strerror (err));
298         sk->select_ctx = NULL;
299         gpgme_release(ctx);
300         return NULL;
301     }
302     update_progress (sk, ++running, pattern);
303     while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
304         gpgme_user_id_t uid = key->uids;
305         if (!key->can_encrypt || key->revoked || key->expired)
306                 continue;
307         debug_print ("%% %s:%d:  insert\n", __FILE__ ,__LINE__ );
308         set_row (clist, key, proto ); 
309         for (; uid; uid = uid->next) {
310                 gchar *raw_mail = NULL;
311
312                 if (!uid->email)
313                         continue;
314                 raw_mail = g_strdup(uid->email);
315                 extract_address(raw_mail);
316                 if (!strcasecmp(pattern, raw_mail)) {
317                         exact_match = TRUE;
318                         g_free(raw_mail);
319                         break;
320                 }
321                 g_free(raw_mail);
322         }
323         num_results++;
324         last_key = key;
325         key = NULL;
326         update_progress (sk, ++running, pattern);
327         while (gtk_events_pending ())
328             gtk_main_iteration ();
329     }
330  
331     if (exact_match == TRUE && num_results == 1) {
332             if (last_key->uids->validity < GPGME_VALIDITY_FULL && 
333                 !use_untrusted(last_key, proto))
334                     exact_match = FALSE;
335     }
336
337     debug_print ("%% %s:%d:  ready\n", __FILE__ ,__LINE__ );
338     if (gpgme_err_code(err) != GPG_ERR_EOF) {
339         debug_print ("** gpgme_op_keylist_next failed: %s",
340                      gpgme_strerror (err));
341         gpgme_op_keylist_end(ctx);
342     }
343     if (!exact_match || num_results != 1) {
344             sk->select_ctx = NULL;
345             gpgme_release (ctx);
346     }
347     /*gtk_cmclist_thaw (select_keys.clist);*/
348     return (exact_match == TRUE && num_results == 1 ? last_key:NULL);
349 }
350
351
352 static void 
353 create_dialog (struct select_keys_s *sk)
354 {
355     GtkWidget *window;
356     GtkWidget *vbox, *vbox2, *hbox;
357     GtkWidget *bbox;
358     GtkWidget *scrolledwin;
359     GtkWidget *clist;
360     GtkWidget *label;
361     GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
362     const char *titles[N_COL_TITLES];
363
364     g_assert (!sk->window);
365     window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
366     gtk_widget_set_size_request (window, 520, 280);
367     gtk_container_set_border_width (GTK_CONTAINER (window), 8);
368     gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
369     gtk_window_set_modal (GTK_WINDOW (window), TRUE);
370     g_signal_connect (G_OBJECT (window), "delete_event",
371                       G_CALLBACK (delete_event_cb), sk);
372     g_signal_connect (G_OBJECT (window), "key_press_event",
373                       G_CALLBACK (key_pressed_cb), sk);
374     MANAGE_WINDOW_SIGNALS_CONNECT (window);
375
376     vbox = gtk_vbox_new (FALSE, 8);
377     gtk_container_add (GTK_CONTAINER (window), vbox);
378
379     hbox  = gtk_hbox_new(FALSE, 4);
380     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
381     label = gtk_label_new ( "" );
382     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
383
384     hbox = gtk_hbox_new (FALSE, 8);
385     gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
386     gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
387
388     scrolledwin = gtk_scrolled_window_new (NULL, NULL);
389     gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
390     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
391                                     GTK_POLICY_AUTOMATIC,
392                                     GTK_POLICY_AUTOMATIC);
393
394     titles[COL_ALGO]     = _("Size");
395     titles[COL_KEYID]    = _("Key ID");
396     titles[COL_NAME]     = _("Name");
397     titles[COL_EMAIL]    = _("Address");
398     titles[COL_VALIDITY] = _("Trust");
399
400     clist = gtk_cmclist_new_with_titles (N_COL_TITLES, (char**)titles);
401     gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
402     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_ALGO,      72);
403     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID,     76);
404     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME,     130);
405     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL,    130);
406     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_VALIDITY,  20);
407     gtk_cmclist_set_selection_mode (GTK_CMCLIST(clist), GTK_SELECTION_BROWSE);
408     g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_NAME].button),
409                       "clicked",
410                       G_CALLBACK(sort_keys_name), sk);
411     g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_EMAIL].button),
412                       "clicked",
413                       G_CALLBACK(sort_keys_email), sk);
414
415     hbox = gtk_hbox_new (FALSE, 8);
416     gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
417
418     gtkut_stock_button_set_create (&bbox, 
419                                    &select_btn, _("_Select"),
420                                    &other_btn, _("_Other"),
421                                    &dont_encrypt_btn, _("Do_n't encrypt"));
422     
423     cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
424     GTK_WIDGET_SET_FLAGS(cancel_btn, GTK_CAN_DEFAULT);
425     gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
426     gtk_widget_show(cancel_btn);
427     gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
428     gtk_widget_grab_default (select_btn);
429
430     g_signal_connect (G_OBJECT (select_btn), "clicked",
431                       G_CALLBACK (select_btn_cb), sk);
432     g_signal_connect (G_OBJECT(cancel_btn), "clicked",
433                       G_CALLBACK (cancel_btn_cb), sk);
434     g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
435                       G_CALLBACK (dont_encrypt_btn_cb), sk);
436     g_signal_connect (G_OBJECT (other_btn), "clicked",
437                       G_CALLBACK (other_btn_cb), sk);
438
439     vbox2 = gtk_vbox_new (FALSE, 4);
440     gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
441
442     sk->window = window;
443     sk->toplabel = GTK_LABEL (label);
444     sk->clist  = GTK_CMCLIST (clist);
445 }
446
447
448 static void
449 open_dialog (struct select_keys_s *sk)
450 {
451     if (!sk->window)
452         create_dialog (sk);
453     manage_window_set_transient (GTK_WINDOW (sk->window));
454     sk->okay = 0;
455     sk->sort_column = N_COL_TITLES; /* use an invalid value */
456     sk->sort_type = GTK_SORT_ASCENDING;
457 }
458
459
460 static void
461 close_dialog (struct select_keys_s *sk)
462 {
463     g_return_if_fail (sk);
464     gtk_widget_destroy (sk->window);
465     sk->window = NULL;
466 }
467
468
469 static gint
470 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
471 {
472     struct select_keys_s *sk = data;
473
474     sk->okay = 0;
475     gtk_main_quit ();
476
477     return TRUE;
478 }
479
480
481 static gboolean
482 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
483 {
484     struct select_keys_s *sk = data;
485
486     g_return_val_if_fail (sk, FALSE);
487     if (event && event->keyval == GDK_Escape) {
488         sk->okay = 0;
489         gtk_main_quit ();
490     }
491     return FALSE;
492 }
493
494
495 static void 
496 select_btn_cb (GtkWidget *widget, gpointer data)
497 {
498     struct select_keys_s *sk = data;
499     int row;
500     gboolean use_key;
501     gpgme_key_t key;
502
503     g_return_if_fail (sk);
504     if (!sk->clist->selection) {
505         debug_print ("** nothing selected");
506         return;
507     }
508     row = GPOINTER_TO_INT(sk->clist->selection->data);
509     key = gtk_cmclist_get_row_data(sk->clist, row);
510     if (key) {
511         if ( key->uids->validity < GPGME_VALIDITY_FULL ) {
512             use_key = use_untrusted(key, sk->proto);
513             if (!use_key) {
514                 debug_print ("** Key untrusted, will not encrypt");
515                 return;
516             }
517         }
518         sk->kset = g_realloc(sk->kset,
519                 sizeof(gpgme_key_t) * (sk->num_keys + 1));
520         gpgme_key_ref(key);
521         sk->kset[sk->num_keys] = key;
522         sk->num_keys++;
523         sk->okay = 1;
524         sk->result = KEY_SELECTION_OK;
525         gtk_main_quit ();
526     }
527 }
528
529
530 static void 
531 cancel_btn_cb (GtkWidget *widget, gpointer data)
532 {
533     struct select_keys_s *sk = data;
534
535     g_return_if_fail (sk);
536     sk->okay = 0;
537     sk->result = KEY_SELECTION_CANCEL;
538     if (sk->select_ctx)
539         gpgme_cancel (sk->select_ctx);
540     gtk_main_quit ();
541 }
542
543 static void 
544 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
545 {
546     struct select_keys_s *sk = data;
547
548     g_return_if_fail (sk);
549     sk->okay = 0;
550     sk->result = KEY_SELECTION_DONT;
551     if (sk->select_ctx)
552         gpgme_cancel (sk->select_ctx);
553     gtk_main_quit ();
554 }
555
556 static void
557 other_btn_cb (GtkWidget *widget, gpointer data)
558 {
559     struct select_keys_s *sk = data;
560     char *uid;
561
562     g_return_if_fail (sk);
563     uid = input_dialog ( _("Add key"),
564                          _("Enter another user or key ID:"),
565                          NULL );
566     if (!uid)
567         return;
568     if (fill_clist (sk, uid, sk->proto) != NULL) {
569             gpgme_release(sk->select_ctx);
570             sk->select_ctx = NULL;
571     }
572     update_progress (sk, 0, sk->pattern);
573     g_free (uid);
574 }
575
576
577 static gboolean
578 use_untrusted (gpgme_key_t key, gpgme_protocol_t proto)
579 {
580     AlertValue aval;
581     gchar *buf = NULL;
582     
583     if (proto != GPGME_PROTOCOL_OpenPGP)
584         return TRUE;
585
586     buf = g_strdup_printf(_("The key of '%s' is not fully trusted.\n"
587                "If you choose to encrypt the message with this key you don't\n"
588                "know for sure that it will go to the person you mean it to.\n"
589                "Do you trust it enough to use it anyway?"), key->uids->email);
590     aval = alertpanel
591             (_("Trust key"),
592              buf,
593              GTK_STOCK_NO, GTK_STOCK_YES, NULL);
594     g_free(buf);
595     if (aval == G_ALERTALTERNATE)
596         return TRUE;
597     else
598         return FALSE;
599 }
600
601
602 static gint 
603 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
604 {
605     gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
606     gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
607     const char *sa, *sb;
608     
609     sa = a? a->uids->name : NULL;
610     sb = b? b->uids->name : NULL;
611     if (!sa)
612         return !!sb;
613     if (!sb)
614         return -1;
615     return g_ascii_strcasecmp(sa, sb);
616 }
617
618 static gint 
619 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
620 {
621     gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
622     gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
623     const char *sa, *sb;
624     
625     sa = a? a->uids->email : NULL;
626     sb = b? b->uids->email : NULL;
627     if (!sa)
628         return !!sb;
629     if (!sb)
630         return -1;
631     return g_ascii_strcasecmp(sa, sb);
632 }
633
634 static void
635 sort_keys ( struct select_keys_s *sk, enum col_titles column)
636 {
637     GtkCMCList *clist = sk->clist;
638
639     switch (column) {
640       case COL_NAME:
641         gtk_cmclist_set_compare_func (clist, cmp_name);
642         break;
643       case COL_EMAIL:
644         gtk_cmclist_set_compare_func (clist, cmp_email);
645         break;
646       default:
647         return;
648     }
649
650     /* column clicked again: toggle as-/decending */
651     if ( sk->sort_column == column) {
652         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
653                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
654     }
655     else
656         sk->sort_type = GTK_SORT_ASCENDING;
657
658     sk->sort_column = column;
659     gtk_cmclist_set_sort_type (clist, sk->sort_type);
660     gtk_cmclist_sort (clist);
661 }
662
663 static void
664 sort_keys_name (GtkWidget *widget, gpointer data)
665 {
666     sort_keys ((struct select_keys_s*)data, COL_NAME);
667 }
668
669 static void
670 sort_keys_email (GtkWidget *widget, gpointer data)
671 {
672     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
673 }
674
675 #endif /*USE_GPGME*/