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