2
* Copyright (C) 2001-2007 the xine project
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation; either version 2 of the License, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program; if not, write to the Free Software
16
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
* Functions to stup and deal with a preferences dialog interfacing with
19
* the xine config system.
21
* Richard Wareham <richwareham@users.sourceforge.net> -- March 2002
22
* Darren Salt <dsalt@users.sourceforge.net> -- December 2004
31
#include <gdk/gdkkeysyms.h>
36
#include <sys/types.h>
40
#include "mediamarks.h"
41
#include "preferences.h"
47
#if defined(WITH_GUDEV) || defined(WITH_HAL)
48
# define GXINE_DEVICE_INFO
52
void *notebook; /* NULL - get from parent */
53
GtkWidget *todefault, *revert, *editable, *label, *separator;
56
#define PREF_ITEM_T(NODE) ((pref_item_t *)(NODE)->data)
59
GtkWidget *notebook; /* contains child pages, not this page */
60
GtkWidget *page, *table;
63
#define PREF_PAGE_T(NODE) ((pref_page_t *)(NODE)->data)
70
#define PREF_T(NODE) ((pref_t *)(NODE)->data)
72
static GtkWidget *prefs_dialog;
73
static GNode *prefs = NULL;
74
static GData *prefs_map = NULL;
75
static int is_visible;
76
static GtkTooltips *tooltips = NULL;
77
static int update_lock = 0;
80
static int select_show_prefs_internal (GNode *page, int exp)
83
pref_t *item = page->data;
84
pref_t *parent = page->parent ? page->parent->data : NULL;
87
if (parent && parent->notebook == GINT_TO_POINTER (1))
88
return 0; /* called too early */
90
child = g_node_first_child (page);
92
if (PREF_T(child)->notebook)
94
/* recursively process subpages (but first we hide them all) */
95
GtkNotebook *notebook = GTK_NOTEBOOK(item->page.notebook);
96
while (gtk_notebook_get_n_pages (notebook))
97
gtk_notebook_remove_page (notebook, 0);
98
for (; child; child = g_node_next_sibling (child))
99
state |= select_show_prefs_internal (child, exp);
103
/* hide/show widgets on this page */
104
pref_item_t *last = NULL;
105
for (; child; child = g_node_next_sibling (child))
107
pref_item_t *pref = child->data;
108
void (*func)(GtkWidget *);
109
if (pref->exp <= exp)
111
func = gtk_widget_show;
117
func = gtk_widget_hide;
120
if (pref->exp <= exp)
122
func (pref->todefault);
124
func (pref->editable);
126
func (pref->separator);
129
gtk_widget_hide (last->separator);
132
/* re-add the page if it has visible widgets */
133
if (parent && state != 2)
134
gtk_notebook_append_page (GTK_NOTEBOOK(parent->notebook), item->page.page,
135
gtk_label_new (item->page.label));
140
static gboolean select_show_pref_widgets (void *data)
142
static const int experience_values[] = { 0, 10, 20, 30 };
143
int exp = *(int *)data;
145
if (exp >= 0 && exp < (int) G_N_ELEMENTS (experience_values))
146
exp = experience_values[exp];
151
gdk_threads_enter ();
152
select_show_prefs_internal (prefs, exp);
153
gdk_threads_leave ();
158
static gboolean entry_cb (GtkEntry *editable, GdkEventFocus *even,
161
xine_cfg_entry_t entry;
162
gchar *key = (gchar *) user_data;
167
logprintf ("preferences: entry cb for key %s\n", key);
169
if (!xine_config_lookup_entry (xine, key, &entry))
172
key = gtk_editable_get_chars (GTK_EDITABLE(editable), 0, -1);
173
if (!strcmp (entry.str_value, key))
176
logprintf ("preferences: updating entry\n");
177
entry.str_value = key;
178
xine_config_update_entry (xine, &entry);
183
static gboolean entry_keypress_cb (GtkEntry *widget, GdkEventKey *event,
186
switch (event->keyval)
190
entry_cb (widget, NULL, data);
194
if ((event->state & GXINE_MODIFIER_MASK) != GDK_CONTROL_MASK)
198
const char *key = g_object_get_data (G_OBJECT(widget), "cfg");
199
xine_cfg_entry_t entry;
200
if (key && xine_config_lookup_entry (xine, key, &entry))
201
gtk_entry_set_text (widget, entry.str_value);
209
static void check_box_cb (GtkToggleButton *togglebutton, gpointer user_data)
211
xine_cfg_entry_t entry;
212
gchar *key = (gchar *) user_data;
218
logprintf ("preferences: check box cb for key %s\n", key);
220
if (!xine_config_lookup_entry (xine, key, &entry))
223
state = gtk_toggle_button_get_active (togglebutton);
224
if (entry.num_value == state)
227
logprintf ("preferences: updating entry\n");
228
entry.num_value = state;
229
xine_config_update_entry (xine, &entry);
232
static void range_cb (GtkAdjustment *adj, const gchar *key)
234
xine_cfg_entry_t entry;
239
logprintf ("preferences: range cb for key %s\n", key);
241
if (!xine_config_lookup_entry (xine, key, &entry) ||
242
entry.num_value == adj->value)
245
logprintf ("preferences: updating entry to %lf\n", adj->value);
246
entry.num_value = adj->value;
247
xine_config_update_entry (xine, &entry);
250
static void spin_cb (GtkSpinButton *widget, const gchar *key)
252
xine_cfg_entry_t entry;
258
logprintf ("preferences: spin cb for key %s\n", key);
260
value = gtk_spin_button_get_value_as_int (widget);
261
if (!xine_config_lookup_entry (xine, key, &entry) ||
262
entry.num_value == value)
265
logprintf ("preferences: updating entry to %d\n", value);
266
entry.num_value = value;
267
xine_config_update_entry (xine, &entry);
270
static void enum_cb (GtkWidget* widget, gpointer data)
272
xine_cfg_entry_t entry;
273
gchar *key = (gchar *) data;
279
logprintf ("preferences: enum cb for key %s\n", key);
281
if (!xine_config_lookup_entry (xine, key, &entry))
284
pos = gtk_combo_box_get_active (GTK_COMBO_BOX(widget));
286
if (entry.num_value != pos)
288
entry.num_value = pos;
289
logprintf ("preferences: updating entry to %d\n", pos);
290
xine_config_update_entry (xine, &entry);
291
if (!strcmp (key, "gui.experience_level"))
292
g_idle_add ((GSourceFunc) select_show_pref_widgets, &pos);
297
#define G_UDEV_API_IS_SUBJECT_TO_CHANGE
298
#include <gudev/gudev.h>
300
static GUdevClient *gudev = NULL;
302
static gboolean check_gudev (void)
304
static const gchar *const subsystems[] = { NULL }; /* at least DVB & block */
306
gudev = g_udev_client_new (subsystems);
311
get_gudev_property (const char *name, const char *property, const char *sysfs)
313
GUdevDevice *dev = g_udev_client_query_by_device_file (gudev, name);
320
prop = property ? g_udev_device_get_property (dev, property) : NULL;
323
/* g_strcompress doesn't handle "\xNN" */
324
char *ptr = ret = malloc (strlen (prop) + 1);
326
while ((c = *prop++))
330
char tmp[] = { '0', prop[0], prop[1], prop[2], 0 };
332
c = (char) strtol (tmp, NULL, 0);
340
prop = sysfs ? g_udev_device_get_sysfs_attr (dev, sysfs) : NULL;
341
ret = (prop && prop[0]) ? strdup (prop) : NULL;
344
g_object_unref ((GObject *)dev);
350
get_hal_device_info (const char *dev)
352
if (!check_gudev () || !dev)
355
int ldev = strlen (dev);
356
char buf[FILENAME_MAX + ldev + 1];
357
strcpy (buf, dev); /* safe! */
360
ldev = strrchr (buf, '/') - buf + 1;
361
int tmp = readlink (dev, buf + ldev, FILENAME_MAX - 1);
363
strcpy (buf, dev); /* safe! */
364
else if (buf[ldev] == '/')
366
memmove (buf, buf + ldev, tmp);
373
while ((p = strstr (buf, "//")))
374
memmove (p, p + 1, strlen (p));
375
while ((p = strstr (buf, "/./")))
376
memmove (p, p + 2, strlen (p) - 1);
377
while ((p = strstr (buf, "/../")))
380
while (--q >= buf && *q != '/')
382
memmove (q, p + 3, strlen (p) - 2);
387
return get_gudev_property (buf, "ID_MODEL_ENC", "name");
389
#elif defined(WITH_HAL)
391
#include <hal/libhal.h>
393
static LibHalContext *hal = NULL;
395
static gboolean check_hal (void)
399
DBusConnection *dbus = dbus_bus_get (DBUS_BUS_SYSTEM, NULL);
402
hal = libhal_ctx_new ();
405
libhal_ctx_set_dbus_connection (hal, dbus);
410
get_hal_property (const char *dev, const char *dev_tag, const char *prop_tag)
413
char **devs = libhal_manager_find_device_string_match
414
(hal, dev_tag, dev, &count, NULL);
419
libhal_free_string_array (devs);
423
char *prop = libhal_device_get_property_string (hal, devs[0], prop_tag, NULL);
424
char *ret = (prop && prop[0]) ? strdup (prop) : NULL;
425
libhal_free_string (prop);
426
libhal_free_string_array (devs);
431
get_hal_device_info (const char *dev)
433
if (!check_hal () || !dev)
436
int ldev = strlen (dev);
437
char buf[FILENAME_MAX + ldev + 1];
438
strcpy (buf, dev); /* safe! */
441
ldev = strrchr (buf, '/') - buf + 1;
442
int tmp = readlink (dev, buf + ldev, FILENAME_MAX - 1);
444
strcpy (buf, dev); /* safe! */
445
else if (buf[ldev] == '/')
447
memmove (buf, buf + ldev, tmp);
454
while ((p = strstr (buf, "//")))
455
memmove (p, p + 1, strlen (p));
456
while ((p = strstr (buf, "/./")))
457
memmove (p, p + 2, strlen (p) - 1);
458
while ((p = strstr (buf, "/../")))
461
while (--q >= buf && *q != '/')
463
memmove (q, p + 3, strlen (p) - 2);
468
#ifdef HAL_DEVICE_FILE_PREFIX
469
char *const buf_unprefixed =
470
strncmp (buf, HAL_DEVICE_FILE_PREFIX, sizeof (HAL_DEVICE_FILE_PREFIX) - 1)
471
? "" : buf + sizeof (HAL_DEVICE_FILE_PREFIX) - 2;
473
char *const buf_unprefixed = buf;
476
return get_hal_property (buf, "block.device", "storage.model")
477
? : get_hal_property (buf, "block.device", "info.product")
478
? : get_hal_property (buf_unprefixed, HAL_DEVICE_FILE_PROPERTY, "info.product")
479
/* FIXME - BSD etc. */
482
#endif /* WITH_HAL */
484
#ifdef GXINE_DEVICE_INFO
486
display_hal_device_info (GtkWidget *widget, const char *dev)
488
GtkLabel *info = g_object_get_data (G_OBJECT (widget), "extra");
491
char *hal = get_hal_device_info (dev);
492
char *content = g_markup_printf_escaped ("<small>%s</small>", hal ? : "");
493
gtk_label_set_markup (info, content);
498
#endif /* WITH_GUDEV || WITH_HAL */
500
static void file_cb (GtkWidget *widget, int response, gpointer data)
502
xine_cfg_entry_t entry;
503
gchar *key = g_object_get_data (data, "cfg");
505
if (update_lock || response != GTK_RESPONSE_ACCEPT)
508
logprintf ("preferences: file cb for key %s\n", key);
510
if (!xine_config_lookup_entry (xine, key, &entry))
513
key = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
514
if (key && strcmp (entry.str_value, key))
516
logprintf ("preferences: updating entry\n");
517
entry.str_value = key;
518
xine_config_update_entry (xine, &entry);
519
#ifdef GXINE_DEVICE_INFO
520
if (entry.num_value == 2 /* XINE_CONFIG_STRING_IS_DEVICE_NAME */)
521
display_hal_device_info (data, key);
527
static void file_activate_cb (GtkWidget *widget, gpointer data)
529
file_cb (widget, GTK_RESPONSE_ACCEPT, data);
532
#ifdef GXINE_DEVICE_INFO
533
static void file_preview_cb (GtkFileChooser *fc, gpointer data)
535
GtkLabel *info = GTK_LABEL (gtk_file_chooser_get_preview_widget (fc));
536
char *file = gtk_file_chooser_get_preview_filename (fc);
538
file = gtk_file_chooser_get_filename (fc);
539
char *content = get_hal_device_info (file);
540
gtk_label_set_text (info, content ? : "");
546
static void file_map_cb (GtkFileChooser *fcb, gpointer fc)
548
if (!g_object_get_data (fc, "gxine"))
550
g_signal_connect_swapped (fcb, "current-folder-changed",
551
G_CALLBACK(file_activate_cb), fc);
552
g_object_set_data (fc, "gxine", fc);
554
#ifdef GXINE_DEVICE_INFO
555
file_preview_cb (fcb, fc);
560
default_item_cb (GtkWidget *w, gpointer key)
562
xine_cfg_entry_t entry;
563
xine_config_lookup_entry (xine, key, &entry);
564
if (entry.type == XINE_CONFIG_TYPE_STRING)
565
entry.str_value = entry.str_default;
567
entry.num_value = entry.num_default;
568
preferences_update_entry (&entry);
571
static inline GtkWidget *
572
create_item_default (const xine_cfg_entry_t * entry)
575
gboolean alloc = FALSE;
579
case XINE_CONFIG_TYPE_ENUM:
580
tip = entry->enum_values[entry->num_default];
583
case XINE_CONFIG_TYPE_RANGE:
584
case XINE_CONFIG_TYPE_NUM:
586
tip = g_strdup_printf ("%d", entry->num_default);
589
case XINE_CONFIG_TYPE_BOOL:
590
tip = entry->num_default ? "✓" : "✗";
593
case XINE_CONFIG_TYPE_STRING:
594
tip = entry->str_default;
601
GtkWidget *widget = ui_button_new_stock (GTK_STOCK_CLEAR);
602
gtk_tooltips_set_tip (tooltips, widget, tip, NULL);
606
g_signal_connect ((GObject *) widget, "clicked",
607
G_CALLBACK (default_item_cb), (gpointer) entry->key);
612
revert_item_cb (GtkWidget *w, gpointer key)
614
xine_cfg_entry_t entry;
615
xine_config_lookup_entry (xine, key, &entry);
616
if (entry.type == XINE_CONFIG_TYPE_STRING)
617
entry.str_value = g_object_get_data ((GObject *) w, "revert");
619
entry.num_value = (int) (intptr_t) g_object_get_data ((GObject *) w, "revert");
620
preferences_update_entry (&entry);
623
static inline GtkWidget *
624
create_item_revert (const xine_cfg_entry_t *entry)
628
gboolean alloc = FALSE;
632
case XINE_CONFIG_TYPE_ENUM:
633
tip = entry->enum_values[entry->num_value];
634
value = entry->num_value;
637
case XINE_CONFIG_TYPE_RANGE:
638
case XINE_CONFIG_TYPE_NUM:
640
tip = g_strdup_printf ("%d", entry->num_value);
641
value = entry->num_value;
644
case XINE_CONFIG_TYPE_BOOL:
645
tip = entry->num_value ? "✓" : "✗";
646
value = entry->num_value;
649
case XINE_CONFIG_TYPE_STRING:
650
tip = entry->str_value;
651
value = (intptr_t) strdup (entry->str_value);
658
GtkWidget *widget = ui_button_new_stock (GTK_STOCK_REVERT_TO_SAVED);
659
g_object_set_data ((GObject *) widget, "revert", (void *) value);
660
g_signal_connect ((GObject *) widget, "clicked",
661
G_CALLBACK (revert_item_cb), (gpointer) entry->key);
662
gtk_tooltips_set_tip (tooltips, widget, tip, NULL);
669
static GtkWidget *create_item_editable (const xine_cfg_entry_t *entry)
675
case XINE_CONFIG_TYPE_ENUM:
678
widget = gtk_combo_box_new_text ();
679
for (i = 0; entry->enum_values[i]; ++i)
681
const char *label = gettext (entry->enum_values[i]);
682
if (label == entry->enum_values[i])
683
label = dgettext (LIB_PACKAGE, label);
684
gtk_combo_box_append_text (GTK_COMBO_BOX(widget), label);
686
gtk_combo_box_set_active (GTK_COMBO_BOX(widget), entry->num_value);
687
g_signal_connect (G_OBJECT(widget), "changed",
688
G_CALLBACK(enum_cb), strdup(entry->key));
692
case XINE_CONFIG_TYPE_STRING:
693
if (entry->num_value)
695
GtkWidget *fc = gtk_file_chooser_dialog_new (entry->key, GTK_WINDOW (prefs_dialog),
696
entry->num_value == 3 /* XINE_CONFIG_STRING_IS_DIRECTORY_NAME */
697
? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
698
: GTK_FILE_CHOOSER_ACTION_OPEN,
699
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
700
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
702
widget = gtk_file_chooser_button_new_with_dialog (fc);
703
gtk_file_chooser_set_local_only ((GtkFileChooser *) fc, TRUE);
705
if (stat (entry->str_value, &st))
707
g_printerr (_("warning: configuration item %s points to a non-existent location %s\n"),
708
entry->key, entry->str_value);
709
xine_log (xine, 0, _("warning: configuration item %s points to a non-existent location %s\n"),
710
entry->key, entry->str_value);
713
gtk_file_chooser_set_filename ((GtkFileChooser *) fc, entry->str_value);
714
#ifdef GXINE_DEVICE_INFO
715
gtk_file_chooser_set_use_preview_label ((GtkFileChooser *) fc, FALSE);
716
GtkWidget *preview = g_object_new (GTK_TYPE_LABEL, "angle", 90.0,
717
"selectable", FALSE, NULL);
718
gtk_file_chooser_set_preview_widget ((GtkFileChooser *) fc, preview);
720
g_object_connect (G_OBJECT(fc),
721
"signal::response", G_CALLBACK(file_cb), widget,
722
"signal::file-activated", G_CALLBACK(file_activate_cb), widget,
723
#ifdef GXINE_DEVICE_INFO
724
"signal::update-preview", G_CALLBACK(file_preview_cb), widget,
725
"signal::map", G_CALLBACK(file_preview_cb), fc,
728
g_signal_connect_after (G_OBJECT(widget), "map", G_CALLBACK(file_map_cb), fc);
732
widget = gtk_entry_new();
733
g_object_connect (G_OBJECT(widget),
734
"signal::key-press-event", G_CALLBACK(entry_keypress_cb), (gchar *)entry->key,
735
"signal::focus-out-event", G_CALLBACK(entry_cb), (gchar *)entry->key,
737
gtk_entry_set_text (GTK_ENTRY(widget), entry->str_value);
739
g_object_set_data (G_OBJECT(widget), "cfg", (gpointer)entry->key);
743
case XINE_CONFIG_TYPE_RANGE: /* slider */
745
GtkObject *adj = gtk_adjustment_new (entry->num_value, entry->range_min,
746
entry->range_max, 1.0, 10.0, 0.0);
747
widget = ui_hscale_new (adj, GTK_POS_TOP, 0);
748
gtk_widget_set_size_request (widget, 128, -1);
749
g_signal_connect (adj, "value-changed",
750
G_CALLBACK (range_cb), (gchar *)entry->key);
754
case XINE_CONFIG_TYPE_NUM:
756
GtkObject *adj = gtk_adjustment_new (entry->num_value, INT_MIN, INT_MAX,
758
widget = ui_spin_button_new (adj);
759
g_signal_connect (G_OBJECT(widget), "value-changed",
760
G_CALLBACK(spin_cb), (gchar *)entry->key);
764
case XINE_CONFIG_TYPE_BOOL:
765
widget = gtk_check_button_new();
766
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(widget),
767
entry->num_value == 1);
768
g_signal_connect (G_OBJECT(widget), "toggled",
769
G_CALLBACK(check_box_cb), (gchar *)entry->key);
770
/* gtk_misc_set_alignment (GTK_MISC(widget), 1.0, 1.0); */
774
widget = gtk_label_new ("?");
775
g_print (_("preferences: unknown type for entry ‘%s’\n"), entry->key);
778
g_datalist_set_data (&prefs_map, entry->key, widget);
779
gtk_tooltips_set_tip (tooltips, widget, entry->help, NULL);
783
static GtkWidget *create_item_label (const xine_cfg_entry_t *entry)
787
const char *labelkey = strrchr (entry->key, '.') + 1;
790
labeltext = g_markup_printf_escaped ("<b>%s</b>\n%s",
791
labelkey, entry->description);
793
labeltext = g_markup_printf_escaped ("<b>• <i>%s</i></b>\n%s",
794
labelkey, entry->description);
796
label = ui_label_new_with_markup (labeltext);
799
gtk_label_set_line_wrap (GTK_LABEL(label), TRUE);
800
gtk_label_set_line_wrap_mode (GTK_LABEL(label), PANGO_WRAP_WORD);
801
gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
802
gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
807
static GtkWidget *create_item_extra (const xine_cfg_entry_t *entry)
811
case XINE_CONFIG_TYPE_STRING:
812
#ifdef GXINE_DEVICE_INFO
813
if (entry->num_value == 2 /* XINE_CONFIG_STRING_IS_DEVICE_NAME */)
814
return gtk_label_new (NULL);
821
static GNode *create_item (const xine_cfg_entry_t *entry)
823
pref_item_t *item = malloc (sizeof (pref_item_t));
824
item->notebook = NULL;
825
item->todefault = create_item_default (entry);
826
item->revert = create_item_revert (entry);
827
item->editable = create_item_editable (entry);
828
item->label = create_item_label (entry);
829
GtkWidget *extra = create_item_extra (entry);
831
g_object_set_data (G_OBJECT (item->editable), "extra", extra);
832
item->separator = gtk_hseparator_new ();
833
item->exp = entry->exp_level;
834
return g_node_new (item);
837
static GNode *create_page (const char *key, size_t length)
839
pref_page_t *page = malloc (sizeof (pref_page_t));
840
page->notebook = GINT_TO_POINTER (1); /* dummy value - filled in later */
841
page->page = page->table = NULL;
842
page->prefix = g_strdup_printf ("%.*s", (int) length, key);
843
page->label = strrchr (page->prefix, '.');
847
page->label = page->prefix;
848
return g_node_new (page);
857
static gboolean find_page_sub (GNode *node, struct page_find_s *find)
859
const pref_page_t *pref = node->data;
862
if (strncmp (pref->prefix, find->key, find->length) ||
863
pref->prefix[find->length])
869
static GNode *find_page (const char *key, size_t length)
871
struct page_find_s find = { key, length, NULL };
872
g_node_traverse (prefs, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
873
(GNodeTraverseFunc) find_page_sub, &find);
877
static gboolean unmix_mixed_page (GNode *node, gboolean *done)
880
GNode *child, *new_page;
882
pref_page_t *page = node->data;
884
/* not a page? return */
888
/* check if this page has mixed content */
889
for (child = g_node_first_child (node); child;
890
child = g_node_next_sibling (child))
891
types |= (PREF_T(child)->notebook ? 1 : 2);
896
/* hmm, odd... found a blank page - delete it */
897
logprintf ("gxine: prefs: eek, found a blank page '%s'\n", page->prefix);
898
gtk_widget_destroy (page->notebook);
901
g_node_destroy (node);
906
/* found a mixed-mode page (this is normal) */
907
prefix = g_strdup_printf ("%s. ", page->prefix);
908
new_page = g_node_prepend (node, create_page (prefix, strlen (prefix)));
911
/* move the new subpage's sibling item nodes into it */
912
child = g_node_first_child (node);
915
GNode *next = g_node_next_sibling (child);
916
pref_t *pref = child->data;
919
g_node_unlink (child);
920
g_node_append (new_page, child);
932
static gboolean focus_item_cb (GtkWidget *widget, GtkDirectionType *dir,
935
const pref_t *pref = node->data;
936
const pref_t *parent = node->parent ? node->parent->data : NULL;
937
GtkWidget *viewport = parent->page.table->parent;
938
GtkAdjustment *adj = gtk_viewport_get_vadjustment ((GtkViewport *)viewport);
940
int wt = pref->item.editable->allocation.y;
941
int wb = wt + pref->item.editable->allocation.height;
942
int lt = pref->item.label->allocation.y;
943
int lb = lt + pref->item.label->allocation.height;
944
int top = wt < lt ? wt : lt;
945
int bottom = wb > lb ? wb : lb;
947
if (top < adj->value)
948
gtk_adjustment_set_value (adj, top);
949
else if (bottom + 4 > adj->value + viewport->allocation.height)
950
gtk_adjustment_set_value (adj, bottom - viewport->allocation.height + 4);
955
static gboolean put_content (GNode *node, gpointer data)
957
pref_t *pref = node->data;
958
pref_t *parent = node->parent ? node->parent->data : NULL;
963
pref_t *child = g_node_first_child (node)->data;
968
/* child pages present - create a notebook */
969
widget = pref->page.notebook = pref->page.page = gtk_notebook_new ();
970
gtk_notebook_set_scrollable (GTK_NOTEBOOK(widget), TRUE);
974
/* child pages not present - create a scrollable table */
975
widget = pref->page.table = gtk_table_new (1, 1, FALSE);
976
pref->page.page = gtk_scrolled_window_new (NULL, NULL);
977
gtk_scrolled_window_set_policy
978
(GTK_SCROLLED_WINDOW(pref->page.page),
979
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
980
gtk_scrolled_window_add_with_viewport
981
(GTK_SCROLLED_WINDOW(pref->page.page), widget);
986
/* add this page to its parent's notebook */
987
gtk_widget_ref (pref->page.page); /* hiding is done by removing */
988
gtk_notebook_append_page (GTK_NOTEBOOK(parent->notebook), pref->page.page,
989
gtk_label_new (pref->page.label));
992
gtk_widget_set_size_request (pref->page.page, 600, 350);
996
/* we have a config item */
997
int r = g_node_child_position (node->parent, node) * 2;
998
GtkTable *table = GTK_TABLE(parent->page.table);
999
gtk_table_attach (table, pref->item.todefault, 0, 1, r, r + 1,
1000
GTK_FILL, GTK_SHRINK, 2, 5);
1001
gtk_table_attach (table, pref->item.revert, 1, 2, r, r + 1,
1002
GTK_FILL, GTK_SHRINK, 2, 5);
1003
GtkWidget *extra = g_object_get_data (G_OBJECT (pref->item.editable), "extra");
1006
GtkBox *box = GTK_BOX (gtk_vbox_new (2, FALSE));
1007
gtk_table_attach (table, (GtkWidget *) box, 2, 3, r, r + 1,
1008
GTK_EXPAND | GTK_FILL, GTK_SHRINK, 2, 5);
1009
gtk_box_pack_start_defaults (box, pref->item.editable);
1010
gtk_box_pack_start_defaults (box, extra);
1011
#ifdef GXINE_DEVICE_INFO
1012
xine_cfg_entry_t entry;
1013
const char *key = g_object_get_data (G_OBJECT (pref->item.editable), "cfg");
1014
if (xine_config_lookup_entry (xine, key, &entry))
1015
display_hal_device_info (pref->item.editable, entry.str_value);
1019
gtk_table_attach (table, pref->item.editable, 2, 3, r, r + 1,
1020
GTK_EXPAND | GTK_FILL, GTK_SHRINK, 2, 5);
1021
gtk_table_attach (table, pref->item.label, 3, 6, r, r + 1,
1022
GTK_EXPAND | GTK_FILL, GTK_SHRINK, 5, 5);
1023
gtk_table_attach (table, pref->item.separator, 0, 6, r + 1, r + 2,
1024
GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 5, 5);
1025
g_signal_connect (pref->item.todefault, "focus",
1026
G_CALLBACK (focus_item_cb), node);
1027
g_signal_connect (pref->item.revert, "focus",
1028
G_CALLBACK (focus_item_cb), node);
1029
g_signal_connect (pref->item.editable, "focus",
1030
G_CALLBACK (focus_item_cb), node);
1036
static GtkWidget *make_prefs_window (void)
1038
xine_cfg_entry_t entry;
1041
if (!xine_config_get_first_entry (xine, &entry))
1044
tooltips = gtk_tooltips_new ();
1045
prefs = create_page ("", 0); /* root page */
1052
if (!entry.description ||
1053
!(point= strrchr (entry.key, '.')) || point == entry.key)
1056
/* ensure that a page (window+table) is present for this config item */
1057
section = find_page (entry.key, point - entry.key);
1061
point = entry.key - 1;
1062
while ((point = strchr (point + 1, '.')))
1064
GNode *self = find_page (entry.key, point - entry.key);
1067
self = create_page (entry.key, point - entry.key);
1068
g_node_append (section, self);
1074
/* add the config item (and create its widgets) */
1075
g_node_append (section, create_item (&entry));
1076
} while (xine_config_get_next_entry (xine, &entry));
1078
/* we now have a full tree*/
1083
g_node_traverse (prefs, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1084
(GNodeTraverseFunc) unmix_mixed_page, &done);
1087
g_node_traverse (prefs, G_PRE_ORDER, G_TRAVERSE_ALL, -1, put_content, NULL);
1089
gtk_tooltips_enable (tooltips);
1090
return PREF_PAGE_T(prefs)->page;
1093
static void response_cb (GtkDialog *dbox, int response, gpointer data)
1098
case GTK_RESPONSE_ACCEPT:
1099
fname = get_config_filename (FILE_CONFIG);
1100
xine_config_save (xine, fname);
1105
gtk_widget_hide (prefs_dialog);
1109
static JSBool js_preferences_show (JSContext *cx, JSObject *obj, uintN argc,
1110
jsval *argv, jsval *rval)
1112
/* se_t *se = (se_t *) JS_GetContextPrivate(cx); */
1113
se_log_fncall_checkinit ("preferences_show");
1114
preferences_show ();
1118
static void preferences_init_dbox (void)
1120
prefs_dialog = gtk_dialog_new_with_buttons (_("Preferences"), NULL, 0,
1121
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1122
GTK_STOCK_CLOSE, GTK_RESPONSE_DELETE_EVENT,
1124
gtk_window_set_default_size (GTK_WINDOW (prefs_dialog), 500, 150);
1125
hide_on_delete (prefs_dialog, &is_visible);
1126
g_signal_connect (G_OBJECT(prefs_dialog), "response",
1127
G_CALLBACK(response_cb), NULL);
1129
/* Make new tabbed box (notebook) */
1130
GtkWidget *prefs_notebook = make_prefs_window ();
1131
gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(prefs_dialog)->vbox),
1134
GtkWidget *w = ui_label_new_with_markup
1135
(_("Items marked “<b>• <i>like this</i></b>” require "
1136
"gxine to be restarted to take effect."));
1137
gtk_box_pack_end (GTK_BOX(GTK_DIALOG(prefs_dialog)->vbox), w,
1141
static gpointer preferences_first_show (gpointer unused)
1143
static int value = 0;
1144
xine_cfg_entry_t entry;
1146
gtk_widget_show_all (GTK_DIALOG (prefs_dialog)->vbox);
1147
if (xine_config_lookup_entry (xine, "gui.experience_level", &entry))
1148
value = entry.num_value;
1150
select_show_pref_widgets (&value);
1154
void preferences_show (void)
1159
gtk_widget_hide (prefs_dialog);
1164
static GOnce once = G_ONCE_INIT;
1165
g_once (&once, preferences_first_show, NULL);
1166
window_present (prefs_dialog, NULL);
1170
void preferences_init (void)
1173
/* script engine functions */
1174
se_defun (gse, NULL, "preferences_show", js_preferences_show, 0, 0,
1175
SE_GROUP_DIALOGUE, NULL, NULL);
1177
preferences_init_dbox ();
1180
void preferences_update_entry (const xine_cfg_entry_t *entry)
1182
gpointer widget = g_datalist_get_data (&prefs_map, entry->key);
1185
switch (entry->type)
1187
case XINE_CONFIG_TYPE_ENUM:
1188
gtk_combo_box_set_active (widget, entry->num_value);
1191
case XINE_CONFIG_TYPE_STRING:
1192
if (entry->num_value)
1194
gtk_file_chooser_set_filename (widget, entry->str_value);
1195
#ifdef GXINE_DEVICE_INFO
1196
display_hal_device_info (widget, entry->str_value);
1200
gtk_entry_set_text (widget, entry->str_value);
1203
case XINE_CONFIG_TYPE_RANGE: /* slider */
1204
gtk_range_set_value (widget, entry->num_value);
1207
case XINE_CONFIG_TYPE_NUM:
1208
gtk_spin_button_set_value (widget, entry->num_value);
1211
case XINE_CONFIG_TYPE_BOOL:
1212
gtk_toggle_button_set_active (widget, entry->num_value == 1);
1218
xine_config_update_entry (xine, entry);