2005-07-05 [colin] 1.9.12cvs20
[claws.git] / src / plugins / pgpmime / select-keys.c
1 /* select-keys.c - GTK+ based key selection
2  *      Copyright (C) 2001 Werner Koch (dd9jn)
3  *
4  * This program is free software; you can redistribute it and/or modify        
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #ifdef USE_GPGME
24 #include <stdio.h>
25 #include <stdlib.h>
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtkmain.h>
31 #include <gtk/gtkwidget.h>
32 #include <gtk/gtkwindow.h>
33 #include <gtk/gtkscrolledwindow.h>
34 #include <gtk/gtkvbox.h>
35 #include <gtk/gtkhbox.h>
36 #include <gtk/gtkclist.h>
37 #include <gtk/gtklabel.h>
38 #include <gtk/gtkentry.h>
39 #include <gtk/gtkhbbox.h>
40 #include <gtk/gtkbutton.h>
41 #include <gtk/gtkstock.h>
42
43 #include "select-keys.h"
44 #include "utils.h"
45 #include "gtkutils.h"
46 #include "inputdialog.h"
47 #include "manage_window.h"
48
49 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
50 #define DIMof(type,member)   DIM(((type *)0)->member)
51
52
53 enum col_titles { 
54     COL_ALGO,
55     COL_KEYID,
56     COL_NAME,
57     COL_EMAIL,
58     COL_VALIDITY,
59
60     N_COL_TITLES
61 };
62
63 struct select_keys_s {
64     int okay;
65     GtkWidget *window;
66     GtkLabel *toplabel;
67     GtkCList *clist;
68     const char *pattern;
69     GpgmeRecipients rset;
70     GpgmeCtx select_ctx;
71
72     GtkSortType sort_type;
73     enum col_titles sort_column;
74     
75 };
76
77
78 static void set_row (GtkCList *clist, GpgmeKey key);
79 static void fill_clist (struct select_keys_s *sk, const char *pattern);
80 static void create_dialog (struct select_keys_s *sk);
81 static void open_dialog (struct select_keys_s *sk);
82 static void close_dialog (struct select_keys_s *sk);
83 static gint delete_event_cb (GtkWidget *widget,
84                              GdkEventAny *event, gpointer data);
85 static gboolean key_pressed_cb (GtkWidget *widget,
86                                 GdkEventKey *event, gpointer data);
87 static void showall_btn_cb (GtkWidget *widget, gpointer data);
88 static void select_btn_cb (GtkWidget *widget, gpointer data);
89 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
90 static void other_btn_cb (GtkWidget *widget, gpointer data);
91 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
92 static void sort_keys_name (GtkWidget *widget, gpointer data);
93 static void sort_keys_email (GtkWidget *widget, gpointer data);
94
95
96 static void
97 update_progress (struct select_keys_s *sk, int running, const char *pattern)
98 {
99     static int windmill[] = { '-', '\\', '|', '/' };
100     char *buf;
101
102     if (!running)
103         buf = g_strdup_printf (_("Please select key for `%s'"), 
104                                pattern);
105     else 
106         buf = g_strdup_printf (_("Collecting info for `%s' ... %c"), 
107                                pattern,
108                                windmill[running%DIM(windmill)]);
109     gtk_label_set_text (sk->toplabel, buf);
110     g_free (buf);
111 }
112
113
114 /**
115  * select_keys_get_recipients:
116  * @recp_names: A list of email addresses
117  * 
118  * Select a list of recipients from a given list of email addresses.
119  * This may pop up a window to present the user a choice, it will also
120  * check that the recipients key are all valid.
121  * 
122  * Return value: NULL on error or a list of list of recipients.
123  **/
124 GpgmeRecipients
125 gpgmegtk_recipient_selection (GSList *recp_names)
126 {
127     struct select_keys_s sk;
128     GpgmeError err;
129
130     memset (&sk, 0, sizeof sk);
131
132     err = gpgme_recipients_new (&sk.rset);
133     if (err) {
134         g_warning ("failed to allocate recipients set: %s",
135                    gpgme_strerror (err));
136         return NULL;
137     }
138
139     open_dialog (&sk);
140
141     do {
142         sk.pattern = recp_names? recp_names->data:NULL;
143         gtk_clist_clear (sk.clist);
144         fill_clist (&sk, sk.pattern);
145         update_progress (&sk, 0, sk.pattern);
146         gtk_main ();
147         if (recp_names)
148             recp_names = recp_names->next;
149     } while (sk.okay && recp_names);
150
151     close_dialog (&sk);
152
153     if (!sk.okay) {
154         gpgme_recipients_release (sk.rset);
155         sk.rset = NULL;
156     }
157     return sk.rset;
158
159
160 static void
161 destroy_key (gpointer data)
162 {
163     GpgmeKey key = data;
164     gpgme_key_release (key);
165 }
166
167 static void
168 set_row (GtkCList *clist, GpgmeKey key)
169 {
170     const char *s;
171     const char *text[N_COL_TITLES];
172     char *algo_buf;
173     int row;
174     gssize by_read = 0, by_written = 0;
175     gchar *ret_str;
176
177     /* first check whether the key is capable of encryption which is not
178      * the case for revoked, expired or sign-only keys */
179     if ( !gpgme_key_get_ulong_attr (key, GPGME_ATTR_CAN_ENCRYPT, NULL, 0 ) )
180         return;
181     
182     algo_buf = g_strdup_printf ("%lu/%s", 
183          gpgme_key_get_ulong_attr (key, GPGME_ATTR_LEN, NULL, 0 ),
184          gpgme_key_get_string_attr (key, GPGME_ATTR_ALGO, NULL, 0 ) );
185     text[COL_ALGO] = algo_buf;
186
187     s = gpgme_key_get_string_attr (key, GPGME_ATTR_KEYID, NULL, 0);
188     if (strlen (s) == 16)
189         s += 8; /* show only the short keyID */
190     text[COL_KEYID] = s;
191
192     s = gpgme_key_get_string_attr (key, GPGME_ATTR_NAME, NULL, 0);
193     ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
194     if (ret_str && by_written) {
195         s = ret_str;
196     }
197     text[COL_NAME] = s;
198
199     s = gpgme_key_get_string_attr (key, GPGME_ATTR_EMAIL, NULL, 0);
200     ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
201     if (ret_str && by_written) {
202         s = ret_str;
203     }
204     text[COL_EMAIL] = s;
205
206     s = gpgme_key_get_string_attr (key, GPGME_ATTR_VALIDITY, NULL, 0);
207     ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
208     if (ret_str && by_written) {
209         s = ret_str;
210     }
211     text[COL_VALIDITY] = s;
212
213     row = gtk_clist_append (clist, (gchar**)text);
214     g_free (algo_buf);
215
216     gtk_clist_set_row_data_full (clist, row, key, destroy_key);
217 }
218
219
220 static void 
221 fill_clist (struct select_keys_s *sk, const char *pattern)
222 {
223     GtkCList *clist;
224     GpgmeCtx ctx;
225     GpgmeError err;
226     GpgmeKey key;
227     int running=0;
228
229     g_return_if_fail (sk);
230     clist = sk->clist;
231     g_return_if_fail (clist);
232
233     debug_print ("select_keys:fill_clist:  pattern `%s'\n", pattern);
234
235     /*gtk_clist_freeze (select_keys.clist);*/
236     err = gpgme_new (&ctx);
237     g_assert (!err);
238
239     sk->select_ctx = ctx;
240
241     update_progress (sk, ++running, pattern);
242     while (gtk_events_pending ())
243         gtk_main_iteration ();
244
245     err = gpgme_op_keylist_start (ctx, pattern, 0);
246     if (err) {
247         debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
248                      pattern, gpgme_strerror (err));
249         sk->select_ctx = NULL;
250         return;
251     }
252     update_progress (sk, ++running, pattern);
253     while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
254         debug_print ("%% %s:%d:  insert\n", __FILE__ ,__LINE__ );
255         set_row (clist, key ); key = NULL;
256         update_progress (sk, ++running, pattern);
257         while (gtk_events_pending ())
258             gtk_main_iteration ();
259     }
260     debug_print ("%% %s:%d:  ready\n", __FILE__ ,__LINE__ );
261     if (err != GPGME_EOF)
262         debug_print ("** gpgme_op_keylist_next failed: %s",
263                      gpgme_strerror (err));
264     sk->select_ctx = NULL;
265     gpgme_release (ctx);
266     /*gtk_clist_thaw (select_keys.clist);*/
267 }
268
269
270 static void 
271 create_dialog (struct select_keys_s *sk)
272 {
273     GtkWidget *window;
274     GtkWidget *vbox, *vbox2, *hbox;
275     GtkWidget *bbox;
276     GtkWidget *scrolledwin;
277     GtkWidget *clist;
278     GtkWidget *label;
279     GtkWidget *select_btn, *cancel_btn, *other_btn;
280     GtkWidget *showall_btn;
281     const char *titles[N_COL_TITLES];
282
283     g_assert (!sk->window);
284     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
285     gtk_widget_set_size_request (window, 520, 280);
286     gtk_container_set_border_width (GTK_CONTAINER (window), 8);
287     gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
288     gtk_window_set_modal (GTK_WINDOW (window), TRUE);
289     g_signal_connect (G_OBJECT (window), "delete_event",
290                       G_CALLBACK (delete_event_cb), sk);
291     g_signal_connect (G_OBJECT (window), "key_press_event",
292                       G_CALLBACK (key_pressed_cb), sk);
293     MANAGE_WINDOW_SIGNALS_CONNECT (window);
294
295     vbox = gtk_vbox_new (FALSE, 8);
296     gtk_container_add (GTK_CONTAINER (window), vbox);
297
298     hbox  = gtk_hbox_new(FALSE, 4);
299     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
300     label = gtk_label_new ( "" );
301     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
302
303     hbox = gtk_hbox_new (FALSE, 8);
304     gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
305     gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
306
307     scrolledwin = gtk_scrolled_window_new (NULL, NULL);
308     gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
309     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
310                                     GTK_POLICY_AUTOMATIC,
311                                     GTK_POLICY_AUTOMATIC);
312
313     titles[COL_ALGO]     = _("Size");
314     titles[COL_KEYID]    = _("Key ID");
315     titles[COL_NAME]     = _("Name");
316     titles[COL_EMAIL]    = _("Address");
317     titles[COL_VALIDITY] = _("Val");
318
319     clist = gtk_clist_new_with_titles (N_COL_TITLES, (char**)titles);
320     gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
321     gtk_clist_set_column_width (GTK_CLIST(clist), COL_ALGO,      72);
322     gtk_clist_set_column_width (GTK_CLIST(clist), COL_KEYID,     76);
323     gtk_clist_set_column_width (GTK_CLIST(clist), COL_NAME,     130);
324     gtk_clist_set_column_width (GTK_CLIST(clist), COL_EMAIL,    130);
325     gtk_clist_set_column_width (GTK_CLIST(clist), COL_VALIDITY,  20);
326     gtk_clist_set_selection_mode (GTK_CLIST(clist), GTK_SELECTION_BROWSE);
327     g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_NAME].button),
328                       "clicked",
329                       G_CALLBACK(sort_keys_name), sk);
330     g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_EMAIL].button),
331                       "clicked",
332                       G_CALLBACK(sort_keys_email), sk);
333
334     hbox = gtk_hbox_new (FALSE, 8);
335     gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
336
337     showall_btn = gtk_button_new_with_label (_(" List all keys "));
338     gtk_widget_show (showall_btn);
339     gtk_box_pack_start (GTK_BOX (hbox), showall_btn, FALSE, FALSE, 0);
340
341     g_signal_connect(G_OBJECT (showall_btn), "clicked",
342                      G_CALLBACK(showall_btn_cb), sk);
343
344     gtkut_stock_button_set_create (&bbox, 
345                                    &select_btn, _("Select"),
346                                    &cancel_btn, GTK_STOCK_CANCEL,
347                                    &other_btn,  _("Other"));
348     gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
349     gtk_widget_grab_default (select_btn);
350
351     g_signal_connect (G_OBJECT (select_btn), "clicked",
352                       G_CALLBACK (select_btn_cb), sk);
353     g_signal_connect (G_OBJECT(cancel_btn), "clicked",
354                       G_CALLBACK (cancel_btn_cb), sk);
355     g_signal_connect (G_OBJECT (other_btn), "clicked",
356                       G_CALLBACK (other_btn_cb), sk);
357
358     vbox2 = gtk_vbox_new (FALSE, 4);
359     gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
360
361     gtk_widget_show_all (window);
362
363     sk->window = window;
364     sk->toplabel = GTK_LABEL (label);
365     sk->clist  = GTK_CLIST (clist);
366 }
367
368
369 static void
370 open_dialog (struct select_keys_s *sk)
371 {
372     if (!sk->window)
373         create_dialog (sk);
374     manage_window_set_transient (GTK_WINDOW (sk->window));
375     sk->okay = 0;
376     sk->sort_column = N_COL_TITLES; /* use an invalid value */
377     sk->sort_type = GTK_SORT_ASCENDING;
378     gtk_widget_show (sk->window);
379 }
380
381
382 static void
383 close_dialog (struct select_keys_s *sk)
384 {
385     g_return_if_fail (sk);
386     gtk_widget_destroy (sk->window);
387     sk->window = NULL;
388 }
389
390
391 static gint
392 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
393 {
394     struct select_keys_s *sk = data;
395
396     sk->okay = 0;
397     gtk_main_quit ();
398
399     return TRUE;
400 }
401
402
403 static gboolean 
404 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
405 {
406     struct select_keys_s *sk = data;
407
408     g_return_val_if_fail (sk, FALSE);
409     if (event && event->keyval == GDK_Escape) {
410         sk->okay = 0;
411         gtk_main_quit ();
412     }
413         return FALSE;
414 }
415
416
417 static void 
418 select_btn_cb (GtkWidget *widget, gpointer data)
419 {
420     struct select_keys_s *sk = data;
421     int row;
422     GpgmeKey key;
423
424     g_return_if_fail (sk);
425     if (!sk->clist->selection) {
426         debug_print ("** nothing selected");
427         return;
428     }
429     row = GPOINTER_TO_INT(sk->clist->selection->data);
430     key = gtk_clist_get_row_data(sk->clist, row);
431     if (key) {
432         const char *s = gpgme_key_get_string_attr (key,
433                                                    GPGME_ATTR_FPR,
434                                                    NULL, 0 );
435         if ( gpgme_key_get_ulong_attr (key, GPGME_ATTR_VALIDITY, NULL, 0 )
436              < GPGME_VALIDITY_FULL ) {
437             debug_print ("** FIXME: we are faking the trust calculation");
438         }
439         if (!gpgme_recipients_add_name_with_validity (sk->rset, s,
440                                                       GPGME_VALIDITY_FULL) ) {
441             sk->okay = 1;
442             gtk_main_quit ();
443         }
444     }
445 }
446
447
448 static void 
449 cancel_btn_cb (GtkWidget *widget, gpointer data)
450 {
451     struct select_keys_s *sk = data;
452
453     g_return_if_fail (sk);
454     sk->okay = 0;
455     if (sk->select_ctx)
456         gpgme_cancel (sk->select_ctx);
457     gtk_main_quit ();
458 }
459
460
461 static void
462 other_btn_cb (GtkWidget *widget, gpointer data)
463 {
464     struct select_keys_s *sk = data;
465     char *uid;
466
467     g_return_if_fail (sk);
468     uid = input_dialog ( _("Add key"),
469                          _("Enter another user or key ID:"),
470                          NULL );
471     if (!uid)
472         return;
473     fill_clist (sk, uid);
474     update_progress (sk, 0, sk->pattern);
475     g_free (uid);
476 }
477
478 static void
479 showall_btn_cb (GtkWidget *widget, gpointer data)
480 {
481     struct select_keys_s *sk = data;
482     char *uid;
483
484     g_return_if_fail (sk);
485     uid = "";
486         
487     fill_clist (sk, uid);
488     update_progress (sk, 0, sk->pattern);
489 }
490
491 static gint 
492 cmp_attr (gconstpointer pa, gconstpointer pb, GpgmeAttr attr)
493 {
494     GpgmeKey a = ((GtkCListRow *)pa)->data;
495     GpgmeKey b = ((GtkCListRow *)pb)->data;
496     const char *sa, *sb;
497     
498     sa = a? gpgme_key_get_string_attr (a, attr, NULL, 0 ) : NULL;
499     sb = b? gpgme_key_get_string_attr (b, attr, NULL, 0 ) : NULL;
500     if (!sa)
501         return !!sb;
502     if (!sb)
503         return -1;
504     return g_ascii_strcasecmp(sa, sb);
505 }
506
507 static gint 
508 cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb)
509 {
510     return cmp_attr (pa, pb, GPGME_ATTR_NAME);
511 }
512
513 static gint 
514 cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb)
515 {
516     return cmp_attr (pa, pb, GPGME_ATTR_EMAIL);
517 }
518
519 static void
520 sort_keys ( struct select_keys_s *sk, enum col_titles column)
521 {
522     GtkCList *clist = sk->clist;
523
524     switch (column) {
525       case COL_NAME:
526         gtk_clist_set_compare_func (clist, cmp_name);
527         break;
528       case COL_EMAIL:
529         gtk_clist_set_compare_func (clist, cmp_email);
530         break;
531       default:
532         return;
533     }
534
535     /* column clicked again: toggle as-/decending */
536     if ( sk->sort_column == column) {
537         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
538                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
539     }
540     else
541         sk->sort_type = GTK_SORT_ASCENDING;
542
543     sk->sort_column = column;
544     gtk_clist_set_sort_type (clist, sk->sort_type);
545     gtk_clist_sort (clist);
546 }
547
548 static void
549 sort_keys_name (GtkWidget *widget, gpointer data)
550 {
551     sort_keys ((struct select_keys_s*)data, COL_NAME);
552 }
553
554 static void
555 sort_keys_email (GtkWidget *widget, gpointer data)
556 {
557     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
558 }
559
560 #endif /*USE_GPGME*/