r1412: Merged OK and Save buttons in the Options box. Revert shades when it would
[rox-filer.git] / ROX-Filer / src / options.c
blobccaa63dbdd9e1a7515bf8ceac8286b9ab8ca023c
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 #include <stdio.h>
72 #include <stdlib.h>
73 #include <string.h>
74 #include <errno.h>
75 #include <ctype.h>
76 #include <gtk/gtk.h>
77 #include <libxml/parser.h>
79 #include "global.h"
81 #include "choices.h"
82 #include "options.h"
83 #include "main.h"
84 #include "gui_support.h"
86 /* Add all option tooltips to this group */
87 static GtkTooltips *option_tooltips = NULL;
88 #define OPTION_TIP(widget, tip) \
89 gtk_tooltips_set_tip(option_tooltips, widget, tip, NULL)
91 /* The Options window. NULL if not yet created. */
92 static GtkWidget *window = NULL;
94 /* "filer_unique" -> (Option *) */
95 static GHashTable *option_hash = NULL;
97 /* A mapping (name -> value) for options which have been loaded by not
98 * yet registered. The options in this table cannot be used until
99 * option_add_*() is called to move them into option_hash.
101 static GHashTable *loading = NULL;
103 /* A mapping (XML name -> OptionBuildFn). When reading the Options.xml
104 * file, this table gives the function used to create the widgets.
106 static GHashTable *widget_builder = NULL;
108 /* A mapping (name -> GtkSizeGroup) of size groups used by the widgets
109 * in the options box. This hash table is created/destroyed every time
110 * the box is opened/destroyed.
112 static GHashTable *size_groups = NULL;
114 /* List of functions to call after all option values are updated */
115 static GList *notify_callbacks = NULL;
117 /* List of functions to call after all options are saved */
118 static GList *saver_callbacks = NULL;
120 static int updating_widgets = 0; /* Ignore change signals when set */
122 static GtkWidget *revert_widget = NULL;
124 /* Static prototypes */
125 static void save_options(gpointer unused);
126 static void revert_options(GtkWidget *widget, gpointer data);
127 static void build_options_window(void);
128 static GtkWidget *build_window_frame(GtkTreeView **tree_view);
129 static void update_option_widgets(void);
130 static void button_patch_set_colour(GtkWidget *button, GdkColor *color);
131 static void option_add(Option *option, const gchar *key);
132 static void set_not_changed(gpointer key, gpointer value, gpointer data);
133 static void load_options(xmlDoc *doc);
134 static gboolean check_anything_changed(void);
136 static const char *process_option_line(gchar *line);
138 static GList *build_label(Option *option, xmlNode *node, guchar *label);
139 static GList *build_spacer(Option *option, xmlNode *node, guchar *label);
140 static GList *build_frame(Option *option, xmlNode *node, guchar *label);
142 static GList *build_toggle(Option *option, xmlNode *node, guchar *label);
143 static GList *build_slider(Option *option, xmlNode *node, guchar *label);
144 static GList *build_entry(Option *option, xmlNode *node, guchar *label);
145 static GList *build_numentry(Option *option, xmlNode *node, guchar *label);
146 static GList *build_radio_group(Option *option, xmlNode *node, guchar *label);
147 static GList *build_colour(Option *option, xmlNode *node, guchar *label);
148 static GList *build_menu(Option *option, xmlNode *node, guchar *label);
149 static GList *build_font(Option *option, xmlNode *node, guchar *label);
151 static gboolean updating_file_format = FALSE;
153 /****************************************************************
154 * EXTERNAL INTERFACE *
155 ****************************************************************/
157 void options_init(void)
159 char *path;
160 xmlDoc *doc;
162 loading = g_hash_table_new(g_str_hash, g_str_equal);
163 option_hash = g_hash_table_new(g_str_hash, g_str_equal);
164 widget_builder = g_hash_table_new(g_str_hash, g_str_equal);
166 path = choices_find_path_load("Options", PROJECT);
167 if (path)
169 /* Load in all the options set in the filer, storing them
170 * temporarily in the loading hash table.
171 * They get moved to option_hash when they're registered.
173 doc = xmlParseFile(path);
174 if (doc)
176 load_options(doc);
177 xmlFreeDoc(doc);
179 else
181 parse_file(path, process_option_line);
182 updating_file_format = TRUE;
185 g_free(path);
188 option_register_widget("label", build_label);
189 option_register_widget("spacer", build_spacer);
190 option_register_widget("frame", build_frame);
192 option_register_widget("toggle", build_toggle);
193 option_register_widget("slider", build_slider);
194 option_register_widget("entry", build_entry);
195 option_register_widget("numentry", build_numentry);
196 option_register_widget("radio-group", build_radio_group);
197 option_register_widget("colour", build_colour);
198 option_register_widget("menu", build_menu);
199 option_register_widget("font", build_font);
202 /* When parsing the XML file, process an element named 'name' by
203 * calling 'builder(option, xml_node, label)'.
204 * builder returns the new widgets to add to the options box.
205 * 'name' should be a static string. Call 'option_check_widget' when
206 * the widget's value is modified.
208 * Functions to set or get the widget's state can be stored in 'option'.
209 * If the option doesn't have a name attribute in Options.xml then
210 * ui will be NULL on entry (this is used for buttons).
212 void option_register_widget(char *name, OptionBuildFn builder)
214 g_hash_table_insert(widget_builder, name, builder);
217 /* This is called when the widget's value is modified by the user.
218 * Reads the new value of the widget into the option and calls
219 * the notify callbacks.
221 void option_check_widget(Option *option)
223 guchar *new = NULL;
225 if (updating_widgets)
226 return; /* Not caused by the user... */
228 g_return_if_fail(option->read_widget != NULL);
230 new = option->read_widget(option);
232 g_return_if_fail(new != NULL);
234 g_hash_table_foreach(option_hash, set_not_changed, NULL);
236 option->has_changed = strcmp(option->value, new) != 0;
238 if (!option->has_changed)
240 g_free(new);
241 return;
244 g_free(option->value);
245 option->value = new;
246 option->int_value = atoi(new);
248 options_notify();
251 /* Call all the notify callbacks. This should happen after any options
252 * have their values changed.
253 * Set each option->has_changed flag before calling this function.
255 void options_notify(void)
257 GList *next;
259 for (next = notify_callbacks; next; next = next->next)
261 OptionNotify *cb = (OptionNotify *) next->data;
263 cb();
266 if (updating_file_format)
268 updating_file_format = FALSE;
269 save_options(NULL);
270 info_message(_("ROX-Filer has converted your Options file "
271 "to the new XML format"));
274 if (revert_widget)
275 gtk_widget_set_sensitive(revert_widget,
276 check_anything_changed());
279 /* Store values used by Revert */
280 static void store_backup(gpointer key, gpointer value, gpointer data)
282 Option *option = (Option *) value;
284 g_free(option->backup);
285 option->backup = g_strdup(option->value);
288 /* Allow the user to edit the options. Returns the window widget (you don't
289 * normally need this). NULL if already open.
291 GtkWidget *options_show(void)
293 if (!option_tooltips)
294 option_tooltips = gtk_tooltips_new();
296 if (g_hash_table_size(loading) != 0)
298 g_printerr(PROJECT ": Some options loaded but not used:\n");
299 g_hash_table_foreach(loading, (GHFunc) puts, NULL);
302 if (window)
304 gtk_window_present(GTK_WINDOW(window));
305 return NULL;
308 g_hash_table_foreach(option_hash, store_backup, NULL);
310 build_options_window();
312 update_option_widgets();
314 gtk_widget_show_all(window);
316 return window;
319 /* Initialise and register a new integer option */
320 void option_add_int(Option *option, const gchar *key, int value)
322 option->value = g_strdup_printf("%d", value);
323 option->int_value = value;
324 option_add(option, key);
327 void option_add_string(Option *option, const gchar *key, const gchar *value)
329 option->value = g_strdup(value);
330 option->int_value = atoi(value);
331 option_add(option, key);
334 /* Add a callback which will be called after any options have changed their
335 * values. If several options change at once, this is called after all
336 * changes.
338 void option_add_notify(OptionNotify *callback)
340 g_return_if_fail(callback != NULL);
342 notify_callbacks = g_list_append(notify_callbacks, callback);
345 /* Call 'callback' after all the options have been saved */
346 void option_add_saver(OptionNotify *callback)
348 g_return_if_fail(callback != NULL);
350 saver_callbacks = g_list_append(saver_callbacks, callback);
353 /****************************************************************
354 * INTERNAL FUNCTIONS *
355 ****************************************************************/
357 /* Option should contain the default value.
358 * It must never be destroyed after being registered (Options are typically
359 * statically allocated).
360 * The key corresponds to the option's name in Options.xml, and to the key
361 * in the saved options file.
363 * On exit, the value will have been updated to the loaded value, if
364 * different to the default.
366 static void option_add(Option *option, const gchar *key)
368 gpointer okey, value;
370 g_return_if_fail(option_hash != NULL);
371 g_return_if_fail(g_hash_table_lookup(option_hash, key) == NULL);
372 g_return_if_fail(option->value != NULL);
374 option->has_changed = FALSE;
376 option->widget = NULL;
377 option->update_widget = NULL;
378 option->read_widget = NULL;
379 option->backup = NULL;
381 g_hash_table_insert(option_hash, (gchar *) key, option);
383 /* Use the value loaded from the file, if any */
384 if (g_hash_table_lookup_extended(loading, key, &okey, &value))
386 option->has_changed = strcmp(option->value, value) != 0;
388 g_free(option->value);
389 option->value = value;
390 option->int_value = atoi(value);
391 g_hash_table_remove(loading, key);
392 g_free(okey);
396 static GtkColorSelectionDialog *current_csel_box = NULL;
397 static GtkFontSelectionDialog *current_fontsel_box = NULL;
399 static void get_new_colour(GtkWidget *ok, Option *option)
401 GtkWidget *csel;
402 GdkColor c;
404 g_return_if_fail(current_csel_box != NULL);
406 csel = current_csel_box->colorsel;
408 gtk_color_selection_get_current_color(GTK_COLOR_SELECTION(csel), &c);
410 button_patch_set_colour(option->widget, &c);
412 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
414 option_check_widget(option);
417 static void open_coloursel(GtkWidget *button, Option *option)
419 GtkColorSelectionDialog *csel;
420 GtkWidget *dialog, *patch;
422 if (current_csel_box)
423 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
425 dialog = gtk_color_selection_dialog_new(NULL);
426 csel = GTK_COLOR_SELECTION_DIALOG(dialog);
427 current_csel_box = csel;
428 gtk_window_set_position(GTK_WINDOW(csel), GTK_WIN_POS_MOUSE);
430 g_signal_connect(dialog, "destroy",
431 G_CALLBACK(gtk_widget_destroyed), &current_csel_box);
432 gtk_widget_hide(csel->help_button);
433 g_signal_connect_swapped(csel->cancel_button, "clicked",
434 G_CALLBACK(gtk_widget_destroy), dialog);
435 g_signal_connect(csel->ok_button, "clicked",
436 G_CALLBACK(get_new_colour), option);
438 patch = GTK_BIN(button)->child;
440 gtk_color_selection_set_current_color(
441 GTK_COLOR_SELECTION(csel->colorsel),
442 &patch->style->bg[GTK_STATE_NORMAL]);
444 gtk_widget_show(dialog);
447 static void font_chosen(GtkWidget *dialog, gint response, Option *option)
449 gchar *font;
451 if (response != GTK_RESPONSE_OK)
452 goto out;
454 font = gtk_font_selection_dialog_get_font_name(
455 GTK_FONT_SELECTION_DIALOG(dialog));
457 gtk_label_set_text(GTK_LABEL(option->widget), font);
459 g_free(font);
461 option_check_widget(option);
463 out:
464 gtk_widget_destroy(dialog);
468 static void open_fontsel(GtkWidget *button, Option *option)
470 if (current_fontsel_box)
471 gtk_widget_destroy(GTK_WIDGET(current_fontsel_box));
473 current_fontsel_box = GTK_FONT_SELECTION_DIALOG(
474 gtk_font_selection_dialog_new(PROJECT));
476 gtk_window_set_position(GTK_WINDOW(current_fontsel_box),
477 GTK_WIN_POS_MOUSE);
479 g_signal_connect(current_fontsel_box, "destroy",
480 G_CALLBACK(gtk_widget_destroyed), &current_fontsel_box);
482 gtk_font_selection_dialog_set_font_name(current_fontsel_box,
483 option->value);
485 g_signal_connect(current_fontsel_box, "response",
486 G_CALLBACK(font_chosen), option);
488 gtk_widget_show(GTK_WIDGET(current_fontsel_box));
491 /* These are used during parsing... */
492 static xmlDocPtr options_doc = NULL;
494 #define DATA(node) (xmlNodeListGetString(options_doc, node->xmlChildrenNode, 1))
496 static void may_add_tip(GtkWidget *widget, xmlNode *element)
498 guchar *data, *tip;
500 data = DATA(element);
501 if (!data)
502 return;
504 tip = g_strstrip(g_strdup(data));
505 g_free(data);
506 if (*tip)
507 OPTION_TIP(widget, _(tip));
508 g_free(tip);
511 /* Returns zero if attribute is not present */
512 static int get_int(xmlNode *node, guchar *attr)
514 guchar *txt;
515 int retval;
517 txt = xmlGetProp(node, attr);
518 if (!txt)
519 return 0;
521 retval = atoi(txt);
522 g_free(txt);
524 return retval;
527 /* Adds 'widget' to the GtkSizeGroup selected by 'index'. This function
528 * does nothing if 'node' has no "sizegroup" attribute.
529 * The value of "sizegroup" is either a key. All widgets with the same
530 * key request the same size.
531 * Size groups are created on the fly and get destroyed when the options
532 * box is closed.
534 static void add_to_size_group(xmlNode *node, GtkWidget *widget)
536 GtkSizeGroup *sg;
537 guchar *name;
539 g_return_if_fail(node != NULL);
540 g_return_if_fail(widget != NULL);
542 name = xmlGetProp(node, "sizegroup");
543 if (!name)
544 return;
546 if (size_groups == NULL)
547 size_groups = g_hash_table_new_full(g_str_hash, g_str_equal,
548 g_free, NULL);
550 sg = (GtkSizeGroup *) g_hash_table_lookup(size_groups, name);
551 if (sg == NULL)
554 sg = (GtkSizeGroup *) gtk_size_group_new(
555 GTK_SIZE_GROUP_HORIZONTAL);
556 g_hash_table_insert(size_groups, name, sg);
557 gtk_size_group_add_widget(sg, widget);
558 g_object_unref(G_OBJECT(sg));
560 else
562 gtk_size_group_add_widget(sg, widget);
563 g_free(name);
567 static GtkWidget *build_radio(xmlNode *radio, GtkWidget *prev)
569 GtkWidget *button;
570 GtkRadioButton *prev_button = (GtkRadioButton *) prev;
571 guchar *label;
573 label = xmlGetProp(radio, "label");
575 button = gtk_radio_button_new_with_label(
576 prev_button ? gtk_radio_button_get_group(prev_button)
577 : NULL,
578 _(label));
579 g_free(label);
581 may_add_tip(button, radio);
583 g_object_set_data(G_OBJECT(button), "value",
584 xmlGetProp(radio, "value"));
586 return button;
589 static void build_menu_item(xmlNode *node, GtkWidget *option_menu)
591 GtkWidget *item, *menu;
592 guchar *label;
594 g_return_if_fail(strcmp(node->name, "item") == 0);
596 label = xmlGetProp(node, "label");
597 item = gtk_menu_item_new_with_label(_(label));
598 g_free(label);
600 menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(option_menu));
601 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
602 gtk_widget_show_all(menu);
604 g_object_set_data(G_OBJECT(item), "value", xmlGetProp(node, "value"));
607 static void build_widget(xmlNode *widget, GtkWidget *box)
609 const char *name = widget->name;
610 OptionBuildFn builder;
611 guchar *oname;
612 Option *option;
613 guchar *label;
615 label = xmlGetProp(widget, "label");
617 if (strcmp(name, "hbox") == 0 || strcmp(name, "vbox") == 0)
619 GtkWidget *nbox;
620 xmlNode *hw;
622 if (name[0] == 'h')
623 nbox = gtk_hbox_new(FALSE, 4);
624 else
625 nbox = gtk_vbox_new(FALSE, 0);
627 if (label)
628 gtk_box_pack_start(GTK_BOX(nbox),
629 gtk_label_new(_(label)), FALSE, TRUE, 4);
630 gtk_box_pack_start(GTK_BOX(box), nbox, FALSE, TRUE, 0);
632 for (hw = widget->xmlChildrenNode; hw; hw = hw->next)
634 if (hw->type == XML_ELEMENT_NODE)
635 build_widget(hw, nbox);
638 g_free(label);
639 return;
642 oname = xmlGetProp(widget, "name");
644 if (oname)
646 option = g_hash_table_lookup(option_hash, oname);
648 if (!option)
650 g_warning("No Option for '%s'!\n", oname);
651 g_free(oname);
652 return;
655 g_free(oname);
657 else
658 option = NULL;
660 builder = g_hash_table_lookup(widget_builder, name);
661 if (builder)
663 GList *widgets, *next;
665 if (option && option->widget)
666 g_warning("Widget for option already exists!");
668 widgets = builder(option, widget, label);
670 for (next = widgets; next; next = next->next)
672 GtkWidget *w = (GtkWidget *) next->data;
673 gtk_box_pack_start(GTK_BOX(box), w, FALSE, TRUE, 0);
675 g_list_free(widgets);
677 else
678 g_warning("Unknown option type '%s'\n", name);
680 g_free(label);
683 static void build_section(xmlNode *section, GtkWidget *notebook,
684 GtkTreeStore *tree_store, GtkTreeIter *parent)
686 guchar *title = NULL;
687 GtkWidget *page;
688 GtkTreeIter iter;
689 xmlNode *widget;
691 title = xmlGetProp(section, "title");
692 page = gtk_vbox_new(FALSE, 4);
693 gtk_container_set_border_width(GTK_CONTAINER(page), 4);
694 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, NULL);
696 gtk_tree_store_append(tree_store, &iter, parent);
697 gtk_tree_store_set(tree_store, &iter, 0, _(title), 1, page, -1);
698 g_free(title);
700 widget = section->xmlChildrenNode;
701 for (; widget; widget = widget->next)
703 if (widget->type == XML_ELEMENT_NODE)
705 if (strcmp(widget->name, "section") == 0)
706 build_section(widget, notebook,
707 tree_store, &iter);
708 else
709 build_widget(widget, page);
714 /* Parse <app_dir>/Options.xml to create the options window.
715 * Sets the global 'window' variable.
717 static void build_options_window(void)
719 GtkTreeView *tree;
720 GtkTreeStore *store;
721 GtkWidget *notebook;
722 xmlDocPtr options_doc;
723 xmlNode *options, *section;
724 gchar *path;
726 notebook = build_window_frame(&tree);
728 path = g_strconcat(app_dir, "/Options.xml", NULL);
729 options_doc = xmlParseFile(path);
731 if (!options_doc)
733 report_error("Internal error: %s unreadable", path);
734 g_free(path);
735 return;
738 g_free(path);
740 options = xmlDocGetRootElement(options_doc);
741 if (strcmp(options->name, "options") == 0)
743 GtkTreePath *treepath;
745 store = (GtkTreeStore *) gtk_tree_view_get_model(tree);
746 section = options->xmlChildrenNode;
747 for (; section; section = section->next)
748 if (section->type == XML_ELEMENT_NODE)
749 build_section(section, notebook, store, NULL);
751 gtk_tree_view_expand_all(tree);
752 treepath = gtk_tree_path_new_first();
753 if (treepath)
755 gtk_tree_view_set_cursor(tree, treepath, NULL, FALSE);
756 gtk_tree_path_free(treepath);
760 xmlFreeDoc(options_doc);
761 options_doc = NULL;
764 static void null_widget(gpointer key, gpointer value, gpointer data)
766 Option *option = (Option *) value;
768 g_return_if_fail(option->widget != NULL);
770 option->widget = NULL;
773 static void options_destroyed(GtkWidget *widget, gpointer data)
775 if (current_csel_box)
776 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
777 if (current_fontsel_box)
778 gtk_widget_destroy(GTK_WIDGET(current_fontsel_box));
780 revert_widget = NULL;
782 if (widget == window)
784 window = NULL;
786 g_hash_table_foreach(option_hash, null_widget, NULL);
788 if (size_groups)
790 g_hash_table_destroy(size_groups);
791 size_groups = NULL;
797 /* The cursor has been changed in the tree view, so switch to the new
798 * page in the notebook.
800 static void tree_cursor_changed(GtkTreeView *tv, gpointer data)
802 GtkTreePath *path = NULL;
803 GtkNotebook *nbook = GTK_NOTEBOOK(data);
804 GtkTreeModel *model;
805 GtkWidget *page = NULL;
806 GtkTreeIter iter;
808 gtk_tree_view_get_cursor(tv, &path, NULL);
809 if (!path)
810 return;
812 model = gtk_tree_view_get_model(tv);
813 gtk_tree_model_get_iter(model, &iter, path);
814 gtk_tree_path_free(path);
815 gtk_tree_model_get(model, &iter, 1, &page, -1);
817 if (page)
818 gtk_notebook_set_current_page(nbook,
819 gtk_notebook_page_num(nbook, page));
822 /* Creates the window and adds the various buttons to it.
823 * Returns the notebook to add sections to and sets the global
824 * 'window'. If 'tree_view' is non-NULL, it stores the address
825 * of the tree view widget there.
827 static GtkWidget *build_window_frame(GtkTreeView **tree_view)
829 GtkWidget *notebook;
830 GtkWidget *tl_vbox, *hbox, *sw, *tv;
831 GtkWidget *actions, *button, *frame;
832 GtkTreeStore *model;
833 char *string, *save_path;
835 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
837 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
838 gtk_window_set_title(GTK_WINDOW(window), _("Options"));
839 g_signal_connect(window, "destroy",
840 G_CALLBACK(options_destroyed), NULL);
841 gtk_container_set_border_width(GTK_CONTAINER(window), 4);
843 tl_vbox = gtk_vbox_new(FALSE, 4);
844 gtk_container_add(GTK_CONTAINER(window), tl_vbox);
846 hbox = gtk_hbox_new(FALSE, 4);
847 gtk_box_pack_start(GTK_BOX(tl_vbox), hbox, TRUE, TRUE, 0);
849 frame = gtk_frame_new(NULL);
850 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
851 gtk_box_pack_end(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
853 notebook = gtk_notebook_new();
854 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
855 gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
856 gtk_container_add(GTK_CONTAINER(frame), notebook);
858 /* scrolled window for the tree view */
859 sw = gtk_scrolled_window_new(NULL, NULL);
860 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
861 GTK_SHADOW_IN);
862 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
863 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
864 gtk_box_pack_start(GTK_BOX(hbox), sw, FALSE, TRUE, 0);
866 /* tree view */
867 model = gtk_tree_store_new(2, G_TYPE_STRING, GTK_TYPE_WIDGET);
868 tv = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
869 g_object_unref(model);
870 gtk_tree_selection_set_mode(
871 gtk_tree_view_get_selection(GTK_TREE_VIEW(tv)),
872 GTK_SELECTION_BROWSE);
873 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv), FALSE);
874 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tv), -1,
875 NULL, gtk_cell_renderer_text_new(), "text", 0, NULL);
876 gtk_container_add(GTK_CONTAINER(sw), tv);
877 g_signal_connect(tv, "cursor_changed",
878 G_CALLBACK(tree_cursor_changed), notebook);
880 actions = gtk_hbutton_box_new();
881 gtk_button_box_set_layout(GTK_BUTTON_BOX(actions),
882 GTK_BUTTONBOX_END);
883 gtk_box_set_spacing(GTK_BOX(actions), 10);
885 gtk_box_pack_start(GTK_BOX(tl_vbox), actions, FALSE, TRUE, 0);
887 revert_widget = button_new_mixed(GTK_STOCK_UNDO, _("_Revert"));
888 GTK_WIDGET_SET_FLAGS(revert_widget, GTK_CAN_DEFAULT);
889 gtk_box_pack_start(GTK_BOX(actions), revert_widget, FALSE, TRUE, 0);
890 g_signal_connect(revert_widget, "clicked",
891 G_CALLBACK(revert_options), NULL);
892 gtk_tooltips_set_tip(option_tooltips, revert_widget,
893 _("Restore all choices to how they were when the "
894 "Options box was opened."), NULL);
895 gtk_widget_set_sensitive(revert_widget, check_anything_changed());
897 button = gtk_button_new_from_stock(GTK_STOCK_OK);
898 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
899 gtk_box_pack_start(GTK_BOX(actions), button, FALSE, TRUE, 0);
900 g_signal_connect_swapped(button, "clicked",
901 G_CALLBACK(save_options), NULL);
902 gtk_widget_grab_default(button);
903 gtk_widget_grab_focus(button);
905 save_path = choices_find_path_save("...", PROJECT, FALSE);
906 if (save_path)
908 string = g_strdup_printf(_("Choices will be saved as:\n%s"),
909 save_path);
910 gtk_tooltips_set_tip(option_tooltips, button, string, NULL);
911 g_free(string);
912 g_free(save_path);
914 else
915 gtk_tooltips_set_tip(option_tooltips, button,
916 _("(saving disabled by CHOICESPATH)"), NULL);
918 if (tree_view)
919 *tree_view = GTK_TREE_VIEW(tv);
921 return notebook;
924 /* Given the last radio button in the group, select whichever
925 * radio button matches the given value.
927 static void radio_group_set_value(GtkRadioButton *last, guchar *value)
929 GSList *next;
931 for (next = gtk_radio_button_get_group(last); next; next = next->next)
933 GtkToggleButton *button = (GtkToggleButton *) next->data;
934 guchar *val;
936 val = g_object_get_data(G_OBJECT(button), "value");
937 g_return_if_fail(val != NULL);
939 if (strcmp(val, value) == 0)
941 gtk_toggle_button_set_active(button, TRUE);
942 return;
946 g_warning("Can't find radio button with value %s\n", value);
949 /* Given the last radio button in the group, return a copy of the
950 * value for the selected radio item.
952 static guchar *radio_group_get_value(GtkRadioButton *last)
954 GSList *next;
956 for (next = gtk_radio_button_get_group(last); next; next = next->next)
958 GtkToggleButton *button = (GtkToggleButton *) next->data;
960 if (gtk_toggle_button_get_active(button))
962 guchar *val;
964 val = g_object_get_data(G_OBJECT(button), "value");
965 g_return_val_if_fail(val != NULL, NULL);
967 return g_strdup(val);
971 return NULL;
974 /* Select this item with this value */
975 static void option_menu_set(GtkOptionMenu *om, guchar *value)
977 GtkWidget *menu;
978 GList *list, *next;
979 int i = 0;
981 menu = gtk_option_menu_get_menu(om);
982 list = gtk_container_get_children(GTK_CONTAINER(menu));
984 for (next = list; next; next = next->next)
986 GObject *item = (GObject *) next->data;
987 guchar *data;
989 data = g_object_get_data(item, "value");
990 g_return_if_fail(data != NULL);
992 if (strcmp(data, value) == 0)
994 gtk_option_menu_set_history(om, i);
995 break;
998 i++;
1001 g_list_free(list);
1004 /* Get the value (static) of the selected item */
1005 static guchar *option_menu_get(GtkOptionMenu *om)
1007 GtkWidget *menu, *item;
1009 menu = gtk_option_menu_get_menu(om);
1010 item = gtk_menu_get_active(GTK_MENU(menu));
1012 return g_object_get_data(G_OBJECT(item), "value");
1015 static void restore_backup(gpointer key, gpointer value, gpointer data)
1017 Option *option = (Option *) value;
1019 g_return_if_fail(option->backup != NULL);
1021 option->has_changed = strcmp(option->value, option->backup) != 0;
1022 if (!option->has_changed)
1023 return;
1025 g_free(option->value);
1026 option->value = g_strdup(option->backup);
1027 option->int_value = atoi(option->value);
1030 static void revert_options(GtkWidget *widget, gpointer data)
1032 g_hash_table_foreach(option_hash, restore_backup, NULL);
1033 options_notify();
1034 update_option_widgets();
1037 static void check_changed_cb(gpointer key, gpointer value, gpointer data)
1039 Option *option = (Option *) value;
1040 gboolean *changed = (gboolean *) data;
1042 g_return_if_fail(option->backup != NULL);
1044 if (*changed)
1045 return;
1047 if (strcmp(option->value, option->backup) != 0)
1048 *changed = TRUE;
1051 static gboolean check_anything_changed(void)
1053 gboolean retval = FALSE;
1055 g_hash_table_foreach(option_hash, check_changed_cb, &retval);
1057 return retval;
1060 static void write_option(gpointer key, gpointer value, gpointer data)
1062 xmlNodePtr doc = (xmlNodePtr) data;
1063 Option *option = (Option *) value;
1064 xmlNodePtr tree;
1066 tree = xmlNewTextChild(doc, NULL, "Option", option->value);
1067 xmlSetProp(tree, "name", (gchar *) key);
1070 /* Save doc as XML as filename, 0 on success or -1 on failure */
1071 static int save_xml_file(xmlDocPtr doc, gchar *filename)
1073 #if LIBXML_VERSION > 20400
1074 if (xmlSaveFormatFileEnc(filename, doc, NULL, 1) < 0)
1075 return 1;
1076 #else
1077 FILE *out;
1079 out = fopen(filename, "w");
1080 if (!out)
1081 return 1;
1083 xmlDocDump(out, doc); /* Some versions return void */
1085 if (fclose(out))
1086 return 1;
1087 #endif
1089 return 0;
1092 static void save_options(gpointer unused)
1094 xmlDoc *doc;
1095 GList *next;
1096 guchar *save, *save_new;
1098 if (!check_anything_changed())
1099 goto out;
1100 save = choices_find_path_save("Options", PROJECT, TRUE);
1101 if (!save)
1102 goto out;
1104 save_new = g_strconcat(save, ".new", NULL);
1106 doc = xmlNewDoc("1.0");
1107 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "Options", NULL));
1109 g_hash_table_foreach(option_hash, write_option,
1110 xmlDocGetRootElement(doc));
1112 if (save_xml_file(doc, save_new) || rename(save_new, save))
1113 report_error(_("Error saving %s: %s"), save, g_strerror(errno));
1115 g_free(save_new);
1116 g_free(save);
1117 xmlFreeDoc(doc);
1119 for (next = saver_callbacks; next; next = next->next)
1121 OptionNotify *cb = (OptionNotify *) next->data;
1122 cb();
1125 out:
1126 if (window)
1127 gtk_widget_destroy(window);
1130 /* Make the widget reflect the current value of the option */
1131 static void update_cb(gpointer key, gpointer value, gpointer data)
1133 Option *option = (Option *) value;
1135 g_return_if_fail(option != NULL);
1136 g_return_if_fail(option->widget != NULL);
1138 updating_widgets++;
1140 if (option->update_widget)
1141 option->update_widget(option);
1143 updating_widgets--;
1146 /* Reflect the values in the Option structures by changing the widgets
1147 * in the Options window.
1149 static void update_option_widgets(void)
1151 g_hash_table_foreach(option_hash, update_cb, NULL);
1154 /* Each of the following update the widget to make it show the current
1155 * value of the option.
1158 static void update_toggle(Option *option)
1160 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(option->widget),
1161 option->int_value);
1164 static void update_entry(Option *option)
1166 gtk_entry_set_text(GTK_ENTRY(option->widget), option->value);
1169 static void update_numentry(Option *option)
1171 gtk_spin_button_set_value(GTK_SPIN_BUTTON(option->widget),
1172 option->int_value);
1175 static void update_radio_group(Option *option)
1177 radio_group_set_value(GTK_RADIO_BUTTON(option->widget), option->value);
1180 static void update_slider(Option *option)
1182 gtk_adjustment_set_value(
1183 gtk_range_get_adjustment(GTK_RANGE(option->widget)),
1184 option->int_value);
1187 static void update_menu(Option *option)
1189 option_menu_set(GTK_OPTION_MENU(option->widget), option->value);
1192 static void update_font(Option *option)
1194 gtk_label_set_text(GTK_LABEL(option->widget), option->value);
1197 static void update_colour(Option *option)
1199 GdkColor colour;
1201 gdk_color_parse(option->value, &colour);
1202 button_patch_set_colour(option->widget, &colour);
1205 /* Each of these read_* calls get the new (string) value of an option
1206 * from the widget.
1209 static guchar *read_toggle(Option *option)
1211 GtkToggleButton *toggle = GTK_TOGGLE_BUTTON(option->widget);
1213 return g_strdup_printf("%d", gtk_toggle_button_get_active(toggle));
1216 static guchar *read_entry(Option *option)
1218 return gtk_editable_get_chars(GTK_EDITABLE(option->widget), 0, -1);
1221 static guchar *read_numentry(Option *option)
1223 return g_strdup_printf("%d", (int)
1224 gtk_spin_button_get_value(GTK_SPIN_BUTTON(option->widget)));
1227 static guchar *read_slider(Option *option)
1229 return g_strdup_printf("%d", (int)
1230 gtk_range_get_adjustment(GTK_RANGE(option->widget))->value);
1233 static guchar *read_radio_group(Option *option)
1235 return radio_group_get_value(GTK_RADIO_BUTTON(option->widget));
1238 static guchar *read_menu(Option *option)
1240 return g_strdup(option_menu_get(GTK_OPTION_MENU(option->widget)));
1243 static guchar *read_font(Option *option)
1245 return g_strdup(gtk_label_get_text(GTK_LABEL(option->widget)));
1248 static guchar *read_colour(Option *option)
1250 GtkStyle *style = GTK_BIN(option->widget)->child->style;
1252 return g_strdup_printf("#%04x%04x%04x",
1253 style->bg[GTK_STATE_NORMAL].red,
1254 style->bg[GTK_STATE_NORMAL].green,
1255 style->bg[GTK_STATE_NORMAL].blue);
1258 static void set_not_changed(gpointer key, gpointer value, gpointer data)
1260 Option *option = (Option *) value;
1262 option->has_changed = FALSE;
1265 /* Builders for decorations (no corresponding option) */
1267 static GList *build_label(Option *option, xmlNode *node, guchar *label)
1269 GtkWidget *widget;
1270 guchar *text;
1271 int help;
1273 g_return_val_if_fail(option == NULL, NULL);
1274 g_return_val_if_fail(label == NULL, NULL);
1276 text = DATA(node);
1277 widget = gtk_label_new(_(text));
1278 g_free(text);
1280 help = get_int(node, "help");
1282 gtk_misc_set_alignment(GTK_MISC(widget), 0, help ? 0.5 : 1);
1283 gtk_label_set_justify(GTK_LABEL(widget), GTK_JUSTIFY_LEFT);
1284 gtk_label_set_line_wrap(GTK_LABEL(widget), TRUE);
1286 if (help)
1288 GtkWidget *hbox, *image, *align;
1290 hbox = gtk_hbox_new(FALSE, 4);
1291 image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO,
1292 GTK_ICON_SIZE_BUTTON);
1293 align = gtk_alignment_new(0, 0, 0, 0);
1295 gtk_container_add(GTK_CONTAINER(align), image);
1296 gtk_box_pack_start(GTK_BOX(hbox), align, FALSE, TRUE, 0);
1297 gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
1299 return g_list_append(NULL, hbox);
1302 return g_list_append(NULL, widget);
1305 static GList *build_spacer(Option *option, xmlNode *node, guchar *label)
1307 GtkWidget *eb;
1309 g_return_val_if_fail(option == NULL, NULL);
1310 g_return_val_if_fail(label == NULL, NULL);
1312 eb = gtk_event_box_new();
1313 gtk_widget_set_size_request(eb, 12, 12);
1315 return g_list_append(NULL, eb);
1318 static GList *build_frame(Option *option, xmlNode *node, guchar *label)
1320 GtkWidget *nbox, *frame;
1321 xmlNode *hw;
1323 g_return_val_if_fail(option == NULL, NULL);
1324 g_return_val_if_fail(label != NULL, NULL);
1326 frame = gtk_frame_new(_(label));
1328 nbox = gtk_vbox_new(FALSE, 0);
1329 gtk_container_set_border_width(GTK_CONTAINER(nbox), 4);
1330 gtk_container_add(GTK_CONTAINER(frame), nbox);
1332 for (hw = node->xmlChildrenNode; hw; hw = hw->next)
1333 if (hw->type == XML_ELEMENT_NODE)
1334 build_widget(hw, nbox);
1336 return g_list_append(NULL, frame);
1339 /* These create new widgets in the options window and set the appropriate
1340 * callbacks.
1343 static GList *build_toggle(Option *option, xmlNode *node, guchar *label)
1345 GtkWidget *toggle;
1347 g_return_val_if_fail(option != NULL, NULL);
1349 toggle = gtk_check_button_new_with_label(_(label));
1351 may_add_tip(toggle, node);
1353 option->update_widget = update_toggle;
1354 option->read_widget = read_toggle;
1355 option->widget = toggle;
1357 g_signal_connect_swapped(toggle, "toggled",
1358 G_CALLBACK(option_check_widget), option);
1360 return g_list_append(NULL, toggle);
1363 static GList *build_slider(Option *option, xmlNode *node, guchar *label)
1365 GtkAdjustment *adj;
1366 GtkWidget *hbox, *slide, *label_wid;
1367 int min, max;
1368 int fixed;
1369 int showvalue;
1370 guchar *end;
1372 g_return_val_if_fail(option != NULL, NULL);
1374 min = get_int(node, "min");
1375 max = get_int(node, "max");
1376 fixed = get_int(node, "fixed");
1377 showvalue = get_int(node, "showvalue");
1379 adj = GTK_ADJUSTMENT(gtk_adjustment_new(0,
1380 min, max, 1, 10, 0));
1382 hbox = gtk_hbox_new(FALSE, 4);
1384 if (label)
1386 label_wid = gtk_label_new(_(label));
1387 gtk_misc_set_alignment(GTK_MISC(label_wid), 0, 0.5);
1388 gtk_box_pack_start(GTK_BOX(hbox), label_wid, FALSE, TRUE, 0);
1389 add_to_size_group(node, label_wid);
1392 end = xmlGetProp(node, "end");
1393 if (end)
1395 gtk_box_pack_end(GTK_BOX(hbox), gtk_label_new(_(end)),
1396 FALSE, TRUE, 0);
1397 g_free(end);
1400 slide = gtk_hscale_new(adj);
1402 if (fixed)
1403 gtk_widget_set_size_request(slide, adj->upper, 24);
1404 if (showvalue)
1406 gtk_scale_set_draw_value(GTK_SCALE(slide), TRUE);
1407 gtk_scale_set_value_pos(GTK_SCALE(slide),
1408 GTK_POS_LEFT);
1409 gtk_scale_set_digits(GTK_SCALE(slide), 0);
1411 else
1412 gtk_scale_set_draw_value(GTK_SCALE(slide), FALSE);
1413 GTK_WIDGET_UNSET_FLAGS(slide, GTK_CAN_FOCUS);
1415 may_add_tip(slide, node);
1417 gtk_box_pack_start(GTK_BOX(hbox), slide, !fixed, TRUE, 0);
1419 option->update_widget = update_slider;
1420 option->read_widget = read_slider;
1421 option->widget = slide;
1423 g_signal_connect_swapped(adj, "value-changed",
1424 G_CALLBACK(option_check_widget), option);
1426 return g_list_append(NULL, hbox);
1429 static GList *build_entry(Option *option, xmlNode *node, guchar *label)
1431 GtkWidget *hbox;
1432 GtkWidget *entry;
1433 GtkWidget *label_wid;
1435 g_return_val_if_fail(option != NULL, NULL);
1437 hbox = gtk_hbox_new(FALSE, 4);
1439 if (label)
1441 label_wid = gtk_label_new(_(label));
1442 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1443 gtk_box_pack_start(GTK_BOX(hbox), label_wid, FALSE, TRUE, 0);
1446 entry = gtk_entry_new();
1447 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
1448 add_to_size_group(node, entry);
1449 may_add_tip(entry, node);
1451 option->update_widget = update_entry;
1452 option->read_widget = read_entry;
1453 option->widget = entry;
1455 g_signal_connect_data(entry, "changed",
1456 G_CALLBACK(option_check_widget), option,
1457 NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
1459 return g_list_append(NULL, hbox);
1462 static GList *build_numentry(Option *option, xmlNode *node, guchar *label)
1464 GtkWidget *hbox;
1465 GtkWidget *spin;
1466 GtkWidget *label_wid;
1467 guchar *unit;
1468 int min, max, step, width;
1470 g_return_val_if_fail(option != NULL, NULL);
1472 min = get_int(node, "min");
1473 max = get_int(node, "max");
1474 step = get_int(node, "step");
1475 width = get_int(node, "width");
1476 unit = xmlGetProp(node, "unit");
1478 hbox = gtk_hbox_new(FALSE, 4);
1480 if (label)
1482 label_wid = gtk_label_new(_(label));
1483 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1484 gtk_box_pack_start(GTK_BOX(hbox), label_wid, FALSE, TRUE, 0);
1485 add_to_size_group(node, label_wid);
1488 spin = gtk_spin_button_new_with_range(min, max, step > 0 ? step : 1);
1489 gtk_entry_set_width_chars(GTK_ENTRY(spin), width > 1 ? width + 1 : -1);
1490 gtk_box_pack_start(GTK_BOX(hbox), spin, FALSE, TRUE, 0);
1491 may_add_tip(spin, node);
1493 if (unit)
1495 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_(unit)),
1496 FALSE, TRUE, 0);
1497 g_free(unit);
1500 option->update_widget = update_numentry;
1501 option->read_widget = read_numentry;
1502 option->widget = spin;
1504 g_signal_connect_swapped(spin, "value-changed",
1505 G_CALLBACK(option_check_widget), option);
1507 return g_list_append(NULL, hbox);
1510 static GList *build_radio_group(Option *option, xmlNode *node, guchar *label)
1512 GList *list = NULL;
1513 GtkWidget *button = NULL;
1514 xmlNode *rn;
1515 int cols;
1517 g_return_val_if_fail(option != NULL, NULL);
1519 for (rn = node->xmlChildrenNode; rn; rn = rn->next)
1521 if (rn->type == XML_ELEMENT_NODE)
1523 button = build_radio(rn, button);
1524 g_signal_connect_swapped(button, "toggled",
1525 G_CALLBACK(option_check_widget), option);
1526 list = g_list_append(list, button);
1530 option->update_widget = update_radio_group;
1531 option->read_widget = read_radio_group;
1532 option->widget = button;
1534 cols = get_int(node, "columns");
1535 if (cols > 1)
1537 GtkWidget *table;
1538 GList *next;
1539 int i, n;
1540 int rows;
1542 n = g_list_length(list);
1543 rows = (n + cols - 1) / cols;
1545 table = gtk_table_new(rows, cols, FALSE);
1547 i = 0;
1548 for (next = list; next; next = next->next)
1550 GtkWidget *button = GTK_WIDGET(next->data);
1551 int left = i / rows;
1552 int top = i % rows;
1554 gtk_table_attach_defaults(GTK_TABLE(table), button,
1555 left, left + 1, top, top + 1);
1557 i++;
1560 g_list_free(list);
1561 list = g_list_prepend(NULL, table);
1564 return list;
1567 static GList *build_colour(Option *option, xmlNode *node, guchar *label)
1569 GtkWidget *hbox, *da, *button, *label_wid;
1571 g_return_val_if_fail(option != NULL, NULL);
1573 hbox = gtk_hbox_new(FALSE, 4);
1575 if (label)
1577 label_wid = gtk_label_new(_(label));
1578 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1579 gtk_box_pack_start(GTK_BOX(hbox), label_wid, TRUE, TRUE, 0);
1582 button = gtk_button_new();
1583 da = gtk_drawing_area_new();
1584 gtk_widget_set_size_request(da, 64, 12);
1585 gtk_container_add(GTK_CONTAINER(button), da);
1586 g_signal_connect(button, "clicked", G_CALLBACK(open_coloursel), option);
1588 may_add_tip(button, node);
1590 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1592 option->update_widget = update_colour;
1593 option->read_widget = read_colour;
1594 option->widget = button;
1596 return g_list_append(NULL, hbox);
1599 static GList *build_menu(Option *option, xmlNode *node, guchar *label)
1601 GtkWidget *hbox, *om, *option_menu, *label_wid;
1602 xmlNode *item;
1604 g_return_val_if_fail(option != NULL, NULL);
1606 hbox = gtk_hbox_new(FALSE, 4);
1608 label_wid = gtk_label_new(_(label));
1609 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1610 gtk_box_pack_start(GTK_BOX(hbox), label_wid, TRUE, TRUE, 0);
1612 option_menu = gtk_option_menu_new();
1613 gtk_box_pack_start(GTK_BOX(hbox), option_menu, FALSE, TRUE, 0);
1615 om = gtk_menu_new();
1616 gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), om);
1618 add_to_size_group(node, option_menu);
1620 for (item = node->xmlChildrenNode; item; item = item->next)
1622 if (item->type == XML_ELEMENT_NODE)
1623 build_menu_item(item, option_menu);
1626 option->update_widget = update_menu;
1627 option->read_widget = read_menu;
1628 option->widget = option_menu;
1630 g_signal_connect_data(option_menu, "changed",
1631 G_CALLBACK(option_check_widget), option,
1632 NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
1634 return g_list_append(NULL, hbox);
1637 static GList *build_font(Option *option, xmlNode *node, guchar *label)
1639 GtkWidget *hbox, *button;
1641 g_return_val_if_fail(option != NULL, NULL);
1643 hbox = gtk_hbox_new(FALSE, 4);
1645 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_(label)),
1646 FALSE, TRUE, 0);
1648 button = gtk_button_new_with_label("");
1649 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1651 option->update_widget = update_font;
1652 option->read_widget = read_font;
1653 option->widget = GTK_BIN(button)->child;
1654 may_add_tip(button, node);
1656 g_signal_connect(button, "clicked", G_CALLBACK(open_fontsel), option);
1658 return g_list_append(NULL, hbox);
1661 static void button_patch_set_colour(GtkWidget *button, GdkColor *colour)
1663 GtkStyle *style;
1664 GtkWidget *patch;
1666 patch = GTK_BIN(button)->child;
1668 style = gtk_style_copy(GTK_WIDGET(patch)->style);
1669 style->bg[GTK_STATE_NORMAL].red = colour->red;
1670 style->bg[GTK_STATE_NORMAL].green = colour->green;
1671 style->bg[GTK_STATE_NORMAL].blue = colour->blue;
1672 gtk_widget_set_style(patch, style);
1673 g_object_unref(G_OBJECT(style));
1675 if (GTK_WIDGET_REALIZED(patch))
1676 gdk_window_clear(patch->window);
1679 static void load_options(xmlDoc *doc)
1681 xmlNode *root, *node;
1683 root = xmlDocGetRootElement(doc);
1685 g_return_if_fail(strcmp(root->name, "Options") == 0);
1687 for (node = root->xmlChildrenNode; node; node = node->next)
1689 gchar *value, *name;
1691 if (node->type != XML_ELEMENT_NODE)
1692 continue;
1693 if (strcmp(node->name, "Option") != 0)
1694 continue;
1695 name = xmlGetProp(node, "name");
1696 if (!name)
1697 continue;
1699 value = xmlNodeGetContent(node);
1701 if (g_hash_table_lookup(loading, name))
1702 g_warning("Duplicate option found!");
1704 g_hash_table_insert(loading, name, value);
1706 /* (don't need to free name or value) */
1710 /* Process one line from the options file (\0 term'd).
1711 * Returns NULL on success, or a pointer to an error message.
1712 * The line is modified.
1714 static const char *process_option_line(gchar *line)
1716 gchar *eq, *c;
1717 gchar *name = line;
1719 g_return_val_if_fail(option_hash != NULL, "No registered options!");
1721 eq = strchr(line, '=');
1722 if (!eq)
1723 return _("Missing '='");
1725 c = eq - 1;
1726 while (c > line && (*c == ' ' || *c == '\t'))
1727 c--;
1728 c[1] = '\0';
1729 c = eq + 1;
1730 while (*c == ' ' || *c == '\t')
1731 c++;
1733 if (g_hash_table_lookup(loading, name))
1734 return "Duplicate option found!";
1736 g_hash_table_insert(loading, g_strdup(name), g_strdup(g_strstrip(c)));
1738 return NULL;