r2228: Made 'Automatic' an icon size, rather than a separate option.
[rox-filer.git] / ROX-Filer / src / options.c
blobc34c534af2dc7764ec02b1719db9f5cfe5ecb224
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, which contains a list of
29 * name/value pairs, 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 OK is clicked:
65 * - If anything changed then:
66 * - All the options are written to the filesystem
67 * - The saver_callbacks are called.
70 #include "config.h"
72 #include <stdio.h>
73 #include <stdlib.h>
74 #include <string.h>
75 #include <errno.h>
76 #include <ctype.h>
77 #include <gtk/gtk.h>
78 #include <libxml/parser.h>
80 #include "global.h"
82 #include "choices.h"
83 #include "options.h"
84 #include "main.h"
85 #include "gui_support.h"
86 #include "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 /* A mapping (name -> GtkSizeGroup) of size groups used by the widgets
111 * in the options box. This hash table is created/destroyed every time
112 * the box is opened/destroyed.
114 static GHashTable *size_groups = NULL;
116 /* List of functions to call after all option values are updated */
117 static GList *notify_callbacks = NULL;
119 /* List of functions to call after all options are saved */
120 static GList *saver_callbacks = NULL;
122 static int updating_widgets = 0; /* Ignore change signals when set */
124 static GtkWidget *revert_widget = NULL;
126 /* Static prototypes */
127 static void save_options(void);
128 static void revert_options(GtkWidget *widget, gpointer data);
129 static void build_options_window(void);
130 static GtkWidget *build_window_frame(GtkTreeView **tree_view);
131 static void update_option_widgets(void);
132 static void button_patch_set_colour(GtkWidget *button, GdkColor *color);
133 static void option_add(Option *option, const gchar *key);
134 static void set_not_changed(gpointer key, gpointer value, gpointer data);
135 static void load_options(xmlDoc *doc);
136 static gboolean check_anything_changed(void);
138 static const char *process_option_line(gchar *line);
140 static GList *build_label(Option *option, xmlNode *node, guchar *label);
141 static GList *build_spacer(Option *option, xmlNode *node, guchar *label);
142 static GList *build_frame(Option *option, xmlNode *node, guchar *label);
144 static GList *build_toggle(Option *option, xmlNode *node, guchar *label);
145 static GList *build_slider(Option *option, xmlNode *node, guchar *label);
146 static GList *build_entry(Option *option, xmlNode *node, guchar *label);
147 static GList *build_numentry(Option *option, xmlNode *node, guchar *label);
148 static GList *build_radio_group(Option *option, xmlNode *node, guchar *label);
149 static GList *build_colour(Option *option, xmlNode *node, guchar *label);
150 static GList *build_menu(Option *option, xmlNode *node, guchar *label);
151 static GList *build_font(Option *option, xmlNode *node, guchar *label);
153 static gboolean updating_file_format = FALSE;
155 /****************************************************************
156 * EXTERNAL INTERFACE *
157 ****************************************************************/
159 void options_init(void)
161 char *path;
162 xmlDoc *doc;
164 loading = g_hash_table_new(g_str_hash, g_str_equal);
165 option_hash = g_hash_table_new(g_str_hash, g_str_equal);
166 widget_builder = g_hash_table_new(g_str_hash, g_str_equal);
168 path = choices_find_path_load("Options", PROJECT);
169 if (path)
171 /* Load in all the options set in the filer, storing them
172 * temporarily in the loading hash table.
173 * They get moved to option_hash when they're registered.
175 doc = xmlParseFile(path);
176 if (doc)
178 load_options(doc);
179 xmlFreeDoc(doc);
181 else
183 parse_file(path, process_option_line);
184 updating_file_format = TRUE;
187 g_free(path);
190 option_register_widget("label", build_label);
191 option_register_widget("spacer", build_spacer);
192 option_register_widget("frame", build_frame);
194 option_register_widget("toggle", build_toggle);
195 option_register_widget("slider", build_slider);
196 option_register_widget("entry", build_entry);
197 option_register_widget("numentry", build_numentry);
198 option_register_widget("radio-group", build_radio_group);
199 option_register_widget("colour", build_colour);
200 option_register_widget("menu", build_menu);
201 option_register_widget("font", build_font);
204 /* When parsing the XML file, process an element named 'name' by
205 * calling 'builder(option, xml_node, label)'.
206 * builder returns the new widgets to add to the options box.
207 * 'name' should be a static string. Call 'option_check_widget' when
208 * the widget's value is modified.
210 * Functions to set or get the widget's state can be stored in 'option'.
211 * If the option doesn't have a name attribute in Options.xml then
212 * ui will be NULL on entry (this is used for buttons).
214 void option_register_widget(char *name, OptionBuildFn builder)
216 g_hash_table_insert(widget_builder, name, builder);
219 /* This is called when the widget's value is modified by the user.
220 * Reads the new value of the widget into the option and calls
221 * the notify callbacks.
223 void option_check_widget(Option *option)
225 guchar *new = NULL;
227 if (updating_widgets)
228 return; /* Not caused by the user... */
230 g_return_if_fail(option->read_widget != NULL);
232 new = option->read_widget(option);
234 g_return_if_fail(new != NULL);
236 g_hash_table_foreach(option_hash, set_not_changed, NULL);
238 option->has_changed = strcmp(option->value, new) != 0;
240 if (!option->has_changed)
242 g_free(new);
243 return;
246 g_free(option->value);
247 option->value = new;
248 option->int_value = atoi(new);
250 options_notify();
253 /* Call all the notify callbacks. This should happen after any options
254 * have their values changed.
255 * Set each option->has_changed flag before calling this function.
257 void options_notify(void)
259 GList *next;
261 for (next = notify_callbacks; next; next = next->next)
263 OptionNotify *cb = (OptionNotify *) next->data;
265 cb();
268 if (updating_file_format)
270 updating_file_format = FALSE;
271 save_options();
272 info_message(_("ROX-Filer has converted your Options file "
273 "to the new XML format"));
276 if (revert_widget)
277 gtk_widget_set_sensitive(revert_widget,
278 check_anything_changed());
281 /* Store values used by Revert */
282 static void store_backup(gpointer key, gpointer value, gpointer data)
284 Option *option = (Option *) value;
286 g_free(option->backup);
287 option->backup = g_strdup(option->value);
290 /* Allow the user to edit the options. Returns the window widget (you don't
291 * normally need this). NULL if already open.
293 GtkWidget *options_show(void)
295 if (!option_tooltips)
296 option_tooltips = gtk_tooltips_new();
298 if (g_hash_table_size(loading) != 0)
300 g_printerr(PROJECT ": Some options loaded but not used:\n");
301 g_hash_table_foreach(loading, (GHFunc) puts, NULL);
304 if (window)
306 gtk_window_present(GTK_WINDOW(window));
307 return NULL;
310 g_hash_table_foreach(option_hash, store_backup, NULL);
312 build_options_window();
314 update_option_widgets();
316 gtk_widget_show_all(window);
318 return window;
321 /* Initialise and register a new integer option */
322 void option_add_int(Option *option, const gchar *key, int value)
324 option->value = g_strdup_printf("%d", value);
325 option->int_value = value;
326 option_add(option, key);
329 void option_add_string(Option *option, const gchar *key, const gchar *value)
331 option->value = g_strdup(value);
332 option->int_value = atoi(value);
333 option_add(option, key);
336 /* Add a callback which will be called after any options have changed their
337 * values. If several options change at once, this is called after all
338 * changes.
340 void option_add_notify(OptionNotify *callback)
342 g_return_if_fail(callback != NULL);
344 notify_callbacks = g_list_append(notify_callbacks, callback);
347 /* Call 'callback' after all the options have been saved */
348 void option_add_saver(OptionNotify *callback)
350 g_return_if_fail(callback != NULL);
352 saver_callbacks = g_list_append(saver_callbacks, callback);
355 /****************************************************************
356 * INTERNAL FUNCTIONS *
357 ****************************************************************/
359 /* Option should contain the default value.
360 * It must never be destroyed after being registered (Options are typically
361 * statically allocated).
362 * The key corresponds to the option's name in Options.xml, and to the key
363 * in the saved options file.
365 * On exit, the value will have been updated to the loaded value, if
366 * different to the default.
368 static void option_add(Option *option, const gchar *key)
370 gpointer okey, value;
372 g_return_if_fail(option_hash != NULL);
373 g_return_if_fail(g_hash_table_lookup(option_hash, key) == NULL);
374 g_return_if_fail(option->value != NULL);
376 option->has_changed = FALSE;
378 option->widget = NULL;
379 option->update_widget = NULL;
380 option->read_widget = NULL;
381 option->backup = NULL;
383 g_hash_table_insert(option_hash, (gchar *) key, option);
385 /* Use the value loaded from the file, if any */
386 if (g_hash_table_lookup_extended(loading, key, &okey, &value))
388 option->has_changed = strcmp(option->value, value) != 0;
390 g_free(option->value);
391 option->value = value;
392 option->int_value = atoi(value);
393 g_hash_table_remove(loading, key);
394 g_free(okey);
398 static GtkColorSelectionDialog *current_csel_box = NULL;
399 static GtkFontSelectionDialog *current_fontsel_box = NULL;
401 static void get_new_colour(GtkWidget *ok, Option *option)
403 GtkWidget *csel;
404 GdkColor c;
406 g_return_if_fail(current_csel_box != NULL);
408 csel = current_csel_box->colorsel;
410 gtk_color_selection_get_current_color(GTK_COLOR_SELECTION(csel), &c);
412 button_patch_set_colour(option->widget, &c);
414 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
416 option_check_widget(option);
419 static void open_coloursel(GtkWidget *button, Option *option)
421 GtkColorSelectionDialog *csel;
422 GtkWidget *dialog, *patch;
424 if (current_csel_box)
425 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
427 dialog = gtk_color_selection_dialog_new(NULL);
428 csel = GTK_COLOR_SELECTION_DIALOG(dialog);
429 current_csel_box = csel;
430 gtk_window_set_position(GTK_WINDOW(csel), GTK_WIN_POS_MOUSE);
432 g_signal_connect(dialog, "destroy",
433 G_CALLBACK(gtk_widget_destroyed), &current_csel_box);
434 gtk_widget_hide(csel->help_button);
435 g_signal_connect_swapped(csel->cancel_button, "clicked",
436 G_CALLBACK(gtk_widget_destroy), dialog);
437 g_signal_connect(csel->ok_button, "clicked",
438 G_CALLBACK(get_new_colour), option);
440 patch = GTK_BIN(button)->child;
442 gtk_color_selection_set_current_color(
443 GTK_COLOR_SELECTION(csel->colorsel),
444 &patch->style->bg[GTK_STATE_NORMAL]);
446 gtk_widget_show(dialog);
449 static void font_chosen(GtkWidget *dialog, gint response, Option *option)
451 gchar *font;
453 if (response != GTK_RESPONSE_OK)
454 goto out;
456 font = gtk_font_selection_dialog_get_font_name(
457 GTK_FONT_SELECTION_DIALOG(dialog));
459 gtk_label_set_text(GTK_LABEL(option->widget), font);
461 g_free(font);
463 option_check_widget(option);
465 out:
466 gtk_widget_destroy(dialog);
470 static void toggle_active_font(GtkToggleButton *toggle, Option *option)
472 if (current_fontsel_box)
473 gtk_widget_destroy(GTK_WIDGET(current_fontsel_box));
475 if (gtk_toggle_button_get_active(toggle))
477 gtk_widget_set_sensitive(option->widget->parent, TRUE);
478 gtk_label_set_text(GTK_LABEL(option->widget), "Sans 12");
480 else
482 gtk_widget_set_sensitive(option->widget->parent, FALSE);
483 gtk_label_set_text(GTK_LABEL(option->widget),
484 _("(use default)"));
487 option_check_widget(option);
490 static void open_fontsel(GtkWidget *button, Option *option)
492 if (current_fontsel_box)
493 gtk_widget_destroy(GTK_WIDGET(current_fontsel_box));
495 current_fontsel_box = GTK_FONT_SELECTION_DIALOG(
496 gtk_font_selection_dialog_new(PROJECT));
498 gtk_window_set_position(GTK_WINDOW(current_fontsel_box),
499 GTK_WIN_POS_MOUSE);
501 g_signal_connect(current_fontsel_box, "destroy",
502 G_CALLBACK(gtk_widget_destroyed), &current_fontsel_box);
504 gtk_font_selection_dialog_set_font_name(current_fontsel_box,
505 option->value);
507 g_signal_connect(current_fontsel_box, "response",
508 G_CALLBACK(font_chosen), option);
510 gtk_widget_show(GTK_WIDGET(current_fontsel_box));
513 /* These are used during parsing... */
514 static xmlDocPtr options_doc = NULL;
516 #define DATA(node) (xmlNodeListGetString(options_doc, node->xmlChildrenNode, 1))
518 static void may_add_tip(GtkWidget *widget, xmlNode *element)
520 guchar *data, *tip;
522 data = DATA(element);
523 if (!data)
524 return;
526 tip = g_strstrip(g_strdup(data));
527 g_free(data);
528 if (*tip)
529 OPTION_TIP(widget, _(tip));
530 g_free(tip);
533 /* Returns zero if attribute is not present */
534 static int get_int(xmlNode *node, guchar *attr)
536 guchar *txt;
537 int retval;
539 txt = xmlGetProp(node, attr);
540 if (!txt)
541 return 0;
543 retval = atoi(txt);
544 g_free(txt);
546 return retval;
549 /* Adds 'widget' to the GtkSizeGroup selected by 'index'. This function
550 * does nothing if 'node' has no "sizegroup" attribute.
551 * The value of "sizegroup" is either a key. All widgets with the same
552 * key request the same size.
553 * Size groups are created on the fly and get destroyed when the options
554 * box is closed.
556 static void add_to_size_group(xmlNode *node, GtkWidget *widget)
558 GtkSizeGroup *sg;
559 guchar *name;
561 g_return_if_fail(node != NULL);
562 g_return_if_fail(widget != NULL);
564 name = xmlGetProp(node, "sizegroup");
565 if (!name)
566 return;
568 if (size_groups == NULL)
569 size_groups = g_hash_table_new_full(g_str_hash, g_str_equal,
570 g_free, NULL);
572 sg = (GtkSizeGroup *) g_hash_table_lookup(size_groups, name);
573 if (sg == NULL)
576 sg = (GtkSizeGroup *) gtk_size_group_new(
577 GTK_SIZE_GROUP_HORIZONTAL);
578 g_hash_table_insert(size_groups, name, sg);
579 gtk_size_group_add_widget(sg, widget);
580 g_object_unref(G_OBJECT(sg));
582 else
584 gtk_size_group_add_widget(sg, widget);
585 g_free(name);
589 static GtkWidget *build_radio(xmlNode *radio, GtkWidget *prev)
591 GtkWidget *button;
592 GtkRadioButton *prev_button = (GtkRadioButton *) prev;
593 guchar *label;
595 label = xmlGetProp(radio, "label");
597 button = gtk_radio_button_new_with_label(
598 prev_button ? gtk_radio_button_get_group(prev_button)
599 : NULL,
600 _(label));
601 g_free(label);
603 may_add_tip(button, radio);
605 g_object_set_data(G_OBJECT(button), "value",
606 xmlGetProp(radio, "value"));
608 return button;
611 static void build_menu_item(xmlNode *node, GtkWidget *option_menu)
613 GtkWidget *item, *menu;
614 guchar *label;
616 g_return_if_fail(strcmp(node->name, "item") == 0);
618 label = xmlGetProp(node, "label");
619 item = gtk_menu_item_new_with_label(_(label));
620 g_free(label);
622 menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(option_menu));
623 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
624 gtk_widget_show_all(menu);
626 g_object_set_data(G_OBJECT(item), "value", xmlGetProp(node, "value"));
629 static void build_widget(xmlNode *widget, GtkWidget *box)
631 const char *name = widget->name;
632 OptionBuildFn builder;
633 guchar *oname;
634 Option *option;
635 guchar *label;
637 label = xmlGetProp(widget, "label");
639 if (strcmp(name, "hbox") == 0 || strcmp(name, "vbox") == 0)
641 GtkWidget *nbox;
642 xmlNode *hw;
644 if (name[0] == 'h')
645 nbox = gtk_hbox_new(FALSE, 4);
646 else
647 nbox = gtk_vbox_new(FALSE, 0);
649 if (label)
650 gtk_box_pack_start(GTK_BOX(nbox),
651 gtk_label_new(_(label)), FALSE, TRUE, 4);
652 gtk_box_pack_start(GTK_BOX(box), nbox, FALSE, TRUE, 0);
654 for (hw = widget->xmlChildrenNode; hw; hw = hw->next)
656 if (hw->type == XML_ELEMENT_NODE)
657 build_widget(hw, nbox);
660 g_free(label);
661 return;
664 oname = xmlGetProp(widget, "name");
666 if (oname)
668 option = g_hash_table_lookup(option_hash, oname);
670 if (!option)
672 g_warning("No Option for '%s'!\n", oname);
673 g_free(oname);
674 return;
677 g_free(oname);
679 else
680 option = NULL;
682 builder = g_hash_table_lookup(widget_builder, name);
683 if (builder)
685 GList *widgets, *next;
687 if (option && option->widget)
688 g_warning("Widget for option already exists!");
690 widgets = builder(option, widget, label);
692 for (next = widgets; next; next = next->next)
694 GtkWidget *w = (GtkWidget *) next->data;
695 gtk_box_pack_start(GTK_BOX(box), w, FALSE, TRUE, 0);
697 g_list_free(widgets);
699 else
700 g_warning("Unknown option type '%s'\n", name);
702 g_free(label);
705 static void build_section(xmlNode *section, GtkWidget *notebook,
706 GtkTreeStore *tree_store, GtkTreeIter *parent)
708 guchar *title = NULL;
709 GtkWidget *page;
710 GtkTreeIter iter;
711 xmlNode *widget;
713 title = xmlGetProp(section, "title");
714 page = gtk_vbox_new(FALSE, 4);
715 gtk_container_set_border_width(GTK_CONTAINER(page), 4);
716 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, NULL);
718 gtk_tree_store_append(tree_store, &iter, parent);
719 gtk_tree_store_set(tree_store, &iter, 0, _(title), 1, page, -1);
720 g_free(title);
722 widget = section->xmlChildrenNode;
723 for (; widget; widget = widget->next)
725 if (widget->type == XML_ELEMENT_NODE)
727 if (strcmp(widget->name, "section") == 0)
728 build_section(widget, notebook,
729 tree_store, &iter);
730 else
731 build_widget(widget, page);
736 /* Parse <app_dir>/Options.xml to create the options window.
737 * Sets the global 'window' variable.
739 static void build_options_window(void)
741 GtkTreeView *tree;
742 GtkTreeStore *store;
743 GtkWidget *notebook;
744 xmlDocPtr options_doc;
745 xmlNode *options, *section;
746 gchar *path;
748 notebook = build_window_frame(&tree);
750 path = g_strconcat(app_dir, "/Options.xml", NULL);
751 options_doc = xmlParseFile(path);
753 if (!options_doc)
755 report_error(_("Internal error: %s unreadable"), path);
756 g_free(path);
757 return;
760 g_free(path);
762 options = xmlDocGetRootElement(options_doc);
763 if (strcmp(options->name, "options") == 0)
765 GtkTreePath *treepath;
767 store = (GtkTreeStore *) gtk_tree_view_get_model(tree);
768 section = options->xmlChildrenNode;
769 for (; section; section = section->next)
770 if (section->type == XML_ELEMENT_NODE)
771 build_section(section, notebook, store, NULL);
773 gtk_tree_view_expand_all(tree);
774 treepath = gtk_tree_path_new_first();
775 if (treepath)
777 gtk_tree_view_set_cursor(tree, treepath, NULL, FALSE);
778 gtk_tree_path_free(treepath);
782 xmlFreeDoc(options_doc);
783 options_doc = NULL;
786 static void null_widget(gpointer key, gpointer value, gpointer data)
788 Option *option = (Option *) value;
790 g_return_if_fail(option->widget != NULL);
792 option->widget = NULL;
795 static void options_destroyed(GtkWidget *widget, gpointer data)
797 if (current_csel_box)
798 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
799 if (current_fontsel_box)
800 gtk_widget_destroy(GTK_WIDGET(current_fontsel_box));
802 revert_widget = NULL;
804 if (check_anything_changed())
805 save_options();
807 if (widget == window)
809 window = NULL;
811 g_hash_table_foreach(option_hash, null_widget, NULL);
813 if (size_groups)
815 g_hash_table_destroy(size_groups);
816 size_groups = NULL;
822 /* The cursor has been changed in the tree view, so switch to the new
823 * page in the notebook.
825 static void tree_cursor_changed(GtkTreeView *tv, gpointer data)
827 GtkTreePath *path = NULL;
828 GtkNotebook *nbook = GTK_NOTEBOOK(data);
829 GtkTreeModel *model;
830 GtkWidget *page = NULL;
831 GtkTreeIter iter;
833 gtk_tree_view_get_cursor(tv, &path, NULL);
834 if (!path)
835 return;
837 model = gtk_tree_view_get_model(tv);
838 gtk_tree_model_get_iter(model, &iter, path);
839 gtk_tree_path_free(path);
840 gtk_tree_model_get(model, &iter, 1, &page, -1);
842 if (page)
843 gtk_notebook_set_current_page(nbook,
844 gtk_notebook_page_num(nbook, page));
847 /* Creates the window and adds the various buttons to it.
848 * Returns the notebook to add sections to and sets the global
849 * 'window'. If 'tree_view' is non-NULL, it stores the address
850 * of the tree view widget there.
852 static GtkWidget *build_window_frame(GtkTreeView **tree_view)
854 GtkWidget *notebook;
855 GtkWidget *tl_vbox, *hbox, *frame, *tv;
856 GtkWidget *actions, *button;
857 GtkTreeStore *model;
858 char *string, *save_path;
860 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
862 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
863 gtk_window_set_title(GTK_WINDOW(window), _("Options"));
864 g_signal_connect(window, "destroy",
865 G_CALLBACK(options_destroyed), NULL);
866 gtk_container_set_border_width(GTK_CONTAINER(window), 4);
868 tl_vbox = gtk_vbox_new(FALSE, 4);
869 gtk_container_add(GTK_CONTAINER(window), tl_vbox);
871 hbox = gtk_hbox_new(FALSE, 4);
872 gtk_box_pack_start(GTK_BOX(tl_vbox), hbox, TRUE, TRUE, 0);
874 frame = gtk_frame_new(NULL);
875 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
876 gtk_box_pack_end(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
878 notebook = gtk_notebook_new();
879 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
880 gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
881 gtk_container_add(GTK_CONTAINER(frame), notebook);
883 frame = gtk_frame_new(NULL);
884 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
885 gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
887 /* tree view */
888 model = gtk_tree_store_new(2, G_TYPE_STRING, GTK_TYPE_WIDGET);
889 tv = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
890 g_object_unref(model);
891 gtk_tree_selection_set_mode(
892 gtk_tree_view_get_selection(GTK_TREE_VIEW(tv)),
893 GTK_SELECTION_BROWSE);
894 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv), FALSE);
895 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tv), -1,
896 NULL, gtk_cell_renderer_text_new(), "text", 0, NULL);
897 gtk_container_add(GTK_CONTAINER(frame), tv);
898 g_signal_connect(tv, "cursor_changed",
899 G_CALLBACK(tree_cursor_changed), notebook);
901 actions = gtk_hbutton_box_new();
902 gtk_button_box_set_layout(GTK_BUTTON_BOX(actions),
903 GTK_BUTTONBOX_END);
904 gtk_box_set_spacing(GTK_BOX(actions), 10);
906 gtk_box_pack_start(GTK_BOX(tl_vbox), actions, FALSE, TRUE, 0);
908 revert_widget = button_new_mixed(GTK_STOCK_UNDO, _("_Revert"));
909 GTK_WIDGET_SET_FLAGS(revert_widget, GTK_CAN_DEFAULT);
910 gtk_box_pack_start(GTK_BOX(actions), revert_widget, FALSE, TRUE, 0);
911 g_signal_connect(revert_widget, "clicked",
912 G_CALLBACK(revert_options), NULL);
913 gtk_tooltips_set_tip(option_tooltips, revert_widget,
914 _("Restore all choices to how they were when the "
915 "Options box was opened."), NULL);
916 gtk_widget_set_sensitive(revert_widget, check_anything_changed());
918 button = gtk_button_new_from_stock(GTK_STOCK_OK);
919 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
920 gtk_box_pack_start(GTK_BOX(actions), button, FALSE, TRUE, 0);
921 g_signal_connect_swapped(button, "clicked",
922 G_CALLBACK(gtk_widget_destroy), window);
923 gtk_widget_grab_default(button);
924 gtk_widget_grab_focus(button);
926 save_path = choices_find_path_save("...", PROJECT, FALSE);
927 if (save_path)
929 string = g_strdup_printf(_("Choices will be saved as:\n%s"),
930 save_path);
931 gtk_tooltips_set_tip(option_tooltips, button, string, NULL);
932 g_free(string);
933 g_free(save_path);
935 else
936 gtk_tooltips_set_tip(option_tooltips, button,
937 _("(saving disabled by CHOICESPATH)"), NULL);
939 if (tree_view)
940 *tree_view = GTK_TREE_VIEW(tv);
942 return notebook;
945 /* Given the last radio button in the group, select whichever
946 * radio button matches the given value.
948 static void radio_group_set_value(GtkRadioButton *last, guchar *value)
950 GSList *next;
952 for (next = gtk_radio_button_get_group(last); next; next = next->next)
954 GtkToggleButton *button = (GtkToggleButton *) next->data;
955 guchar *val;
957 val = g_object_get_data(G_OBJECT(button), "value");
958 g_return_if_fail(val != NULL);
960 if (strcmp(val, value) == 0)
962 gtk_toggle_button_set_active(button, TRUE);
963 return;
967 g_warning("Can't find radio button with value %s\n", value);
970 /* Given the last radio button in the group, return a copy of the
971 * value for the selected radio item.
973 static guchar *radio_group_get_value(GtkRadioButton *last)
975 GSList *next;
977 for (next = gtk_radio_button_get_group(last); next; next = next->next)
979 GtkToggleButton *button = (GtkToggleButton *) next->data;
981 if (gtk_toggle_button_get_active(button))
983 guchar *val;
985 val = g_object_get_data(G_OBJECT(button), "value");
986 g_return_val_if_fail(val != NULL, NULL);
988 return g_strdup(val);
992 return NULL;
995 /* Select this item with this value */
996 static void option_menu_set(GtkOptionMenu *om, guchar *value)
998 GtkWidget *menu;
999 GList *list, *next;
1000 int i = 0;
1002 menu = gtk_option_menu_get_menu(om);
1003 list = gtk_container_get_children(GTK_CONTAINER(menu));
1005 for (next = list; next; next = next->next)
1007 GObject *item = (GObject *) next->data;
1008 guchar *data;
1010 data = g_object_get_data(item, "value");
1011 g_return_if_fail(data != NULL);
1013 if (strcmp(data, value) == 0)
1015 gtk_option_menu_set_history(om, i);
1016 break;
1019 i++;
1022 g_list_free(list);
1025 /* Get the value (static) of the selected item */
1026 static guchar *option_menu_get(GtkOptionMenu *om)
1028 GtkWidget *menu, *item;
1030 menu = gtk_option_menu_get_menu(om);
1031 item = gtk_menu_get_active(GTK_MENU(menu));
1033 return g_object_get_data(G_OBJECT(item), "value");
1036 static void restore_backup(gpointer key, gpointer value, gpointer data)
1038 Option *option = (Option *) value;
1040 g_return_if_fail(option->backup != NULL);
1042 option->has_changed = strcmp(option->value, option->backup) != 0;
1043 if (!option->has_changed)
1044 return;
1046 g_free(option->value);
1047 option->value = g_strdup(option->backup);
1048 option->int_value = atoi(option->value);
1051 static void revert_options(GtkWidget *widget, gpointer data)
1053 g_hash_table_foreach(option_hash, restore_backup, NULL);
1054 options_notify();
1055 update_option_widgets();
1058 static void check_changed_cb(gpointer key, gpointer value, gpointer data)
1060 Option *option = (Option *) value;
1061 gboolean *changed = (gboolean *) data;
1063 g_return_if_fail(option->backup != NULL);
1065 if (*changed)
1066 return;
1068 if (strcmp(option->value, option->backup) != 0)
1069 *changed = TRUE;
1072 static gboolean check_anything_changed(void)
1074 gboolean retval = FALSE;
1076 g_hash_table_foreach(option_hash, check_changed_cb, &retval);
1078 return retval;
1081 static void write_option(gpointer key, gpointer value, gpointer data)
1083 xmlNodePtr doc = (xmlNodePtr) data;
1084 Option *option = (Option *) value;
1085 xmlNodePtr tree;
1087 tree = xmlNewTextChild(doc, NULL, "Option", option->value);
1088 xmlSetProp(tree, "name", (gchar *) key);
1091 static void save_options(void)
1093 xmlDoc *doc;
1094 GList *next;
1095 guchar *save, *save_new;
1097 save = choices_find_path_save("Options", PROJECT, TRUE);
1098 if (!save)
1099 goto out;
1101 save_new = g_strconcat(save, ".new", NULL);
1103 doc = xmlNewDoc("1.0");
1104 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "Options", NULL));
1106 g_hash_table_foreach(option_hash, write_option,
1107 xmlDocGetRootElement(doc));
1109 if (save_xml_file(doc, save_new) || rename(save_new, save))
1110 report_error(_("Error saving %s: %s"), save, g_strerror(errno));
1112 g_free(save_new);
1113 g_free(save);
1114 xmlFreeDoc(doc);
1116 for (next = saver_callbacks; next; next = next->next)
1118 OptionNotify *cb = (OptionNotify *) next->data;
1119 cb();
1122 out:
1123 if (window)
1124 gtk_widget_destroy(window);
1127 /* Make the widget reflect the current value of the option */
1128 static void update_cb(gpointer key, gpointer value, gpointer data)
1130 Option *option = (Option *) value;
1132 g_return_if_fail(option != NULL);
1133 g_return_if_fail(option->widget != NULL);
1135 updating_widgets++;
1137 if (option->update_widget)
1138 option->update_widget(option);
1140 updating_widgets--;
1143 /* Reflect the values in the Option structures by changing the widgets
1144 * in the Options window.
1146 static void update_option_widgets(void)
1148 g_hash_table_foreach(option_hash, update_cb, NULL);
1151 /* Each of the following update the widget to make it show the current
1152 * value of the option.
1155 static void update_toggle(Option *option)
1157 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(option->widget),
1158 option->int_value);
1161 static void update_entry(Option *option)
1163 gtk_entry_set_text(GTK_ENTRY(option->widget), option->value);
1166 static void update_numentry(Option *option)
1168 gtk_spin_button_set_value(GTK_SPIN_BUTTON(option->widget),
1169 option->int_value);
1172 static void update_radio_group(Option *option)
1174 radio_group_set_value(GTK_RADIO_BUTTON(option->widget), option->value);
1177 static void update_slider(Option *option)
1179 gtk_adjustment_set_value(
1180 gtk_range_get_adjustment(GTK_RANGE(option->widget)),
1181 option->int_value);
1184 static void update_menu(Option *option)
1186 option_menu_set(GTK_OPTION_MENU(option->widget), option->value);
1189 static void update_font(Option *option)
1191 GtkToggleButton *active;
1192 gboolean have_font = option->value[0] != '\0';
1194 active = g_object_get_data(G_OBJECT(option->widget), "rox_override");
1196 if (active)
1198 gtk_toggle_button_set_active(active, have_font);
1199 gtk_widget_set_sensitive(option->widget->parent, have_font);
1202 gtk_label_set_text(GTK_LABEL(option->widget),
1203 have_font ? option->value
1204 : (guchar *) _("(use default)"));
1207 static void update_colour(Option *option)
1209 GdkColor colour;
1211 gdk_color_parse(option->value, &colour);
1212 button_patch_set_colour(option->widget, &colour);
1215 /* Each of these read_* calls get the new (string) value of an option
1216 * from the widget.
1219 static guchar *read_toggle(Option *option)
1221 GtkToggleButton *toggle = GTK_TOGGLE_BUTTON(option->widget);
1223 return g_strdup_printf("%d", gtk_toggle_button_get_active(toggle));
1226 static guchar *read_entry(Option *option)
1228 return gtk_editable_get_chars(GTK_EDITABLE(option->widget), 0, -1);
1231 static guchar *read_numentry(Option *option)
1233 return g_strdup_printf("%d", (int)
1234 gtk_spin_button_get_value(GTK_SPIN_BUTTON(option->widget)));
1237 static guchar *read_slider(Option *option)
1239 return g_strdup_printf("%d", (int)
1240 gtk_range_get_adjustment(GTK_RANGE(option->widget))->value);
1243 static guchar *read_radio_group(Option *option)
1245 return radio_group_get_value(GTK_RADIO_BUTTON(option->widget));
1248 static guchar *read_menu(Option *option)
1250 return g_strdup(option_menu_get(GTK_OPTION_MENU(option->widget)));
1253 static guchar *read_font(Option *option)
1255 GtkToggleButton *active;
1257 active = g_object_get_data(G_OBJECT(option->widget), "rox_override");
1258 if (active && !gtk_toggle_button_get_active(active))
1259 return g_strdup("");
1261 return g_strdup(gtk_label_get_text(GTK_LABEL(option->widget)));
1264 static guchar *read_colour(Option *option)
1266 GtkStyle *style = GTK_BIN(option->widget)->child->style;
1268 return g_strdup_printf("#%04x%04x%04x",
1269 style->bg[GTK_STATE_NORMAL].red,
1270 style->bg[GTK_STATE_NORMAL].green,
1271 style->bg[GTK_STATE_NORMAL].blue);
1274 static void set_not_changed(gpointer key, gpointer value, gpointer data)
1276 Option *option = (Option *) value;
1278 option->has_changed = FALSE;
1281 /* Builders for decorations (no corresponding option) */
1283 static GList *build_label(Option *option, xmlNode *node, guchar *label)
1285 GtkWidget *widget;
1286 guchar *text;
1287 int help;
1289 g_return_val_if_fail(option == NULL, NULL);
1290 g_return_val_if_fail(label == NULL, NULL);
1292 text = DATA(node);
1293 widget = gtk_label_new(_(text));
1294 g_free(text);
1296 help = get_int(node, "help");
1298 gtk_misc_set_alignment(GTK_MISC(widget), 0, help ? 0.5 : 1);
1299 gtk_label_set_justify(GTK_LABEL(widget), GTK_JUSTIFY_LEFT);
1300 gtk_label_set_line_wrap(GTK_LABEL(widget), TRUE);
1302 if (help)
1304 GtkWidget *hbox, *image, *align, *spacer;
1306 hbox = gtk_hbox_new(FALSE, 4);
1307 image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO,
1308 GTK_ICON_SIZE_BUTTON);
1309 align = gtk_alignment_new(0, 0, 0, 0);
1311 gtk_container_add(GTK_CONTAINER(align), image);
1312 gtk_box_pack_start(GTK_BOX(hbox), align, FALSE, TRUE, 0);
1313 gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
1315 spacer = gtk_event_box_new();
1316 gtk_widget_set_size_request(spacer, 6, 6);
1318 return g_list_append(g_list_append(NULL, hbox), spacer);
1321 return g_list_append(NULL, widget);
1324 static GList *build_spacer(Option *option, xmlNode *node, guchar *label)
1326 GtkWidget *eb;
1328 g_return_val_if_fail(option == NULL, NULL);
1329 g_return_val_if_fail(label == NULL, NULL);
1331 eb = gtk_event_box_new();
1332 gtk_widget_set_size_request(eb, 12, 12);
1334 return g_list_append(NULL, eb);
1337 static GList *build_frame(Option *option, xmlNode *node, guchar *label)
1339 GtkWidget *nbox, *frame, *label_widget;
1340 xmlNode *hw;
1341 PangoAttrList *list;
1342 PangoAttribute *attr;
1344 g_return_val_if_fail(option == NULL, NULL);
1345 g_return_val_if_fail(label != NULL, NULL);
1347 frame = gtk_frame_new(_(label));
1348 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
1350 /* Make the title bold */
1351 label_widget = gtk_frame_get_label_widget(GTK_FRAME(frame));
1352 attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
1354 attr->start_index = 0;
1355 attr->end_index = -1;
1356 list = pango_attr_list_new();
1357 pango_attr_list_insert(list, attr);
1358 gtk_label_set_attributes(GTK_LABEL(label_widget), list);
1360 nbox = gtk_vbox_new(FALSE, 0);
1361 gtk_container_set_border_width(GTK_CONTAINER(nbox), 12);
1362 gtk_container_add(GTK_CONTAINER(frame), nbox);
1364 for (hw = node->xmlChildrenNode; hw; hw = hw->next)
1365 if (hw->type == XML_ELEMENT_NODE)
1366 build_widget(hw, nbox);
1368 return g_list_append(NULL, frame);
1371 /* These create new widgets in the options window and set the appropriate
1372 * callbacks.
1375 static GList *build_toggle(Option *option, xmlNode *node, guchar *label)
1377 GtkWidget *toggle;
1379 g_return_val_if_fail(option != NULL, NULL);
1381 toggle = gtk_check_button_new_with_label(_(label));
1383 may_add_tip(toggle, node);
1385 option->update_widget = update_toggle;
1386 option->read_widget = read_toggle;
1387 option->widget = toggle;
1389 g_signal_connect_swapped(toggle, "toggled",
1390 G_CALLBACK(option_check_widget), option);
1392 return g_list_append(NULL, toggle);
1395 static GList *build_slider(Option *option, xmlNode *node, guchar *label)
1397 GtkAdjustment *adj;
1398 GtkWidget *hbox, *slide, *label_wid;
1399 int min, max;
1400 int fixed;
1401 int showvalue;
1402 guchar *end;
1404 g_return_val_if_fail(option != NULL, NULL);
1406 min = get_int(node, "min");
1407 max = get_int(node, "max");
1408 fixed = get_int(node, "fixed");
1409 showvalue = get_int(node, "showvalue");
1411 adj = GTK_ADJUSTMENT(gtk_adjustment_new(0,
1412 min, max, 1, 10, 0));
1414 hbox = gtk_hbox_new(FALSE, 4);
1416 if (label)
1418 label_wid = gtk_label_new(_(label));
1419 gtk_misc_set_alignment(GTK_MISC(label_wid), 0, 0.5);
1420 gtk_box_pack_start(GTK_BOX(hbox), label_wid, FALSE, TRUE, 0);
1421 add_to_size_group(node, label_wid);
1424 end = xmlGetProp(node, "end");
1425 if (end)
1427 gtk_box_pack_end(GTK_BOX(hbox), gtk_label_new(_(end)),
1428 FALSE, TRUE, 0);
1429 g_free(end);
1432 slide = gtk_hscale_new(adj);
1434 if (fixed)
1435 gtk_widget_set_size_request(slide, adj->upper, 24);
1436 if (showvalue)
1438 gtk_scale_set_draw_value(GTK_SCALE(slide), TRUE);
1439 gtk_scale_set_value_pos(GTK_SCALE(slide),
1440 GTK_POS_LEFT);
1441 gtk_scale_set_digits(GTK_SCALE(slide), 0);
1443 else
1444 gtk_scale_set_draw_value(GTK_SCALE(slide), FALSE);
1445 GTK_WIDGET_UNSET_FLAGS(slide, GTK_CAN_FOCUS);
1447 may_add_tip(slide, node);
1449 gtk_box_pack_start(GTK_BOX(hbox), slide, !fixed, TRUE, 0);
1451 option->update_widget = update_slider;
1452 option->read_widget = read_slider;
1453 option->widget = slide;
1455 g_signal_connect_swapped(adj, "value-changed",
1456 G_CALLBACK(option_check_widget), option);
1458 return g_list_append(NULL, hbox);
1461 static GList *build_entry(Option *option, xmlNode *node, guchar *label)
1463 GtkWidget *hbox;
1464 GtkWidget *entry;
1465 GtkWidget *label_wid;
1467 g_return_val_if_fail(option != NULL, NULL);
1469 hbox = gtk_hbox_new(FALSE, 4);
1471 if (label)
1473 label_wid = gtk_label_new(_(label));
1474 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1475 gtk_box_pack_start(GTK_BOX(hbox), label_wid, FALSE, TRUE, 0);
1478 entry = gtk_entry_new();
1479 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
1480 add_to_size_group(node, entry);
1481 may_add_tip(entry, node);
1483 option->update_widget = update_entry;
1484 option->read_widget = read_entry;
1485 option->widget = entry;
1487 g_signal_connect_data(entry, "changed",
1488 G_CALLBACK(option_check_widget), option,
1489 NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
1491 return g_list_append(NULL, hbox);
1494 static GList *build_numentry(Option *option, xmlNode *node, guchar *label)
1496 GtkWidget *hbox;
1497 GtkWidget *spin;
1498 GtkWidget *label_wid;
1499 guchar *unit;
1500 int min, max, step, width;
1502 g_return_val_if_fail(option != NULL, NULL);
1504 min = get_int(node, "min");
1505 max = get_int(node, "max");
1506 step = get_int(node, "step");
1507 width = get_int(node, "width");
1508 unit = xmlGetProp(node, "unit");
1510 hbox = gtk_hbox_new(FALSE, 4);
1512 if (label)
1514 label_wid = gtk_label_new(_(label));
1515 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1516 gtk_box_pack_start(GTK_BOX(hbox), label_wid, FALSE, TRUE, 0);
1517 add_to_size_group(node, label_wid);
1520 spin = gtk_spin_button_new_with_range(min, max, step > 0 ? step : 1);
1521 gtk_entry_set_width_chars(GTK_ENTRY(spin), width > 1 ? width + 1 : -1);
1522 gtk_box_pack_start(GTK_BOX(hbox), spin, FALSE, TRUE, 0);
1523 may_add_tip(spin, node);
1525 if (unit)
1527 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_(unit)),
1528 FALSE, TRUE, 0);
1529 g_free(unit);
1532 option->update_widget = update_numentry;
1533 option->read_widget = read_numentry;
1534 option->widget = spin;
1536 g_signal_connect_swapped(spin, "value-changed",
1537 G_CALLBACK(option_check_widget), option);
1539 return g_list_append(NULL, hbox);
1542 static GList *build_radio_group(Option *option, xmlNode *node, guchar *label)
1544 GList *list = NULL;
1545 GtkWidget *button = NULL;
1546 xmlNode *rn;
1547 int cols;
1549 g_return_val_if_fail(option != NULL, NULL);
1551 for (rn = node->xmlChildrenNode; rn; rn = rn->next)
1553 if (rn->type == XML_ELEMENT_NODE)
1555 button = build_radio(rn, button);
1556 g_signal_connect_swapped(button, "toggled",
1557 G_CALLBACK(option_check_widget), option);
1558 list = g_list_append(list, button);
1562 option->update_widget = update_radio_group;
1563 option->read_widget = read_radio_group;
1564 option->widget = button;
1566 cols = get_int(node, "columns");
1567 if (cols > 1)
1569 GtkWidget *table;
1570 GList *next;
1571 int i, n;
1572 int rows;
1574 n = g_list_length(list);
1575 rows = (n + cols - 1) / cols;
1577 table = gtk_table_new(rows, cols, FALSE);
1579 i = 0;
1580 for (next = list; next; next = next->next)
1582 GtkWidget *button = GTK_WIDGET(next->data);
1583 int left = i / rows;
1584 int top = i % rows;
1586 gtk_table_attach_defaults(GTK_TABLE(table), button,
1587 left, left + 1, top, top + 1);
1589 i++;
1592 g_list_free(list);
1593 list = g_list_prepend(NULL, table);
1596 return list;
1599 static GList *build_colour(Option *option, xmlNode *node, guchar *label)
1601 GtkWidget *hbox, *da, *button, *label_wid;
1603 g_return_val_if_fail(option != NULL, NULL);
1605 hbox = gtk_hbox_new(FALSE, 4);
1607 if (label)
1609 label_wid = gtk_label_new(_(label));
1610 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1611 gtk_box_pack_start(GTK_BOX(hbox), label_wid, TRUE, TRUE, 0);
1614 button = gtk_button_new();
1615 da = gtk_drawing_area_new();
1616 gtk_widget_set_size_request(da, 64, 12);
1617 gtk_container_add(GTK_CONTAINER(button), da);
1618 g_signal_connect(button, "clicked", G_CALLBACK(open_coloursel), option);
1620 may_add_tip(button, node);
1622 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1624 option->update_widget = update_colour;
1625 option->read_widget = read_colour;
1626 option->widget = button;
1628 return g_list_append(NULL, hbox);
1631 static GList *build_menu(Option *option, xmlNode *node, guchar *label)
1633 GtkWidget *hbox, *om, *option_menu, *label_wid;
1634 xmlNode *item;
1636 g_return_val_if_fail(option != NULL, NULL);
1638 hbox = gtk_hbox_new(FALSE, 4);
1640 label_wid = gtk_label_new(_(label));
1641 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1642 gtk_box_pack_start(GTK_BOX(hbox), label_wid, TRUE, TRUE, 0);
1644 option_menu = gtk_option_menu_new();
1645 gtk_box_pack_start(GTK_BOX(hbox), option_menu, FALSE, TRUE, 0);
1647 om = gtk_menu_new();
1648 gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), om);
1650 add_to_size_group(node, option_menu);
1652 for (item = node->xmlChildrenNode; item; item = item->next)
1654 if (item->type == XML_ELEMENT_NODE)
1655 build_menu_item(item, option_menu);
1658 option->update_widget = update_menu;
1659 option->read_widget = read_menu;
1660 option->widget = option_menu;
1662 g_signal_connect_data(option_menu, "changed",
1663 G_CALLBACK(option_check_widget), option,
1664 NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
1666 return g_list_append(NULL, hbox);
1669 static GList *build_font(Option *option, xmlNode *node, guchar *label)
1671 GtkWidget *hbox, *button;
1672 GtkWidget *active = NULL;
1673 int override;
1675 g_return_val_if_fail(option != NULL, NULL);
1677 override = get_int(node, "override");
1679 hbox = gtk_hbox_new(FALSE, 4);
1681 if (override)
1683 /* Add a check button to enable the font chooser. If off,
1684 * the option's value is "".
1686 active = gtk_check_button_new_with_label(_(label));
1687 gtk_box_pack_start(GTK_BOX(hbox), active, FALSE, TRUE, 0);
1688 g_signal_connect(active, "toggled",
1689 G_CALLBACK(toggle_active_font), option);
1691 else
1692 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_(label)),
1693 FALSE, TRUE, 0);
1695 button = gtk_button_new_with_label("");
1696 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1698 option->update_widget = update_font;
1699 option->read_widget = read_font;
1700 option->widget = GTK_BIN(button)->child;
1701 may_add_tip(button, node);
1703 g_object_set_data(G_OBJECT(option->widget), "rox_override", active);
1705 g_signal_connect(button, "clicked", G_CALLBACK(open_fontsel), option);
1707 return g_list_append(NULL, hbox);
1710 static void button_patch_set_colour(GtkWidget *button, GdkColor *colour)
1712 GtkStyle *style;
1713 GtkWidget *patch;
1715 patch = GTK_BIN(button)->child;
1717 style = gtk_style_copy(GTK_WIDGET(patch)->style);
1718 style->bg[GTK_STATE_NORMAL].red = colour->red;
1719 style->bg[GTK_STATE_NORMAL].green = colour->green;
1720 style->bg[GTK_STATE_NORMAL].blue = colour->blue;
1721 gtk_widget_set_style(patch, style);
1722 g_object_unref(G_OBJECT(style));
1724 if (GTK_WIDGET_REALIZED(patch))
1725 gdk_window_clear(patch->window);
1728 static void load_options(xmlDoc *doc)
1730 xmlNode *root, *node;
1732 root = xmlDocGetRootElement(doc);
1734 g_return_if_fail(strcmp(root->name, "Options") == 0);
1736 for (node = root->xmlChildrenNode; node; node = node->next)
1738 gchar *value, *name;
1740 if (node->type != XML_ELEMENT_NODE)
1741 continue;
1742 if (strcmp(node->name, "Option") != 0)
1743 continue;
1744 name = xmlGetProp(node, "name");
1745 if (!name)
1746 continue;
1748 value = xmlNodeGetContent(node);
1750 if (g_hash_table_lookup(loading, name))
1751 g_warning("Duplicate option found!");
1753 g_hash_table_insert(loading, name, value);
1755 /* (don't need to free name or value) */
1759 /* Process one line from the options file (\0 term'd).
1760 * Returns NULL on success, or a pointer to an error message.
1761 * The line is modified.
1763 static const char *process_option_line(gchar *line)
1765 gchar *eq, *c;
1766 gchar *name = line;
1768 g_return_val_if_fail(option_hash != NULL, "No registered options!");
1770 eq = strchr(line, '=');
1771 if (!eq)
1772 return _("Missing '='");
1774 c = eq - 1;
1775 while (c > line && (*c == ' ' || *c == '\t'))
1776 c--;
1777 c[1] = '\0';
1778 c = eq + 1;
1779 while (*c == ' ' || *c == '\t')
1780 c++;
1782 if (g_hash_table_lookup(loading, name))
1783 return "Duplicate option found!";
1785 g_hash_table_insert(loading, g_strdup(name), g_strdup(g_strstrip(c)));
1787 return NULL;