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