r4777: Don't display a warning about deprecated options; it just confuses people
[rox-filer/ma.git] / ROX-Filer / src / options.c
blobb17da736159b677b1d999ee1fc5f3fd7f5e207a5
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* options.c - code for handling user choices */
22 /* How it works:
24 * On startup:
26 * - The <Choices>/PROJECT/Options file is read in, which contains a list of
27 * name/value pairs, and these are stored in the 'loading' hash table.
29 * - Each part of the filer then calls option_add_int(), or a related function,
30 * supplying the name for each option and a default value. Once an option is
31 * registered, it is removed from the loading table.
33 * - If things need to happen when values change, modules register with
34 * option_add_notify().
36 * - option_register_widget() can be used during initialisation (any time
37 * before the Options box is displayed) to tell the system how to render a
38 * particular type of option.
40 * - Finally, all notify callbacks are called. Use the Option->has_changed
41 * field to work out what has changed from the defaults.
43 * When the user opens the Options box:
45 * - The Options.xml file is read and used to create the Options dialog box.
46 * Each element in the file has a key corresponding to an option named
47 * above.
49 * - For each widget in the box, the current value of the option is used to
50 * set the widget's state.
52 * - All current values are saved for a possible Revert later.
54 * When the user changes an option or clicks on Revert:
56 * - The option values are updated.
58 * - All notify callbacks are called. Use the Option->has_changed field
59 * to see what changed.
61 * When OK is clicked:
63 * - If anything changed then:
64 * - All the options are written to the filesystem
65 * - The saver_callbacks are called.
68 #include "config.h"
70 #include <stdio.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <errno.h>
74 #include <ctype.h>
75 #include <gtk/gtk.h>
76 #include <libxml/parser.h>
78 #include "global.h"
80 #include "choices.h"
81 #include "options.h"
82 #include "main.h"
83 #include "gui_support.h"
84 #include "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(void);
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);
135 static int get_int(xmlNode *node, guchar *attr);
136 static void may_add_tip(GtkWidget *widget, xmlNode *element);
137 static void add_to_size_group(xmlNode *node, GtkWidget *widget);
139 static const char *process_option_line(gchar *line);
141 static GList *build_label(Option *option, xmlNode *node, guchar *label);
142 static GList *build_spacer(Option *option, xmlNode *node, guchar *label);
143 static GList *build_frame(Option *option, xmlNode *node, guchar *label);
145 static GList *build_toggle(Option *option, xmlNode *node, guchar *label);
146 static GList *build_slider(Option *option, xmlNode *node, guchar *label);
147 static GList *build_entry(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);
152 static GList *build_numentry(Option *option, xmlNode *node, guchar *label);
153 static void update_numentry(Option *option);
154 static guchar *read_numentry(Option *option);
156 static gboolean updating_file_format = FALSE;
158 /****************************************************************
159 * EXTERNAL INTERFACE *
160 ****************************************************************/
162 void options_init(void)
164 char *path;
165 xmlDoc *doc;
167 loading = g_hash_table_new(g_str_hash, g_str_equal);
168 option_hash = g_hash_table_new(g_str_hash, g_str_equal);
169 widget_builder = g_hash_table_new(g_str_hash, g_str_equal);
171 path = choices_find_xdg_path_load("Options", PROJECT, SITE);
172 if (path)
174 /* Load in all the options set in the filer, storing them
175 * temporarily in the loading hash table.
176 * They get moved to option_hash when they're registered.
178 doc = xmlParseFile(path);
179 if (doc)
181 load_options(doc);
182 xmlFreeDoc(doc);
184 else
186 parse_file(path, process_option_line);
187 updating_file_format = TRUE;
190 g_free(path);
193 option_register_widget("label", build_label);
194 option_register_widget("spacer", build_spacer);
195 option_register_widget("frame", build_frame);
197 option_register_widget("toggle", build_toggle);
198 option_register_widget("slider", build_slider);
199 option_register_widget("entry", build_entry);
200 option_register_widget("numentry", build_numentry);
201 option_register_widget("radio-group", build_radio_group);
202 option_register_widget("colour", build_colour);
203 option_register_widget("menu", build_menu);
204 option_register_widget("font", build_font);
207 /* When parsing the XML file, process an element named 'name' by
208 * calling 'builder(option, xml_node, label)'.
209 * builder returns the new widgets to add to the options box.
210 * 'name' should be a static string. Call 'option_check_widget' when
211 * the widget's value is modified.
213 * Functions to set or get the widget's state can be stored in 'option'.
214 * If the option doesn't have a name attribute in Options.xml then
215 * ui will be NULL on entry (this is used for buttons).
217 void option_register_widget(char *name, OptionBuildFn builder)
219 g_hash_table_insert(widget_builder, name, builder);
222 /* This is called when the widget's value is modified by the user.
223 * Reads the new value of the widget into the option and calls
224 * the notify callbacks.
226 void option_check_widget(Option *option)
228 guchar *new = NULL;
230 if (updating_widgets)
231 return; /* Not caused by the user... */
233 g_return_if_fail(option->read_widget != NULL);
235 new = option->read_widget(option);
237 g_return_if_fail(new != NULL);
239 g_hash_table_foreach(option_hash, set_not_changed, NULL);
241 option->has_changed = strcmp(option->value, new) != 0;
243 if (!option->has_changed)
245 g_free(new);
246 return;
249 g_free(option->value);
250 option->value = new;
251 option->int_value = atoi(new);
253 options_notify();
256 /* Call all the notify callbacks. This should happen after any options
257 * have their values changed.
258 * Set each option->has_changed flag before calling this function.
260 void options_notify(void)
262 GList *next;
264 for (next = notify_callbacks; next; next = next->next)
266 OptionNotify *cb = (OptionNotify *) next->data;
268 cb();
271 if (updating_file_format)
273 updating_file_format = FALSE;
274 save_options();
275 info_message(_("ROX-Filer has converted your Options file "
276 "to the new XML format"));
279 if (revert_widget)
280 gtk_widget_set_sensitive(revert_widget,
281 check_anything_changed());
284 /* Store values used by Revert */
285 static void store_backup(gpointer key, gpointer value, gpointer data)
287 Option *option = (Option *) value;
289 g_free(option->backup);
290 option->backup = g_strdup(option->value);
293 /* Allow the user to edit the options. Returns the window widget (you don't
294 * normally need this). NULL if already open.
296 GtkWidget *options_show(void)
298 if (!option_tooltips)
299 option_tooltips = gtk_tooltips_new();
301 /* For debugging
302 if (g_hash_table_size(loading) != 0)
304 g_print(PROJECT ": Some options loaded but not used:\n");
305 g_hash_table_foreach(loading, (GHFunc) puts, NULL);
309 if (window)
311 gtk_window_present(GTK_WINDOW(window));
312 return NULL;
315 g_hash_table_foreach(option_hash, store_backup, NULL);
317 build_options_window();
319 update_option_widgets();
321 gtk_widget_show_all(window);
323 return window;
326 /* Initialise and register a new integer option */
327 void option_add_int(Option *option, const gchar *key, int value)
329 option->value = g_strdup_printf("%d", value);
330 option->int_value = value;
331 option_add(option, key);
334 void option_add_string(Option *option, const gchar *key, const gchar *value)
336 option->value = g_strdup(value);
337 option->int_value = atoi(value);
338 option_add(option, key);
341 /* Add a callback which will be called after any options have changed their
342 * values. If several options change at once, this is called after all
343 * changes.
345 void option_add_notify(OptionNotify *callback)
347 g_return_if_fail(callback != NULL);
349 notify_callbacks = g_list_append(notify_callbacks, callback);
352 /* Call 'callback' after all the options have been saved */
353 void option_add_saver(OptionNotify *callback)
355 g_return_if_fail(callback != NULL);
357 saver_callbacks = g_list_append(saver_callbacks, callback);
360 /* Base class for building numentry widgets with particular ranges */
361 GList *build_numentry_base(Option *option, xmlNode *node,
362 guchar *label, GtkAdjustment *adj)
364 GtkWidget *hbox;
365 GtkWidget *spin;
366 GtkWidget *label_wid;
367 guchar *unit;
368 int width;
370 width = get_int(node, "width");
371 unit = xmlGetProp(node, "unit");
373 hbox = gtk_hbox_new(FALSE, 4);
375 if (label)
377 label_wid = gtk_label_new(_(label));
378 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
379 gtk_box_pack_start(GTK_BOX(hbox), label_wid, FALSE, TRUE, 0);
380 add_to_size_group(node, label_wid);
383 spin = gtk_spin_button_new(adj, adj->step_increment, 0);
384 gtk_entry_set_width_chars(GTK_ENTRY(spin),
385 width > 1 ? width + 1 : 2);
386 gtk_box_pack_start(GTK_BOX(hbox), spin, FALSE, TRUE, 0);
387 may_add_tip(spin, node);
389 if (unit)
391 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_(unit)),
392 FALSE, TRUE, 0);
393 g_free(unit);
396 option->update_widget = update_numentry;
397 option->read_widget = read_numentry;
398 option->widget = spin;
400 g_signal_connect_swapped(spin, "value-changed",
401 G_CALLBACK(option_check_widget), option);
403 return g_list_append(NULL, hbox);
406 /****************************************************************
407 * INTERNAL FUNCTIONS *
408 ****************************************************************/
410 /* Option should contain the default value.
411 * It must never be destroyed after being registered (Options are typically
412 * statically allocated).
413 * The key corresponds to the option's name in Options.xml, and to the key
414 * in the saved options file.
416 * On exit, the value will have been updated to the loaded value, if
417 * different to the default.
419 static void option_add(Option *option, const gchar *key)
421 gpointer okey, value;
423 g_return_if_fail(option_hash != NULL);
424 g_return_if_fail(g_hash_table_lookup(option_hash, key) == NULL);
425 g_return_if_fail(option->value != NULL);
427 option->has_changed = FALSE;
429 option->widget = NULL;
430 option->update_widget = NULL;
431 option->read_widget = NULL;
432 option->backup = NULL;
434 g_hash_table_insert(option_hash, (gchar *) key, option);
436 /* Use the value loaded from the file, if any */
437 if (g_hash_table_lookup_extended(loading, key, &okey, &value))
439 option->has_changed = strcmp(option->value, value) != 0;
441 g_free(option->value);
442 option->value = value;
443 option->int_value = atoi(value);
444 g_hash_table_remove(loading, key);
445 g_free(okey);
449 static GtkColorSelectionDialog *current_csel_box = NULL;
450 static GtkFontSelectionDialog *current_fontsel_box = NULL;
452 static void get_new_colour(GtkWidget *ok, Option *option)
454 GtkWidget *csel;
455 GdkColor c;
457 g_return_if_fail(current_csel_box != NULL);
459 csel = current_csel_box->colorsel;
461 gtk_color_selection_get_current_color(GTK_COLOR_SELECTION(csel), &c);
463 button_patch_set_colour(option->widget, &c);
465 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
467 option_check_widget(option);
470 static void open_coloursel(GtkWidget *button, Option *option)
472 GtkColorSelectionDialog *csel;
473 GtkWidget *dialog, *patch;
475 if (current_csel_box)
476 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
478 dialog = gtk_color_selection_dialog_new(NULL);
479 csel = GTK_COLOR_SELECTION_DIALOG(dialog);
480 current_csel_box = csel;
481 gtk_window_set_position(GTK_WINDOW(csel), GTK_WIN_POS_MOUSE);
483 g_signal_connect(dialog, "destroy",
484 G_CALLBACK(gtk_widget_destroyed), &current_csel_box);
485 gtk_widget_hide(csel->help_button);
486 g_signal_connect_swapped(csel->cancel_button, "clicked",
487 G_CALLBACK(gtk_widget_destroy), dialog);
488 g_signal_connect(csel->ok_button, "clicked",
489 G_CALLBACK(get_new_colour), option);
491 patch = GTK_BIN(button)->child;
493 gtk_color_selection_set_current_color(
494 GTK_COLOR_SELECTION(csel->colorsel),
495 &patch->style->bg[GTK_STATE_NORMAL]);
497 gtk_widget_show(dialog);
500 static void font_chosen(GtkWidget *dialog, gint response, Option *option)
502 gchar *font;
504 if (response != GTK_RESPONSE_OK)
505 goto out;
507 font = gtk_font_selection_dialog_get_font_name(
508 GTK_FONT_SELECTION_DIALOG(dialog));
510 gtk_label_set_text(GTK_LABEL(option->widget), font);
512 g_free(font);
514 option_check_widget(option);
516 out:
517 gtk_widget_destroy(dialog);
521 static void toggle_active_font(GtkToggleButton *toggle, Option *option)
523 if (current_fontsel_box)
524 gtk_widget_destroy(GTK_WIDGET(current_fontsel_box));
526 if (gtk_toggle_button_get_active(toggle))
528 gtk_widget_set_sensitive(option->widget->parent, TRUE);
529 gtk_label_set_text(GTK_LABEL(option->widget), "Sans 12");
531 else
533 gtk_widget_set_sensitive(option->widget->parent, FALSE);
534 gtk_label_set_text(GTK_LABEL(option->widget),
535 _("(use default)"));
538 option_check_widget(option);
541 static void open_fontsel(GtkWidget *button, Option *option)
543 if (current_fontsel_box)
544 gtk_widget_destroy(GTK_WIDGET(current_fontsel_box));
546 current_fontsel_box = GTK_FONT_SELECTION_DIALOG(
547 gtk_font_selection_dialog_new(PROJECT));
549 gtk_window_set_position(GTK_WINDOW(current_fontsel_box),
550 GTK_WIN_POS_MOUSE);
552 g_signal_connect(current_fontsel_box, "destroy",
553 G_CALLBACK(gtk_widget_destroyed), &current_fontsel_box);
555 gtk_font_selection_dialog_set_font_name(current_fontsel_box,
556 option->value);
558 g_signal_connect(current_fontsel_box, "response",
559 G_CALLBACK(font_chosen), option);
561 gtk_widget_show(GTK_WIDGET(current_fontsel_box));
564 /* These are used during parsing... */
565 static xmlDocPtr options_doc = NULL;
567 #define DATA(node) (xmlNodeListGetString(options_doc, node->xmlChildrenNode, 1))
569 static void may_add_tip(GtkWidget *widget, xmlNode *element)
571 guchar *data, *tip;
573 data = DATA(element);
574 if (!data)
575 return;
577 tip = g_strstrip(g_strdup(data));
578 g_free(data);
579 if (*tip)
580 OPTION_TIP(widget, _(tip));
581 g_free(tip);
584 /* Returns zero if attribute is not present */
585 static int get_int(xmlNode *node, guchar *attr)
587 guchar *txt;
588 int retval;
590 txt = xmlGetProp(node, attr);
591 if (!txt)
592 return 0;
594 retval = atoi(txt);
595 g_free(txt);
597 return retval;
600 /* Adds 'widget' to the GtkSizeGroup selected by 'index'. This function
601 * does nothing if 'node' has no "sizegroup" attribute.
602 * The value of "sizegroup" is either a key. All widgets with the same
603 * key request the same size.
604 * Size groups are created on the fly and get destroyed when the options
605 * box is closed.
607 static void add_to_size_group(xmlNode *node, GtkWidget *widget)
609 GtkSizeGroup *sg;
610 guchar *name;
612 g_return_if_fail(node != NULL);
613 g_return_if_fail(widget != NULL);
615 name = xmlGetProp(node, "sizegroup");
616 if (!name)
617 return;
619 if (size_groups == NULL)
620 size_groups = g_hash_table_new_full(g_str_hash, g_str_equal,
621 g_free, NULL);
623 sg = (GtkSizeGroup *) g_hash_table_lookup(size_groups, name);
624 if (sg == NULL)
627 sg = (GtkSizeGroup *) gtk_size_group_new(
628 GTK_SIZE_GROUP_HORIZONTAL);
629 g_hash_table_insert(size_groups, name, sg);
630 gtk_size_group_add_widget(sg, widget);
631 g_object_unref(G_OBJECT(sg));
633 else
635 gtk_size_group_add_widget(sg, widget);
636 g_free(name);
640 static GtkWidget *build_radio(xmlNode *radio, GtkWidget *prev)
642 GtkWidget *button;
643 GtkRadioButton *prev_button = (GtkRadioButton *) prev;
644 guchar *label;
646 label = xmlGetProp(radio, "label");
648 button = gtk_radio_button_new_with_label(
649 prev_button ? gtk_radio_button_get_group(prev_button)
650 : NULL,
651 _(label));
652 g_free(label);
654 may_add_tip(button, radio);
656 g_object_set_data(G_OBJECT(button), "value",
657 xmlGetProp(radio, "value"));
659 return button;
662 static void build_menu_item(xmlNode *node, GtkWidget *menu)
664 GtkWidget *item;
665 guchar *label;
667 g_return_if_fail(strcmp(node->name, "item") == 0);
669 label = xmlGetProp(node, "label");
670 item = gtk_menu_item_new_with_label(_(label));
671 g_free(label);
673 gtk_widget_show(item);
674 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
676 g_object_set_data(G_OBJECT(item), "value", xmlGetProp(node, "value"));
679 static void build_widget(xmlNode *widget, GtkWidget *box)
681 const char *name = widget->name;
682 OptionBuildFn builder;
683 guchar *oname;
684 Option *option;
685 guchar *label;
687 label = xmlGetProp(widget, "label");
689 if (strcmp(name, "hbox") == 0 || strcmp(name, "vbox") == 0)
691 GtkWidget *nbox;
692 xmlNode *hw;
694 if (name[0] == 'h')
695 nbox = gtk_hbox_new(FALSE, 4);
696 else
697 nbox = gtk_vbox_new(FALSE, 0);
699 if (label)
700 gtk_box_pack_start(GTK_BOX(nbox),
701 gtk_label_new(_(label)), FALSE, TRUE, 4);
702 gtk_box_pack_start(GTK_BOX(box), nbox, FALSE, TRUE, 0);
704 for (hw = widget->xmlChildrenNode; hw; hw = hw->next)
706 if (hw->type == XML_ELEMENT_NODE)
707 build_widget(hw, nbox);
710 g_free(label);
711 return;
714 oname = xmlGetProp(widget, "name");
716 if (oname)
718 option = g_hash_table_lookup(option_hash, oname);
720 if (!option)
722 g_warning("No Option for '%s'!\n", oname);
723 g_free(oname);
724 return;
727 g_free(oname);
729 else
730 option = NULL;
732 builder = g_hash_table_lookup(widget_builder, name);
733 if (builder)
735 GList *widgets, *next;
737 if (option && option->widget)
738 g_warning("Widget for option already exists!");
740 widgets = builder(option, widget, label);
742 for (next = widgets; next; next = next->next)
744 GtkWidget *w = (GtkWidget *) next->data;
745 gtk_box_pack_start(GTK_BOX(box), w, FALSE, TRUE, 0);
747 g_list_free(widgets);
749 else
750 g_warning("Unknown option type '%s'\n", name);
752 g_free(label);
755 static void build_section(xmlNode *section, GtkWidget *notebook,
756 GtkTreeStore *tree_store, GtkTreeIter *parent)
758 guchar *title = NULL;
759 GtkWidget *page;
760 GtkTreeIter iter;
761 xmlNode *widget;
763 title = xmlGetProp(section, "title");
764 page = gtk_vbox_new(FALSE, 4);
765 gtk_container_set_border_width(GTK_CONTAINER(page), 4);
766 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, NULL);
768 gtk_tree_store_append(tree_store, &iter, parent);
769 gtk_tree_store_set(tree_store, &iter, 0, _(title), 1, page, -1);
770 g_free(title);
772 widget = section->xmlChildrenNode;
773 for (; widget; widget = widget->next)
775 if (widget->type == XML_ELEMENT_NODE)
777 if (strcmp(widget->name, "section") == 0)
778 build_section(widget, notebook,
779 tree_store, &iter);
780 else
781 build_widget(widget, page);
786 /* Parse <app_dir>/Options.xml to create the options window.
787 * Sets the global 'window' variable.
789 static void build_options_window(void)
791 GtkTreeView *tree;
792 GtkTreeStore *store;
793 GtkWidget *notebook;
794 xmlDocPtr options_doc;
795 xmlNode *options, *section;
796 gchar *path;
798 notebook = build_window_frame(&tree);
800 path = g_strconcat(app_dir, "/Options.xml", NULL);
801 options_doc = xmlParseFile(path);
803 if (!options_doc)
805 report_error(_("Internal error: %s unreadable"), path);
806 g_free(path);
807 return;
810 g_free(path);
812 options = xmlDocGetRootElement(options_doc);
813 if (strcmp(options->name, "options") == 0)
815 GtkTreePath *treepath;
817 store = (GtkTreeStore *) gtk_tree_view_get_model(tree);
818 section = options->xmlChildrenNode;
819 for (; section; section = section->next)
820 if (section->type == XML_ELEMENT_NODE)
821 build_section(section, notebook, store, NULL);
823 gtk_tree_view_expand_all(tree);
824 treepath = gtk_tree_path_new_first();
825 if (treepath)
827 gtk_tree_view_set_cursor(tree, treepath, NULL, FALSE);
828 gtk_tree_path_free(treepath);
832 xmlFreeDoc(options_doc);
833 options_doc = NULL;
836 static void null_widget(gpointer key, gpointer value, gpointer data)
838 Option *option = (Option *) value;
840 g_return_if_fail(option->widget != NULL);
842 option->widget = NULL;
845 static void options_destroyed(GtkWidget *widget, gpointer data)
847 if (current_csel_box)
848 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
849 if (current_fontsel_box)
850 gtk_widget_destroy(GTK_WIDGET(current_fontsel_box));
852 revert_widget = NULL;
854 if (check_anything_changed())
855 save_options();
857 if (widget == window)
859 window = NULL;
861 g_hash_table_foreach(option_hash, null_widget, NULL);
863 if (size_groups)
865 g_hash_table_destroy(size_groups);
866 size_groups = NULL;
872 /* The cursor has been changed in the tree view, so switch to the new
873 * page in the notebook.
875 static void tree_cursor_changed(GtkTreeView *tv, gpointer data)
877 GtkTreePath *path = NULL;
878 GtkNotebook *nbook = GTK_NOTEBOOK(data);
879 GtkTreeModel *model;
880 GtkWidget *page = NULL;
881 GtkTreeIter iter;
883 gtk_tree_view_get_cursor(tv, &path, NULL);
884 if (!path)
885 return;
887 model = gtk_tree_view_get_model(tv);
888 gtk_tree_model_get_iter(model, &iter, path);
889 gtk_tree_path_free(path);
890 gtk_tree_model_get(model, &iter, 1, &page, -1);
892 if (page)
894 gtk_notebook_set_current_page(nbook,
895 gtk_notebook_page_num(nbook, page));
896 g_object_unref(page);
900 /* Creates the window and adds the various buttons to it.
901 * Returns the notebook to add sections to and sets the global
902 * 'window'. If 'tree_view' is non-NULL, it stores the address
903 * of the tree view widget there.
905 static GtkWidget *build_window_frame(GtkTreeView **tree_view)
907 GtkWidget *notebook;
908 GtkWidget *tl_vbox, *hbox, *frame, *tv;
909 GtkWidget *actions, *button;
910 GtkTreeStore *model;
911 char *string, *save_path;
913 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
915 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
916 gtk_window_set_title(GTK_WINDOW(window), _("Options"));
917 g_signal_connect(window, "destroy",
918 G_CALLBACK(options_destroyed), NULL);
919 gtk_container_set_border_width(GTK_CONTAINER(window), 4);
921 tl_vbox = gtk_vbox_new(FALSE, 4);
922 gtk_container_add(GTK_CONTAINER(window), tl_vbox);
924 hbox = gtk_hbox_new(FALSE, 4);
925 gtk_box_pack_start(GTK_BOX(tl_vbox), hbox, TRUE, TRUE, 0);
927 frame = gtk_frame_new(NULL);
928 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
929 gtk_box_pack_end(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
931 notebook = gtk_notebook_new();
932 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
933 gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
934 gtk_container_add(GTK_CONTAINER(frame), notebook);
936 frame = gtk_frame_new(NULL);
937 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
938 gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
940 /* tree view */
941 model = gtk_tree_store_new(2, G_TYPE_STRING, GTK_TYPE_WIDGET);
942 tv = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
943 g_object_unref(model);
944 gtk_tree_selection_set_mode(
945 gtk_tree_view_get_selection(GTK_TREE_VIEW(tv)),
946 GTK_SELECTION_BROWSE);
947 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv), FALSE);
948 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tv), -1,
949 NULL, gtk_cell_renderer_text_new(), "text", 0, NULL);
950 gtk_container_add(GTK_CONTAINER(frame), tv);
951 g_signal_connect(tv, "cursor_changed",
952 G_CALLBACK(tree_cursor_changed), notebook);
954 actions = gtk_hbutton_box_new();
955 gtk_button_box_set_layout(GTK_BUTTON_BOX(actions),
956 GTK_BUTTONBOX_END);
957 gtk_box_set_spacing(GTK_BOX(actions), 10);
959 gtk_box_pack_start(GTK_BOX(tl_vbox), actions, FALSE, TRUE, 0);
961 revert_widget = button_new_mixed(GTK_STOCK_UNDO, _("_Revert"));
962 GTK_WIDGET_SET_FLAGS(revert_widget, GTK_CAN_DEFAULT);
963 gtk_box_pack_start(GTK_BOX(actions), revert_widget, FALSE, TRUE, 0);
964 g_signal_connect(revert_widget, "clicked",
965 G_CALLBACK(revert_options), NULL);
966 gtk_tooltips_set_tip(option_tooltips, revert_widget,
967 _("Restore all choices to how they were when the "
968 "Options box was opened."), NULL);
969 gtk_widget_set_sensitive(revert_widget, check_anything_changed());
971 button = gtk_button_new_from_stock(GTK_STOCK_OK);
972 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
973 gtk_box_pack_start(GTK_BOX(actions), button, FALSE, TRUE, 0);
974 g_signal_connect_swapped(button, "clicked",
975 G_CALLBACK(gtk_widget_destroy), window);
976 gtk_widget_grab_default(button);
977 gtk_widget_grab_focus(button);
979 save_path = choices_find_xdg_path_save("...", PROJECT, SITE, FALSE);
980 if (save_path)
982 string = g_strdup_printf(_("Choices will be saved as:\n%s"),
983 save_path);
984 gtk_tooltips_set_tip(option_tooltips, button, string, NULL);
985 g_free(string);
986 g_free(save_path);
988 else
989 gtk_tooltips_set_tip(option_tooltips, button,
990 _("(saving disabled by CHOICESPATH)"), NULL);
992 if (tree_view)
993 *tree_view = GTK_TREE_VIEW(tv);
995 return notebook;
998 /* Given the last radio button in the group, select whichever
999 * radio button matches the given value.
1001 static void radio_group_set_value(GtkRadioButton *last, guchar *value)
1003 GSList *next;
1005 for (next = gtk_radio_button_get_group(last); next; next = next->next)
1007 GtkToggleButton *button = (GtkToggleButton *) next->data;
1008 guchar *val;
1010 val = g_object_get_data(G_OBJECT(button), "value");
1011 g_return_if_fail(val != NULL);
1013 if (strcmp(val, value) == 0)
1015 gtk_toggle_button_set_active(button, TRUE);
1016 return;
1020 g_warning("Can't find radio button with value %s\n", value);
1023 /* Given the last radio button in the group, return a copy of the
1024 * value for the selected radio item.
1026 static guchar *radio_group_get_value(GtkRadioButton *last)
1028 GSList *next;
1030 for (next = gtk_radio_button_get_group(last); next; next = next->next)
1032 GtkToggleButton *button = (GtkToggleButton *) next->data;
1034 if (gtk_toggle_button_get_active(button))
1036 guchar *val;
1038 val = g_object_get_data(G_OBJECT(button), "value");
1039 g_return_val_if_fail(val != NULL, NULL);
1041 return g_strdup(val);
1045 return NULL;
1048 /* Select this item with this value */
1049 static void option_menu_set(GtkOptionMenu *om, guchar *value)
1051 GtkWidget *menu;
1052 GList *list, *next;
1053 int i = 0;
1055 menu = gtk_option_menu_get_menu(om);
1056 list = gtk_container_get_children(GTK_CONTAINER(menu));
1058 for (next = list; next; next = next->next)
1060 GObject *item = (GObject *) next->data;
1061 guchar *data;
1063 data = g_object_get_data(item, "value");
1064 g_return_if_fail(data != NULL);
1066 if (strcmp(data, value) == 0)
1068 gtk_option_menu_set_history(om, i);
1069 break;
1072 i++;
1075 g_list_free(list);
1078 /* Get the value (static) of the selected item */
1079 static guchar *option_menu_get(GtkOptionMenu *om)
1081 GtkWidget *menu, *item;
1083 menu = gtk_option_menu_get_menu(om);
1084 item = gtk_menu_get_active(GTK_MENU(menu));
1086 return g_object_get_data(G_OBJECT(item), "value");
1089 static void restore_backup(gpointer key, gpointer value, gpointer data)
1091 Option *option = (Option *) value;
1093 g_return_if_fail(option->backup != NULL);
1095 option->has_changed = strcmp(option->value, option->backup) != 0;
1096 if (!option->has_changed)
1097 return;
1099 g_free(option->value);
1100 option->value = g_strdup(option->backup);
1101 option->int_value = atoi(option->value);
1104 static void revert_options(GtkWidget *widget, gpointer data)
1106 g_hash_table_foreach(option_hash, restore_backup, NULL);
1107 options_notify();
1108 update_option_widgets();
1111 static void check_changed_cb(gpointer key, gpointer value, gpointer data)
1113 Option *option = (Option *) value;
1114 gboolean *changed = (gboolean *) data;
1116 g_return_if_fail(option->backup != NULL);
1118 if (*changed)
1119 return;
1121 if (strcmp(option->value, option->backup) != 0)
1122 *changed = TRUE;
1125 static gboolean check_anything_changed(void)
1127 gboolean retval = FALSE;
1129 g_hash_table_foreach(option_hash, check_changed_cb, &retval);
1131 return retval;
1134 static void write_option(gpointer key, gpointer value, gpointer data)
1136 xmlNodePtr doc = (xmlNodePtr) data;
1137 Option *option = (Option *) value;
1138 xmlNodePtr tree;
1140 tree = xmlNewTextChild(doc, NULL, "Option", option->value);
1141 xmlSetProp(tree, "name", (gchar *) key);
1144 static void save_options(void)
1146 xmlDoc *doc;
1147 GList *next;
1148 guchar *save, *save_new;
1150 save = choices_find_xdg_path_save("Options", PROJECT, SITE, TRUE);
1151 if (!save)
1152 goto out;
1154 save_new = g_strconcat(save, ".new", NULL);
1156 doc = xmlNewDoc("1.0");
1157 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "Options", NULL));
1159 g_hash_table_foreach(option_hash, write_option,
1160 xmlDocGetRootElement(doc));
1162 if (save_xml_file(doc, save_new) || rename(save_new, save))
1163 report_error(_("Error saving %s: %s"), save, g_strerror(errno));
1165 g_free(save_new);
1166 g_free(save);
1167 xmlFreeDoc(doc);
1169 for (next = saver_callbacks; next; next = next->next)
1171 OptionNotify *cb = (OptionNotify *) next->data;
1172 cb();
1175 out:
1176 if (window)
1177 gtk_widget_destroy(window);
1180 /* Make the widget reflect the current value of the option */
1181 static void update_cb(gpointer key, gpointer value, gpointer data)
1183 Option *option = (Option *) value;
1185 g_return_if_fail(option != NULL);
1186 if (option->widget == NULL) {
1187 g_warning("No widget for option %s", (char *) key);
1188 return;
1191 updating_widgets++;
1193 if (option->update_widget)
1194 option->update_widget(option);
1196 updating_widgets--;
1199 /* Reflect the values in the Option structures by changing the widgets
1200 * in the Options window.
1202 static void update_option_widgets(void)
1204 g_hash_table_foreach(option_hash, update_cb, NULL);
1207 /* Each of the following update the widget to make it show the current
1208 * value of the option.
1211 static void update_toggle(Option *option)
1213 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(option->widget),
1214 option->int_value);
1217 static void update_entry(Option *option)
1219 gtk_entry_set_text(GTK_ENTRY(option->widget), option->value);
1222 static void update_numentry(Option *option)
1224 gtk_spin_button_set_value(GTK_SPIN_BUTTON(option->widget),
1225 option->int_value);
1228 static void update_radio_group(Option *option)
1230 radio_group_set_value(GTK_RADIO_BUTTON(option->widget), option->value);
1233 static void update_slider(Option *option)
1235 gtk_adjustment_set_value(
1236 gtk_range_get_adjustment(GTK_RANGE(option->widget)),
1237 option->int_value);
1240 static void update_menu(Option *option)
1242 option_menu_set(GTK_OPTION_MENU(option->widget), option->value);
1245 static void update_font(Option *option)
1247 GtkToggleButton *active;
1248 gboolean have_font = option->value[0] != '\0';
1250 active = g_object_get_data(G_OBJECT(option->widget), "rox_override");
1252 if (active)
1254 gtk_toggle_button_set_active(active, have_font);
1255 gtk_widget_set_sensitive(option->widget->parent, have_font);
1258 gtk_label_set_text(GTK_LABEL(option->widget),
1259 have_font ? option->value
1260 : (guchar *) _("(use default)"));
1263 static void update_colour(Option *option)
1265 GdkColor colour;
1267 gdk_color_parse(option->value, &colour);
1268 button_patch_set_colour(option->widget, &colour);
1271 /* Each of these read_* calls get the new (string) value of an option
1272 * from the widget.
1275 static guchar *read_toggle(Option *option)
1277 GtkToggleButton *toggle = GTK_TOGGLE_BUTTON(option->widget);
1279 return g_strdup_printf("%d", gtk_toggle_button_get_active(toggle));
1282 static guchar *read_entry(Option *option)
1284 return gtk_editable_get_chars(GTK_EDITABLE(option->widget), 0, -1);
1287 static guchar *read_numentry(Option *option)
1289 return g_strdup_printf("%d", (int)
1290 gtk_spin_button_get_value(GTK_SPIN_BUTTON(option->widget)));
1293 static guchar *read_slider(Option *option)
1295 return g_strdup_printf("%d", (int)
1296 gtk_range_get_adjustment(GTK_RANGE(option->widget))->value);
1299 static guchar *read_radio_group(Option *option)
1301 return radio_group_get_value(GTK_RADIO_BUTTON(option->widget));
1304 static guchar *read_menu(Option *option)
1306 return g_strdup(option_menu_get(GTK_OPTION_MENU(option->widget)));
1309 static guchar *read_font(Option *option)
1311 GtkToggleButton *active;
1313 active = g_object_get_data(G_OBJECT(option->widget), "rox_override");
1314 if (active && !gtk_toggle_button_get_active(active))
1315 return g_strdup("");
1317 return g_strdup(gtk_label_get_text(GTK_LABEL(option->widget)));
1320 static guchar *read_colour(Option *option)
1322 GtkStyle *style = GTK_BIN(option->widget)->child->style;
1324 return g_strdup_printf("#%04x%04x%04x",
1325 style->bg[GTK_STATE_NORMAL].red,
1326 style->bg[GTK_STATE_NORMAL].green,
1327 style->bg[GTK_STATE_NORMAL].blue);
1330 static void set_not_changed(gpointer key, gpointer value, gpointer data)
1332 Option *option = (Option *) value;
1334 option->has_changed = FALSE;
1337 /* Builders for decorations (no corresponding option) */
1339 static GList *build_label(Option *option, xmlNode *node, guchar *label)
1341 GtkWidget *widget;
1342 guchar *text;
1343 int help;
1345 g_return_val_if_fail(option == NULL, NULL);
1346 g_return_val_if_fail(label == NULL, NULL);
1348 text = DATA(node);
1349 widget = gtk_label_new(_(text));
1350 g_free(text);
1352 help = get_int(node, "help");
1354 gtk_misc_set_alignment(GTK_MISC(widget), 0, help ? 0.5 : 1);
1355 gtk_label_set_justify(GTK_LABEL(widget), GTK_JUSTIFY_LEFT);
1356 gtk_label_set_line_wrap(GTK_LABEL(widget), TRUE);
1358 if (help)
1360 GtkWidget *hbox, *image, *align, *spacer;
1362 hbox = gtk_hbox_new(FALSE, 4);
1363 image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO,
1364 GTK_ICON_SIZE_BUTTON);
1365 align = gtk_alignment_new(0, 0, 0, 0);
1367 gtk_container_add(GTK_CONTAINER(align), image);
1368 gtk_box_pack_start(GTK_BOX(hbox), align, FALSE, TRUE, 0);
1369 gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
1371 spacer = gtk_event_box_new();
1372 gtk_widget_set_size_request(spacer, 6, 6);
1374 return g_list_append(g_list_append(NULL, hbox), spacer);
1377 return g_list_append(NULL, widget);
1380 static GList *build_spacer(Option *option, xmlNode *node, guchar *label)
1382 GtkWidget *eb;
1384 g_return_val_if_fail(option == NULL, NULL);
1385 g_return_val_if_fail(label == NULL, NULL);
1387 eb = gtk_event_box_new();
1388 gtk_widget_set_size_request(eb, 12, 12);
1390 return g_list_append(NULL, eb);
1393 static GList *build_frame(Option *option, xmlNode *node, guchar *label)
1395 GtkWidget *nbox, *frame, *label_widget;
1396 xmlNode *hw;
1397 PangoAttrList *list;
1398 PangoAttribute *attr;
1400 g_return_val_if_fail(option == NULL, NULL);
1401 g_return_val_if_fail(label != NULL, NULL);
1403 frame = gtk_frame_new(_(label));
1404 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
1406 /* Make the title bold */
1407 label_widget = gtk_frame_get_label_widget(GTK_FRAME(frame));
1408 attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
1410 attr->start_index = 0;
1411 attr->end_index = -1;
1412 list = pango_attr_list_new();
1413 pango_attr_list_insert(list, attr);
1414 gtk_label_set_attributes(GTK_LABEL(label_widget), list);
1416 nbox = gtk_vbox_new(FALSE, 0);
1417 gtk_container_set_border_width(GTK_CONTAINER(nbox), 12);
1418 gtk_container_add(GTK_CONTAINER(frame), nbox);
1420 for (hw = node->xmlChildrenNode; hw; hw = hw->next)
1421 if (hw->type == XML_ELEMENT_NODE)
1422 build_widget(hw, nbox);
1424 return g_list_append(NULL, frame);
1427 /* These create new widgets in the options window and set the appropriate
1428 * callbacks.
1431 static GList *build_toggle(Option *option, xmlNode *node, guchar *label)
1433 GtkWidget *toggle;
1435 g_return_val_if_fail(option != NULL, NULL);
1437 toggle = gtk_check_button_new_with_label(_(label));
1439 may_add_tip(toggle, node);
1441 option->update_widget = update_toggle;
1442 option->read_widget = read_toggle;
1443 option->widget = toggle;
1445 g_signal_connect_swapped(toggle, "toggled",
1446 G_CALLBACK(option_check_widget), option);
1448 return g_list_append(NULL, toggle);
1451 static GList *build_slider(Option *option, xmlNode *node, guchar *label)
1453 GtkAdjustment *adj;
1454 GtkWidget *hbox, *slide, *label_wid;
1455 int min, max;
1456 int fixed;
1457 int showvalue;
1458 guchar *end;
1460 g_return_val_if_fail(option != NULL, NULL);
1462 min = get_int(node, "min");
1463 max = get_int(node, "max");
1464 fixed = get_int(node, "fixed");
1465 showvalue = get_int(node, "showvalue");
1467 adj = GTK_ADJUSTMENT(gtk_adjustment_new(0,
1468 min, max, 1, 10, 0));
1470 hbox = gtk_hbox_new(FALSE, 4);
1472 if (label)
1474 label_wid = gtk_label_new(_(label));
1475 gtk_misc_set_alignment(GTK_MISC(label_wid), 0, 0.5);
1476 gtk_box_pack_start(GTK_BOX(hbox), label_wid, FALSE, TRUE, 0);
1477 add_to_size_group(node, label_wid);
1480 end = xmlGetProp(node, "end");
1481 if (end)
1483 gtk_box_pack_end(GTK_BOX(hbox), gtk_label_new(_(end)),
1484 FALSE, TRUE, 0);
1485 g_free(end);
1488 slide = gtk_hscale_new(adj);
1490 if (fixed)
1491 gtk_widget_set_size_request(slide, adj->upper, 24);
1492 if (showvalue)
1494 gtk_scale_set_draw_value(GTK_SCALE(slide), TRUE);
1495 gtk_scale_set_value_pos(GTK_SCALE(slide),
1496 GTK_POS_LEFT);
1497 gtk_scale_set_digits(GTK_SCALE(slide), 0);
1499 else
1500 gtk_scale_set_draw_value(GTK_SCALE(slide), FALSE);
1501 GTK_WIDGET_UNSET_FLAGS(slide, GTK_CAN_FOCUS);
1503 may_add_tip(slide, node);
1505 gtk_box_pack_start(GTK_BOX(hbox), slide, !fixed, TRUE, 0);
1507 option->update_widget = update_slider;
1508 option->read_widget = read_slider;
1509 option->widget = slide;
1511 g_signal_connect_swapped(adj, "value-changed",
1512 G_CALLBACK(option_check_widget), option);
1514 return g_list_append(NULL, hbox);
1517 static GList *build_entry(Option *option, xmlNode *node, guchar *label)
1519 GtkWidget *hbox;
1520 GtkWidget *entry;
1521 GtkWidget *label_wid;
1523 g_return_val_if_fail(option != NULL, NULL);
1525 hbox = gtk_hbox_new(FALSE, 4);
1527 if (label)
1529 label_wid = gtk_label_new(_(label));
1530 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1531 gtk_box_pack_start(GTK_BOX(hbox), label_wid, FALSE, TRUE, 0);
1534 entry = gtk_entry_new();
1535 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
1536 add_to_size_group(node, entry);
1537 may_add_tip(entry, node);
1539 option->update_widget = update_entry;
1540 option->read_widget = read_entry;
1541 option->widget = entry;
1543 g_signal_connect_data(entry, "changed",
1544 G_CALLBACK(option_check_widget), option,
1545 NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
1547 return g_list_append(NULL, hbox);
1550 static GList *build_numentry(Option *option, xmlNode *node, guchar *label)
1552 GtkObject *adj;
1553 int min, max, step;
1555 g_return_val_if_fail(option != NULL, NULL);
1557 min = get_int(node, "min");
1558 max = get_int(node, "max");
1559 step = MAX(1, get_int(node, "step"));
1561 adj = gtk_adjustment_new(min, min, max, step, step * 10, 1);
1563 return build_numentry_base(option, node, label, GTK_ADJUSTMENT(adj));
1566 static GList *build_radio_group(Option *option, xmlNode *node, guchar *label)
1568 GList *list = NULL;
1569 GtkWidget *button = NULL;
1570 xmlNode *rn;
1571 int cols;
1573 g_return_val_if_fail(option != NULL, NULL);
1575 for (rn = node->xmlChildrenNode; rn; rn = rn->next)
1577 if (rn->type == XML_ELEMENT_NODE)
1579 button = build_radio(rn, button);
1580 g_signal_connect_swapped(button, "toggled",
1581 G_CALLBACK(option_check_widget), option);
1582 list = g_list_append(list, button);
1586 option->update_widget = update_radio_group;
1587 option->read_widget = read_radio_group;
1588 option->widget = button;
1590 cols = get_int(node, "columns");
1591 if (cols > 1)
1593 GtkWidget *table;
1594 GList *next;
1595 int i, n;
1596 int rows;
1598 n = g_list_length(list);
1599 rows = (n + cols - 1) / cols;
1601 table = gtk_table_new(rows, cols, FALSE);
1603 i = 0;
1604 for (next = list; next; next = next->next)
1606 GtkWidget *button = GTK_WIDGET(next->data);
1607 int left = i / rows;
1608 int top = i % rows;
1610 gtk_table_attach_defaults(GTK_TABLE(table), button,
1611 left, left + 1, top, top + 1);
1613 i++;
1616 g_list_free(list);
1617 list = g_list_prepend(NULL, table);
1620 return list;
1623 static GList *build_colour(Option *option, xmlNode *node, guchar *label)
1625 GtkWidget *hbox, *da, *button, *label_wid;
1627 g_return_val_if_fail(option != NULL, NULL);
1629 hbox = gtk_hbox_new(FALSE, 4);
1631 if (label)
1633 label_wid = gtk_label_new(_(label));
1634 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1635 gtk_box_pack_start(GTK_BOX(hbox), label_wid, TRUE, TRUE, 0);
1638 button = gtk_button_new();
1639 da = gtk_drawing_area_new();
1640 gtk_widget_set_size_request(da, 64, 12);
1641 gtk_container_add(GTK_CONTAINER(button), da);
1642 g_signal_connect(button, "clicked", G_CALLBACK(open_coloursel), option);
1644 may_add_tip(button, node);
1646 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1648 option->update_widget = update_colour;
1649 option->read_widget = read_colour;
1650 option->widget = button;
1652 return g_list_append(NULL, hbox);
1655 static GList *build_menu(Option *option, xmlNode *node, guchar *label)
1657 GtkWidget *hbox, *om, *option_menu, *label_wid;
1658 xmlNode *item;
1660 g_return_val_if_fail(option != NULL, NULL);
1662 hbox = gtk_hbox_new(FALSE, 4);
1664 label_wid = gtk_label_new(_(label));
1665 gtk_misc_set_alignment(GTK_MISC(label_wid), 1.0, 0.5);
1666 gtk_box_pack_start(GTK_BOX(hbox), label_wid, TRUE, TRUE, 0);
1668 option_menu = gtk_option_menu_new();
1669 gtk_box_pack_start(GTK_BOX(hbox), option_menu, FALSE, TRUE, 0);
1671 om = gtk_menu_new();
1673 for (item = node->xmlChildrenNode; item; item = item->next)
1675 if (item->type == XML_ELEMENT_NODE)
1676 build_menu_item(item, om);
1679 gtk_widget_show(om);
1680 gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), om);
1681 add_to_size_group(node, option_menu);
1683 option->update_widget = update_menu;
1684 option->read_widget = read_menu;
1685 option->widget = option_menu;
1687 g_signal_connect_data(option_menu, "changed",
1688 G_CALLBACK(option_check_widget), option,
1689 NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
1691 return g_list_append(NULL, hbox);
1694 static GList *build_font(Option *option, xmlNode *node, guchar *label)
1696 GtkWidget *hbox, *button;
1697 GtkWidget *active = NULL;
1698 int override;
1700 g_return_val_if_fail(option != NULL, NULL);
1702 override = get_int(node, "override");
1704 hbox = gtk_hbox_new(FALSE, 4);
1706 if (override)
1708 /* Add a check button to enable the font chooser. If off,
1709 * the option's value is "".
1711 active = gtk_check_button_new_with_label(_(label));
1712 gtk_box_pack_start(GTK_BOX(hbox), active, FALSE, TRUE, 0);
1713 g_signal_connect(active, "toggled",
1714 G_CALLBACK(toggle_active_font), option);
1716 else
1717 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_(label)),
1718 FALSE, TRUE, 0);
1720 button = gtk_button_new_with_label("");
1721 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
1723 option->update_widget = update_font;
1724 option->read_widget = read_font;
1725 option->widget = GTK_BIN(button)->child;
1726 may_add_tip(button, node);
1728 g_object_set_data(G_OBJECT(option->widget), "rox_override", active);
1730 g_signal_connect(button, "clicked", G_CALLBACK(open_fontsel), option);
1732 return g_list_append(NULL, hbox);
1735 static void button_patch_set_colour(GtkWidget *button, GdkColor *colour)
1737 GtkStyle *style;
1738 GtkWidget *patch;
1740 patch = GTK_BIN(button)->child;
1742 style = gtk_style_copy(GTK_WIDGET(patch)->style);
1743 style->bg[GTK_STATE_NORMAL].red = colour->red;
1744 style->bg[GTK_STATE_NORMAL].green = colour->green;
1745 style->bg[GTK_STATE_NORMAL].blue = colour->blue;
1746 gtk_widget_set_style(patch, style);
1747 g_object_unref(G_OBJECT(style));
1749 if (GTK_WIDGET_REALIZED(patch))
1750 gdk_window_clear(patch->window);
1753 static void load_options(xmlDoc *doc)
1755 xmlNode *root, *node;
1757 root = xmlDocGetRootElement(doc);
1759 g_return_if_fail(strcmp(root->name, "Options") == 0);
1761 for (node = root->xmlChildrenNode; node; node = node->next)
1763 gchar *value, *name;
1765 if (node->type != XML_ELEMENT_NODE)
1766 continue;
1767 if (strcmp(node->name, "Option") != 0)
1768 continue;
1769 name = xmlGetProp(node, "name");
1770 if (!name)
1771 continue;
1773 value = xmlNodeGetContent(node);
1775 if (g_hash_table_lookup(loading, name))
1776 g_warning("Duplicate option found!");
1778 g_hash_table_insert(loading, name, value);
1780 /* (don't need to free name or value) */
1784 /* Process one line from the options file (\0 term'd).
1785 * Returns NULL on success, or a pointer to an error message.
1786 * The line is modified.
1788 static const char *process_option_line(gchar *line)
1790 gchar *eq, *c;
1791 gchar *name = line;
1793 g_return_val_if_fail(option_hash != NULL, "No registered options!");
1795 eq = strchr(line, '=');
1796 if (!eq)
1797 return _("Missing '='");
1799 c = eq - 1;
1800 while (c > line && (*c == ' ' || *c == '\t'))
1801 c--;
1802 c[1] = '\0';
1803 c = eq + 1;
1804 while (*c == ' ' || *c == '\t')
1805 c++;
1807 if (g_hash_table_lookup(loading, name))
1808 return "Duplicate option found!";
1810 g_hash_table_insert(loading, g_strdup(name), g_strdup(g_strstrip(c)));
1812 return NULL;