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