2009-02-15 [colin] 3.7.0cvs66
[claws.git] / src / plugins / pgpcore / select-keys.c
1 /* select-keys.c - GTK+ based key selection
2  *      Copyright (C) 2001-2009 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 || key->disabled)
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 || key->disabled)
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     /* TRANSLATORS: check that the accelerators in _Select, _Other and
419      * Do_n't encrypt are different than the one in the stock Cancel
420      * button */
421     gtkut_stock_button_set_create (&bbox, 
422                                    &select_btn, _("_Select"),
423                                    &other_btn, _("_Other"),
424                                    &dont_encrypt_btn, _("Do_n't encrypt"));
425     
426     cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
427     GTK_WIDGET_SET_FLAGS(cancel_btn, GTK_CAN_DEFAULT);
428     gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
429     gtk_widget_show(cancel_btn);
430     gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
431     gtk_widget_grab_default (select_btn);
432
433     g_signal_connect (G_OBJECT (select_btn), "clicked",
434                       G_CALLBACK (select_btn_cb), sk);
435     g_signal_connect (G_OBJECT(cancel_btn), "clicked",
436                       G_CALLBACK (cancel_btn_cb), sk);
437     g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
438                       G_CALLBACK (dont_encrypt_btn_cb), sk);
439     g_signal_connect (G_OBJECT (other_btn), "clicked",
440                       G_CALLBACK (other_btn_cb), sk);
441
442     vbox2 = gtk_vbox_new (FALSE, 4);
443     gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
444
445     sk->window = window;
446     sk->toplabel = GTK_LABEL (label);
447     sk->clist  = GTK_CMCLIST (clist);
448 }
449
450
451 static void
452 open_dialog (struct select_keys_s *sk)
453 {
454     if (!sk->window)
455         create_dialog (sk);
456     manage_window_set_transient (GTK_WINDOW (sk->window));
457     sk->okay = 0;
458     sk->sort_column = N_COL_TITLES; /* use an invalid value */
459     sk->sort_type = GTK_SORT_ASCENDING;
460 }
461
462
463 static void
464 close_dialog (struct select_keys_s *sk)
465 {
466     g_return_if_fail (sk);
467     gtk_widget_destroy (sk->window);
468     sk->window = NULL;
469 }
470
471
472 static gint
473 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
474 {
475     struct select_keys_s *sk = data;
476
477     sk->okay = 0;
478     gtk_main_quit ();
479
480     return TRUE;
481 }
482
483
484 static gboolean
485 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
486 {
487     struct select_keys_s *sk = data;
488
489     g_return_val_if_fail (sk, FALSE);
490     if (event && event->keyval == GDK_Escape) {
491         sk->okay = 0;
492         gtk_main_quit ();
493     }
494     return FALSE;
495 }
496
497
498 static void 
499 select_btn_cb (GtkWidget *widget, gpointer data)
500 {
501     struct select_keys_s *sk = data;
502     int row;
503     gboolean use_key;
504     gpgme_key_t key;
505
506     g_return_if_fail (sk);
507     if (!sk->clist->selection) {
508         debug_print ("** nothing selected");
509         return;
510     }
511     row = GPOINTER_TO_INT(sk->clist->selection->data);
512     key = gtk_cmclist_get_row_data(sk->clist, row);
513     if (key) {
514         if ( key->uids->validity < GPGME_VALIDITY_FULL ) {
515             use_key = use_untrusted(key, sk->proto);
516             if (!use_key) {
517                 debug_print ("** Key untrusted, will not encrypt");
518                 return;
519             }
520         }
521         sk->kset = g_realloc(sk->kset,
522                 sizeof(gpgme_key_t) * (sk->num_keys + 1));
523         gpgme_key_ref(key);
524         sk->kset[sk->num_keys] = key;
525         sk->num_keys++;
526         sk->okay = 1;
527         sk->result = KEY_SELECTION_OK;
528         gtk_main_quit ();
529     }
530 }
531
532
533 static void 
534 cancel_btn_cb (GtkWidget *widget, gpointer data)
535 {
536     struct select_keys_s *sk = data;
537
538     g_return_if_fail (sk);
539     sk->okay = 0;
540     sk->result = KEY_SELECTION_CANCEL;
541     if (sk->select_ctx)
542         gpgme_cancel (sk->select_ctx);
543     gtk_main_quit ();
544 }
545
546 static void 
547 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
548 {
549     struct select_keys_s *sk = data;
550
551     g_return_if_fail (sk);
552     sk->okay = 0;
553     sk->result = KEY_SELECTION_DONT;
554     if (sk->select_ctx)
555         gpgme_cancel (sk->select_ctx);
556     gtk_main_quit ();
557 }
558
559 static void
560 other_btn_cb (GtkWidget *widget, gpointer data)
561 {
562     struct select_keys_s *sk = data;
563     char *uid;
564
565     g_return_if_fail (sk);
566     uid = input_dialog ( _("Add key"),
567                          _("Enter another user or key ID:"),
568                          NULL );
569     if (!uid)
570         return;
571     if (fill_clist (sk, uid, sk->proto) != NULL) {
572             gpgme_release(sk->select_ctx);
573             sk->select_ctx = NULL;
574     }
575     update_progress (sk, 0, sk->pattern);
576     g_free (uid);
577 }
578
579
580 static gboolean
581 use_untrusted (gpgme_key_t key, gpgme_protocol_t proto)
582 {
583     AlertValue aval;
584     gchar *buf = NULL;
585     
586     if (proto != GPGME_PROTOCOL_OpenPGP)
587         return TRUE;
588
589     buf = g_strdup_printf(_("The key of '%s' is not fully trusted.\n"
590                "If you choose to encrypt the message with this key you don't\n"
591                "know for sure that it will go to the person you mean it to.\n"
592                "Do you trust it enough to use it anyway?"), key->uids->email);
593     aval = alertpanel
594             (_("Trust key"),
595              buf,
596              GTK_STOCK_NO, GTK_STOCK_YES, NULL);
597     g_free(buf);
598     if (aval == G_ALERTALTERNATE)
599         return TRUE;
600     else
601         return FALSE;
602 }
603
604
605 static gint 
606 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
607 {
608     gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
609     gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
610     const char *sa, *sb;
611     
612     sa = a? a->uids->name : NULL;
613     sb = b? b->uids->name : NULL;
614     if (!sa)
615         return !!sb;
616     if (!sb)
617         return -1;
618     return g_ascii_strcasecmp(sa, sb);
619 }
620
621 static gint 
622 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
623 {
624     gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
625     gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
626     const char *sa, *sb;
627     
628     sa = a? a->uids->email : NULL;
629     sb = b? b->uids->email : NULL;
630     if (!sa)
631         return !!sb;
632     if (!sb)
633         return -1;
634     return g_ascii_strcasecmp(sa, sb);
635 }
636
637 static void
638 sort_keys ( struct select_keys_s *sk, enum col_titles column)
639 {
640     GtkCMCList *clist = sk->clist;
641
642     switch (column) {
643       case COL_NAME:
644         gtk_cmclist_set_compare_func (clist, cmp_name);
645         break;
646       case COL_EMAIL:
647         gtk_cmclist_set_compare_func (clist, cmp_email);
648         break;
649       default:
650         return;
651     }
652
653     /* column clicked again: toggle as-/decending */
654     if ( sk->sort_column == column) {
655         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
656                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
657     }
658     else
659         sk->sort_type = GTK_SORT_ASCENDING;
660
661     sk->sort_column = column;
662     gtk_cmclist_set_sort_type (clist, sk->sort_type);
663     gtk_cmclist_sort (clist);
664 }
665
666 static void
667 sort_keys_name (GtkWidget *widget, gpointer data)
668 {
669     sort_keys ((struct select_keys_s*)data, COL_NAME);
670 }
671
672 static void
673 sort_keys_email (GtkWidget *widget, gpointer data)
674 {
675     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
676 }
677
678 #endif /*USE_GPGME*/