sync with 0.8.1cvs13
[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
48 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
49 #define DIMof(type,member)   DIM(((type *)0)->member)
50
51
52 enum col_titles { 
53     COL_ALGO,
54     COL_KEYID,
55     COL_NAME,
56     COL_EMAIL,
57     COL_VALIDITY,
58
59     N_COL_TITLES
60 };
61
62 struct select_keys_s {
63     int okay;
64     GtkWidget *window;
65     GtkLabel *toplabel;
66     GtkCList *clist;
67     const char *pattern;
68     GpgmeRecipients rset;
69     GpgmeCtx select_ctx;
70
71     GtkSortType sort_type;
72     enum col_titles sort_column;
73     
74 };
75
76
77 static void set_row (GtkCList *clist, GpgmeKey key);
78 static void fill_clist (struct select_keys_s *sk, const char *pattern);
79 static void create_dialog (struct select_keys_s *sk);
80 static void open_dialog (struct select_keys_s *sk);
81 static void close_dialog (struct select_keys_s *sk);
82 static gint delete_event_cb (GtkWidget *widget,
83                              GdkEventAny *event, gpointer data);
84 static void key_pressed_cb (GtkWidget *widget,
85                             GdkEventKey *event, gpointer data);
86 static void select_btn_cb (GtkWidget *widget, gpointer data);
87 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
88 static void other_btn_cb (GtkWidget *widget, gpointer data);
89 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
90 static void sort_keys_name (GtkWidget *widget, gpointer data);
91 static void sort_keys_email (GtkWidget *widget, gpointer data);
92
93
94 static void
95 update_progress (struct select_keys_s *sk, int running, const char *pattern)
96 {
97     static int windmill[] = { '-', '\\', '|', '/' };
98     char *buf;
99
100     if (!running)
101         buf = g_strdup_printf (_("Please select key for `%s'"), 
102                                pattern);
103     else 
104         buf = g_strdup_printf (_("Collecting info for `%s' ... %c"), 
105                                pattern,
106                                windmill[running%DIM(windmill)]);
107     gtk_label_set_text (sk->toplabel, buf);
108     g_free (buf);
109 }
110
111
112 /**
113  * select_keys_get_recipients:
114  * @recp_names: A list of email addresses
115  * 
116  * Select a list of recipients from a given list of email addresses.
117  * This may pop up a window to present the user a choice, it will also
118  * check that the recipients key are all valid.
119  * 
120  * Return value: NULL on error or a list of list of recipients.
121  **/
122 GpgmeRecipients
123 gpgmegtk_recipient_selection (GSList *recp_names)
124 {
125     struct select_keys_s sk;
126     GpgmeError err;
127
128     memset ( &sk, 0, sizeof sk);
129
130     err = gpgme_recipients_new (&sk.rset);
131     if (err) {
132         g_warning ("failed to allocate recipients set: %s",
133                    gpgme_strerror (err));
134         return NULL;
135     }
136
137     open_dialog (&sk);
138
139     do {
140         sk.pattern = recp_names? recp_names->data:NULL;
141         gtk_clist_clear (sk.clist);
142         fill_clist (&sk, sk.pattern);
143         update_progress (&sk, 0, sk.pattern);
144         gtk_main ();
145         if (recp_names)
146             recp_names = recp_names->next;
147     } while (sk.okay && recp_names);
148
149     close_dialog (&sk);
150
151     if (!sk.okay) {
152         gpgme_recipients_release (sk.rset);
153         sk.rset = NULL;
154     }
155     return sk.rset;
156
157
158 static void
159 destroy_key (gpointer data)
160 {
161     GpgmeKey key = data;
162     gpgme_key_release (key);
163 }
164
165 static void
166 set_row (GtkCList *clist, GpgmeKey key)
167 {
168     const char *s;
169     const char *text[N_COL_TITLES];
170     char *algo_buf;
171     int row;
172
173     /* first check whether the key is capable of encryption which is not
174      * the case for revoked, expired or sign-only keys */
175     if ( !gpgme_key_get_ulong_attr (key, GPGME_ATTR_CAN_ENCRYPT, NULL, 0 ) )
176         return;
177     
178     algo_buf = g_strdup_printf ("%lu/%s", 
179          gpgme_key_get_ulong_attr (key, GPGME_ATTR_LEN, NULL, 0 ),
180          gpgme_key_get_string_attr (key, GPGME_ATTR_ALGO, NULL, 0 ) );
181     text[COL_ALGO] = algo_buf;
182
183     s = gpgme_key_get_string_attr (key, GPGME_ATTR_KEYID, NULL, 0);
184     if (strlen (s) == 16)
185         s += 8; /* show only the short keyID */
186     text[COL_KEYID] = s;
187
188     s = gpgme_key_get_string_attr (key, GPGME_ATTR_NAME, NULL, 0);
189     text[COL_NAME] = s;
190
191     s = gpgme_key_get_string_attr (key, GPGME_ATTR_EMAIL, NULL, 0);
192     text[COL_EMAIL] = s;
193
194     s = gpgme_key_get_string_attr (key, GPGME_ATTR_VALIDITY, NULL, 0);
195     text[COL_VALIDITY] = s;
196
197     row = gtk_clist_append (clist, (gchar**)text);
198     g_free (algo_buf);
199
200     gtk_clist_set_row_data_full (clist, row, key, destroy_key);
201 }
202
203
204 static void 
205 fill_clist (struct select_keys_s *sk, const char *pattern)
206 {
207     GtkCList *clist;
208     GpgmeCtx ctx;
209     GpgmeError err;
210     GpgmeKey key;
211     int running=0;
212
213     g_return_if_fail (sk);
214     clist = sk->clist;
215     g_return_if_fail (clist);
216
217     debug_print ("select_keys:fill_clist:  pattern `%s'\n", pattern);
218
219     /*gtk_clist_freeze (select_keys.clist);*/
220     err = gpgme_new (&ctx);
221     g_assert (!err);
222
223     sk->select_ctx = ctx;
224
225     update_progress (sk, ++running, pattern);
226     while (gtk_events_pending ())
227         gtk_main_iteration ();
228
229     err = gpgme_op_keylist_start (ctx, pattern, 0);
230     if (err) {
231         debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
232                      pattern, gpgme_strerror (err));
233         sk->select_ctx = NULL;
234         return;
235     }
236     update_progress (sk, ++running, pattern);
237     while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
238         debug_print ("%% %s:%d:  insert\n", __FILE__ ,__LINE__ );
239         set_row (clist, key ); key = NULL;
240         update_progress (sk, ++running, pattern);
241         while (gtk_events_pending ())
242             gtk_main_iteration ();
243     }
244     debug_print ("%% %s:%d:  ready\n", __FILE__ ,__LINE__ );
245     if (err != GPGME_EOF)
246         debug_print ("** gpgme_op_keylist_next failed: %s",
247                      gpgme_strerror (err));
248     sk->select_ctx = NULL;
249     gpgme_release (ctx);
250     /*gtk_clist_thaw (select_keys.clist);*/
251 }
252
253
254 static void 
255 create_dialog (struct select_keys_s *sk)
256 {
257     GtkWidget *window;
258     GtkWidget *vbox, *vbox2, *hbox;
259     GtkWidget *bbox;
260     GtkWidget *scrolledwin;
261     GtkWidget *clist;
262     GtkWidget *label;
263     GtkWidget *other_btn, *select_btn, *cancel_btn;
264     const char *titles[N_COL_TITLES];
265
266     g_assert (!sk->window);
267     window = gtk_window_new (GTK_WINDOW_DIALOG);
268     gtk_widget_set_usize (window, 500, 320);
269     gtk_container_set_border_width (GTK_CONTAINER (window), 8);
270     gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
271     gtk_window_set_modal (GTK_WINDOW (window), TRUE);
272     gtk_signal_connect (GTK_OBJECT (window), "delete_event",
273                         GTK_SIGNAL_FUNC (delete_event_cb), sk);
274     gtk_signal_connect (GTK_OBJECT (window), "key_press_event",
275                         GTK_SIGNAL_FUNC (key_pressed_cb), sk);
276
277     vbox = gtk_vbox_new (FALSE, 8);
278     gtk_container_add (GTK_CONTAINER (window), vbox);
279
280     hbox  = gtk_hbox_new(FALSE, 4);
281     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
282     label = gtk_label_new ( "" );
283     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
284
285
286     hbox = gtk_hbox_new (FALSE, 8);
287     gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
288     gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
289
290
291     scrolledwin = gtk_scrolled_window_new (NULL, NULL);
292     gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
293     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
294                                     GTK_POLICY_AUTOMATIC,
295                                     GTK_POLICY_AUTOMATIC);
296
297     titles[COL_ALGO]     = _("Size");
298     titles[COL_KEYID]    = _("Key ID");
299     titles[COL_NAME]     = _("Name");
300     titles[COL_EMAIL]    = _("Address");
301     titles[COL_VALIDITY] = _("Val");
302
303     clist = gtk_clist_new_with_titles (N_COL_TITLES, (char**)titles);
304     gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
305     gtk_clist_set_column_width (GTK_CLIST(clist), COL_ALGO,      40);
306     gtk_clist_set_column_width (GTK_CLIST(clist), COL_KEYID,     60);
307     gtk_clist_set_column_width (GTK_CLIST(clist), COL_NAME,     100);
308     gtk_clist_set_column_width (GTK_CLIST(clist), COL_EMAIL,    100);
309     gtk_clist_set_column_width (GTK_CLIST(clist), COL_VALIDITY,  20);
310     gtk_clist_set_selection_mode (GTK_CLIST(clist), GTK_SELECTION_BROWSE);
311     gtk_signal_connect (GTK_OBJECT(GTK_CLIST(clist)->column[COL_NAME].button),
312                         "clicked",
313                         GTK_SIGNAL_FUNC(sort_keys_name), sk);
314     gtk_signal_connect (GTK_OBJECT(GTK_CLIST(clist)->column[COL_EMAIL].button),
315                         "clicked",
316                         GTK_SIGNAL_FUNC(sort_keys_email), sk);
317
318     hbox = gtk_hbox_new (FALSE, 8);
319     gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
320
321     gtkut_button_set_create (&bbox, 
322                              &other_btn,  _("Other"),
323                              &select_btn, _("Select"),
324                              &cancel_btn, _("Cancel"));
325     gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
326     gtk_widget_grab_default (select_btn);
327
328     gtk_signal_connect (GTK_OBJECT (other_btn), "clicked",
329                         GTK_SIGNAL_FUNC (other_btn_cb), sk);
330     gtk_signal_connect (GTK_OBJECT (select_btn), "clicked",
331                         GTK_SIGNAL_FUNC (select_btn_cb), sk);
332     gtk_signal_connect (GTK_OBJECT(cancel_btn), "clicked",
333                         GTK_SIGNAL_FUNC (cancel_btn_cb), sk);
334     
335
336     vbox2 = gtk_vbox_new (FALSE, 4);
337     gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
338
339     gtk_widget_show_all (window);
340     sk->window = window;
341     sk->toplabel = GTK_LABEL (label);
342     sk->clist  = GTK_CLIST (clist);
343 }
344
345
346 static void
347 open_dialog (struct select_keys_s *sk)
348 {
349     if ( !sk->window )
350         create_dialog (sk);
351     sk->okay = 0;
352     sk->sort_column = N_COL_TITLES; /* use an invalid value */
353     sk->sort_type = GTK_SORT_ASCENDING;
354     gtk_widget_show (sk->window);
355 }
356
357
358 static void
359 close_dialog (struct select_keys_s *sk)
360 {
361     g_return_if_fail (sk);
362     gtk_widget_destroy (sk->window);
363     sk->window = NULL;
364 }
365
366
367 static gint
368 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
369 {
370     struct select_keys_s *sk = data;
371
372     sk->okay = 0;
373     gtk_main_quit ();
374
375     return TRUE;
376 }
377
378
379 static void 
380 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
381 {
382     struct select_keys_s *sk = data;
383
384     g_return_if_fail (sk);
385     if (event && event->keyval == GDK_Escape) {
386         sk->okay = 0;
387         gtk_main_quit ();
388     }
389 }
390
391
392 static void 
393 select_btn_cb (GtkWidget *widget, gpointer data)
394 {
395     struct select_keys_s *sk = data;
396     int row;
397     GpgmeKey key;
398
399     g_return_if_fail (sk);
400     if (!sk->clist->selection) {
401         debug_print ("** nothing selected");
402         return;
403     }
404     row = GPOINTER_TO_INT(sk->clist->selection->data);
405     key = gtk_clist_get_row_data(sk->clist, row);
406     if (key) {
407         const char *s = gpgme_key_get_string_attr (key,
408                                                    GPGME_ATTR_FPR,
409                                                    NULL, 0 );
410         if ( gpgme_key_get_ulong_attr (key, GPGME_ATTR_VALIDITY, NULL, 0 )
411              < GPGME_VALIDITY_FULL ) {
412             debug_print ("** FIXME: we are faking the trust calculation");
413         }
414         if (!gpgme_recipients_add_name_with_validity (sk->rset, s,
415                                                       GPGME_VALIDITY_FULL) ) {
416             sk->okay = 1;
417             gtk_main_quit ();
418         }
419     }
420 }
421
422
423 static void 
424 cancel_btn_cb (GtkWidget *widget, gpointer data)
425 {
426     struct select_keys_s *sk = data;
427
428     g_return_if_fail (sk);
429     sk->okay = 0;
430     if (sk->select_ctx)
431         gpgme_cancel (sk->select_ctx);
432     gtk_main_quit ();
433 }
434
435
436 static void
437 other_btn_cb (GtkWidget *widget, gpointer data)
438 {
439     struct select_keys_s *sk = data;
440     char *uid;
441
442     g_return_if_fail (sk);
443     uid = input_dialog ( _("Add key"),
444                          _("Enter another user or key ID\n"),
445                          NULL );
446     if (!uid)
447         return;
448     fill_clist (sk, uid);
449     update_progress (sk, 0, sk->pattern);
450     g_free (uid);
451 }
452
453
454 static gint 
455 cmp_attr (gconstpointer pa, gconstpointer pb, GpgmeAttr attr)
456 {
457     GpgmeKey a = ((GtkCListRow *)pa)->data;
458     GpgmeKey b = ((GtkCListRow *)pb)->data;
459     const char *sa, *sb;
460     
461     sa = a? gpgme_key_get_string_attr (a, attr, NULL, 0 ) : NULL;
462     sb = b? gpgme_key_get_string_attr (b, attr, NULL, 0 ) : NULL;
463     if (!sa)
464         return !!sb;
465     if (!sb)
466         return -1;
467     return strcasecmp(sa, sb);
468 }
469
470 static gint 
471 cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb)
472 {
473     return cmp_attr (pa, pb, GPGME_ATTR_NAME);
474 }
475
476 static gint 
477 cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb)
478 {
479     return cmp_attr (pa, pb, GPGME_ATTR_EMAIL);
480 }
481
482 static void
483 sort_keys ( struct select_keys_s *sk, enum col_titles column)
484 {
485     GtkCList *clist = sk->clist;
486
487     switch (column) {
488       case COL_NAME:
489         gtk_clist_set_compare_func (clist, cmp_name);
490         break;
491       case COL_EMAIL:
492         gtk_clist_set_compare_func (clist, cmp_email);
493         break;
494       default:
495         return;
496     }
497
498     /* column clicked again: toggle as-/decending */
499     if ( sk->sort_column == column) {
500         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
501                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
502     }
503     else
504         sk->sort_type = GTK_SORT_ASCENDING;
505
506     sk->sort_column = column;
507     gtk_clist_set_sort_type (clist, sk->sort_type);
508     gtk_clist_sort (clist);
509 }
510
511 static void
512 sort_keys_name (GtkWidget *widget, gpointer data)
513 {
514     sort_keys ((struct select_keys_s*)data, COL_NAME);
515 }
516
517 static void
518 sort_keys_email (GtkWidget *widget, gpointer data)
519 {
520     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
521 }
522
523 #endif /*USE_GPGME*/