0.9.3claws12
[claws.git] / src / 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 <gdk/gdkkeysyms.h>
29 #include <gtk/gtkmain.h>
30 #include <gtk/gtkwidget.h>
31 #include <gtk/gtkwindow.h>
32 #include <gtk/gtkscrolledwindow.h>
33 #include <gtk/gtkvbox.h>
34 #include <gtk/gtkhbox.h>
35 #include <gtk/gtkclist.h>
36 #include <gtk/gtklabel.h>
37 #include <gtk/gtkentry.h>
38 #include <gtk/gtkhbbox.h>
39 #include <gtk/gtkbutton.h>
40 #include <gtk/gtksignal.h>
41
42 #include "intl.h"
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 void 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
175     /* first check whether the key is capable of encryption which is not
176      * the case for revoked, expired or sign-only keys */
177     if ( !gpgme_key_get_ulong_attr (key, GPGME_ATTR_CAN_ENCRYPT, NULL, 0 ) )
178         return;
179     
180     algo_buf = g_strdup_printf ("%lu/%s", 
181          gpgme_key_get_ulong_attr (key, GPGME_ATTR_LEN, NULL, 0 ),
182          gpgme_key_get_string_attr (key, GPGME_ATTR_ALGO, NULL, 0 ) );
183     text[COL_ALGO] = algo_buf;
184
185     s = gpgme_key_get_string_attr (key, GPGME_ATTR_KEYID, NULL, 0);
186     if (strlen (s) == 16)
187         s += 8; /* show only the short keyID */
188     text[COL_KEYID] = s;
189
190     s = gpgme_key_get_string_attr (key, GPGME_ATTR_NAME, NULL, 0);
191     text[COL_NAME] = s;
192
193     s = gpgme_key_get_string_attr (key, GPGME_ATTR_EMAIL, NULL, 0);
194     text[COL_EMAIL] = s;
195
196     s = gpgme_key_get_string_attr (key, GPGME_ATTR_VALIDITY, NULL, 0);
197     text[COL_VALIDITY] = s;
198
199     row = gtk_clist_append (clist, (gchar**)text);
200     g_free (algo_buf);
201
202     gtk_clist_set_row_data_full (clist, row, key, destroy_key);
203 }
204
205
206 static void 
207 fill_clist (struct select_keys_s *sk, const char *pattern)
208 {
209     GtkCList *clist;
210     GpgmeCtx ctx;
211     GpgmeError err;
212     GpgmeKey key;
213     int running=0;
214
215     g_return_if_fail (sk);
216     clist = sk->clist;
217     g_return_if_fail (clist);
218
219     debug_print ("select_keys:fill_clist:  pattern `%s'\n", pattern);
220
221     /*gtk_clist_freeze (select_keys.clist);*/
222     err = gpgme_new (&ctx);
223     g_assert (!err);
224
225     sk->select_ctx = ctx;
226
227     update_progress (sk, ++running, pattern);
228     while (gtk_events_pending ())
229         gtk_main_iteration ();
230
231     err = gpgme_op_keylist_start (ctx, pattern, 0);
232     if (err) {
233         debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
234                      pattern, gpgme_strerror (err));
235         sk->select_ctx = NULL;
236         return;
237     }
238     update_progress (sk, ++running, pattern);
239     while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
240         debug_print ("%% %s:%d:  insert\n", __FILE__ ,__LINE__ );
241         set_row (clist, key ); key = NULL;
242         update_progress (sk, ++running, pattern);
243         while (gtk_events_pending ())
244             gtk_main_iteration ();
245     }
246     debug_print ("%% %s:%d:  ready\n", __FILE__ ,__LINE__ );
247     if (err != GPGME_EOF)
248         debug_print ("** gpgme_op_keylist_next failed: %s",
249                      gpgme_strerror (err));
250     sk->select_ctx = NULL;
251     gpgme_release (ctx);
252     /*gtk_clist_thaw (select_keys.clist);*/
253 }
254
255
256 static void 
257 create_dialog (struct select_keys_s *sk)
258 {
259     GtkWidget *window;
260     GtkWidget *vbox, *vbox2, *hbox;
261     GtkWidget *bbox;
262     GtkWidget *scrolledwin;
263     GtkWidget *clist;
264     GtkWidget *label;
265     GtkWidget *select_btn, *cancel_btn, *other_btn;
266     GtkWidget *showall_btn;
267     const char *titles[N_COL_TITLES];
268
269     g_assert (!sk->window);
270     window = gtk_window_new (GTK_WINDOW_DIALOG);
271     gtk_widget_set_usize (window, 520, 280);
272     gtk_container_set_border_width (GTK_CONTAINER (window), 8);
273     gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
274     gtk_window_set_modal (GTK_WINDOW (window), TRUE);
275     gtk_signal_connect (GTK_OBJECT (window), "delete_event",
276                         GTK_SIGNAL_FUNC (delete_event_cb), sk);
277     gtk_signal_connect (GTK_OBJECT (window), "key_press_event",
278                         GTK_SIGNAL_FUNC (key_pressed_cb), sk);
279     MANAGE_WINDOW_SIGNALS_CONNECT (window);
280
281     vbox = gtk_vbox_new (FALSE, 8);
282     gtk_container_add (GTK_CONTAINER (window), vbox);
283
284     hbox  = gtk_hbox_new(FALSE, 4);
285     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
286     label = gtk_label_new ( "" );
287     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
288
289     hbox = gtk_hbox_new (FALSE, 8);
290     gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
291     gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
292
293     scrolledwin = gtk_scrolled_window_new (NULL, NULL);
294     gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
295     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
296                                     GTK_POLICY_AUTOMATIC,
297                                     GTK_POLICY_AUTOMATIC);
298
299     titles[COL_ALGO]     = _("Size");
300     titles[COL_KEYID]    = _("Key ID");
301     titles[COL_NAME]     = _("Name");
302     titles[COL_EMAIL]    = _("Address");
303     titles[COL_VALIDITY] = _("Val");
304
305     clist = gtk_clist_new_with_titles (N_COL_TITLES, (char**)titles);
306     gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
307     gtk_clist_set_column_width (GTK_CLIST(clist), COL_ALGO,      72);
308     gtk_clist_set_column_width (GTK_CLIST(clist), COL_KEYID,     76);
309     gtk_clist_set_column_width (GTK_CLIST(clist), COL_NAME,     130);
310     gtk_clist_set_column_width (GTK_CLIST(clist), COL_EMAIL,    130);
311     gtk_clist_set_column_width (GTK_CLIST(clist), COL_VALIDITY,  20);
312     gtk_clist_set_selection_mode (GTK_CLIST(clist), GTK_SELECTION_BROWSE);
313     gtk_signal_connect (GTK_OBJECT(GTK_CLIST(clist)->column[COL_NAME].button),
314                         "clicked",
315                         GTK_SIGNAL_FUNC(sort_keys_name), sk);
316     gtk_signal_connect (GTK_OBJECT(GTK_CLIST(clist)->column[COL_EMAIL].button),
317                         "clicked",
318                         GTK_SIGNAL_FUNC(sort_keys_email), sk);
319
320     hbox = gtk_hbox_new (FALSE, 8);
321     gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
322
323     showall_btn = gtk_button_new_with_label (_(" List all keys "));
324     gtk_widget_show (showall_btn);
325     gtk_box_pack_start (GTK_BOX (hbox), showall_btn, FALSE, FALSE, 0);
326
327     gtk_signal_connect (GTK_OBJECT (showall_btn), "clicked",
328                         GTK_SIGNAL_FUNC (showall_btn_cb), sk);
329
330     gtkut_button_set_create (&bbox, 
331                              &select_btn, _("Select"),
332                              &cancel_btn, _("Cancel"),
333                              &other_btn,  _("Other"));
334     gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
335     gtk_widget_grab_default (select_btn);
336
337     gtk_signal_connect (GTK_OBJECT (select_btn), "clicked",
338                         GTK_SIGNAL_FUNC (select_btn_cb), sk);
339     gtk_signal_connect (GTK_OBJECT(cancel_btn), "clicked",
340                         GTK_SIGNAL_FUNC (cancel_btn_cb), sk);
341     gtk_signal_connect (GTK_OBJECT (other_btn), "clicked",
342                         GTK_SIGNAL_FUNC (other_btn_cb), sk);
343
344     vbox2 = gtk_vbox_new (FALSE, 4);
345     gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
346
347     gtk_widget_show_all (window);
348
349     sk->window = window;
350     sk->toplabel = GTK_LABEL (label);
351     sk->clist  = GTK_CLIST (clist);
352 }
353
354
355 static void
356 open_dialog (struct select_keys_s *sk)
357 {
358     if (!sk->window)
359         create_dialog (sk);
360     manage_window_set_transient (GTK_WINDOW (sk->window));
361     sk->okay = 0;
362     sk->sort_column = N_COL_TITLES; /* use an invalid value */
363     sk->sort_type = GTK_SORT_ASCENDING;
364     gtk_widget_show (sk->window);
365 }
366
367
368 static void
369 close_dialog (struct select_keys_s *sk)
370 {
371     g_return_if_fail (sk);
372     gtk_widget_destroy (sk->window);
373     sk->window = NULL;
374 }
375
376
377 static gint
378 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
379 {
380     struct select_keys_s *sk = data;
381
382     sk->okay = 0;
383     gtk_main_quit ();
384
385     return TRUE;
386 }
387
388
389 static void 
390 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
391 {
392     struct select_keys_s *sk = data;
393
394     g_return_if_fail (sk);
395     if (event && event->keyval == GDK_Escape) {
396         sk->okay = 0;
397         gtk_main_quit ();
398     }
399 }
400
401
402 static void 
403 select_btn_cb (GtkWidget *widget, gpointer data)
404 {
405     struct select_keys_s *sk = data;
406     int row;
407     GpgmeKey key;
408
409     g_return_if_fail (sk);
410     if (!sk->clist->selection) {
411         debug_print ("** nothing selected");
412         return;
413     }
414     row = GPOINTER_TO_INT(sk->clist->selection->data);
415     key = gtk_clist_get_row_data(sk->clist, row);
416     if (key) {
417         const char *s = gpgme_key_get_string_attr (key,
418                                                    GPGME_ATTR_FPR,
419                                                    NULL, 0 );
420         if ( gpgme_key_get_ulong_attr (key, GPGME_ATTR_VALIDITY, NULL, 0 )
421              < GPGME_VALIDITY_FULL ) {
422             debug_print ("** FIXME: we are faking the trust calculation");
423         }
424         if (!gpgme_recipients_add_name_with_validity (sk->rset, s,
425                                                       GPGME_VALIDITY_FULL) ) {
426             sk->okay = 1;
427             gtk_main_quit ();
428         }
429     }
430 }
431
432
433 static void 
434 cancel_btn_cb (GtkWidget *widget, gpointer data)
435 {
436     struct select_keys_s *sk = data;
437
438     g_return_if_fail (sk);
439     sk->okay = 0;
440     if (sk->select_ctx)
441         gpgme_cancel (sk->select_ctx);
442     gtk_main_quit ();
443 }
444
445
446 static void
447 other_btn_cb (GtkWidget *widget, gpointer data)
448 {
449     struct select_keys_s *sk = data;
450     char *uid;
451
452     g_return_if_fail (sk);
453     uid = input_dialog ( _("Add key"),
454                          _("Enter another user or key ID:"),
455                          NULL );
456     if (!uid)
457         return;
458     fill_clist (sk, uid);
459     update_progress (sk, 0, sk->pattern);
460     g_free (uid);
461 }
462
463 static void
464 showall_btn_cb (GtkWidget *widget, gpointer data)
465 {
466     struct select_keys_s *sk = data;
467     char *uid;
468
469     g_return_if_fail (sk);
470     uid = "";
471         
472     fill_clist (sk, uid);
473     update_progress (sk, 0, sk->pattern);
474 }
475
476 static gint 
477 cmp_attr (gconstpointer pa, gconstpointer pb, GpgmeAttr attr)
478 {
479     GpgmeKey a = ((GtkCListRow *)pa)->data;
480     GpgmeKey b = ((GtkCListRow *)pb)->data;
481     const char *sa, *sb;
482     
483     sa = a? gpgme_key_get_string_attr (a, attr, NULL, 0 ) : NULL;
484     sb = b? gpgme_key_get_string_attr (b, attr, NULL, 0 ) : NULL;
485     if (!sa)
486         return !!sb;
487     if (!sb)
488         return -1;
489     return strcasecmp(sa, sb);
490 }
491
492 static gint 
493 cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb)
494 {
495     return cmp_attr (pa, pb, GPGME_ATTR_NAME);
496 }
497
498 static gint 
499 cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb)
500 {
501     return cmp_attr (pa, pb, GPGME_ATTR_EMAIL);
502 }
503
504 static void
505 sort_keys ( struct select_keys_s *sk, enum col_titles column)
506 {
507     GtkCList *clist = sk->clist;
508
509     switch (column) {
510       case COL_NAME:
511         gtk_clist_set_compare_func (clist, cmp_name);
512         break;
513       case COL_EMAIL:
514         gtk_clist_set_compare_func (clist, cmp_email);
515         break;
516       default:
517         return;
518     }
519
520     /* column clicked again: toggle as-/decending */
521     if ( sk->sort_column == column) {
522         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
523                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
524     }
525     else
526         sk->sort_type = GTK_SORT_ASCENDING;
527
528     sk->sort_column = column;
529     gtk_clist_set_sort_type (clist, sk->sort_type);
530     gtk_clist_sort (clist);
531 }
532
533 static void
534 sort_keys_name (GtkWidget *widget, gpointer data)
535 {
536     sort_keys ((struct select_keys_s*)data, COL_NAME);
537 }
538
539 static void
540 sort_keys_email (GtkWidget *widget, gpointer data)
541 {
542     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
543 }
544
545 #endif /*USE_GPGME*/