sync with sylpheed 0.4.65cvs10
[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 void key_pressed_cb (GtkWidget *widget,
83                             GdkEventKey *event, gpointer data);
84 static void select_btn_cb (GtkWidget *widget, gpointer data);
85 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
86 static void other_btn_cb (GtkWidget *widget, gpointer data);
87 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
88 static void sort_keys_name (GtkWidget *widget, gpointer data);
89 static void sort_keys_email (GtkWidget *widget, gpointer data);
90
91
92 static void
93 update_progress (struct select_keys_s *sk, int running, const char *pattern)
94 {
95     static int windmill[] = { '-', '\\', '|', '/' };
96     char *buf;
97
98     if (!running)
99         buf = g_strdup_printf (_("Please select key for `%s'"), 
100                                pattern);
101     else 
102         buf = g_strdup_printf (_("Collecting info for `%s' ... %c"), 
103                                pattern,
104                                windmill[running%DIM(windmill)]);
105     gtk_label_set_text (sk->toplabel, buf);
106     g_free (buf);
107 }
108
109
110 /**
111  * select_keys_get_recipients:
112  * @recp_names: A list of email addresses
113  * 
114  * Select a list of recipients from a given list of email addresses.
115  * This may pop up a window to present the user a choice, it will also
116  * check that the recipients key are all valid.
117  * 
118  * Return value: NULL on error or a list of list of recipients.
119  **/
120 GpgmeRecipients
121 gpgmegtk_recipient_selection (GSList *recp_names)
122 {
123     struct select_keys_s sk;
124     GpgmeError err;
125
126     memset ( &sk, 0, sizeof sk);
127
128     err = gpgme_recipients_new (&sk.rset);
129     if (err) {
130         g_message ("** failed to allocate recipients set: %s",
131                    gpgme_strerror (err));
132         return NULL;
133     }
134         
135     open_dialog (&sk);
136
137     do {
138         sk.pattern = recp_names? recp_names->data:NULL;
139         gtk_clist_clear (sk.clist);
140         fill_clist (&sk, sk.pattern);
141         update_progress (&sk, 0, sk.pattern);
142         gtk_main ();
143         if (recp_names)
144             recp_names = recp_names->next;
145     } while (sk.okay && recp_names);
146
147     close_dialog (&sk);
148
149     if (!sk.okay) {
150         gpgme_recipients_release (sk.rset);
151         sk.rset = NULL;
152     }
153     return sk.rset;
154
155
156 static void
157 destroy_key (gpointer data)
158 {
159     GpgmeKey key = data;
160     gpgme_key_release (key);
161 }
162
163 static void
164 set_row (GtkCList *clist, GpgmeKey key)
165 {
166     const char *s;
167     const char *text[N_COL_TITLES];
168     char *algo_buf;
169     int row;
170
171     /* first check whether the key is capable of encryption which is not
172      * the case for revoked, expired or sign-only keys */
173     if ( !gpgme_key_get_ulong_attr (key, GPGME_ATTR_CAN_ENCRYPT, NULL, 0 ) )
174         return;
175     
176     algo_buf = g_strdup_printf ("%lu/%s", 
177          gpgme_key_get_ulong_attr (key, GPGME_ATTR_LEN, NULL, 0 ),
178          gpgme_key_get_string_attr (key, GPGME_ATTR_ALGO, NULL, 0 ) );
179     text[COL_ALGO] = algo_buf;
180
181     s = gpgme_key_get_string_attr (key, GPGME_ATTR_KEYID, NULL, 0);
182     if (strlen (s) == 16)
183         s += 8; /* show only the short keyID */
184     text[COL_KEYID] = s;
185
186     s = gpgme_key_get_string_attr (key, GPGME_ATTR_NAME, NULL, 0);
187     text[COL_NAME] = s;
188
189     s = gpgme_key_get_string_attr (key, GPGME_ATTR_EMAIL, NULL, 0);
190     text[COL_EMAIL] = s;
191
192     s = gpgme_key_get_string_attr (key, GPGME_ATTR_VALIDITY, NULL, 0);
193     text[COL_VALIDITY] = s;
194
195     row = gtk_clist_append (clist, (gchar**)text);
196     g_free (algo_buf);
197
198     gtk_clist_set_row_data_full (clist, row, key, destroy_key);
199 }
200
201
202 static void 
203 fill_clist (struct select_keys_s *sk, const char *pattern)
204 {
205     GtkCList *clist;
206     GpgmeCtx ctx;
207     GpgmeError err;
208     GpgmeKey key;
209     int running=0;
210
211     g_return_if_fail (sk);
212     clist = sk->clist;
213     g_return_if_fail (clist);
214
215     debug_print ("select_keys:fill_clist:  pattern `%s'\n", pattern);
216
217     /*gtk_clist_freeze (select_keys.clist);*/
218     err = gpgme_new (&ctx);
219     g_assert (!err);
220
221     sk->select_ctx = ctx;
222
223     update_progress (sk, ++running, pattern);
224     while (gtk_events_pending ())
225         gtk_main_iteration ();
226
227     err = gpgme_op_keylist_start (ctx, pattern, 0);
228     if (err) {
229         g_message ("** gpgme_op_keylist_start(%s) failed: %s",
230                    pattern, gpgme_strerror (err));
231         sk->select_ctx = NULL;
232         return;
233     }
234     update_progress (sk, ++running, pattern);
235     while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
236         debug_print ("%% %s:%d:  insert\n", __FILE__ ,__LINE__ );
237         set_row (clist, key ); key = NULL;
238         update_progress (sk, ++running, pattern);
239         while (gtk_events_pending ())
240             gtk_main_iteration ();
241     }
242     debug_print ("%% %s:%d:  ready\n", __FILE__ ,__LINE__ );
243     if (err != GPGME_EOF)
244         g_message ("** gpgme_op_keylist_next failed: %s",
245                    gpgme_strerror (err));
246     sk->select_ctx = NULL;
247     gpgme_release (ctx);
248     /*gtk_clist_thaw (select_keys.clist);*/
249 }
250
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 (close_dialog), 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 void 
368 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
369 {
370     struct select_keys_s *sk = data;
371
372     g_return_if_fail (sk);
373     if (event && event->keyval == GDK_Escape) {
374         sk->okay = 0;
375         gtk_main_quit ();
376     }
377 }
378
379
380 static void 
381 select_btn_cb (GtkWidget *widget, gpointer data)
382 {
383     struct select_keys_s *sk = data;
384     int row;
385     GpgmeKey key;
386
387     g_return_if_fail (sk);
388     if (!sk->clist->selection) {
389         g_message ("** nothing selected");
390         return;
391     }
392     row = GPOINTER_TO_INT(sk->clist->selection->data);
393     key = gtk_clist_get_row_data(sk->clist, row);
394     if (key) {
395         const char *s = gpgme_key_get_string_attr (key,
396                                                    GPGME_ATTR_FPR,
397                                                    NULL, 0 );
398         if ( gpgme_key_get_ulong_attr (key, GPGME_ATTR_VALIDITY, NULL, 0 )
399              < GPGME_VALIDITY_FULL ) {
400             g_message ("** FIXME: we are faking the trust calculation");
401         }
402         if (!gpgme_recipients_add_name_with_validity (sk->rset, s,
403                                                       GPGME_VALIDITY_FULL) ) {
404             sk->okay = 1;
405             gtk_main_quit ();
406         }
407     }
408 }
409
410
411 static void 
412 cancel_btn_cb (GtkWidget *widget, gpointer data)
413 {
414     struct select_keys_s *sk = data;
415
416     g_return_if_fail (sk);
417     sk->okay = 0;
418     if (sk->select_ctx)
419         gpgme_cancel (sk->select_ctx);
420     gtk_main_quit ();
421 }
422
423
424 static void
425 other_btn_cb (GtkWidget *widget, gpointer data)
426 {
427     struct select_keys_s *sk = data;
428     char *uid;
429
430     g_return_if_fail (sk);
431     uid = input_dialog ( _("Add key"),
432                          _("Enter another user or key ID\n"),
433                          NULL );
434     if (!uid)
435         return;
436     fill_clist (sk, uid);
437     update_progress (sk, 0, sk->pattern);
438     g_free (uid);
439 }
440
441
442 static gint 
443 cmp_attr (gconstpointer pa, gconstpointer pb, GpgmeAttr attr)
444 {
445     GpgmeKey a = ((GtkCListRow *)pa)->data;
446     GpgmeKey b = ((GtkCListRow *)pb)->data;
447     const char *sa, *sb;
448     
449     sa = a? gpgme_key_get_string_attr (a, attr, NULL, 0 ) : NULL;
450     sb = b? gpgme_key_get_string_attr (b, attr, NULL, 0 ) : NULL;
451     if (!sa)
452         return !!sb;
453     if (!sb)
454         return -1;
455     return strcasecmp(sa, sb);
456 }
457
458 static gint 
459 cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb)
460 {
461     return cmp_attr (pa, pb, GPGME_ATTR_NAME);
462 }
463
464 static gint 
465 cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb)
466 {
467     return cmp_attr (pa, pb, GPGME_ATTR_EMAIL);
468 }
469
470 static void
471 sort_keys ( struct select_keys_s *sk, enum col_titles column)
472 {
473     GtkCList *clist = sk->clist;
474
475     switch (column) {
476       case COL_NAME:
477         gtk_clist_set_compare_func (clist, cmp_name);
478         break;
479       case COL_EMAIL:
480         gtk_clist_set_compare_func (clist, cmp_email);
481         break;
482       default:
483         return;
484     }
485
486     /* column clicked again: toggle as-/decending */
487     if ( sk->sort_column == column) {
488         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
489                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
490     }
491     else
492         sk->sort_type = GTK_SORT_ASCENDING;
493
494     sk->sort_column = column;
495     gtk_clist_set_sort_type (clist, sk->sort_type);
496     gtk_clist_sort (clist);
497 }
498
499 static void
500 sort_keys_name (GtkWidget *widget, gpointer data)
501 {
502     sort_keys ((struct select_keys_s*)data, COL_NAME);
503 }
504
505 static void
506 sort_keys_email (GtkWidget *widget, gpointer data)
507 {
508     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
509 }
510
511
512 #endif /*USE_GPGME*/