r1298: Panel icons highlight when the pointer is over them.
[rox-filer.git] / ROX-Filer / src / options.c
blob0c1c0821bcee7ff24864556eb9ff9f81d4da8f3d
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, Thomas Leonard, <tal197@users.sourceforge.net>.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* options.c - code for handling user choices */
24 /* How it works:
26 * On startup:
28 * - The <Choices>/PROJECT/Options file is read in. Each line
29 * is a name/value pair, and these are stored in the 'loading' hash table.
31 * - Each part of the filer then calls option_add_int(), or a related function,
32 * supplying the name for each option and a default value. Once an option is
33 * registered, it is removed from the loading table.
35 * - If things need to happen when values change, modules register with
36 * option_add_notify().
38 * - option_register_widget() can be used during initialisation (any time
39 * before the Options box is displayed) to tell the system how to render a
40 * particular type of option.
42 * - Finally, all notify callbacks are called. Use the Option->has_changed
43 * field to work out what has changed from the defaults.
45 * When the user opens the Options box:
47 * - The Options.xml file is read and used to create the Options dialog box.
48 * Each element in the file has a key corresponding to an option named
49 * above.
51 * - For each widget in the box, the current value of the option is used to
52 * set the widget's state.
54 * - All current values are saved for a possible Revert later.
56 * When the user changes an option or clicks on Revert:
58 * - The option values are updated.
60 * - All notify callbacks are called. Use the Option->has_changed field
61 * to see what changed.
63 * When Save is clicked:
65 * - All the options are written to the filesystem and the saver_callbacks are
66 * called.
69 #include "config.h"
71 #undef GTK_DISABLE_DEPRECATED
73 #include <stdio.h>
74 #include <stdlib.h>
75 #include <string.h>
76 #include <errno.h>
77 #include <ctype.h>
78 #include <gtk/gtk.h>
79 #include <libxml/parser.h>
81 #include "global.h"
83 #include "choices.h"
84 #include "options.h"
85 #include "main.h"
86 #include "gui_support.h"
88 /* Add all option tooltips to this group */
89 static GtkTooltips *option_tooltips = NULL;
90 #define OPTION_TIP(widget, tip) \
91 gtk_tooltips_set_tip(option_tooltips, widget, tip, NULL)
93 /* The Options window. NULL if not yet created. */
94 static GtkWidget *window = NULL;
96 /* "filer_unique" -> (Option *) */
97 static GHashTable *option_hash = NULL;
99 /* A mapping (name -> value) for options which have been loaded by not
100 * yet registered. The options in this table cannot be used until
101 * option_add_*() is called to move them into option_hash.
103 static GHashTable *loading = NULL;
105 /* A mapping (XML name -> OptionBuildFn). When reading the Options.xml
106 * file, this table gives the function used to create the widgets.
108 static GHashTable *widget_builder = NULL;
110 /* List of functions to call after all option values are updated */
111 static GList *notify_callbacks = NULL;
113 /* List of functions to call after all options are saved */
114 static GList *saver_callbacks = NULL;
116 static int updating_widgets = 0; /* Ignore change signals when set */
118 /* Static prototypes */
119 static void save_options(gpointer unused);
120 static void revert_options(GtkWidget *widget, gpointer data);
121 static void build_options_window(void);
122 static GtkWidget *build_frame(void);
123 static void update_option_widgets(void);
124 static void button_patch_set_colour(GtkWidget *button, GdkColor *color);
125 static void option_add(Option *option, const gchar *key);
126 static void set_not_changed(gpointer key, gpointer value, gpointer data);
127 static void load_options(xmlDoc *doc);
129 static const char *process_option_line(gchar *line);
131 static GList *build_toggle(Option *option, xmlNode *node, guchar *label);
132 static GList *build_slider(Option *option, xmlNode *node, guchar *label);
133 static GList *build_entry(Option *option, xmlNode *node, guchar *label);
134 static GList *build_radio_group(Option *option, xmlNode *node, guchar *label);
135 static GList *build_colour(Option *option, xmlNode *node, guchar *label);
136 static GList *build_menu(Option *option, xmlNode *node, guchar *label);
137 static GList *build_font(Option *option, xmlNode *node, guchar *label);
139 static gboolean updating_file_format = FALSE;
141 /****************************************************************
142 * EXTERNAL INTERFACE *
143 ****************************************************************/
145 void options_init(void)
147 char *path;
148 xmlDoc *doc;
150 loading = g_hash_table_new(g_str_hash, g_str_equal);
151 option_hash = g_hash_table_new(g_str_hash, g_str_equal);
152 widget_builder = g_hash_table_new(g_str_hash, g_str_equal);
154 path = choices_find_path_load("Options", PROJECT);
155 if (path)
157 /* Load in all the options set in the filer, storing them
158 * temporarily in the loading hash table.
159 * They get moved to option_hash when they're registered.
161 doc = xmlParseFile(path);
162 if (doc)
164 load_options(doc);
165 xmlFreeDoc(doc);
167 else
169 parse_file(path, process_option_line);
170 updating_file_format = TRUE;
173 g_free(path);
176 option_register_widget("toggle", build_toggle);
177 option_register_widget("slider", build_slider);
178 option_register_widget("entry", build_entry);
179 option_register_widget("radio-group", build_radio_group);
180 option_register_widget("colour", build_colour);
181 option_register_widget("menu", build_menu);
182 option_register_widget("font", build_font);
185 /* When parsing the XML file, process an element named 'name' by
186 * calling 'builder(option, xml_node, label)'.
187 * builder returns the new widgets to add to the options box.
188 * 'name' should be a static string. Call 'option_check_widget' when
189 * the widget's value is modified.
191 * Functions to set or get the widget's state can be stored in 'option'.
192 * If the option doesn't have a name attribute in Options.xml then
193 * ui will be NULL on entry (this is used for buttons).
195 void option_register_widget(char *name, OptionBuildFn builder)
197 g_hash_table_insert(widget_builder, name, builder);
200 /* This is called when the widget's value is modified by the user.
201 * Reads the new value of the widget into the option and calls
202 * the notify callbacks.
204 void option_check_widget(Option *option)
206 guchar *new = NULL;
208 if (updating_widgets)
209 return; /* Not caused by the user... */
211 g_return_if_fail(option->read_widget != NULL);
213 new = option->read_widget(option);
215 g_return_if_fail(new != NULL);
217 g_hash_table_foreach(option_hash, set_not_changed, NULL);
219 option->has_changed = strcmp(option->value, new) != 0;
221 if (!option->has_changed)
223 g_free(new);
224 return;
227 g_free(option->value);
228 option->value = new;
229 option->int_value = atoi(new);
231 options_notify();
234 /* Call all the notify callbacks. This should happen after any options
235 * have their values changed. Set each has_changed before calling.
237 void options_notify(void)
239 GList *next;
241 for (next = notify_callbacks; next; next = next->next)
243 OptionNotify *cb = (OptionNotify *) next->data;
245 cb();
248 if (updating_file_format)
250 updating_file_format = FALSE;
251 save_options(NULL);
252 report_error(_("ROX-Filer has converted your Options file "
253 "to the new XML format"));
257 /* Store values used by Revert */
258 static void store_backup(gpointer key, gpointer value, gpointer data)
260 Option *option = (Option *) value;
262 g_free(option->backup);
263 option->backup = g_strdup(option->value);
266 /* Allow the user to edit the options. Returns the window widget (you don't
267 * normally need this). NULL if already open.
269 GtkWidget *options_show(void)
271 if (!option_tooltips)
272 option_tooltips = gtk_tooltips_new();
274 if (g_hash_table_size(loading) != 0)
276 g_printerr(PROJECT ": Some options loaded but not used:\n");
277 g_hash_table_foreach(loading, (GHFunc) puts, NULL);
280 if (window)
282 gtk_window_present(GTK_WINDOW(window));
283 return NULL;
286 g_hash_table_foreach(option_hash, store_backup, NULL);
288 build_options_window();
290 update_option_widgets();
292 gtk_widget_show_all(window);
294 return window;
297 /* Initialise and register a new integer option */
298 void option_add_int(Option *option, const gchar *key, int value)
300 option->value = g_strdup_printf("%d", value);
301 option->int_value = value;
302 option_add(option, key);
305 void option_add_string(Option *option, const gchar *key, const gchar *value)
307 option->value = g_strdup(value);
308 option->int_value = atoi(value);
309 option_add(option, key);
312 /* Add a callback which will be called after any options have changed their
313 * values. If serveral options change at once, this is called after all
314 * changes.
316 void option_add_notify(OptionNotify *callback)
318 g_return_if_fail(callback != NULL);
320 notify_callbacks = g_list_append(notify_callbacks, callback);
323 /* Call 'callback' after all the options have been saved */
324 void option_add_saver(OptionNotify *callback)
326 g_return_if_fail(callback != NULL);
328 saver_callbacks = g_list_append(saver_callbacks, callback);
331 /****************************************************************
332 * INTERNAL FUNCTIONS *
333 ****************************************************************/
335 /* Option should contain the default value.
336 * It must never be destroyed after being registered (Options are typically
337 * statically allocated).
338 * The key corresponds to the option's name in Options.xml, and to the key
339 * in the saved options file.
341 * On exit, the value will have been updated to the loaded value, if
342 * different to the default.
344 static void option_add(Option *option, const gchar *key)
346 gpointer okey, value;
348 g_return_if_fail(option_hash != NULL);
349 g_return_if_fail(g_hash_table_lookup(option_hash, key) == NULL);
350 g_return_if_fail(option->value != NULL);
352 option->has_changed = FALSE;
354 option->widget = NULL;
355 option->update_widget = NULL;
356 option->read_widget = NULL;
357 option->backup = NULL;
359 g_hash_table_insert(option_hash, (gchar *) key, option);
361 /* Use the value loaded from the file, if any */
362 if (g_hash_table_lookup_extended(loading, key, &okey, &value))
364 option->has_changed = strcmp(option->value, value) != 0;
366 g_free(option->value);
367 option->value = value;
368 option->int_value = atoi(value);
369 g_hash_table_remove(loading, key);
370 g_free(okey);
374 static GtkColorSelectionDialog *current_csel_box = NULL;
375 static GtkFontSelectionDialog *current_fontsel_box = NULL;
377 static void get_new_colour(GtkWidget *ok, Option *option)
379 GtkWidget *csel;
380 gdouble c[4];
381 GdkColor colour;
383 g_return_if_fail(current_csel_box != NULL);
385 csel = current_csel_box->colorsel;
387 gtk_color_selection_get_color(GTK_COLOR_SELECTION(csel), c);
388 colour.red = c[0] * 0xffff;
389 colour.green = c[1] * 0xffff;
390 colour.blue = c[2] * 0xffff;
392 button_patch_set_colour(option->widget, &colour);
394 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
396 option_check_widget(option);
399 static void set_to_null(gpointer *data)
401 *data = NULL;
404 static void open_coloursel(GtkWidget *button, Option *option)
406 GtkColorSelectionDialog *csel;
407 GtkWidget *dialog, *patch;
408 gdouble c[4];
410 if (current_csel_box)
411 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
413 dialog = gtk_color_selection_dialog_new(NULL);
414 csel = GTK_COLOR_SELECTION_DIALOG(dialog);
415 current_csel_box = csel;
416 gtk_window_set_position(GTK_WINDOW(csel), GTK_WIN_POS_MOUSE);
418 gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
419 GTK_SIGNAL_FUNC(set_to_null),
420 (GtkObject *) &current_csel_box);
421 gtk_widget_hide(csel->help_button);
422 gtk_signal_connect_object(GTK_OBJECT(csel->cancel_button), "clicked",
423 GTK_SIGNAL_FUNC(gtk_widget_destroy),
424 GTK_OBJECT(dialog));
425 gtk_signal_connect(GTK_OBJECT(csel->ok_button), "clicked",
426 GTK_SIGNAL_FUNC(get_new_colour), option);
428 patch = GTK_BIN(button)->child;
430 c[0] = ((gdouble) patch->style->bg[GTK_STATE_NORMAL].red) / 0xffff;
431 c[1] = ((gdouble) patch->style->bg[GTK_STATE_NORMAL].green) / 0xffff;
432 c[2] = ((gdouble) patch->style->bg[GTK_STATE_NORMAL].blue) / 0xffff;
433 gtk_color_selection_set_color(GTK_COLOR_SELECTION(csel->colorsel), c);
435 gtk_widget_show(dialog);
438 static void font_chosen(GtkWidget *dialog, gint response, Option *option)
440 gchar *font;
442 if (response != GTK_RESPONSE_OK)
443 goto out;
445 font = gtk_font_selection_dialog_get_font_name(
446 GTK_FONT_SELECTION_DIALOG(dialog));
448 gtk_label_set_text(GTK_LABEL(option->widget), font);
450 g_free(font);
452 option_check_widget(option);
454 out:
455 gtk_widget_destroy(dialog);
459 static void open_fontsel(GtkWidget *button, Option *option)
461 if (current_fontsel_box)
462 gtk_widget_destroy(GTK_WIDGET(current_fontsel_box));
464 current_fontsel_box = GTK_FONT_SELECTION_DIALOG(
465 gtk_font_selection_dialog_new(PROJECT));
467 gtk_window_set_position(GTK_WINDOW(current_fontsel_box),
468 GTK_WIN_POS_MOUSE);
470 gtk_signal_connect_object(GTK_OBJECT(current_fontsel_box), "destroy",
471 GTK_SIGNAL_FUNC(set_to_null),
472 (GtkObject *) &current_fontsel_box);
474 gtk_font_selection_dialog_set_font_name(current_fontsel_box,
475 option->value);
477 gtk_signal_connect(GTK_OBJECT(current_fontsel_box), "response",
478 GTK_SIGNAL_FUNC(font_chosen), option);
480 gtk_widget_show(GTK_WIDGET(current_fontsel_box));
483 /* These are used during parsing... */
484 static xmlDocPtr options_doc = NULL;
486 #define DATA(node) (xmlNodeListGetString(options_doc, node->xmlChildrenNode, 1))
488 static void may_add_tip(GtkWidget *widget, xmlNode *element)
490 guchar *data, *tip;
492 data = DATA(element);
493 if (!data)
494 return;
496 tip = g_strstrip(g_strdup(data));
497 g_free(data);
498 if (*tip)
499 OPTION_TIP(widget, _(tip));
500 g_free(tip);
503 static int get_int(xmlNode *node, guchar *attr)
505 guchar *txt;
506 int retval;
508 txt = xmlGetProp(node, attr);
509 if (!txt)
510 return 0;
512 retval = atoi(txt);
513 g_free(txt);
515 return retval;
518 static GtkWidget *build_radio(xmlNode *radio, GtkWidget *prev)
520 GtkWidget *button;
521 GtkRadioButton *prev_button = (GtkRadioButton *) prev;
522 guchar *label;
524 label = xmlGetProp(radio, "label");
526 button = gtk_radio_button_new_with_label(
527 prev_button ? gtk_radio_button_group(prev_button)
528 : NULL,
529 _(label));
530 g_free(label);
532 may_add_tip(button, radio);
534 gtk_object_set_data(GTK_OBJECT(button), "value",
535 xmlGetProp(radio, "value"));
537 return button;
540 static void build_menu_item(xmlNode *node, GtkWidget *option_menu)
542 GtkWidget *item, *menu;
543 guchar *label;
545 g_return_if_fail(strcmp(node->name, "item") == 0);
547 label = xmlGetProp(node, "label");
548 item = gtk_menu_item_new_with_label(_(label));
549 g_free(label);
551 menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(option_menu));
552 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
553 gtk_widget_show_all(menu);
555 gtk_object_set_data(GTK_OBJECT(item),
556 "value", xmlGetProp(node, "value"));
559 static void build_widget(xmlNode *widget, GtkWidget *box)
561 const char *name = widget->name;
562 OptionBuildFn builder;
563 guchar *oname;
564 Option *option;
565 guchar *label;
567 if (strcmp(name, "label") == 0)
569 GtkWidget *label;
570 guchar *text;
572 text = DATA(widget);
573 label = gtk_label_new(_(text));
574 g_free(text);
576 gtk_misc_set_alignment(GTK_MISC(label), 0, 1);
577 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
578 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
579 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
580 return;
582 else if (strcmp(name, "spacer") == 0)
584 GtkWidget *eb;
586 eb = gtk_event_box_new();
587 gtk_widget_set_usize(eb, 12, 12);
588 gtk_box_pack_start(GTK_BOX(box), eb, FALSE, TRUE, 0);
589 return;
592 label = xmlGetProp(widget, "label");
594 if (strcmp(name, "hbox") == 0 || strcmp(name, "vbox") == 0)
596 GtkWidget *nbox;
597 xmlNode *hw;
599 if (name[0] == 'h')
600 nbox = gtk_hbox_new(FALSE, 4);
601 else
602 nbox = gtk_vbox_new(FALSE, 4);
604 if (label)
605 gtk_box_pack_start(GTK_BOX(nbox),
606 gtk_label_new(_(label)), FALSE, TRUE, 4);
607 gtk_box_pack_start(GTK_BOX(box), nbox, FALSE, TRUE, 0);
609 for (hw = widget->xmlChildrenNode; hw; hw = hw->next)
611 if (hw->type == XML_ELEMENT_NODE)
612 build_widget(hw, nbox);
615 g_free(label);
616 return;
619 oname = xmlGetProp(widget, "name");
621 if (oname)
623 option = g_hash_table_lookup(option_hash, oname);
625 if (!option)
627 g_warning("No Option for '%s'!\n", oname);
628 g_free(oname);
629 return;
632 g_free(oname);
634 else
635 option = NULL;
637 builder = g_hash_table_lookup(widget_builder, name);
638 if (builder)
640 GList *widgets, *next;
642 if (option && option->widget)
643 g_warning("Widget for option already exists!");
645 widgets = builder(option, widget, label);
647 for (next = widgets; next; next = next->next)
649 GtkWidget *w = (GtkWidget *) next->data;
650 gtk_box_pack_start(GTK_BOX(box), w, FALSE, TRUE, 0);
652 g_list_free(widgets);
654 else
655 g_warning("Unknown option type '%s'\n", name);
657 g_free(label);
660 static void build_sections(xmlNode *options, GtkWidget *sections_box)
662 xmlNode *section = options->xmlChildrenNode;
664 g_return_if_fail(strcmp(options->name, "options") == 0);
666 for (; section; section = section->next)
668 guchar *title;
669 GtkWidget *page, *scrolled_area;
670 xmlNode *widget;
672 if (section->type != XML_ELEMENT_NODE)
673 continue;
675 title = xmlGetProp(section, "title");
676 page = gtk_vbox_new(FALSE, 0);
677 gtk_container_set_border_width(GTK_CONTAINER(page), 4);
679 scrolled_area = gtk_scrolled_window_new(NULL, NULL);
680 gtk_scrolled_window_set_policy(
681 GTK_SCROLLED_WINDOW(scrolled_area),
682 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
684 gtk_scrolled_window_add_with_viewport(
685 GTK_SCROLLED_WINDOW(scrolled_area),
686 page);
688 gtk_notebook_append_page(GTK_NOTEBOOK(sections_box),
689 scrolled_area,
690 gtk_label_new(_(title)));
692 widget = section->xmlChildrenNode;
693 for (; widget; widget = widget->next)
695 if (widget->type == XML_ELEMENT_NODE)
696 build_widget(widget, page);
699 g_free(title);
703 /* Parse <app_dir>/Options.xml to create the options window.
704 * Sets the global 'window' variable.
706 static void build_options_window(void)
708 GtkWidget *sections_box;
709 xmlDocPtr options_doc;
710 gchar *path;
712 sections_box = build_frame();
714 path = g_strconcat(app_dir, "/Options.xml", NULL);
715 options_doc = xmlParseFile(path);
717 if (!options_doc)
719 report_error("Internal error: %s unreadable", path);
720 g_free(path);
721 return;
724 g_free(path);
726 build_sections(xmlDocGetRootElement(options_doc), sections_box);
728 xmlFreeDoc(options_doc);
729 options_doc = NULL;
732 static void null_widget(gpointer key, gpointer value, gpointer data)
734 Option *option = (Option *) value;
736 g_return_if_fail(option->widget != NULL);
738 option->widget = NULL;
741 static void options_destroyed(GtkWidget *widget, gpointer data)
743 if (current_csel_box)
744 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
745 if (current_fontsel_box)
746 gtk_widget_destroy(GTK_WIDGET(current_fontsel_box));
748 if (widget == window)
750 window = NULL;
752 g_hash_table_foreach(option_hash, null_widget, NULL);
756 /* Creates the window and adds the various buttons to it.
757 * Returns the vbox to add sections to and sets the global
758 * 'window'.
760 static GtkWidget *build_frame(void)
762 GtkWidget *sections_box;
763 GtkWidget *tl_vbox;
764 GtkWidget *actions, *button;
765 char *string, *save_path;
767 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
769 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
770 gtk_window_set_title(GTK_WINDOW(window), _("Options"));
771 gtk_signal_connect(GTK_OBJECT(window), "destroy",
772 GTK_SIGNAL_FUNC(options_destroyed), NULL);
773 gtk_container_set_border_width(GTK_CONTAINER(window), 4);
774 gtk_window_set_default_size(GTK_WINDOW(window), -1, 400);
776 tl_vbox = gtk_vbox_new(FALSE, 4);
777 gtk_container_add(GTK_CONTAINER(window), tl_vbox);
779 sections_box = gtk_notebook_new();
780 gtk_notebook_set_scrollable(GTK_NOTEBOOK(sections_box), TRUE);
781 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(sections_box), GTK_POS_LEFT);
782 gtk_box_pack_start(GTK_BOX(tl_vbox), sections_box, TRUE, TRUE, 0);
784 actions = gtk_hbutton_box_new();
785 gtk_button_box_set_layout(GTK_BUTTON_BOX(actions),
786 GTK_BUTTONBOX_END);
787 gtk_button_box_set_spacing(GTK_BUTTON_BOX(actions), 10);
789 save_path = choices_find_path_save("...", PROJECT, FALSE);
790 if (save_path)
791 gtk_box_pack_start(GTK_BOX(tl_vbox), actions, FALSE, TRUE, 0);
792 else
794 GtkWidget *hbox;
796 hbox = gtk_hbox_new(FALSE, 0);
797 gtk_box_pack_start(GTK_BOX(tl_vbox), hbox, FALSE, TRUE, 0);
798 gtk_box_pack_start(GTK_BOX(hbox),
799 gtk_label_new(_("(saving disabled by CHOICESPATH)")),
800 FALSE, FALSE, 0);
801 gtk_box_pack_start(GTK_BOX(hbox), actions, TRUE, TRUE, 0);
804 button = button_new_mixed(GTK_STOCK_UNDO, _("_Revert"));
805 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
806 gtk_box_pack_start(GTK_BOX(actions), button, FALSE, TRUE, 0);
807 gtk_signal_connect(GTK_OBJECT(button), "clicked",
808 GTK_SIGNAL_FUNC(revert_options), NULL);
809 gtk_tooltips_set_tip(option_tooltips, button,
810 _("Restore all choices to how they were when the "
811 "Options box was opened."), NULL);
813 button = button_new_mixed(GTK_STOCK_APPLY, _("_OK"));
814 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
815 gtk_box_pack_start(GTK_BOX(actions), button, FALSE, TRUE, 0);
816 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
817 GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(window));
819 if (save_path)
821 button = gtk_button_new_from_stock(GTK_STOCK_SAVE);
822 gtk_box_pack_start(GTK_BOX(actions), button, FALSE, TRUE, 0);
823 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
824 GTK_SIGNAL_FUNC(save_options), NULL);
826 string = g_strdup_printf(_("Choices will be saved as:\n%s"),
827 save_path);
828 gtk_tooltips_set_tip(option_tooltips, button, string, NULL);
829 g_free(save_path);
830 g_free(string);
831 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
832 gtk_widget_grab_default(button);
833 gtk_widget_grab_focus(button);
836 return sections_box;
839 /* Given the last radio button in the group, select whichever
840 * radio button matches the given value.
842 static void radio_group_set_value(GtkRadioButton *last, guchar *value)
844 GSList *next;
846 next = gtk_radio_button_group(last);
847 while (next)
849 GtkToggleButton *button = (GtkToggleButton *) next->data;
850 guchar *val;
852 val = gtk_object_get_data(GTK_OBJECT(button), "value");
853 g_return_if_fail(val != NULL);
855 if (strcmp(val, value) == 0)
857 gtk_toggle_button_set_active(button, TRUE);
858 return;
861 next = next->next;
864 g_warning("Can't find radio button with value %s\n", value);
867 /* Given the last radio button in the group, return a copy of the
868 * value for the selected radio item.
870 static guchar *radio_group_get_value(GtkRadioButton *last)
872 GSList *next;
874 next = gtk_radio_button_group(last);
875 while (next)
877 GtkToggleButton *button = (GtkToggleButton *) next->data;
879 if (gtk_toggle_button_get_active(button))
881 guchar *val;
883 val = gtk_object_get_data(GTK_OBJECT(button), "value");
884 g_return_val_if_fail(val != NULL, NULL);
886 return g_strdup(val);
889 next = next->next;
892 return NULL;
895 /* Select this item with this value */
896 static void option_menu_set(GtkOptionMenu *om, guchar *value)
898 GtkWidget *menu;
899 GList *list, *next;
900 int i = 0;
902 menu = gtk_option_menu_get_menu(om);
903 list = gtk_container_children(GTK_CONTAINER(menu));
905 for (next = list; next; next = next->next)
907 GtkObject *item = (GtkObject *) next->data;
908 guchar *data;
910 data = gtk_object_get_data(item, "value");
911 g_return_if_fail(data != NULL);
913 if (strcmp(data, value) == 0)
915 gtk_option_menu_set_history(om, i);
916 break;
919 i++;
922 g_list_free(list);
925 /* Get the value (static) of the selected item */
926 static guchar *option_menu_get(GtkOptionMenu *om)
928 GtkWidget *menu, *item;
930 menu = gtk_option_menu_get_menu(om);
931 item = gtk_menu_get_active(GTK_MENU(menu));
933 return gtk_object_get_data(GTK_OBJECT(item), "value");
936 static void restore_backup(gpointer key, gpointer value, gpointer data)
938 Option *option = (Option *) value;
940 g_return_if_fail(option->backup != NULL);
942 option->has_changed = strcmp(option->value, option->backup) != 0;
943 if (!option->has_changed)
944 return;
946 g_free(option->value);
947 option->value = g_strdup(option->backup);
948 option->int_value = atoi(option->value);
951 static void revert_options(GtkWidget *widget, gpointer data)
953 g_hash_table_foreach(option_hash, restore_backup, NULL);
954 options_notify();
955 update_option_widgets();
958 static void write_option(gpointer key, gpointer value, gpointer data)
960 xmlNodePtr doc = (xmlNodePtr) data;
961 Option *option = (Option *) value;
962 xmlNodePtr tree;
964 tree = xmlNewTextChild(doc, NULL, "Option", option->value);
965 xmlSetProp(tree, "name", (gchar *) key);
968 /* Save doc as XML as filename, 0 on success or -1 on failure */
969 static int save_xml_file(xmlDocPtr doc, gchar *filename)
971 #if LIBXML_VERSION > 20400
972 if (xmlSaveFormatFileEnc(filename, doc, NULL, 1) < 0)
973 return 1;
974 #else
975 FILE *out;
977 out = fopen(filename, "w");
978 if (!out)
979 return 1;
981 xmlDocDump(out, doc); /* Some versions return void */
983 if (fclose(out))
984 return 1;
985 #endif
987 return 0;
990 static void save_options(gpointer unused)
992 xmlDoc *doc;
993 GList *next;
994 guchar *save, *save_new;
996 save = choices_find_path_save("Options", PROJECT, TRUE);
997 if (!save)
999 report_error(_("Could not save options: %s"),
1000 _("Choices saving is disabled by "
1001 "CHOICESPATH variable"));
1002 return;
1005 save_new = g_strconcat(save, ".new", NULL);
1007 doc = xmlNewDoc("1.0");
1008 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "Options", NULL));
1010 g_hash_table_foreach(option_hash, write_option,
1011 xmlDocGetRootElement(doc));
1013 if (save_xml_file(doc, save_new) || rename(save_new, save))
1014 report_error(_("Error saving %s: %s"), save, g_strerror(errno));
1016 g_free(save_new);
1017 g_free(save);
1018 xmlFreeDoc(doc);
1020 for (next = saver_callbacks; next; next = next->next)
1022 OptionNotify *cb = (OptionNotify *) next->data;
1023 cb();
1026 if (window)
1027 gtk_widget_destroy(window);
1030 /* Make the widget reflect the current value of the option */
1031 static void update_cb(gpointer key, gpointer value, gpointer data)
1033 Option *option = (Option *) value;
1035 g_return_if_fail(option != NULL);
1036 g_return_if_fail(option->widget != NULL);
1038 updating_widgets++;
1040 if (option->update_widget)
1041 option->update_widget(option);
1043 updating_widgets--;
1046 /* Reflect the values in the Option structures by changing the widgets
1047 * in the Options window.
1049 static void update_option_widgets(void)
1051 g_hash_table_foreach(option_hash, update_cb, NULL);
1054 /* Each of the following update the widget to make it show the current
1055 * value of the option.
1058 static void update_toggle(Option *option)
1060 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(option->widget),
1061 option->int_value);
1064 static void update_entry(Option *option)
1066 gtk_entry_set_text(GTK_ENTRY(option->widget), option->value);
1069 static void update_radio_group(Option *option)
1071 radio_group_set_value(GTK_RADIO_BUTTON(option->widget), option->value);
1074 static void update_slider(Option *option)
1076 gtk_adjustment_set_value(
1077 gtk_range_get_adjustment(GTK_RANGE(option->widget)),
1078 option->int_value);
1081 static void update_menu(Option *option)
1083 option_menu_set(GTK_OPTION_MENU(option->widget), option->value);
1086 static void update_font(Option *option)
1088 gtk_label_set_text(GTK_LABEL(option->widget), option->value);
1091 static void update_colour(Option *option)
1093 GdkColor colour;
1095 gdk_color_parse(option->value, &colour);
1096 button_patch_set_colour(option->widget, &colour);
1099 /* Each of these read_* calls get the new (string) value of an option
1100 * from the widget.
1103 static guchar *read_toggle(Option *option)
1105 GtkToggleButton *toggle = GTK_TOGGLE_BUTTON(option->widget);
1107 return g_strdup_printf("%d", gtk_toggle_button_get_active(toggle));
1110 static guchar *read_entry(Option *option)
1112 return gtk_editable_get_chars(GTK_EDITABLE(option->widget), 0, -1);
1115 static guchar *read_slider(Option *option)
1117 return g_strdup_printf("%f",
1118 gtk_range_get_adjustment(GTK_RANGE(option->widget))->value);
1121 static guchar *read_radio_group(Option *option)
1123 return radio_group_get_value(GTK_RADIO_BUTTON(option->widget));
1126 static guchar *read_menu(Option *option)
1128 return g_strdup(option_menu_get(GTK_OPTION_MENU(option->widget)));
1131 static guchar *read_font(Option *option)
1133 return g_strdup(gtk_label_get_text(GTK_LABEL(option->widget)));
1136 static guchar *read_colour(Option *option)
1138 GtkStyle *style = GTK_BIN(option->widget)->child->style;
1140 return g_strdup_printf("#%04x%04x%04x",
1141 style->bg[GTK_STATE_NORMAL].red,
1142 style->bg[GTK_STATE_NORMAL].green,
1143 style->bg[GTK_STATE_NORMAL].blue);
1146 static void set_not_changed(gpointer key, gpointer value, gpointer data)
1148 Option *option = (Option *) value;
1150 option->has_changed = FALSE;
1153 /* These create new widgets in the options window and set the appropriate
1154 * callbacks.
1157 static GList *build_toggle(Option *option, xmlNode *node, guchar *label)
1159 GtkWidget *toggle;
1161 g_return_val_if_fail(option != NULL, NULL);
1163 toggle = gtk_check_button_new_with_label(_(label));
1165 may_add_tip(toggle, node);
1167 option->update_widget = update_toggle;
1168 option->read_widget = read_toggle;
1169 option->widget = toggle;
1171 gtk_signal_connect_object(GTK_OBJECT(toggle), "toggled",
1172 GTK_SIGNAL_FUNC(option_check_widget),
1173 (GtkObject *) option);
1175 return g_list_append(NULL, toggle);
1178 static GList *build_slider(Option *option, xmlNode *node, guchar *label)
1180 GtkAdjustment *adj;
1181 GtkWidget *hbox, *slide;
1182 int min, max;
1183 int fixed;
1184 int showvalue;
1185 guchar *end;
1187 g_return_val_if_fail(option != NULL, NULL);
1189 min = get_int(node, "min");
1190 max = get_int(node, "max");
1191 fixed = get_int(node, "fixed");
1192 showvalue = get_int(node, "showvalue");
1194 adj = GTK_ADJUSTMENT(gtk_adjustment_new(0,
1195 min, max, 1, 10, 0));
1197 hbox = gtk_hbox_new(FALSE, 4);
1198 gtk_box_pack_start(GTK_BOX(hbox),
1199 gtk_label_new(_(label)),
1200 FALSE, TRUE, 0);
1202 end = xmlGetProp(node, "end");
1203 if (end)
1205 gtk_box_pack_end(GTK_BOX(hbox), gtk_label_new(_(end)),
1206 FALSE, TRUE, 0);
1207 g_free(end);
1210 slide = gtk_hscale_new(adj);
1212 if (fixed)
1213 gtk_widget_set_usize(slide, adj->upper, 24);
1214 if (showvalue)
1216 gtk_scale_set_draw_value(GTK_SCALE(slide), TRUE);
1217 gtk_scale_set_value_pos(GTK_SCALE(slide),
1218 GTK_POS_LEFT);
1219 gtk_scale_set_digits(GTK_SCALE(slide), 0);
1221 else
1222 gtk_scale_set_draw_value(GTK_SCALE(slide), FALSE);
1223 GTK_WIDGET_UNSET_FLAGS(slide, GTK_CAN_FOCUS);
1225 may_add_tip(slide, node);
1227 gtk_box_pack_start(GTK_BOX(hbox), slide, !fixed, TRUE, 0);
1229 option->update_widget = update_slider;
1230 option->read_widget = read_slider;
1231 option->widget = slide;
1233 gtk_signal_connect_object(GTK_OBJECT(adj), "value-changed",
1234 GTK_SIGNAL_FUNC(option_check_widget),
1235 (GtkObject *) option);
1237 return g_list_append(NULL, hbox);
1240 static GList *build_entry(Option *option, xmlNode *node, guchar *label)
1242 GtkWidget *hbox;
1243 GtkWidget *entry;
1245 g_return_val_if_fail(option != NULL, NULL);
1247 hbox = gtk_hbox_new(FALSE, 4);
1249 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_(label)),
1250 FALSE, TRUE, 0);
1252 entry = gtk_entry_new();
1253 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
1254 may_add_tip(entry, node);
1256 option->update_widget = update_entry;
1257 option->read_widget = read_entry;
1258 option->widget = entry;
1260 gtk_signal_connect_object_after(GTK_OBJECT(entry), "changed",
1261 GTK_SIGNAL_FUNC(option_check_widget),
1262 (GtkObject *) option);
1264 return g_list_append(NULL, hbox);
1267 static GList *build_radio_group(Option *option, xmlNode *node, guchar *label)
1269 GList *list = NULL;
1270 GtkWidget *button = NULL;
1271 xmlNode *rn;
1273 g_return_val_if_fail(option != NULL, NULL);
1275 for (rn = node->xmlChildrenNode; rn; rn = rn->next)
1277 if (rn->type == XML_ELEMENT_NODE)
1279 button = build_radio(rn, button);
1280 gtk_signal_connect_object(GTK_OBJECT(button), "toggled",
1281 GTK_SIGNAL_FUNC(option_check_widget),
1282 (GtkObject *) option);
1283 list = g_list_append(list, button);
1287 option->update_widget = update_radio_group;
1288 option->read_widget = read_radio_group;
1289 option->widget = button;
1291 return list;
1294 static GList *build_colour(Option *option, xmlNode *node, guchar *label)
1296 GtkWidget *hbox, *da, *button;
1297 int lpos;
1299 g_return_val_if_fail(option != NULL, NULL);
1301 /* lpos gives the position for the label
1302 * 0: label comes before the button
1303 * non-zero: label comes after the button
1305 lpos = get_int(node, "lpos");
1307 hbox = gtk_hbox_new(FALSE, 4);
1308 if (lpos == 0)
1309 gtk_box_pack_start(GTK_BOX(hbox),
1310 gtk_label_new(_(label)),
1311 FALSE, TRUE, 0);
1313 button = gtk_button_new();
1314 da = gtk_drawing_area_new();
1315 gtk_drawing_area_size(GTK_DRAWING_AREA(da), 64, 12);
1316 gtk_container_add(GTK_CONTAINER(button), da);
1317 gtk_signal_connect(GTK_OBJECT(button), "clicked",
1318 GTK_SIGNAL_FUNC(open_coloursel), option);
1320 may_add_tip(button, node);
1322 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1323 if (lpos)
1324 gtk_box_pack_start(GTK_BOX(hbox),
1325 gtk_label_new(_(label)),
1326 FALSE, TRUE, 0);
1328 option->update_widget = update_colour;
1329 option->read_widget = read_colour;
1330 option->widget = button;
1332 return g_list_append(NULL, hbox);
1335 static GList *build_menu(Option *option, xmlNode *node, guchar *label)
1337 GtkWidget *hbox, *om, *option_menu;
1338 xmlNode *item;
1339 GtkWidget *menu;
1340 GList *list, *kids;
1341 int min_w = 4, min_h = 4;
1343 g_return_val_if_fail(option != NULL, NULL);
1345 hbox = gtk_hbox_new(FALSE, 4);
1347 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_(label)),
1348 FALSE, TRUE, 0);
1350 option_menu = gtk_option_menu_new();
1351 gtk_box_pack_start(GTK_BOX(hbox), option_menu, FALSE, TRUE, 0);
1353 om = gtk_menu_new();
1354 gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), om);
1356 for (item = node->xmlChildrenNode; item; item = item->next)
1358 if (item->type == XML_ELEMENT_NODE)
1359 build_menu_item(item, option_menu);
1362 menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(option_menu));
1363 list = kids = gtk_container_children(GTK_CONTAINER(menu));
1365 while (kids)
1367 GtkWidget *item = (GtkWidget *) kids->data;
1368 GtkRequisition req;
1370 gtk_widget_size_request(item, &req);
1371 if (req.width > min_w)
1372 min_w = req.width;
1373 if (req.height > min_h)
1374 min_h = req.height;
1376 kids = kids->next;
1379 g_list_free(list);
1381 gtk_widget_set_usize(option_menu,
1382 min_w + 50, /* Else node doesn't work! */
1383 min_h + 4);
1385 option->update_widget = update_menu;
1386 option->read_widget = read_menu;
1387 option->widget = option_menu;
1389 gtk_signal_connect_object_after(GTK_OBJECT(option_menu), "changed",
1390 GTK_SIGNAL_FUNC(option_check_widget),
1391 (GtkObject *) option);
1393 return g_list_append(NULL, hbox);
1396 static GList *build_font(Option *option, xmlNode *node, guchar *label)
1398 GtkWidget *hbox, *button;
1400 g_return_val_if_fail(option != NULL, NULL);
1402 hbox = gtk_hbox_new(FALSE, 4);
1404 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_(label)),
1405 FALSE, TRUE, 0);
1407 button = gtk_button_new_with_label("");
1408 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1410 option->update_widget = update_font;
1411 option->read_widget = read_font;
1412 option->widget = GTK_BIN(button)->child;
1413 may_add_tip(button, node);
1415 gtk_signal_connect(GTK_OBJECT(button), "clicked",
1416 GTK_SIGNAL_FUNC(open_fontsel), (GtkObject *) option);
1418 return g_list_append(NULL, hbox);
1421 static void button_patch_set_colour(GtkWidget *button, GdkColor *colour)
1423 GtkStyle *style;
1424 GtkWidget *patch;
1426 patch = GTK_BIN(button)->child;
1428 style = gtk_style_copy(GTK_WIDGET(patch)->style);
1429 style->bg[GTK_STATE_NORMAL].red = colour->red;
1430 style->bg[GTK_STATE_NORMAL].green = colour->green;
1431 style->bg[GTK_STATE_NORMAL].blue = colour->blue;
1432 gtk_widget_set_style(patch, style);
1433 gtk_style_unref(style);
1435 if (GTK_WIDGET_REALIZED(patch))
1436 gdk_window_clear(patch->window);
1439 static void load_options(xmlDoc *doc)
1441 xmlNode *root, *node;
1443 root = xmlDocGetRootElement(doc);
1445 g_return_if_fail(strcmp(root->name, "Options") == 0);
1447 for (node = root->xmlChildrenNode; node; node = node->next)
1449 gchar *value, *name;
1451 if (node->type != XML_ELEMENT_NODE)
1452 continue;
1453 if (strcmp(node->name, "Option") != 0)
1454 continue;
1455 name = xmlGetProp(node, "name");
1456 if (!name)
1457 continue;
1459 value = xmlNodeGetContent(node);
1461 if (g_hash_table_lookup(loading, name))
1462 g_warning("Duplicate option found!");
1464 g_hash_table_insert(loading, name, value);
1466 /* (don't need to free name or value) */
1470 /* Process one line from the options file (\0 term'd).
1471 * Returns NULL on success, or a pointer to an error message.
1472 * The line is modified.
1474 static const char *process_option_line(gchar *line)
1476 gchar *eq, *c;
1477 gchar *name = line;
1479 g_return_val_if_fail(option_hash != NULL, "No registered options!");
1481 eq = strchr(line, '=');
1482 if (!eq)
1483 return _("Missing '='");
1485 c = eq - 1;
1486 while (c > line && (*c == ' ' || *c == '\t'))
1487 c--;
1488 c[1] = '\0';
1489 c = eq + 1;
1490 while (*c == ' ' || *c == '\t')
1491 c++;
1493 if (g_hash_table_lookup(loading, name))
1494 return "Duplicate option found!";
1496 g_hash_table_insert(loading, g_strdup(name), g_strdup(g_strstrip(c)));
1498 return NULL;