638c8ae2ef6e2bcc94d2f1864937ec2ea0a417f9
[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_message ("** 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         g_message ("** 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         g_message ("** 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
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 *other_btn, *select_btn, *cancel_btn;
266     const char *titles[N_COL_TITLES];
267
268     g_assert (!sk->window);
269     window = gtk_window_new (GTK_WINDOW_DIALOG);
270     gtk_widget_set_usize (window, 500, 320);
271     gtk_container_set_border_width (GTK_CONTAINER (window), 8);
272     gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
273     gtk_window_set_modal (GTK_WINDOW (window), TRUE);
274     gtk_signal_connect (GTK_OBJECT (window), "delete_event",
275                         GTK_SIGNAL_FUNC (delete_event_cb), sk);
276     gtk_signal_connect (GTK_OBJECT (window), "key_press_event",
277                         GTK_SIGNAL_FUNC (key_pressed_cb), sk);
278
279     vbox = gtk_vbox_new (FALSE, 8);
280     gtk_container_add (GTK_CONTAINER (window), vbox);
281
282     hbox  = gtk_hbox_new(FALSE, 4);
283     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
284     label = gtk_label_new ( "" );
285     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
286
287
288     hbox = gtk_hbox_new (FALSE, 8);
289     gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
290     gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
291
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,      40);
308     gtk_clist_set_column_width (GTK_CLIST(clist), COL_KEYID,     60);
309     gtk_clist_set_column_width (GTK_CLIST(clist), COL_NAME,     100);
310     gtk_clist_set_column_width (GTK_CLIST(clist), COL_EMAIL,    100);
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     gtkut_button_set_create (&bbox, 
324                              &other_btn,  _("Other"),
325                              &select_btn, _("Select"),
326                              &cancel_btn, _("Cancel"));
327     gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
328     gtk_widget_grab_default (select_btn);
329
330     gtk_signal_connect (GTK_OBJECT (other_btn), "clicked",
331                         GTK_SIGNAL_FUNC (other_btn_cb), sk);
332     gtk_signal_connect (GTK_OBJECT (select_btn), "clicked",
333                         GTK_SIGNAL_FUNC (select_btn_cb), sk);
334     gtk_signal_connect (GTK_OBJECT(cancel_btn), "clicked",
335                         GTK_SIGNAL_FUNC (cancel_btn_cb), sk);
336     
337
338     vbox2 = gtk_vbox_new (FALSE, 4);
339     gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
340
341     gtk_widget_show_all (window);
342     sk->window = window;
343     sk->toplabel = GTK_LABEL (label);
344     sk->clist  = GTK_CLIST (clist);
345 }
346
347
348 static void
349 open_dialog (struct select_keys_s *sk)
350 {
351     if ( !sk->window )
352         create_dialog (sk);
353     sk->okay = 0;
354     sk->sort_column = N_COL_TITLES; /* use an invalid value */
355     sk->sort_type = GTK_SORT_ASCENDING;
356     gtk_widget_show (sk->window);
357 }
358
359
360 static void
361 close_dialog (struct select_keys_s *sk)
362 {
363     g_return_if_fail (sk);
364     gtk_widget_destroy (sk->window);
365     sk->window = NULL;
366 }
367
368
369 static gint
370 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
371 {
372     struct select_keys_s *sk = data;
373
374     sk->okay = 0;
375     gtk_main_quit ();
376
377     return TRUE;
378 }
379
380
381 static void 
382 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
383 {
384     struct select_keys_s *sk = data;
385
386     g_return_if_fail (sk);
387     if (event && event->keyval == GDK_Escape) {
388         sk->okay = 0;
389         gtk_main_quit ();
390     }
391 }
392
393
394 static void 
395 select_btn_cb (GtkWidget *widget, gpointer data)
396 {
397     struct select_keys_s *sk = data;
398     int row;
399     GpgmeKey key;
400
401     g_return_if_fail (sk);
402     if (!sk->clist->selection) {
403         g_message ("** nothing selected");
404         return;
405     }
406     row = GPOINTER_TO_INT(sk->clist->selection->data);
407     key = gtk_clist_get_row_data(sk->clist, row);
408     if (key) {
409         const char *s = gpgme_key_get_string_attr (key,
410                                                    GPGME_ATTR_FPR,
411                                                    NULL, 0 );
412         if ( gpgme_key_get_ulong_attr (key, GPGME_ATTR_VALIDITY, NULL, 0 )
413              < GPGME_VALIDITY_FULL ) {
414             g_message ("** FIXME: we are faking the trust calculation");
415         }
416         if (!gpgme_recipients_add_name_with_validity (sk->rset, s,
417                                                       GPGME_VALIDITY_FULL) ) {
418             sk->okay = 1;
419             gtk_main_quit ();
420         }
421     }
422 }
423
424
425 static void 
426 cancel_btn_cb (GtkWidget *widget, gpointer data)
427 {
428     struct select_keys_s *sk = data;
429
430     g_return_if_fail (sk);
431     sk->okay = 0;
432     if (sk->select_ctx)
433         gpgme_cancel (sk->select_ctx);
434     gtk_main_quit ();
435 }
436
437
438 static void
439 other_btn_cb (GtkWidget *widget, gpointer data)
440 {
441     struct select_keys_s *sk = data;
442     char *uid;
443
444     g_return_if_fail (sk);
445     uid = input_dialog ( _("Add key"),
446                          _("Enter another user or key ID\n"),
447                          NULL );
448     if (!uid)
449         return;
450     fill_clist (sk, uid);
451     update_progress (sk, 0, sk->pattern);
452     g_free (uid);
453 }
454
455
456 static gint 
457 cmp_attr (gconstpointer pa, gconstpointer pb, GpgmeAttr attr)
458 {
459     GpgmeKey a = ((GtkCListRow *)pa)->data;
460     GpgmeKey b = ((GtkCListRow *)pb)->data;
461     const char *sa, *sb;
462     
463     sa = a? gpgme_key_get_string_attr (a, attr, NULL, 0 ) : NULL;
464     sb = b? gpgme_key_get_string_attr (b, attr, NULL, 0 ) : NULL;
465     if (!sa)
466         return !!sb;
467     if (!sb)
468         return -1;
469     return strcasecmp(sa, sb);
470 }
471
472 static gint 
473 cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb)
474 {
475     return cmp_attr (pa, pb, GPGME_ATTR_NAME);
476 }
477
478 static gint 
479 cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb)
480 {
481     return cmp_attr (pa, pb, GPGME_ATTR_EMAIL);
482 }
483
484 static void
485 sort_keys ( struct select_keys_s *sk, enum col_titles column)
486 {
487     GtkCList *clist = sk->clist;
488
489     switch (column) {
490       case COL_NAME:
491         gtk_clist_set_compare_func (clist, cmp_name);
492         break;
493       case COL_EMAIL:
494         gtk_clist_set_compare_func (clist, cmp_email);
495         break;
496       default:
497         return;
498     }
499
500     /* column clicked again: toggle as-/decending */
501     if ( sk->sort_column == column) {
502         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
503                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
504     }
505     else
506         sk->sort_type = GTK_SORT_ASCENDING;
507
508     sk->sort_column = column;
509     gtk_clist_set_sort_type (clist, sk->sort_type);
510     gtk_clist_sort (clist);
511 }
512
513 static void
514 sort_keys_name (GtkWidget *widget, gpointer data)
515 {
516     sort_keys ((struct select_keys_s*)data, COL_NAME);
517 }
518
519 static void
520 sort_keys_email (GtkWidget *widget, gpointer data)
521 {
522     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
523 }
524
525
526 #endif /*USE_GPGME*/