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