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