Usual Windows fixes
[claws.git] / src / plugins / pgpcore / select-keys.c
1 /* select-keys.c - GTK+ based key selection
2  *      Copyright (C) 2001-2012 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_user_id_t uid, 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     gpgme_user_id_t last_uid = NULL;
278     cm_return_val_if_fail (sk, NULL);
279     clist = sk->clist;
280     cm_return_val_if_fail (clist, NULL);
281
282     debug_print ("select_keys:fill_clist:  pattern '%s' proto %d\n", pattern, proto);
283
284     /*gtk_cmclist_freeze (select_keys.clist);*/
285     err = gpgme_new (&ctx);
286     g_assert (!err);
287
288     gpgme_set_protocol(ctx, proto);
289     sk->select_ctx = ctx;
290
291     update_progress (sk, ++running, pattern);
292     while (gtk_events_pending ())
293         gtk_main_iteration ();
294
295     err = gpgme_op_keylist_start (ctx, pattern, 0);
296     if (err) {
297         debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
298                      pattern, gpgme_strerror (err));
299         sk->select_ctx = NULL;
300         gpgme_release(ctx);
301         return NULL;
302     }
303     update_progress (sk, ++running, pattern);
304     while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
305         gpgme_user_id_t uid = key->uids;
306         if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
307                 continue;
308         debug_print ("%% %s:%d:  insert\n", __FILE__ ,__LINE__ );
309         set_row (clist, key, proto ); 
310         for (; uid; uid = uid->next) {
311                 gchar *raw_mail = NULL;
312
313                 if (!uid->email)
314                         continue;
315                 raw_mail = g_strdup(uid->email);
316                 extract_address(raw_mail);
317                 if (!strcasecmp(pattern, raw_mail)) {
318                         exact_match = TRUE;
319                         last_uid = uid;
320                         g_free(raw_mail);
321                         break;
322                 }
323                 g_free(raw_mail);
324         }
325         num_results++;
326         last_key = key;
327         key = NULL;
328         update_progress (sk, ++running, pattern);
329         while (gtk_events_pending ())
330             gtk_main_iteration ();
331     }
332  
333     if (exact_match == TRUE && num_results == 1) {
334             if (last_key->uids->validity < GPGME_VALIDITY_FULL && 
335                 !use_untrusted(last_key, last_uid, proto))
336                     exact_match = FALSE;
337     }
338
339     debug_print ("%% %s:%d:  ready\n", __FILE__ ,__LINE__ );
340     if (gpgme_err_code(err) != GPG_ERR_EOF) {
341         debug_print ("** gpgme_op_keylist_next failed: %s",
342                      gpgme_strerror (err));
343         gpgme_op_keylist_end(ctx);
344     }
345     if (!exact_match || num_results != 1) {
346             sk->select_ctx = NULL;
347             gpgme_release (ctx);
348     }
349     /*gtk_cmclist_thaw (select_keys.clist);*/
350     return (exact_match == TRUE && num_results == 1 ? last_key:NULL);
351 }
352
353
354 static void 
355 create_dialog (struct select_keys_s *sk)
356 {
357     GtkWidget *window;
358     GtkWidget *vbox, *vbox2, *hbox;
359     GtkWidget *bbox;
360     GtkWidget *scrolledwin;
361     GtkWidget *clist;
362     GtkWidget *label;
363     GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
364     const char *titles[N_COL_TITLES];
365
366     g_assert (!sk->window);
367     window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
368     gtk_widget_set_size_request (window, 520, 280);
369     gtk_container_set_border_width (GTK_CONTAINER (window), 8);
370     gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
371     gtk_window_set_modal (GTK_WINDOW (window), TRUE);
372     g_signal_connect (G_OBJECT (window), "delete_event",
373                       G_CALLBACK (delete_event_cb), sk);
374     g_signal_connect (G_OBJECT (window), "key_press_event",
375                       G_CALLBACK (key_pressed_cb), sk);
376     MANAGE_WINDOW_SIGNALS_CONNECT (window);
377
378     vbox = gtk_vbox_new (FALSE, 8);
379     gtk_container_add (GTK_CONTAINER (window), vbox);
380
381     hbox  = gtk_hbox_new(FALSE, 4);
382     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
383     label = gtk_label_new ( "" );
384     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
385
386     hbox = gtk_hbox_new (FALSE, 8);
387     gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
388     gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
389
390     scrolledwin = gtk_scrolled_window_new (NULL, NULL);
391     gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
392     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
393                                     GTK_POLICY_AUTOMATIC,
394                                     GTK_POLICY_AUTOMATIC);
395
396     titles[COL_ALGO]     = _("Size");
397     titles[COL_KEYID]    = _("Key ID");
398     titles[COL_NAME]     = _("Name");
399     titles[COL_EMAIL]    = _("Address");
400     titles[COL_VALIDITY] = _("Trust");
401
402     clist = gtk_cmclist_new_with_titles (N_COL_TITLES, (char**)titles);
403     gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
404     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_ALGO,      72);
405     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID,     76);
406     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME,     130);
407     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL,    130);
408     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_VALIDITY,  20);
409     gtk_cmclist_set_selection_mode (GTK_CMCLIST(clist), GTK_SELECTION_BROWSE);
410     g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_NAME].button),
411                       "clicked",
412                       G_CALLBACK(sort_keys_name), sk);
413     g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_EMAIL].button),
414                       "clicked",
415                       G_CALLBACK(sort_keys_email), sk);
416
417     hbox = gtk_hbox_new (FALSE, 8);
418     gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
419
420     /* TRANSLATORS: check that the accelerators in _Select, _Other and
421      * Do_n't encrypt are different than the one in the stock Cancel
422      * button */
423     gtkut_stock_button_set_create (&bbox, 
424                                    &select_btn, _("_Select"),
425                                    &other_btn, _("_Other"),
426                                    &dont_encrypt_btn, _("Do_n't encrypt"));
427     
428     cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
429     gtkut_widget_set_can_default(cancel_btn, TRUE);
430     gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
431     gtk_widget_show(cancel_btn);
432     gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
433     gtk_widget_grab_default (select_btn);
434
435     g_signal_connect (G_OBJECT (select_btn), "clicked",
436                       G_CALLBACK (select_btn_cb), sk);
437     g_signal_connect (G_OBJECT(cancel_btn), "clicked",
438                       G_CALLBACK (cancel_btn_cb), sk);
439     g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
440                       G_CALLBACK (dont_encrypt_btn_cb), sk);
441     g_signal_connect (G_OBJECT (other_btn), "clicked",
442                       G_CALLBACK (other_btn_cb), sk);
443
444     vbox2 = gtk_vbox_new (FALSE, 4);
445     gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
446
447     sk->window = window;
448     sk->toplabel = GTK_LABEL (label);
449     sk->clist  = GTK_CMCLIST (clist);
450 }
451
452
453 static void
454 open_dialog (struct select_keys_s *sk)
455 {
456     if (!sk->window)
457         create_dialog (sk);
458     manage_window_set_transient (GTK_WINDOW (sk->window));
459     sk->okay = 0;
460     sk->sort_column = N_COL_TITLES; /* use an invalid value */
461     sk->sort_type = GTK_SORT_ASCENDING;
462 }
463
464
465 static void
466 close_dialog (struct select_keys_s *sk)
467 {
468     cm_return_if_fail (sk);
469     gtk_widget_destroy (sk->window);
470     sk->window = NULL;
471 }
472
473
474 static gint
475 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
476 {
477     struct select_keys_s *sk = data;
478
479     sk->okay = 0;
480     gtk_main_quit ();
481
482     return TRUE;
483 }
484
485
486 static gboolean
487 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
488 {
489     struct select_keys_s *sk = data;
490
491     cm_return_val_if_fail (sk, FALSE);
492     if (event && event->keyval == GDK_KEY_Escape) {
493         sk->okay = 0;
494         gtk_main_quit ();
495     }
496     return FALSE;
497 }
498
499
500 static void 
501 select_btn_cb (GtkWidget *widget, gpointer data)
502 {
503     struct select_keys_s *sk = data;
504     int row;
505     gboolean use_key;
506     gpgme_key_t key;
507
508     cm_return_if_fail (sk);
509     if (!sk->clist->selection) {
510         debug_print ("** nothing selected");
511         return;
512     }
513     row = GPOINTER_TO_INT(sk->clist->selection->data);
514     key = gtk_cmclist_get_row_data(sk->clist, row);
515     if (key) {
516         gpgme_user_id_t uid;
517         for (uid = key->uids; uid; uid = uid->next) {
518                 gchar *raw_mail = NULL;
519
520                 if (!uid->email)
521                         continue;
522                 raw_mail = g_strdup(uid->email);
523                 extract_address(raw_mail);
524                 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
525                         g_free(raw_mail);
526                         break;
527                 }
528                 g_free(raw_mail);
529         }
530         if (!uid)
531                 uid = key->uids;
532
533         if ( uid->validity < GPGME_VALIDITY_FULL ) {
534             use_key = use_untrusted(key, uid, sk->proto);
535             if (!use_key) {
536                 debug_print ("** Key untrusted, will not encrypt");
537                 return;
538             }
539         }
540         sk->kset = g_realloc(sk->kset,
541                 sizeof(gpgme_key_t) * (sk->num_keys + 1));
542         gpgme_key_ref(key);
543         sk->kset[sk->num_keys] = key;
544         sk->num_keys++;
545         sk->okay = 1;
546         sk->result = KEY_SELECTION_OK;
547         gtk_main_quit ();
548     }
549 }
550
551
552 static void 
553 cancel_btn_cb (GtkWidget *widget, gpointer data)
554 {
555     struct select_keys_s *sk = data;
556
557     cm_return_if_fail (sk);
558     sk->okay = 0;
559     sk->result = KEY_SELECTION_CANCEL;
560     if (sk->select_ctx)
561         gpgme_cancel (sk->select_ctx);
562     gtk_main_quit ();
563 }
564
565 static void 
566 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
567 {
568     struct select_keys_s *sk = data;
569
570     cm_return_if_fail (sk);
571     sk->okay = 0;
572     sk->result = KEY_SELECTION_DONT;
573     if (sk->select_ctx)
574         gpgme_cancel (sk->select_ctx);
575     gtk_main_quit ();
576 }
577
578 static void
579 other_btn_cb (GtkWidget *widget, gpointer data)
580 {
581     struct select_keys_s *sk = data;
582     char *uid;
583
584     cm_return_if_fail (sk);
585     uid = input_dialog ( _("Add key"),
586                          _("Enter another user or key ID:"),
587                          NULL );
588     if (!uid)
589         return;
590     if (fill_clist (sk, uid, sk->proto) != NULL) {
591             gpgme_release(sk->select_ctx);
592             sk->select_ctx = NULL;
593     }
594     update_progress (sk, 0, sk->pattern);
595     g_free (uid);
596 }
597
598
599 static gboolean
600 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
601 {
602     AlertValue aval;
603     gchar *buf = NULL;
604     gchar *title = NULL;
605     if (proto != GPGME_PROTOCOL_OpenPGP)
606         return TRUE;
607
608     title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
609     buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
610                "If you choose to encrypt the message with this key, you don't\n"
611                "know for sure that it will go to the person you mean it to.\n\n"
612                "Key details: ID %s, primary identity %s &lt;%s&gt;\n\n"
613                "Do you trust this key enough to use it anyway?"), 
614                key->subkeys->keyid, key->uids->name, key->uids->email);
615     aval = alertpanel
616             (title, buf,
617              GTK_STOCK_NO, GTK_STOCK_YES, NULL);
618     g_free(buf);
619     g_free(title);
620     if (aval == G_ALERTALTERNATE)
621         return TRUE;
622     else
623         return FALSE;
624 }
625
626
627 static gint 
628 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
629 {
630     gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
631     gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
632     const char *sa, *sb;
633     
634     sa = a? a->uids->name : NULL;
635     sb = b? b->uids->name : NULL;
636     if (!sa)
637         return !!sb;
638     if (!sb)
639         return -1;
640     return g_ascii_strcasecmp(sa, sb);
641 }
642
643 static gint 
644 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
645 {
646     gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
647     gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
648     const char *sa, *sb;
649     
650     sa = a? a->uids->email : NULL;
651     sb = b? b->uids->email : NULL;
652     if (!sa)
653         return !!sb;
654     if (!sb)
655         return -1;
656     return g_ascii_strcasecmp(sa, sb);
657 }
658
659 static void
660 sort_keys ( struct select_keys_s *sk, enum col_titles column)
661 {
662     GtkCMCList *clist = sk->clist;
663
664     switch (column) {
665       case COL_NAME:
666         gtk_cmclist_set_compare_func (clist, cmp_name);
667         break;
668       case COL_EMAIL:
669         gtk_cmclist_set_compare_func (clist, cmp_email);
670         break;
671       default:
672         return;
673     }
674
675     /* column clicked again: toggle as-/decending */
676     if ( sk->sort_column == column) {
677         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
678                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
679     }
680     else
681         sk->sort_type = GTK_SORT_ASCENDING;
682
683     sk->sort_column = column;
684     gtk_cmclist_set_sort_type (clist, sk->sort_type);
685     gtk_cmclist_sort (clist);
686 }
687
688 static void
689 sort_keys_name (GtkWidget *widget, gpointer data)
690 {
691     sort_keys ((struct select_keys_s*)data, COL_NAME);
692 }
693
694 static void
695 sort_keys_email (GtkWidget *widget, gpointer data)
696 {
697     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
698 }
699
700 #endif /*USE_GPGME*/