1 /* confdialog.c - GPGME based configuration dialog for GPA.
2    Copyright (C) 2007, 2008 g10 Code GmbH
3 
4    This file is part of GPA.
5 
6    GPA is free software; you can redistribute it and/or modify it
7    under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10 
11    GPA is distributed in the hope that it will be useful, but WITHOUT
12    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
14    License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
18 
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <assert.h>
26 #include <string.h>
27 #include <errno.h>
28 
29 #include <gpgme.h>
30 #include <glib.h>
31 #include <gtk/gtk.h>
32 
33 #include "i18n.h"
34 #include "gpgmetools.h"
35 #include "gtktools.h"
36 #include "options.h"
37 #include "gpa.h"
38 
39 /* Violation of GNOME standards: Cancel does not revert previous
40    apply.  We do not auto-apply or syntax check after focus
41    change.  */
42 
43 
44 /* Internal public interface.  */
45 static void hide_backend_config (void);
46 
47 
48 /* Some global variables.  */
49 
50 /* Custom response IDs.  */
51 #define CUSTOM_RESPONSE_RESET 1
52 
53 
54 /* The configuration dialog.  */
55 static GtkWidget *dialog;
56 
57 /* The notebook with one page for each component.  */
58 static gpgme_ctx_t dialog_ctx;
59 
60 /* The notebook with one page for each component.  */
61 static GtkWidget *dialog_notebook;
62 
63 /* The current configuration.  */
64 static gpgme_conf_comp_t dialog_conf;
65 
66 /* If we modified something in the current tab.  */
67 static int dialog_tab_modified;
68 
69 /* The expert level.  */
70 static gpgme_conf_level_t dialog_level;
71 
72 
73 /* We define the following behaviour for options:
74 
75    An option of alt-type NONE gets a check button, and if it has the
76    LIST flag set, also a spin button for a repeat count.  The spin
77    button is only active if the button is checked.
78 
79    An option of a different alt-type does not get a check box button
80    but a combo box and a text entry field.  If the option does have a
81    default or default description, the combo box contains an entry for
82    "Use default value", which sets the text entry field to that
83    default (and makes it insensitive?).  Otherwise the combo box
84    contains an entry for "Option not active" which makes the entry
85    field insensitive.  If the option has the OPTIONAl flag set, the
86    combo box contains an entry for "Use default argument" which sets
87    the text entry field to the no arg value or description, if any
88    (and makes it insensitive?).  In any case, the combo box contains
89    an entry for "Use custom value" which makes the text entry field
90    sensitive and editable.
91 
92    Options with the LIST flag set could get a more sophisticated
93    dialog where you have "Add" and "Remove" buttons, or a multi-line
94    entry field.  Currently, having the LIST flag and OPTIONAL flag at
95    the same time creates an ambiguity, and entering commas in values
96    is not supported.  */
97 typedef enum
98   {
99     OPTION_SIMPLE,
100     OPTION_SPIN,
101     OPTION_ENTRY,
102     OPTION_OPT_ENTRY
103   } option_type_t;
104 
105 typedef enum
106   {
107     COMBO_UNDEF = -1,
108     COMBO_DEFAULT = 0,
109     COMBO_CUSTOM = 1,
110     COMBO_NO_ARG = 2
111   } option_combo_t;
112 
113 
114 /* This structure combines an option with its GUI elements.  */
115 typedef struct option_widget_s
116 {
117   /* The option.  */
118   gpgme_conf_opt_t option;
119 
120   /* We remember the type of the option to make life easier on us.  */
121   option_type_t type;
122 
123   /* The check button (or, in the case of COMBO being not NULL, the
124      label) widget.  */
125   GtkWidget *check;
126 
127   /* The entry or spin button widget.  */
128   GtkWidget *widget;
129 
130   /* The current user setting in string representation.  This defaults
131      to the last input by the user, followed by the current setting,
132      followed by the default.  */
133   char *saved_value;
134 
135   /* This helps to know about state changes.  */
136   int old_combo_box_state;
137 
138   /* The combo box, if any.  Only used for OPTION_ENTRY and
139      OPTION_OPT_ENTRY.  The combo has
140      COMBO_DEFAULT = 0: Use default value or Do not use option.
141      COMBO_CUSTOM = 1: Use custom value
142      COMBO_NO_ARG = 2: Use default argument (for OPTION_OPT_ENTRY).  */
143   GtkWidget *combo;
144 } *option_widget_t;
145 
146 
147 #if 0
148 /* Not needed anymore, as all stock actions work on all tabs.
149    However, might come in handy at some point.  */
150 
151 static gpgme_conf_comp_t
152 dialog_current_comp (void)
153 {
154   gpgme_conf_comp_t comp = dialog_conf;
155   int page = gtk_notebook_get_current_page (GTK_NOTEBOOK (dialog_notebook));
156   const char *label;
157 
158   /* Iterate over all options in the current tab, and reset them.  */
159 
160   label = gtk_notebook_get_tab_label_text
161     (GTK_NOTEBOOK (dialog_notebook),
162      gtk_notebook_get_nth_page (GTK_NOTEBOOK (dialog_notebook), page));
163 
164   while (comp)
165     {
166       if (! strcmp (label, comp->description))
167 	break;
168       comp = comp->next;
169     }
170 
171   assert (comp);
172   return comp;
173 }
174 #endif
175 
176 
177 /* Convert an argument value to a string representation.  Lists are
178    converted to comma-separated values, empty strings in lists are
179    surrounded by double-quotes.  */
180 static char *
arg_to_str(gpgme_conf_arg_t arg,gpgme_conf_type_t type)181 arg_to_str (gpgme_conf_arg_t arg, gpgme_conf_type_t type)
182 {
183   static char *result;
184   char *new_result = NULL;
185 
186   if (result)
187     {
188       g_free (result);
189       result = NULL;
190     }
191 
192   while (arg)
193     {
194       if (result)
195 	{
196 	  new_result = g_strdup_printf ("%s,", result);
197 	  g_free (result);
198 	  result = new_result;
199 	}
200       else
201 	result = g_strdup ("");
202 
203       if (!arg->no_arg)
204         {
205           switch (type)
206             {
207             case GPGME_CONF_NONE:
208             case GPGME_CONF_UINT32:
209               new_result = g_strdup_printf ("%s%u", result, arg->value.uint32);
210               g_free (result);
211               result = new_result;
212               break;
213 
214             case GPGME_CONF_INT32:
215               new_result = g_strdup_printf ("%s%i", result, arg->value.int32);
216               g_free (result);
217               result = new_result;
218               break;
219 
220             case GPGME_CONF_STRING:
221             case GPGME_CONF_PATHNAME:
222             case GPGME_CONF_LDAP_SERVER:
223               new_result = g_strdup_printf ("%s%s", result, arg->value.string);
224               g_free (result);
225               result = new_result;
226               break;
227 
228             default:
229               assert (!"Not supported.");
230               break;
231             }
232         }
233       arg = arg->next;
234     }
235   return result;
236 }
237 
238 
239 /* Extract the argument from a widget.  */
240 static gpgme_conf_arg_t
option_widget_to_arg(option_widget_t opt)241 option_widget_to_arg (option_widget_t opt)
242 {
243   gpgme_error_t err;
244   gpgme_conf_opt_t option = opt->option;
245   gpgme_conf_arg_t arg = NULL;
246 
247   if (opt->type == OPTION_SIMPLE || opt->type == OPTION_SPIN)
248     {
249       int active;
250       unsigned int count = 1;
251 
252       active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (opt->check));
253       if (!active)
254 	return NULL;
255 
256       if (opt->type == OPTION_SPIN)
257 	count = gtk_spin_button_get_value (GTK_SPIN_BUTTON (opt->widget));
258 
259       err = gpgme_conf_arg_new (&arg, option->alt_type, &count);
260       if (err)
261 	gpa_gpgme_error (err);
262 
263       return arg;
264     }
265   else
266     {
267       int combo;
268       gpgme_conf_arg_t *argp;
269       char *val;
270       char *valp;
271 
272       combo = gtk_combo_box_get_active (GTK_COMBO_BOX (opt->combo));
273       if (combo == COMBO_DEFAULT)
274 	return NULL;
275 
276       if (combo == COMBO_NO_ARG)
277 	{
278 	  /* A single no-arg option.  */
279 	  err = gpgme_conf_arg_new (&arg, option->alt_type, NULL);
280 	  if (err)
281 	    gpa_gpgme_error (err);
282 
283 	  return arg;
284 	}
285 
286       argp = &arg;
287 
288       val = g_strdup (gtk_entry_get_text (GTK_ENTRY (opt->widget)));
289       valp = val;
290       do
291 	{
292 	  while (*valp == ' ' || *valp == '\t')
293 	    valp++;
294 
295 	  if (*valp == ',' || *valp == '\0')
296 	    {
297 	      if (option->flags & GPGME_CONF_OPTIONAL)
298 		err = gpgme_conf_arg_new (argp, option->alt_type, NULL);
299 	      else
300 		/* This seems a useful default to make things simpler
301 		   for the user, as the OPTIONAL flag is not much
302 		   used.  */
303 		err = gpgme_conf_arg_new (argp, option->alt_type, "");
304 
305 	      if (err)
306 		gpa_gpgme_error (err);
307 
308 	      argp = &(*argp)->next;
309 
310 	      /* Skip comma.  */
311 	      if (*valp)
312 		valp++;
313 	    }
314 	  else
315 	    {
316 	      char *str = valp;
317 	      char *strend = valp;
318 	      int done = 0;
319 	      int in_quote = 0;
320 
321 	      /* Remove quoting.  */
322 	      do
323 		{
324 		  if (in_quote)
325 		    {
326 		      if (*valp == '\0')
327 			done = 1;
328 		      else if (*valp == '"')
329 			{
330 			  in_quote = 0;
331 			  valp++;
332 			}
333 		      else
334 			*(strend++) = *(valp++);
335 		    }
336 		  else
337 		    {
338 		      if (*valp == ',' || *valp == '\0')
339 			done = 1;
340 		      else if (*valp == '"')
341 			{
342 			  in_quote = 1;
343 			  valp++;
344 			}
345 		      else
346 			*(strend++) = *(valp++);
347 		    }
348 		}
349 	      while (! done);
350 
351 	      if (*valp == ',')
352 		valp++;
353 
354 	      /* Find end of the string.  */
355 	      while (strend > str && (*strend == ' ' || *strend == '\t'))
356 		strend--;
357 
358 	      *strend = '\0';
359 
360 	      switch (option->alt_type)
361 		{
362 		case GPGME_CONF_NONE:
363 		case GPGME_CONF_UINT32:
364 		  {
365 		    unsigned int nr;
366 		    nr = strtoul (str, NULL, 0);
367 		    err = gpgme_conf_arg_new (argp, option->alt_type, &nr);
368 		    if (err)
369 		      gpa_gpgme_error (err);
370 
371 		    argp = &(*argp)->next;
372 		  }
373 		  break;
374 
375 		case GPGME_CONF_INT32:
376 		  {
377 		    int nr;
378 		    nr = strtoul (str, NULL, 0);
379 		    err = gpgme_conf_arg_new (argp, option->alt_type, &nr);
380 		    if (err)
381 		      gpa_gpgme_error (err);
382 
383 		    argp = &(*argp)->next;
384 		  }
385 		  break;
386 
387 		case GPGME_CONF_STRING:
388 		case GPGME_CONF_LDAP_SERVER:
389 		case GPGME_CONF_PATHNAME:
390 		  {
391 		    err = gpgme_conf_arg_new (argp, option->alt_type, str);
392 		    if (err)
393 		      gpa_gpgme_error (err);
394 
395 		    argp = &(*argp)->next;
396 		  }
397 		  break;
398 
399 		default:
400 		  assert (!"Not supported.");
401 		  break;
402 		}
403 	    }
404 	}
405       while (*valp);
406 
407       g_free (val);
408       return arg;
409     }
410 }
411 
412 
413 /* Compare two arguments and returns true if they are equal.  */
414 static int
args_are_equal(gpgme_conf_arg_t arg1,gpgme_conf_arg_t arg2,gpgme_conf_type_t type)415 args_are_equal (gpgme_conf_arg_t arg1, gpgme_conf_arg_t arg2,
416 		gpgme_conf_type_t type)
417 {
418   while (arg1 && arg2)
419     {
420       if ((!arg1->no_arg ^ !arg2->no_arg))
421         return 0;
422       if (!arg1->no_arg)
423         {
424           switch (type)
425             {
426             case GPGME_CONF_NONE:
427             case GPGME_CONF_UINT32:
428               if (arg1->value.uint32 != arg2->value.uint32)
429                 return 0;
430               break;
431 
432             case GPGME_CONF_INT32:
433               if (arg1->value.int32 != arg2->value.int32)
434                 return 0;
435               break;
436 
437             case GPGME_CONF_STRING:
438             case GPGME_CONF_LDAP_SERVER:
439             case GPGME_CONF_PATHNAME:
440               if (strcmp (arg1->value.string, arg2->value.string))
441                 return 0;
442               break;
443 
444             default:
445               assert (!"Not supported.");
446               break;
447             }
448         }
449 
450       arg1 = arg1->next;
451       arg2 = arg2->next;
452     }
453   if (arg1 || arg2)
454     return 0;
455   return 1;
456 }
457 
458 
459 /* Commit all the changes in component COMP.  */
460 static void
save_options(gpgme_conf_comp_t comp)461 save_options (gpgme_conf_comp_t comp)
462 {
463   gpgme_conf_opt_t option = comp->options;
464   gpgme_error_t err;
465   int changed;
466 
467   changed = 0;
468   while (option)
469     {
470       option_widget_t opt = option->user_data;
471       gpgme_conf_arg_t arg;
472 
473       /* Exclude group headers etc.  */
474       if (!opt)
475 	{
476 	  option = option->next;
477 	  continue;
478 	}
479 
480       arg = option_widget_to_arg (opt);
481       if (! args_are_equal (arg, option->value, option->alt_type))
482 	{
483 	  err = gpgme_conf_opt_change (option, 0, arg);
484 	  if (err)
485 	    gpa_gpgme_error (err);
486 	  changed++;
487 
488 	  /* FIXME: Disable this.  */
489 	  g_debug ("Changing component %s, option %s",
490 		   comp->name, option->name);
491 	}
492 
493       option = option->next;
494     }
495 
496   if (changed)
497     {
498       err = gpgme_op_conf_save (dialog_ctx, comp);
499       if (err)
500 	gpa_gpgme_warning (err);
501     }
502 }
503 
504 
505 static void
save_all_options(void)506 save_all_options (void)
507 {
508   gpgme_conf_comp_t comp;
509 
510   /* Save all tabs.  */
511   comp = dialog_conf;
512   while (comp)
513     {
514       save_options (comp);
515       comp = comp->next;
516     }
517 }
518 
519 
520 /* Update user-changes to the option widget OPT.  */
521 static void
update_option(option_widget_t opt)522 update_option (option_widget_t opt)
523 {
524   gpgme_conf_opt_t option;
525 
526   option = opt->option;
527 
528   if (opt->type == OPTION_SPIN)
529     {
530       int active;
531 
532       active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (opt->check));
533 
534       gtk_widget_set_sensitive (opt->widget, active);
535     }
536   else if (opt->type == OPTION_ENTRY || opt->type == OPTION_OPT_ENTRY)
537     {
538       int combo;
539       GtkWidget *entry = opt->widget;
540 
541       combo = gtk_combo_box_get_active (GTK_COMBO_BOX (opt->combo));
542 
543       if (opt->old_combo_box_state == COMBO_UNDEF)
544 	{
545 	  /* Initialization.  */
546 	  if (option->value)
547 	    {
548 	      /* A single no-arg argument can be represented directly
549 		 in the combo box.  Otherwise, we use a custom
550 		 argument and comma-separated lists.  */
551 	      if (option->value->no_arg && !option->value->next)
552 		combo = COMBO_NO_ARG;
553 	      else
554 		combo = COMBO_CUSTOM;
555 	    }
556 	  else
557 	    combo = COMBO_DEFAULT;
558 	  gtk_combo_box_set_active (GTK_COMBO_BOX (opt->combo), combo);
559 	}
560       else if (combo == opt->old_combo_box_state)
561 	return;
562       else if (opt->old_combo_box_state == COMBO_CUSTOM)
563 	{
564 	  if (opt->saved_value)
565 	    g_free (opt->saved_value);
566 	  opt->saved_value = g_strdup
567 	    (gtk_entry_get_text (GTK_ENTRY (opt->widget)));
568 	}
569 
570       if (combo == COMBO_DEFAULT)
571 	{
572 	  if (option->default_value)
573 	    gtk_entry_set_text (GTK_ENTRY (entry),
574 				arg_to_str (option->default_value,
575 					    option->alt_type));
576 	  else if (option->default_description)
577 	    gtk_entry_set_text (GTK_ENTRY (entry),
578 				option->default_description);
579 	  else
580 	    gtk_entry_set_text (GTK_ENTRY (entry), "");
581 
582 	  gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE);
583 	  gtk_widget_set_sensitive (entry, FALSE);
584 	}
585       else if (combo == COMBO_NO_ARG)
586 	{
587 	  if (option->no_arg_value)
588 	    gtk_entry_set_text (GTK_ENTRY (entry),
589 				arg_to_str (option->no_arg_value,
590 					    option->alt_type));
591 	  else if (option->no_arg_description)
592 	    gtk_entry_set_text (GTK_ENTRY (entry),
593 				option->no_arg_description);
594 	  else
595 	    gtk_entry_set_text (GTK_ENTRY (entry), "");
596 
597 	  gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE);
598 	  gtk_widget_set_sensitive (entry, FALSE);
599 	  /* FIXME: Change focus.  */
600 	}
601       else if (combo == COMBO_CUSTOM)
602 	{
603 	  if (opt->saved_value)
604 	    gtk_entry_set_text (GTK_ENTRY (entry), opt->saved_value);
605 	  else if (option->value)
606 	    gtk_entry_set_text (GTK_ENTRY (entry),
607 				arg_to_str (option->value,
608 					    option->alt_type));
609 	  else if (option->default_value)
610 	    {
611 	      gtk_entry_set_text (GTK_ENTRY (entry),
612 				  arg_to_str (option->default_value,
613 					      option->alt_type));
614 	      /* A default (rather than current) value is selected
615 		 initially.  */
616 	      gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
617 	    }
618 	  else
619 	    gtk_entry_set_text (GTK_ENTRY (entry), "");
620 
621 	  gtk_editable_set_editable (GTK_EDITABLE (entry), TRUE);
622 	  gtk_widget_set_sensitive (entry, TRUE);
623 	  /* FIXME: Change focus.  */
624 	}
625 
626       opt->old_combo_box_state = combo;
627     }
628 }
629 
630 
631 /* Update the tab when it is modified (if modified is true) or reset
632    it (else).  */
633 static void
update_modified(int modified)634 update_modified (int modified)
635 {
636   if (modified)
637     {
638       /* Run with every button click, so keep it short.  */
639       if (!dialog_tab_modified)
640 	{
641 	  dialog_tab_modified = 1;
642 	  gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
643 					     GTK_RESPONSE_APPLY, TRUE);
644 	  gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
645 					     CUSTOM_RESPONSE_RESET, TRUE);
646 	}
647     }
648   else
649     {
650       /* This is also called at initialization time.  */
651 
652       dialog_tab_modified = 0;
653       gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
654 					 GTK_RESPONSE_APPLY, FALSE);
655       gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
656 					     CUSTOM_RESPONSE_RESET, FALSE);
657     }
658 }
659 
660 
661 static void
update_modified_cb(void * dummy)662 update_modified_cb (void *dummy)
663 {
664   update_modified (1);
665 }
666 
667 
668 /* Update a toggled checkbox button.  */
669 static void
option_checkbutton_toggled(GtkToggleButton * button,gpointer data)670 option_checkbutton_toggled (GtkToggleButton *button, gpointer data)
671 {
672   option_widget_t opt = data;
673 
674   update_option (opt);
675   update_modified (1);
676 }
677 
678 
679 /* Update a toggled combo box.  */
680 static void
option_combobox_changed(GtkComboBox * combo,gpointer data)681 option_combobox_changed (GtkComboBox *combo, gpointer data)
682 {
683   option_widget_t opt = data;
684 
685   update_option (opt);
686   update_modified (1);
687 }
688 
689 
690 static void create_dialog_tabs (void);
691 
692 
693 /* Handle stock response "apply".  */
694 static void
dialog_response_apply(GtkDialog * dummy)695 dialog_response_apply (GtkDialog *dummy)
696 {
697   save_all_options ();
698 
699   /* Reload configuration.  */
700   create_dialog_tabs ();
701 }
702 
703 
704 /* Soft reset which reuses the configuration at last load.  */
705 static void
reset_options(gpgme_conf_comp_t comp)706 reset_options (gpgme_conf_comp_t comp)
707 {
708   gpgme_conf_opt_t option;
709 
710   option = comp->options;
711 
712   while (option)
713     {
714       option_widget_t opt = option->user_data;
715 
716       if (!opt)
717 	{
718 	  /* Exclude group headers etc.  */
719 	  option = option->next;
720 	  continue;
721 	}
722 
723       if (opt->type == OPTION_SIMPLE || opt->type == OPTION_SPIN)
724 	{
725 	  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (opt->check),
726 					!!option->value);
727 	}
728       else if (opt->type == OPTION_ENTRY || opt->type == OPTION_OPT_ENTRY)
729 	{
730 	  /* Force re-initialization.  */
731 	  opt->old_combo_box_state = COMBO_UNDEF;
732 	  update_option (opt);
733 	}
734 
735     option = option->next;
736   }
737 }
738 
739 
740 static void
reset_all_options(void)741 reset_all_options (void)
742 {
743   gpgme_conf_comp_t comp;
744 
745   /* Save all tabs.  */
746   comp = dialog_conf;
747   while (comp)
748     {
749       reset_options (comp);
750       comp = comp->next;
751     }
752 }
753 
754 
755 /* Handle stock response "reset".  */
756 static void
dialog_response_reset(GtkDialog * dummy)757 dialog_response_reset (GtkDialog *dummy)
758 {
759 #if 0
760   /* Allow to use the reset button also as a refresh button.  Note:
761      This is kind of awkward, because the reset button is only
762      sensitive if local modifications exist.  Currently, we require a
763      dialog cancel/reopen cycle or an expert level change for a "hard"
764      reset.  */
765   create_dialog_tabs ();
766 #else
767   /* Soft reset, using last loaded configuration.  Much faster, and
768      consistent with sensitivity of Reset button.  */
769   reset_all_options ();
770   update_modified (0);
771 #endif
772 }
773 
774 
775 /* Handle stock responses like OK, apply and cancel.  */
776 static int
dialog_response(GtkDialog * dlg,gint response,gpointer data)777 dialog_response (GtkDialog *dlg, gint response, gpointer data)
778 {
779   switch (response)
780     {
781     case GTK_RESPONSE_ACCEPT:
782     case GTK_RESPONSE_YES:
783       save_all_options ();
784       hide_backend_config ();
785       break;
786 
787     case GTK_RESPONSE_CANCEL:
788     case GTK_RESPONSE_CLOSE:
789     case GTK_RESPONSE_REJECT:
790     case GTK_RESPONSE_NO:
791     case GTK_RESPONSE_DELETE_EVENT:
792       hide_backend_config ();
793       break;
794 
795     case GTK_RESPONSE_APPLY:
796       dialog_response_apply (dlg);
797       break;
798 
799     case CUSTOM_RESPONSE_RESET:
800       dialog_response_reset (dlg);
801       break;
802 
803     default:
804       g_warning ("unhandled response: %i", response);
805       /* Do whatever is the default.  */
806       return FALSE;
807     }
808 
809   /* Don't do anything.  We handled it all.  */
810   return TRUE;
811 }
812 
813 
814 /* Determine the width of a checkbox.  We use this to align labels
815    with and without checkboxes vertically.  This approach is not
816    proper, as Gtk+ can not automatically adjust the padding when the
817    theme switches, but it is simple.  */
818 static gint
get_checkbox_width(void)819 get_checkbox_width (void)
820 {
821   GtkWidget *checkbox;
822   GtkRequisition checkbox_size;
823 
824   /* We do not save the result, as it may change with a theme change.
825      In this case we will at least refresh eventually.  */
826 
827   checkbox = gtk_check_button_new ();
828   gtk_widget_size_request (checkbox, &checkbox_size);
829   gtk_widget_destroy (checkbox);
830 
831   return checkbox_size.width;
832 }
833 
834 
835 /* A callback to be used with gtk_container_foreach, which removes each
836    widget unconditionally.  */
837 static void
remove_from_container(GtkWidget * widget,gpointer data)838 remove_from_container (GtkWidget *widget, gpointer data)
839 {
840   GtkWidget *container = data;
841 
842   gtk_container_remove (GTK_CONTAINER (container), widget);
843 }
844 
845 
846 /* Return true iff the component COMP has any displayed options.  */
847 static gboolean
comp_has_options(gpgme_conf_comp_t comp)848 comp_has_options (gpgme_conf_comp_t comp)
849 {
850   gpgme_conf_opt_t option;
851   gboolean has_options;
852 
853   /* Skip over all components that do not have any options.  This can
854      happen for example with old installed versions of components, or
855      if there are only options with a higher expert level.  */
856   has_options = FALSE;
857   option = comp->options;
858   while (option)
859     {
860       if (option->level <= dialog_level)
861 	{
862 	  has_options = TRUE;
863 	  break;
864 	}
865       option = option->next;
866     }
867 
868   return has_options;
869 }
870 
871 
872 /* Return true iff the group GROUP has any displayed options.  If the
873    result is FALSE, also set NEXT_GROUP to the start of the next
874    group, or NULL if there is no more group.  */
875 static gboolean
group_has_options(gpgme_conf_opt_t option,gpgme_conf_opt_t * next_group)876 group_has_options (gpgme_conf_opt_t option, gpgme_conf_opt_t *next_group)
877 {
878   gboolean has_options;
879 
880   /* Skip the group header.  */
881   option = option->next;
882 
883   has_options = FALSE;
884   while (option && ! (option->flags & GPGME_CONF_GROUP))
885     {
886       if (option->level <= dialog_level)
887 	{
888 	  has_options = TRUE;
889 	  break;
890 	}
891       option = option->next;
892     }
893 
894   if (! has_options && next_group)
895     *next_group = option;
896 
897   return has_options;
898 }
899 
900 
901 static void
create_dialog_tabs_2(gpgme_conf_comp_t old_conf,gpgme_conf_comp_t new_conf)902 create_dialog_tabs_2 (gpgme_conf_comp_t old_conf, gpgme_conf_comp_t new_conf)
903 {
904   gpgme_conf_comp_t comp;
905   gpgme_conf_comp_t comp_alt;
906   int reset;
907   int page_nr;
908   gint checkbox_width = get_checkbox_width ();
909   /* We keep size groups for the field elements across all tabs for
910      visual consistency.  */
911   GtkSizeGroup *size_group[2];
912 
913   size_group[0] = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
914   size_group[1] = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
915 
916   /* In most cases, the new components are the same as the old
917      components.  We catch this special case here, and if it is true,
918      we reuse the existing tabs in the notebook below.  Otherwise, we
919      reset everything.  */
920   reset = 0;
921   comp = old_conf;
922   comp_alt = new_conf;
923   while (comp && comp_alt)
924     {
925       /* A mismatch is found if either the description changes, or if
926 	 a component changes from no options to some options or vice
927 	 verse (as we suppress generating tabs for components without
928 	 options below).  */
929 
930       if (strcmp (comp->description, comp_alt->description)
931 	  || comp_has_options (comp) != comp_has_options (comp_alt))
932 	break;
933 
934       comp = comp->next;
935       comp_alt = comp_alt->next;
936     }
937   if (comp || comp_alt)
938     reset = 1;
939 
940   if (reset)
941     {
942       gtk_widget_hide (dialog_notebook);
943 
944       /* Remove the current tabs.  */
945       gtk_container_foreach (GTK_CONTAINER (dialog_notebook),
946 			     &remove_from_container, dialog_notebook);
947     }
948 
949   comp = new_conf;
950   page_nr = 0;
951 
952   while (comp)
953     {
954       GtkWidget *label;
955       GtkWidget *page;
956       gpgme_conf_opt_t option;
957       /* For each group in the component, we keep track of a frame,
958 	 and the table inside the frame.  */
959       GtkWidget *frame = NULL;
960       GtkWidget *frame_vbox = NULL;
961 
962       /* Skip over all components that do not have any options.  This
963 	 can happen for example with old installed versions of
964 	 components, or if there are only options with a higher expert
965 	 level.  */
966       if (! comp_has_options (comp))
967 	{
968 	  comp = comp->next;
969 	  continue;
970 	}
971 
972       if (reset)
973 	{
974 	  char *description;
975 	  /* FIXME: Might need to put pages into scrolled panes if
976 	     there are too many.  */
977 	  page = gtk_vbox_new (FALSE, 0);
978 	  gtk_container_add (GTK_CONTAINER (dialog_notebook), page);
979 
980 	  description = xstrdup (comp->description);
981 	  percent_unescape (description, 0);
982 	  label = gtk_label_new (description);
983 	  xfree (description);
984 	  gtk_notebook_set_tab_label
985 	    (GTK_NOTEBOOK (dialog_notebook),
986 	     gtk_notebook_get_nth_page (GTK_NOTEBOOK (dialog_notebook),
987 					page_nr),
988 	     label);
989 	}
990       else
991 	{
992 	  page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (dialog_notebook),
993 					    page_nr);
994 
995 	  gtk_container_foreach (GTK_CONTAINER (page),
996 				 &remove_from_container, page);
997 	}
998 
999       option = comp->options;
1000 	  /* All ungrouped options come first.  We put them in a frame
1001 	     titled "Main", just so we have a consistent layout.  */
1002       if (option && ! (option->flags & GPGME_CONF_GROUP))
1003 	{
1004 	  frame = gtk_frame_new (NULL);
1005 	  gtk_box_pack_start (GTK_BOX (page), frame, TRUE, TRUE, 0);
1006 	  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
1007 
1008 	  label = gtk_label_new (_("<b>Main</b>"));
1009 	  gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1010 	  gtk_frame_set_label_widget (GTK_FRAME (frame), label);
1011 
1012 	  frame_vbox = gtk_vbox_new (FALSE, 0);
1013 	  gtk_container_add (GTK_CONTAINER (frame), frame_vbox);
1014 	}
1015 
1016       while (option)
1017 	{
1018 	  if (option->flags & GPGME_CONF_GROUP)
1019 	    {
1020 	      char *name;
1021 	      const char *title;
1022 
1023 	      if (! group_has_options (option, &option))
1024 		continue;
1025 
1026 	      frame = gtk_frame_new (NULL);
1027 	      gtk_box_pack_start (GTK_BOX (page), frame, FALSE, FALSE, 0);
1028 	      gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
1029 
1030 	      /* For i18n reasons we use the description.  It might be
1031                  better to add a new field to privide a localized
1032                  version of the Group name.  Maybe the argname can be
1033                  used for it.  AFAICS, we would only need to prefix
1034                  the description with the group name and gpgconf would
1035                  instantly privide that. */
1036 	      title = (option->argname && *option->argname)?
1037 			option->argname : option->description;
1038 	      name = g_strdup_printf ("<b>%s</b>", title);
1039 	      percent_unescape (name, 0);
1040 	      label = gtk_label_new (name);
1041 	      xfree (name);
1042 
1043 	      gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1044 	      gtk_frame_set_label_widget (GTK_FRAME (frame), label);
1045 
1046 	      frame_vbox = gtk_vbox_new (FALSE, 0);
1047 	      gtk_container_add (GTK_CONTAINER (frame), frame_vbox);
1048 	    }
1049 	  else if (option->level <= dialog_level)
1050 	    {
1051 	      GtkWidget *vbox;
1052 	      GtkWidget *hbox;
1053 	      option_widget_t opt;
1054 	      GtkWidget *widget;
1055 	      GtkWidget *entry;
1056 
1057 	      opt = g_new0 (struct option_widget_s, 1);
1058 	      opt->option = option;
1059 	      option->user_data = opt;
1060 
1061 	      /* Add the entry fields for the option, depending on its
1062 		 type and flags.  */
1063 	      if (option->alt_type == GPGME_CONF_NONE
1064 		  && !(option->flags & GPGME_CONF_LIST))
1065 		opt->type = OPTION_SIMPLE;
1066 	      else if (option->alt_type == GPGME_CONF_NONE
1067 		       && (option->flags & GPGME_CONF_LIST))
1068 		opt->type = OPTION_SPIN;
1069 	      else if (option->flags & GPGME_CONF_OPTIONAL
1070 		       && !(option->flags & GPGME_CONF_LIST))
1071 		opt->type = OPTION_OPT_ENTRY;
1072 	      else
1073 		opt->type = OPTION_ENTRY;
1074 
1075 	      /* We create a vbox for the option, in case we want to
1076 		 support multi-hbox setups for options.  Currently we
1077 		 only add one hbox to that vbox though.  */
1078 
1079 	      vbox = gtk_vbox_new (FALSE, 0);
1080 	      gtk_box_pack_start (GTK_BOX (frame_vbox), vbox, TRUE, TRUE, 0);
1081 
1082 	      hbox = gtk_hbox_new (FALSE, 0);
1083 	      gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
1084 
1085 	      /* Add the entry fields for the option, depending on its
1086 		 type and flags.  */
1087 	      if (opt->type == OPTION_SIMPLE)
1088 		{
1089 		  widget = gtk_check_button_new_with_label (option->name);
1090 		  opt->check = widget;
1091 		  gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
1092 
1093 		  /* Add the checkbox label to the size group.  */
1094 		  gtk_size_group_add_widget (GTK_SIZE_GROUP (size_group[0]),
1095 					     widget);
1096 		  if (option->value)
1097 		    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
1098 						  TRUE);
1099 		}
1100 	      else if (opt->type == OPTION_SPIN)
1101 		{
1102 		  widget = gtk_check_button_new_with_label (option->name);
1103 		  opt->check = widget;
1104 		  gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
1105 
1106 		  /* Add the checkbox label to the size group.  */
1107 		  gtk_size_group_add_widget (GTK_SIZE_GROUP (size_group[0]),
1108 					     widget);
1109 		  if (option->value)
1110 		    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
1111 						  TRUE);
1112 
1113 		  /* For list options without arguments, we use a
1114 		     simple spin button to capture the repeat
1115 		     count.  */
1116 		  widget = gtk_spin_button_new_with_range (1, 100, 1);
1117 		  opt->widget = widget;
1118 
1119 		  if (option->value)
1120 		    gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget),
1121 					       option->value->value.count);
1122 
1123 		  g_signal_connect ((gpointer) widget, "value-changed",
1124 				    G_CALLBACK (update_modified_cb), NULL);
1125 
1126 		  gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
1127 		}
1128 	      else
1129 		{
1130 		  GtkWidget *align;
1131 		  guint pt, pb, pl, pr;
1132 
1133 		  align = gtk_alignment_new (0, 0.5, 0, 0);
1134 		  gtk_alignment_get_padding (GTK_ALIGNMENT (align),
1135 					     &pt, &pb, &pl, &pr);
1136 		  gtk_alignment_set_padding (GTK_ALIGNMENT (align), pt, pb,
1137 					     pl + checkbox_width, pr);
1138 		  gtk_box_pack_start (GTK_BOX (hbox), align, FALSE, FALSE, 0);
1139 
1140 		  widget = gtk_label_new (option->name);
1141 		  opt->check = widget;
1142 		  gtk_container_add (GTK_CONTAINER (align), widget);
1143 
1144 		  /* Add the checkbox label to the size group.  */
1145 		  gtk_size_group_add_widget (GTK_SIZE_GROUP (size_group[0]),
1146 					     align);
1147 
1148 		  widget = gtk_combo_box_new_text ();
1149 		  gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
1150 		  gtk_size_group_add_widget (GTK_SIZE_GROUP (size_group[1]),
1151 					     widget);
1152 		  opt->combo = widget;
1153 
1154 		  entry = gtk_entry_new ();
1155 		  gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
1156 
1157 		  opt->widget = entry;
1158 
1159 		  g_signal_connect ((gpointer) entry, "changed",
1160 				    G_CALLBACK (update_modified_cb), NULL);
1161 
1162 		  if (option->flags & GPGME_CONF_DEFAULT
1163 		      || option->flags & GPGME_CONF_DEFAULT_DESC)
1164 		    gtk_combo_box_append_text
1165 		      (GTK_COMBO_BOX (widget),
1166 		       (option->flags & GPGME_CONF_LIST) ?
1167 		       _("Use default values") : _("Use default value"));
1168 		  else
1169 		    gtk_combo_box_append_text
1170 		      (GTK_COMBO_BOX (widget), _("Do not use option"));
1171 
1172 		  gtk_combo_box_append_text
1173 		    (GTK_COMBO_BOX (widget),
1174 		     (option->flags & GPGME_CONF_LIST) ?
1175 		     _("Use custom values") : _("Use custom value"));
1176 
1177 		  if (opt->type == OPTION_OPT_ENTRY)
1178 		    gtk_combo_box_append_text
1179 		      (GTK_COMBO_BOX (widget), _("Use default argument"));
1180 		}
1181 
1182 	      /* Force update.  */
1183 	      opt->old_combo_box_state = COMBO_UNDEF;
1184 	      update_option (opt);
1185 
1186 	      if (opt->combo)
1187 		g_signal_connect ((gpointer) opt->combo, "changed",
1188 				  G_CALLBACK (option_combobox_changed),
1189 				  opt);
1190 	      else
1191 		g_signal_connect ((gpointer) opt->check, "toggled",
1192 				  G_CALLBACK (option_checkbutton_toggled),
1193 				  opt);
1194 
1195 	      /* Grey out options which can not be changed at all.  */
1196 	      if (option->flags & GPGME_CONF_NO_CHANGE)
1197 		gtk_widget_set_sensitive (vbox, FALSE);
1198 
1199 #if GTK_CHECK_VERSION (2, 12, 0)
1200 	      /* Add a tooltip description.  */
1201 	      if (option->description)
1202 		{
1203 		  char *description = xstrdup (option->description);
1204 
1205 	          percent_unescape (description, 0);
1206 		  gtk_widget_set_tooltip_text (vbox, description);
1207 		  xfree (description);
1208 		}
1209 #endif
1210 	    }
1211 	  option = option->next;
1212 	}
1213       page_nr++;
1214       comp = comp->next;
1215     }
1216 
1217   /* Release our references to the size groups.  */
1218   g_object_unref (size_group[0]);
1219   g_object_unref (size_group[1]);
1220 
1221   gtk_widget_show_all (dialog_notebook);
1222 
1223   update_modified (0);
1224 }
1225 
1226 
1227 static void
create_dialog_tabs(void)1228 create_dialog_tabs (void)
1229 {
1230   gpgme_error_t err;
1231   gpgme_conf_comp_t new_conf;
1232   int page;
1233   int nr_pages;
1234   char *current_tab = NULL;
1235 
1236   if (dialog_notebook)
1237     {
1238       /* Remember the current tab by its label.  */
1239       page = gtk_notebook_get_current_page (GTK_NOTEBOOK (dialog_notebook));
1240       if (page >= 0)
1241 	current_tab = strdup (gtk_notebook_get_tab_label_text
1242 			      (GTK_NOTEBOOK (dialog_notebook),
1243 			       gtk_notebook_get_nth_page
1244 			       (GTK_NOTEBOOK (dialog_notebook), page)));
1245     }
1246 
1247   err = gpgme_op_conf_load (dialog_ctx, &new_conf);
1248   if (err)
1249     {
1250       gpa_gpgme_warning (err);
1251       return;
1252     }
1253 
1254   create_dialog_tabs_2 (dialog_conf, new_conf);
1255   gpgme_conf_release (dialog_conf);
1256   dialog_conf = new_conf;
1257 
1258   if (current_tab)
1259     {
1260       /* Rediscover the current tab.  */
1261       page = 0;
1262       nr_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (dialog_notebook));
1263       while (page < nr_pages)
1264 	{
1265 	  const char *label = gtk_notebook_get_tab_label_text
1266 	    (GTK_NOTEBOOK (dialog_notebook),
1267 	     gtk_notebook_get_nth_page (GTK_NOTEBOOK (dialog_notebook), page));
1268 	  if (! strcmp (label, current_tab))
1269 	    break;
1270 	  page++;
1271 	}
1272       if (page < nr_pages)
1273 	gtk_notebook_set_current_page (GTK_NOTEBOOK (dialog_notebook), page);
1274 
1275       free (current_tab);
1276     }
1277 }
1278 
1279 
1280 static void
dialog_level_chooser_cb(GtkComboBox * level_chooser,gpointer * data)1281 dialog_level_chooser_cb (GtkComboBox *level_chooser, gpointer *data)
1282 {
1283   gpgme_conf_level_t level;
1284 
1285   level = gtk_combo_box_get_active (GTK_COMBO_BOX (level_chooser));
1286 
1287   if (level == dialog_level)
1288     return;
1289 
1290   if (dialog_tab_modified)
1291     {
1292       GtkWidget *window;
1293       GtkWidget *hbox;
1294       GtkWidget *labelMessage;
1295       GtkWidget *pixmap;
1296       gint result;
1297 
1298       window = gtk_dialog_new_with_buttons
1299 	(_("GPA Message"), (GtkWindow *) dialog, GTK_DIALOG_MODAL,
1300 	 GTK_STOCK_APPLY, GTK_RESPONSE_APPLY,
1301 	 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
1302 
1303       gtk_container_set_border_width (GTK_CONTAINER (window), 5);
1304       gtk_dialog_set_default_response (GTK_DIALOG (window),
1305 				       GTK_RESPONSE_CANCEL);
1306 
1307       hbox = gtk_hbox_new (FALSE, 0);
1308       gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1309       gtk_box_pack_start_defaults (GTK_BOX (GTK_DIALOG (window)->vbox), hbox);
1310       pixmap = gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO,
1311 					 GTK_ICON_SIZE_DIALOG);
1312       gtk_box_pack_start (GTK_BOX (hbox), pixmap, TRUE, FALSE, 10);
1313       labelMessage = gtk_label_new (_("There are unapplied changes by you. "
1314 				      "Changing the expert setting will apply "
1315 				      "those changes.  Do you want to "
1316 				      "continue?"));
1317       gtk_label_set_line_wrap (GTK_LABEL (labelMessage), TRUE);
1318       gtk_box_pack_start (GTK_BOX (hbox), labelMessage, TRUE, FALSE, 10);
1319 
1320       gtk_widget_show_all (window);
1321       result = gtk_dialog_run (GTK_DIALOG (window));
1322       gtk_widget_destroy (window);
1323 
1324       if (result != GTK_RESPONSE_APPLY)
1325 	{
1326 	  gtk_combo_box_set_active (GTK_COMBO_BOX (level_chooser),
1327 				    dialog_level);
1328 	  return;
1329 	}
1330     }
1331 
1332   save_all_options ();
1333 
1334   /* Note: We know intimately that this matches GPGME_CONF_BASIC,
1335      GPGME_CONF_ADVANCED and GPGME_CONF_BEGINNER.  */
1336   dialog_level = level;
1337   create_dialog_tabs ();
1338 }
1339 
1340 
1341 /* Return a new dialog widget.  */
1342 static GtkDialog *
create_dialog(void)1343 create_dialog (void)
1344 {
1345   gpgme_error_t err;
1346   GtkWidget *dialog_vbox;
1347   GtkWidget *hbox;
1348   GtkWidget *level_chooser;
1349   GtkWidget *label;
1350   gint xpad;
1351   gint ypad;
1352 
1353   /* Check error.  */
1354   err = gpgme_new (&dialog_ctx);
1355   if (err)
1356     gpa_gpgme_error (err);
1357 
1358   dialog = gtk_dialog_new_with_buttons (_("Crypto Backend Configuration"),
1359 					NULL /* transient parent */,
1360 					0,
1361 					NULL);
1362   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1363                           GTK_STOCK_APPLY, GTK_RESPONSE_APPLY,
1364                           _("Reset"), CUSTOM_RESPONSE_RESET,
1365                           GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1366                           GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1367                           NULL );
1368   gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
1369                                            GTK_RESPONSE_ACCEPT,
1370                                            GTK_RESPONSE_CANCEL,
1371                                            CUSTOM_RESPONSE_RESET,
1372                                            GTK_RESPONSE_APPLY,
1373                                            -1);
1374 
1375   g_signal_connect ((gpointer) dialog, "response",
1376 		    G_CALLBACK (dialog_response), NULL);
1377 
1378   dialog_vbox = GTK_DIALOG (dialog)->vbox;
1379   /*  gtk_box_set_spacing (GTK_CONTAINER (dialog_vbox), 5); */
1380 
1381   hbox = gtk_hbox_new (FALSE, 0);
1382 
1383   label = gtk_label_new (_("Configure the tools of the GnuPG system."));
1384   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
1385   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
1386 
1387   label = gtk_label_new (_("Level:"));
1388   gtk_misc_get_padding (GTK_MISC (label), &xpad, &ypad);
1389   xpad += 5;
1390   gtk_misc_set_padding (GTK_MISC (label), xpad, ypad);
1391   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
1392 
1393   level_chooser = gtk_combo_box_new_text ();
1394   /* Note: We know intimately that this matches GPGME_CONF_BASIC,
1395      GPGME_CONF_ADVANCED and GPGME_CONF_BEGINNER.  */
1396   gtk_combo_box_append_text (GTK_COMBO_BOX (level_chooser), _("Basic"));
1397   gtk_combo_box_append_text (GTK_COMBO_BOX (level_chooser), _("Advanced"));
1398   gtk_combo_box_append_text (GTK_COMBO_BOX (level_chooser), _("Expert"));
1399   g_signal_connect ((gpointer) level_chooser, "changed",
1400 		    G_CALLBACK (dialog_level_chooser_cb), NULL);
1401 
1402   gtk_box_pack_start (GTK_BOX (hbox), level_chooser, FALSE, FALSE, 0);
1403   gtk_box_pack_start (GTK_BOX (dialog_vbox), hbox, FALSE, FALSE, 0);
1404   gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1405   if (gpa_options_get_simplified_ui (gpa_options_get_instance ()))
1406     dialog_level = GPGME_CONF_BASIC;
1407   else
1408     dialog_level = GPGME_CONF_ADVANCED;
1409   gtk_combo_box_set_active (GTK_COMBO_BOX (level_chooser), dialog_level);
1410 
1411   dialog_notebook = gtk_notebook_new ();
1412   gtk_container_set_border_width (GTK_CONTAINER (dialog_notebook), 5);
1413   gtk_box_pack_start (GTK_BOX (dialog_vbox), dialog_notebook, TRUE, TRUE, 0);
1414 
1415   /* This should also be run on show after hide.  */
1416   dialog_tab_modified = 0;
1417   create_dialog_tabs ();
1418 
1419   return GTK_DIALOG (dialog);
1420 }
1421 
1422 
1423 GtkWidget *
gpa_backend_config_dialog_new(void)1424 gpa_backend_config_dialog_new (void)
1425 {
1426   assert (! dialog);
1427 
1428   create_dialog ();
1429 
1430   return dialog;
1431 }
1432 
1433 
1434 static void
hide_backend_config(void)1435 hide_backend_config (void)
1436 {
1437   gtk_widget_destroy (dialog);
1438   dialog = NULL;
1439   dialog_notebook = NULL;
1440 
1441   gpgme_conf_release (dialog_conf);
1442   dialog_conf = NULL;
1443 
1444   gpgme_release (dialog_ctx);
1445   dialog_ctx = NULL;
1446 }
1447 
1448 
1449 
1450 /* Load the value of option NAME of component CNAME from the backend.
1451    If none is configured, return NULL.  Caller must g_free the
1452    returned value.  */
1453 char *
gpa_load_gpgconf_string(const char * cname,const char * name)1454 gpa_load_gpgconf_string (const char *cname, const char *name)
1455 {
1456   gpg_error_t err;
1457   gpgme_ctx_t ctx;
1458   gpgme_conf_comp_t conf_list, conf;
1459   gpgme_conf_opt_t opt;
1460   char *retval = NULL;
1461 
1462   err = gpgme_new (&ctx);
1463   if (err)
1464     {
1465       gpa_gpgme_error (err);
1466       return NULL;
1467     }
1468 
1469   err = gpgme_op_conf_load (ctx, &conf_list);
1470   if (err)
1471     {
1472       gpa_gpgme_warning (err);
1473       gpgme_release (ctx);
1474       return NULL;
1475     }
1476 
1477   for (conf = conf_list; conf; conf = conf->next)
1478     {
1479       if ( !strcmp (conf->name, cname) )
1480         {
1481           for (opt = conf->options; opt; opt = opt->next)
1482             if ( !(opt->flags & GPGME_CONF_GROUP)
1483                  && !strcmp (opt->name, name))
1484               {
1485                 if (opt->value && opt->alt_type == GPGME_CONF_STRING)
1486                   retval = g_strdup (opt->value->value.string);
1487                 break;
1488               }
1489           break;
1490         }
1491     }
1492 
1493   gpgme_conf_release (conf_list);
1494   gpgme_release (ctx);
1495   return retval;
1496 }
1497 
1498 
1499 /* Set the option NAME in component "CNAME" to VALUE.  The option
1500    needs to be of type string. */
1501 void
gpa_store_gpgconf_string(const char * cname,const char * name,const char * value)1502 gpa_store_gpgconf_string (const char *cname,
1503                           const char *name, const char *value)
1504 {
1505   gpg_error_t err;
1506   gpgme_ctx_t ctx;
1507   gpgme_conf_comp_t conf_list, conf;
1508   gpgme_conf_opt_t opt;
1509   gpgme_conf_arg_t arg;
1510 
1511   err = gpgme_conf_arg_new (&arg, GPGME_CONF_STRING, (char*)value);
1512   if (err)
1513     {
1514       gpa_gpgme_warning (err);
1515       return;
1516     }
1517 
1518   err = gpgme_new (&ctx);
1519   if (err)
1520     {
1521       gpa_gpgme_error (err);
1522       return;
1523     }
1524 
1525   err = gpgme_op_conf_load (ctx, &conf_list);
1526   if (err)
1527     {
1528       gpa_gpgme_error (err);
1529       gpgme_release (ctx);
1530       return;
1531     }
1532 
1533   for (conf = conf_list; conf; conf = conf->next)
1534     {
1535       if ( !strcmp (conf->name, cname) )
1536         {
1537           for (opt = conf->options; opt; opt = opt->next)
1538             if ( !(opt->flags & GPGME_CONF_GROUP)
1539                  && !strcmp (opt->name, name))
1540               {
1541                 if (opt->alt_type == GPGME_CONF_STRING
1542                     && !args_are_equal (arg, opt->value, opt->alt_type))
1543                   {
1544                     err = gpgme_conf_opt_change (opt, 0, arg);
1545                     if (err)
1546                       gpa_gpgme_error (err);
1547                     else
1548                       {
1549                         err = gpgme_op_conf_save (ctx, conf);
1550                         if (err)
1551                           gpa_gpgme_error (err);
1552                       }
1553                   }
1554                 break;
1555               }
1556           break;
1557         }
1558     }
1559 
1560   gpgme_conf_release (conf_list);
1561   gpgme_release (ctx);
1562 }
1563 
1564 
1565 /* Read the configured keyserver from the backend.  If none is
1566    configured, return NULL.  Caller must g_free the returned value.  */
1567 char *
gpa_load_configured_keyserver(void)1568 gpa_load_configured_keyserver (void)
1569 {
1570 #ifdef ENABLE_KEYSERVER_SUPPORT
1571   return gpa_load_gpgconf_string ("gpg", "keyserver");
1572 #else
1573   return NULL;
1574 #endif
1575 }
1576 
1577 /* Save the configured keyserver from the backend.  If none is
1578    configured, return NULL.  Caller must g_free the returned value.  */
1579 void
gpa_store_configured_keyserver(const char * value)1580 gpa_store_configured_keyserver (const char *value)
1581 {
1582 #ifdef ENABLE_KEYSERVER_SUPPORT
1583   gpa_store_gpgconf_string ("gpg", "keyserver", value);
1584 #endif
1585 }
1586 
1587 
1588 /* Ask the user whether to configure GnuPG to use a keyserver.  Return
1589    NULL if it could or shall not be configured or the name of the
1590    keyserver which needs to be g_freed.  */
1591 char *
gpa_configure_keyserver(GtkWidget * parent)1592 gpa_configure_keyserver (GtkWidget *parent)
1593 {
1594 #ifdef ENABLE_KEYSERVER_SUPPORT
1595   GtkWidget *msgbox;
1596   char *keyserver;
1597 
1598   msgbox = gtk_message_dialog_new
1599     (GTK_WINDOW(parent), GTK_DIALOG_MODAL,
1600      GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE,
1601      "%s\n\n%s",
1602      _("A keyserver has not been configured."),
1603      _("Configure backend to use a keyserver?"));
1604   gtk_dialog_add_buttons (GTK_DIALOG (msgbox),
1605                           _("_Yes"), GTK_RESPONSE_YES,
1606                           _("_No"), GTK_RESPONSE_NO, NULL);
1607   if (gtk_dialog_run (GTK_DIALOG (msgbox)) != GTK_RESPONSE_YES)
1608     {
1609       gtk_widget_destroy (msgbox);
1610       return NULL;
1611     }
1612   gtk_widget_destroy (msgbox);
1613   gpa_store_configured_keyserver ("hkp://keys.gnupg.net");
1614   keyserver = gpa_load_configured_keyserver ();
1615   if (!keyserver)
1616     {
1617       gpa_show_warn
1618         (parent, NULL, _("Configuring the backend to use a keyserver failed"));
1619       return NULL;
1620     }
1621   return keyserver;
1622 #else
1623   (void)parent;
1624   return NULL
1625 #endif
1626 }
1627