Fix bug #3380. Initialize widget before callback handler for 'clicked'
[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 ? sk.pattern : "NULL");
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                 if (uid->revoked || uid->invalid)
312                         continue;
313                 raw_mail = g_strdup(uid->email);
314                 extract_address(raw_mail);
315                 if (!strcasecmp(pattern, raw_mail)) {
316                         exact_match = TRUE;
317                         last_uid = uid;
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, last_uid, 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, 560, 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,      70);
403     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID,    120);
404     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME,     115);
405     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL,    140);
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     gtkut_widget_set_can_default(cancel_btn, TRUE);
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     cm_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     cm_return_val_if_fail (sk, FALSE);
490     if (event && event->keyval == GDK_KEY_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     cm_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         gpgme_user_id_t uid;
515         for (uid = key->uids; uid; uid = uid->next) {
516                 gchar *raw_mail = NULL;
517
518                 if (!uid->email)
519                         continue;
520                 raw_mail = g_strdup(uid->email);
521                 extract_address(raw_mail);
522                 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
523                         g_free(raw_mail);
524                         break;
525                 }
526                 g_free(raw_mail);
527         }
528         if (!uid)
529                 uid = key->uids;
530
531         if ( uid->validity < GPGME_VALIDITY_FULL ) {
532             use_key = use_untrusted(key, uid, sk->proto);
533             if (!use_key) {
534                 debug_print ("** Key untrusted, will not encrypt");
535                 return;
536             }
537         }
538         sk->kset = g_realloc(sk->kset,
539                 sizeof(gpgme_key_t) * (sk->num_keys + 1));
540         gpgme_key_ref(key);
541         sk->kset[sk->num_keys] = key;
542         sk->num_keys++;
543         sk->okay = 1;
544         sk->result = KEY_SELECTION_OK;
545         gtk_main_quit ();
546     }
547 }
548
549
550 static void 
551 cancel_btn_cb (GtkWidget *widget, gpointer data)
552 {
553     struct select_keys_s *sk = data;
554
555     cm_return_if_fail (sk);
556     sk->okay = 0;
557     sk->result = KEY_SELECTION_CANCEL;
558     if (sk->select_ctx)
559         gpgme_cancel (sk->select_ctx);
560     gtk_main_quit ();
561 }
562
563 static void 
564 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
565 {
566     struct select_keys_s *sk = data;
567
568     cm_return_if_fail (sk);
569     sk->okay = 0;
570     sk->result = KEY_SELECTION_DONT;
571     if (sk->select_ctx)
572         gpgme_cancel (sk->select_ctx);
573     gtk_main_quit ();
574 }
575
576 static void
577 other_btn_cb (GtkWidget *widget, gpointer data)
578 {
579     struct select_keys_s *sk = data;
580     char *uid;
581
582     cm_return_if_fail (sk);
583     uid = input_dialog ( _("Add key"),
584                          _("Enter another user or key ID:"),
585                          NULL );
586     if (!uid)
587         return;
588     if (fill_clist (sk, uid, sk->proto) != NULL) {
589             gpgme_release(sk->select_ctx);
590             sk->select_ctx = NULL;
591     }
592     update_progress (sk, 0, sk->pattern);
593     g_free (uid);
594 }
595
596
597 static gboolean
598 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
599 {
600     AlertValue aval;
601     gchar *buf = NULL;
602     gchar *title = NULL;
603     if (proto != GPGME_PROTOCOL_OpenPGP)
604         return TRUE;
605
606     title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
607     buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
608                "If you choose to encrypt the message with this key, you don't\n"
609                "know for sure that it will go to the person you mean it to.\n\n"
610                "Key details: ID %s, primary identity %s &lt;%s&gt;\n\n"
611                "Do you trust this key enough to use it anyway?"), 
612                key->subkeys->keyid, key->uids->name, key->uids->email);
613     aval = alertpanel
614             (title, buf,
615              GTK_STOCK_NO, GTK_STOCK_YES, NULL);
616     g_free(buf);
617     g_free(title);
618     if (aval == G_ALERTALTERNATE)
619         return TRUE;
620     else
621         return FALSE;
622 }
623
624
625 static gint 
626 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
627 {
628     gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
629     gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
630     const char *sa, *sb;
631     
632     sa = a? a->uids->name : NULL;
633     sb = b? b->uids->name : NULL;
634     if (!sa)
635         return !!sb;
636     if (!sb)
637         return -1;
638     return g_ascii_strcasecmp(sa, sb);
639 }
640
641 static gint 
642 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
643 {
644     gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
645     gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
646     const char *sa, *sb;
647     
648     sa = a? a->uids->email : NULL;
649     sb = b? b->uids->email : NULL;
650     if (!sa)
651         return !!sb;
652     if (!sb)
653         return -1;
654     return g_ascii_strcasecmp(sa, sb);
655 }
656
657 static void
658 sort_keys ( struct select_keys_s *sk, enum col_titles column)
659 {
660     GtkCMCList *clist = sk->clist;
661
662     switch (column) {
663       case COL_NAME:
664         gtk_cmclist_set_compare_func (clist, cmp_name);
665         break;
666       case COL_EMAIL:
667         gtk_cmclist_set_compare_func (clist, cmp_email);
668         break;
669       default:
670         return;
671     }
672
673     /* column clicked again: toggle as-/decending */
674     if ( sk->sort_column == column) {
675         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
676                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
677     }
678     else
679         sk->sort_type = GTK_SORT_ASCENDING;
680
681     sk->sort_column = column;
682     gtk_cmclist_set_sort_type (clist, sk->sort_type);
683     gtk_cmclist_sort (clist);
684 }
685
686 static void
687 sort_keys_name (GtkWidget *widget, gpointer data)
688 {
689     sort_keys ((struct select_keys_s*)data, COL_NAME);
690 }
691
692 static void
693 sort_keys_email (GtkWidget *widget, gpointer data)
694 {
695     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
696 }
697
698 #endif /*USE_GPGME*/