Audacious  $Id:Doxyfile42802007-03-2104:39:00Znenolod$
ui_preferences.c
Go to the documentation of this file.
1 /*
2  * ui_preferences.c
3  * Copyright 2006-2011 William Pitcock, Tomasz Moń, Michael Färber, and
4  * John Lindgren
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright notice,
10  * this list of conditions, and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions, and the following disclaimer in the documentation
14  * provided with the distribution.
15  *
16  * This software is provided "as is" and without any warranty, express or
17  * implied. In no event shall the authors be liable for any damages arising from
18  * the use of this software.
19  */
20 
21 #include <string.h>
22 
23 #include <gdk/gdkkeysyms.h>
24 #include <gtk/gtk.h>
25 
26 #include <libaudcore/hook.h>
27 
28 #include "debug.h"
29 #include "i18n.h"
30 #include "misc.h"
31 #include "output.h"
32 #include "playlist.h"
33 #include "plugin.h"
34 #include "plugins.h"
35 #include "preferences.h"
36 #include "ui_preferences.h"
37 
38 #ifdef USE_CHARDET
39 #include <libguess.h>
40 #endif
41 
47 };
48 
49 typedef struct {
50  const char *icon_path;
51  const char *name;
52 } Category;
53 
54 typedef struct {
55  const char *name;
56  const char *tag;
58 
59 static /* GtkWidget * */ void * prefswin = NULL;
60 static GtkWidget *category_treeview = NULL;
61 static GtkWidget *category_notebook = NULL;
62 
63 /* prefswin widgets */
64 GtkWidget *titlestring_entry;
65 
66 static Category categories[] = {
67  {"audio.png", N_("Audio")},
68  {"connectivity.png", N_("Network")},
69  {"playlist.png", N_("Playlist")},
70  {"info.png", N_("Song Info")},
71  {"plugins.png", N_("Plugins")},
72 };
73 
74 static int n_categories = G_N_ELEMENTS(categories);
75 
77  { N_("Artist") , "${artist}" },
78  { N_("Album") , "${album}" },
79  { N_("Title") , "${title}" },
80  { N_("Tracknumber"), "${track-number}" },
81  { N_("Genre") , "${genre}" },
82  { N_("Filename") , "${file-name}" },
83  { N_("Filepath") , "${file-path}" },
84  { N_("Date") , "${date}" },
85  { N_("Year") , "${year}" },
86  { N_("Comment") , "${comment}" },
87  { N_("Codec") , "${codec}" },
88  { N_("Quality") , "${quality}" },
89 };
90 static const unsigned int n_title_field_tags = G_N_ELEMENTS(title_field_tags);
91 
92 #ifdef USE_CHARDET
93 static ComboBoxElements chardet_detector_presets[] = {
94  {"", N_("None")},
95  {GUESS_REGION_AR, N_("Arabic")},
96  {GUESS_REGION_BL, N_("Baltic")},
97  {GUESS_REGION_CN, N_("Chinese")},
98  {GUESS_REGION_GR, N_("Greek")},
99  {GUESS_REGION_HW, N_("Hebrew")},
100  {GUESS_REGION_JP, N_("Japanese")},
101  {GUESS_REGION_KR, N_("Korean")},
102  {GUESS_REGION_PL, N_("Polish")},
103  {GUESS_REGION_RU, N_("Russian")},
104  {GUESS_REGION_TW, N_("Taiwanese")},
105  {GUESS_REGION_TR, N_("Turkish")}};
106 #endif
107 
109  { GINT_TO_POINTER(16), "16" },
110  { GINT_TO_POINTER(24), "24" },
111  { GINT_TO_POINTER(32), "32" },
112  {GINT_TO_POINTER (0), N_("Floating point")},
113 };
114 
115 typedef struct {
116  void *next;
117  GtkWidget *container;
118  const char * pg_name;
119  const char * img_url;
121 
123 
124 static void * create_output_plugin_box (void);
125 static void output_bit_depth_changed (void);
126 
127 static PreferencesWidget rg_mode_widgets[] = {
128  {WIDGET_CHK_BTN, N_("Album mode"), .cfg_type = VALUE_BOOLEAN, .cname = "replay_gain_album"}};
129 
130 static PreferencesWidget audio_page_widgets[] = {
131  {WIDGET_LABEL, N_("<b>Output Settings</b>")},
132  {WIDGET_CUSTOM, .data = {.populate = create_output_plugin_box}},
133  {WIDGET_COMBO_BOX, N_("Bit depth:"),
134  .cfg_type = VALUE_INT, .cname = "output_bit_depth", .callback = output_bit_depth_changed,
135  .data = {.combo = {bitdepth_elements, G_N_ELEMENTS (bitdepth_elements)}}},
136  {WIDGET_SPIN_BTN, N_("Buffer size:"),
137  .cfg_type = VALUE_INT, .cname = "output_buffer_size",
138  .data = {.spin_btn = {100, 10000, 1000, N_("ms")}}},
139  {WIDGET_CHK_BTN, N_("Soft clipping"),
140  .cfg_type = VALUE_BOOLEAN, .cname = "soft_clipping"},
141  {WIDGET_CHK_BTN, N_("Use software volume control (not recommended)"),
142  .cfg_type = VALUE_BOOLEAN, .cname = "software_volume_control"},
143  {WIDGET_LABEL, N_("<b>Replay Gain</b>")},
144  {WIDGET_CHK_BTN, N_("Enable Replay Gain"),
145  .cfg_type = VALUE_BOOLEAN, .cname = "enable_replay_gain"},
146  {WIDGET_BOX, .child = TRUE, .data = {.box = {rg_mode_widgets, G_N_ELEMENTS (rg_mode_widgets), TRUE}}},
147  {WIDGET_CHK_BTN, N_("Prevent clipping (recommended)"), .child = TRUE,
148  .cfg_type = VALUE_BOOLEAN, .cname = "enable_clipping_prevention"},
149  {WIDGET_LABEL, N_("<b>Adjust Levels</b>"), .child = TRUE},
150  {WIDGET_SPIN_BTN, N_("Amplify all files:"), .child = TRUE,
151  .cfg_type = VALUE_FLOAT, .cname = "replay_gain_preamp",
152  .data = {.spin_btn = {-15, 15, 0.1, N_("dB")}}},
153  {WIDGET_SPIN_BTN, N_("Amplify untagged files:"), .child = TRUE,
154  .cfg_type = VALUE_FLOAT, .cname = "default_gain",
155  .data = {.spin_btn = {-15, 15, 0.1, N_("dB")}}}};
156 
157 static PreferencesWidget proxy_host_port_elements[] = {
158  {WIDGET_ENTRY, N_("Proxy hostname:"), .cfg_type = VALUE_STRING, .cname = "proxy_host"},
159  {WIDGET_ENTRY, N_("Proxy port:"), .cfg_type = VALUE_STRING, .cname = "proxy_port"}};
160 
161 static PreferencesWidget proxy_auth_elements[] = {
162  {WIDGET_ENTRY, N_("Proxy username:"), .cfg_type = VALUE_STRING, .cname = "proxy_user"},
163  {WIDGET_ENTRY, N_("Proxy password:"), .cfg_type = VALUE_STRING, .cname = "proxy_pass",
164  .data = {.entry = {.password = TRUE}}}};
165 
166 static PreferencesWidget connectivity_page_widgets[] = {
167  {WIDGET_LABEL, N_("<b>Proxy Configuration</b>"), NULL, NULL, NULL, FALSE},
168  {WIDGET_CHK_BTN, N_("Enable proxy usage"), .cfg_type = VALUE_BOOLEAN, .cname = "use_proxy"},
169  {WIDGET_TABLE, .child = TRUE, .data = {.table = {proxy_host_port_elements,
170  G_N_ELEMENTS (proxy_host_port_elements)}}},
171  {WIDGET_CHK_BTN, N_("Use authentication with proxy"),
172  .cfg_type = VALUE_BOOLEAN, .cname = "use_proxy_auth"},
173  {WIDGET_TABLE, .child = TRUE, .data = {.table = {proxy_auth_elements,
174  G_N_ELEMENTS (proxy_auth_elements)}}}
175 };
176 
177 static PreferencesWidget chardet_elements[] = {
178 #ifdef USE_CHARDET
179  {WIDGET_COMBO_BOX, N_("Auto character encoding detector for:"),
180  .cfg_type = VALUE_STRING, .cname = "chardet_detector", .child = TRUE,
181  .data = {.combo = {chardet_detector_presets, G_N_ELEMENTS (chardet_detector_presets)}}},
182 #endif
183  {WIDGET_ENTRY, N_("Fallback character encodings:"), .cfg_type = VALUE_STRING,
184  .cname = "chardet_fallback", .child = TRUE}};
185 
186 static PreferencesWidget playlist_page_widgets[] = {
187  {WIDGET_LABEL, N_("<b>Behavior</b>"), NULL, NULL, NULL, FALSE},
188  {WIDGET_CHK_BTN, N_("Continue playback on startup"),
189  .cfg_type = VALUE_BOOLEAN, .cname = "resume_playback_on_startup"},
190  {WIDGET_CHK_BTN, N_("Advance when the current song is deleted"),
191  .cfg_type = VALUE_BOOLEAN, .cname = "advance_on_delete"},
192  {WIDGET_CHK_BTN, N_("Clear the playlist when opening files"),
193  .cfg_type = VALUE_BOOLEAN, .cname = "clear_playlist"},
194  {WIDGET_CHK_BTN, N_("Open files in a temporary playlist"),
195  .cfg_type = VALUE_BOOLEAN, .cname = "open_to_temporary"},
196  {WIDGET_CHK_BTN, N_("Do not load metadata for songs until played"),
197  .cfg_type = VALUE_BOOLEAN, .cname = "metadata_on_play",
198  .callback = playlist_trigger_scan},
199  {WIDGET_LABEL, N_("<b>Compatibility</b>"), NULL, NULL, NULL, FALSE},
200  {WIDGET_CHK_BTN, N_("Interpret \\ (backward slash) as a folder delimiter"),
201  .cfg_type = VALUE_BOOLEAN, .cname = "convert_backslash"},
202  {WIDGET_TABLE, .data = {.table = {chardet_elements,
203  G_N_ELEMENTS (chardet_elements)}}}
204 };
205 
206 static PreferencesWidget song_info_page_widgets[] = {
207  {WIDGET_LABEL, N_("<b>Album Art</b>")},
208  {WIDGET_LABEL, N_("Search for images matching these words (comma-separated):")},
209  {WIDGET_ENTRY, .cfg_type = VALUE_STRING, .cname = "cover_name_include"},
210  {WIDGET_LABEL, N_("Exclude images matching these words (comma-separated):")},
211  {WIDGET_ENTRY, .cfg_type = VALUE_STRING, .cname = "cover_name_exclude"},
212  {WIDGET_CHK_BTN, N_("Search for images matching song file name"),
213  .cfg_type = VALUE_BOOLEAN, .cname = "use_file_cover"},
214  {WIDGET_CHK_BTN, N_("Search recursively"),
215  .cfg_type = VALUE_BOOLEAN, .cname = "recurse_for_cover"},
216  {WIDGET_SPIN_BTN, N_("Search depth:"), .child = TRUE,
217  .cfg_type = VALUE_INT, .cname = "recurse_for_cover_depth",
218  .data = {.spin_btn = {0, 100, 1}}},
219  {WIDGET_LABEL, N_("<b>Popup Information</b>")},
220  {WIDGET_CHK_BTN, N_("Show popup information"),
221  .cfg_type = VALUE_BOOLEAN, .cname = "show_filepopup_for_tuple"},
222  {WIDGET_SPIN_BTN, N_("Popup delay (tenths of a second):"), .child = TRUE,
223  .cfg_type = VALUE_INT, .cname = "filepopup_delay",
224  .data = {.spin_btn = {0, 100, 1}}},
225  {WIDGET_CHK_BTN, N_("Show time scale for current song"), .child = TRUE,
226  .cfg_type = VALUE_BOOLEAN, .cname = "filepopup_showprogressbar"}};
227 
228 #define TITLESTRING_NPRESETS 6
229 
230 static const char * const titlestring_presets[TITLESTRING_NPRESETS] = {
231  "${title}",
232  "${?artist:${artist} - }${title}",
233  "${?artist:${artist} - }${?album:${album} - }${title}",
234  "${?artist:${artist} - }${?album:${album} - }${?track-number:${track-number}. }${title}",
235  "${?artist:${artist} }${?album:[ ${album} ] }${?artist:- }${?track-number:${track-number}. }${title}",
236  "${?album:${album} - }${title}"};
237 
238 static const char * const titlestring_preset_names[TITLESTRING_NPRESETS] = {
239  N_("TITLE"),
240  N_("ARTIST - TITLE"),
241  N_("ARTIST - ALBUM - TITLE"),
242  N_("ARTIST - ALBUM - TRACK. TITLE"),
243  N_("ARTIST [ ALBUM ] - TRACK. TITLE"),
244  N_("ALBUM - TITLE")};
245 
247 
248 static void
249 change_category(GtkNotebook * notebook,
250  GtkTreeSelection * selection)
251 {
252  GtkTreeModel *model;
253  GtkTreeIter iter;
254  int index;
255 
256  if (!gtk_tree_selection_get_selected(selection, &model, &iter))
257  return;
258 
259  gtk_tree_model_get(model, &iter, CATEGORY_VIEW_COL_ID, &index, -1);
260  gtk_notebook_set_current_page(notebook, index);
261 }
262 
263 static void
264 editable_insert_text(GtkEditable * editable,
265  const char * text,
266  int * pos)
267 {
268  gtk_editable_insert_text(editable, text, strlen(text), pos);
269 }
270 
271 static void
272 titlestring_tag_menu_callback(GtkMenuItem * menuitem,
273  void * data)
274 {
275  const char *separator = " - ";
276  int item = GPOINTER_TO_INT(data);
277  int pos;
278 
279  pos = gtk_editable_get_position(GTK_EDITABLE(titlestring_entry));
280 
281  /* insert separator as needed */
282  if (g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(titlestring_entry)), -1) > 0)
283  editable_insert_text(GTK_EDITABLE(titlestring_entry), separator, &pos);
284 
285  editable_insert_text(GTK_EDITABLE(titlestring_entry), _(title_field_tags[item].tag),
286  &pos);
287 
288  gtk_editable_set_position(GTK_EDITABLE(titlestring_entry), pos);
289 }
290 
291 static void
293  void * data)
294 {
295  GtkMenu * menu = data;
296  gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME);
297 }
298 
299 static void update_titlestring_cbox (GtkComboBox * cbox, const char * format)
300 {
301  int preset;
302  for (preset = 0; preset < TITLESTRING_NPRESETS; preset ++)
303  {
304  if (! strcmp (titlestring_presets[preset], format))
305  break;
306  }
307 
308  if (gtk_combo_box_get_active (cbox) != preset)
309  gtk_combo_box_set_active (cbox, preset);
310 }
311 
312 static void on_titlestring_entry_changed (GtkEntry * entry, GtkComboBox * cbox)
313 {
314  const char * format = gtk_entry_get_text (entry);
315  set_string (NULL, "generic_title_format", format);
316  update_titlestring_cbox (cbox, format);
318 }
319 
320 static void on_titlestring_cbox_changed (GtkComboBox * cbox, GtkEntry * entry)
321 {
322  int preset = gtk_combo_box_get_active (cbox);
323  if (preset < TITLESTRING_NPRESETS)
324  gtk_entry_set_text (entry, titlestring_presets[preset]);
325 }
326 
327 static void widget_set_bool (const PreferencesWidget * widget, bool_t value)
328 {
329  g_return_if_fail (widget->cfg_type == VALUE_BOOLEAN);
330 
331  if (widget->cfg)
332  * (bool_t *) widget->cfg = value;
333  else if (widget->cname)
334  set_bool (widget->csect, widget->cname, value);
335 
336  if (widget->callback)
337  widget->callback ();
338 }
339 
340 static bool_t widget_get_bool (const PreferencesWidget * widget)
341 {
342  g_return_val_if_fail (widget->cfg_type == VALUE_BOOLEAN, FALSE);
343 
344  if (widget->cfg)
345  return * (bool_t *) widget->cfg;
346  else if (widget->cname)
347  return get_bool (widget->csect, widget->cname);
348  else
349  return FALSE;
350 }
351 
352 static void widget_set_int (const PreferencesWidget * widget, int value)
353 {
354  g_return_if_fail (widget->cfg_type == VALUE_INT);
355 
356  if (widget->cfg)
357  * (int *) widget->cfg = value;
358  else if (widget->cname)
359  set_int (widget->csect, widget->cname, value);
360 
361  if (widget->callback)
362  widget->callback ();
363 }
364 
365 static int widget_get_int (const PreferencesWidget * widget)
366 {
367  g_return_val_if_fail (widget->cfg_type == VALUE_INT, 0);
368 
369  if (widget->cfg)
370  return * (int *) widget->cfg;
371  else if (widget->cname)
372  return get_int (widget->csect, widget->cname);
373  else
374  return 0;
375 }
376 
377 static void widget_set_double (const PreferencesWidget * widget, double value)
378 {
379  g_return_if_fail (widget->cfg_type == VALUE_FLOAT);
380 
381  if (widget->cfg)
382  * (float *) widget->cfg = value;
383  else if (widget->cname)
384  set_double (widget->csect, widget->cname, value);
385 
386  if (widget->callback)
387  widget->callback ();
388 }
389 
390 static double widget_get_double (const PreferencesWidget * widget)
391 {
392  g_return_val_if_fail (widget->cfg_type == VALUE_FLOAT, 0);
393 
394  if (widget->cfg)
395  return * (float *) widget->cfg;
396  else if (widget->cname)
397  return get_double (widget->csect, widget->cname);
398  else
399  return 0;
400 }
401 
402 static void widget_set_string (const PreferencesWidget * widget, const char * value)
403 {
404  g_return_if_fail (widget->cfg_type == VALUE_STRING);
405 
406  if (widget->cfg)
407  {
408  g_free (* (char * *) widget->cfg);
409  * (char * *) widget->cfg = g_strdup (value);
410  }
411  else if (widget->cname)
412  set_string (widget->csect, widget->cname, value);
413 
414  if (widget->callback)
415  widget->callback ();
416 }
417 
418 static char * widget_get_string (const PreferencesWidget * widget)
419 {
420  g_return_val_if_fail (widget->cfg_type == VALUE_STRING, NULL);
421 
422  if (widget->cfg)
423  return g_strdup (* (char * *) widget->cfg);
424  else if (widget->cname)
425  return get_string (widget->csect, widget->cname);
426  else
427  return NULL;
428 }
429 
430 static void on_font_btn_font_set (GtkFontButton * button, const PreferencesWidget * widget)
431 {
432  widget_set_string (widget, gtk_font_button_get_font_name (button));
433 }
434 
435 static void on_spin_btn_changed_int (GtkSpinButton * button, const PreferencesWidget * widget)
436 {
437  widget_set_int (widget, gtk_spin_button_get_value_as_int (button));
438 }
439 
440 static void on_spin_btn_changed_float (GtkSpinButton * button, const PreferencesWidget * widget)
441 {
442  widget_set_double (widget, gtk_spin_button_get_value (button));
443 }
444 
445 static void fill_category_list (GtkTreeView * treeview, GtkNotebook * notebook)
446 {
447  GtkListStore *store;
448  GtkCellRenderer *renderer;
449  GtkTreeViewColumn *column;
450  GtkTreeSelection *selection;
451  GtkTreeIter iter;
452  GdkPixbuf *img;
453  CategoryQueueEntry *qlist;
454  int i;
455 
456  column = gtk_tree_view_column_new();
457  gtk_tree_view_column_set_title(column, _("Category"));
458  gtk_tree_view_append_column(treeview, column);
459  gtk_tree_view_column_set_spacing(column, 2);
460 
461  renderer = gtk_cell_renderer_pixbuf_new();
462  gtk_tree_view_column_pack_start(column, renderer, FALSE);
463  gtk_tree_view_column_set_attributes(column, renderer, "pixbuf", 0, NULL);
464 
465  renderer = gtk_cell_renderer_text_new();
466  gtk_tree_view_column_pack_start(column, renderer, FALSE);
467  gtk_tree_view_column_set_attributes(column, renderer, "text", 1, NULL);
468 
469  g_object_set ((GObject *) renderer, "wrap-width", 96, "wrap-mode",
470  PANGO_WRAP_WORD_CHAR, NULL);
471 
472  store = gtk_list_store_new(CATEGORY_VIEW_N_COLS,
473  GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_INT);
474  gtk_tree_view_set_model(treeview, GTK_TREE_MODEL(store));
475 
476  for (i = 0; i < n_categories; i ++)
477  {
478  char * path = g_strdup_printf ("%s/images/%s",
479  get_path (AUD_PATH_DATA_DIR), categories[i].icon_path);
480  img = gdk_pixbuf_new_from_file (path, NULL);
481  g_free (path);
482 
483  gtk_list_store_append(store, &iter);
484  gtk_list_store_set(store, &iter,
487  gettext(categories[i].name), CATEGORY_VIEW_COL_ID,
488  i, -1);
489  g_object_unref(img);
490  }
491 
492  selection = gtk_tree_view_get_selection(treeview);
493 
494  g_signal_connect_swapped(selection, "changed",
495  G_CALLBACK(change_category), notebook);
496 
497  /* mark the treeview widget as available to third party plugins */
498  category_treeview = GTK_WIDGET(treeview);
499 
500  /* prefswin_page_queue_destroy already pops the queue forward for us. */
501  for (qlist = category_queue; qlist != NULL; qlist = category_queue)
502  {
503  CategoryQueueEntry *ent = (CategoryQueueEntry *) qlist;
504 
505  prefswin_page_new(ent->container, ent->pg_name, ent->img_url);
507  }
508 }
509 
510 static void on_radio_button_toggled (GtkWidget * button, const PreferencesWidget * widget)
511 {
512  if (gtk_toggle_button_get_active ((GtkToggleButton *) button))
513  widget_set_int (widget, widget->data.radio_btn.value);
514 }
515 
516 static void init_radio_button (GtkWidget * button, const PreferencesWidget * widget)
517 {
518  if (widget->cfg_type != VALUE_INT)
519  return;
520 
521  if (widget_get_int (widget) == widget->data.radio_btn.value)
522  gtk_toggle_button_set_active ((GtkToggleButton *) button, TRUE);
523 
524  g_signal_connect (button, "toggled", (GCallback) on_radio_button_toggled, (void *) widget);
525 }
526 
527 static void on_toggle_button_toggled (GtkToggleButton * button, const PreferencesWidget * widget)
528 {
529  bool_t active = gtk_toggle_button_get_active (button);
530  widget_set_bool (widget, active);
531 
532  GtkWidget * child = g_object_get_data ((GObject *) button, "child");
533  if (child)
534  gtk_widget_set_sensitive (child, active);
535 }
536 
537 static void init_toggle_button (GtkWidget * button, const PreferencesWidget * widget)
538 {
539  if (widget->cfg_type != VALUE_BOOLEAN)
540  return;
541 
542  gtk_toggle_button_set_active ((GtkToggleButton *) button, widget_get_bool (widget));
543  g_signal_connect (button, "toggled", (GCallback) on_toggle_button_toggled, (void *) widget);
544 }
545 
546 static void on_entry_changed (GtkEntry * entry, const PreferencesWidget * widget)
547 {
548  widget_set_string (widget, gtk_entry_get_text (entry));
549 }
550 
551 static void on_cbox_changed_int (GtkComboBox * combobox, const PreferencesWidget * widget)
552 {
553  int position = gtk_combo_box_get_active (combobox);
554  widget_set_int (widget, GPOINTER_TO_INT (widget->data.combo.elements[position].value));
555 }
556 
557 static void on_cbox_changed_string (GtkComboBox * combobox, const PreferencesWidget * widget)
558 {
559  int position = gtk_combo_box_get_active (combobox);
560  widget_set_string (widget, widget->data.combo.elements[position].value);
561 }
562 
563 static void fill_cbox (GtkWidget * combobox, const PreferencesWidget * widget, const char * domain)
564 {
565  for (int i = 0; i < widget->data.combo.n_elements; i ++)
566  gtk_combo_box_text_append_text ((GtkComboBoxText *) combobox,
567  dgettext (domain, widget->data.combo.elements[i].label));
568 
569  switch (widget->cfg_type)
570  {
571  case VALUE_INT:;
572  int ivalue = widget_get_int (widget);
573 
574  for (int i = 0; i < widget->data.combo.n_elements; i++)
575  {
576  if (GPOINTER_TO_INT (widget->data.combo.elements[i].value) == ivalue)
577  {
578  gtk_combo_box_set_active ((GtkComboBox *) combobox, i);
579  break;
580  }
581  }
582 
583  g_signal_connect (combobox, "changed", (GCallback) on_cbox_changed_int, (void *) widget);
584  break;
585 
586  case VALUE_STRING:;
587  char * value = widget_get_string (widget);
588 
589  for(int i = 0; i < widget->data.combo.n_elements; i++)
590  {
591  if (value && ! strcmp (widget->data.combo.elements[i].value, value))
592  {
593  gtk_combo_box_set_active ((GtkComboBox *) combobox, i);
594  break;
595  }
596  }
597 
598  g_free (value);
599 
600  g_signal_connect (combobox, "changed", (GCallback) on_cbox_changed_string, (void *) widget);
601  break;
602 
603  default:
604  break;
605  }
606 }
607 
608 static void create_spin_button (const PreferencesWidget * widget, GtkWidget * *
609  label_pre, GtkWidget * * spin_btn, GtkWidget * * label_past, const char *
610  domain)
611 {
612  g_return_if_fail(widget->type == WIDGET_SPIN_BTN);
613 
614  * label_pre = gtk_label_new (dgettext (domain, widget->label));
615 
616  *spin_btn = gtk_spin_button_new_with_range(widget->data.spin_btn.min,
617  widget->data.spin_btn.max,
618  widget->data.spin_btn.step);
619 
620 
621  if (widget->tooltip)
622  gtk_widget_set_tooltip_text (* spin_btn, dgettext (domain,
623  widget->tooltip));
624 
625  if (widget->data.spin_btn.right_label) {
626  * label_past = gtk_label_new (dgettext (domain,
627  widget->data.spin_btn.right_label));
628  }
629 
630  switch (widget->cfg_type)
631  {
632  case VALUE_INT:
633  gtk_spin_button_set_value ((GtkSpinButton *) * spin_btn, widget_get_int (widget));
634  g_signal_connect (* spin_btn, "value_changed", (GCallback)
635  on_spin_btn_changed_int, (void *) widget);
636  break;
637  case VALUE_FLOAT:
638  gtk_spin_button_set_value ((GtkSpinButton *) * spin_btn, widget_get_double (widget));
639  g_signal_connect (* spin_btn, "value_changed", (GCallback)
640  on_spin_btn_changed_float, (void *) widget);
641  break;
642  default:
643  break;
644  }
645 }
646 
647 void create_font_btn (const PreferencesWidget * widget, GtkWidget * * label,
648  GtkWidget * * font_btn, const char * domain)
649 {
650  *font_btn = gtk_font_button_new();
651  gtk_font_button_set_use_font(GTK_FONT_BUTTON(*font_btn), TRUE);
652  gtk_font_button_set_use_size(GTK_FONT_BUTTON(*font_btn), TRUE);
653  gtk_widget_set_hexpand(*font_btn, TRUE);
654  if (widget->label) {
655  * label = gtk_label_new_with_mnemonic (dgettext (domain, widget->label));
656  gtk_label_set_use_markup(GTK_LABEL(*label), TRUE);
657  gtk_misc_set_alignment(GTK_MISC(*label), 1, 0.5);
658  gtk_label_set_justify(GTK_LABEL(*label), GTK_JUSTIFY_RIGHT);
659  gtk_label_set_mnemonic_widget(GTK_LABEL(*label), *font_btn);
660  }
661 
662  if (widget->data.font_btn.title)
663  gtk_font_button_set_title (GTK_FONT_BUTTON (* font_btn),
664  dgettext (domain, widget->data.font_btn.title));
665 
666  char * name = widget_get_string (widget);
667  if (name)
668  {
669  gtk_font_button_set_font_name ((GtkFontButton *) * font_btn, name);
670  g_free (name);
671  }
672 
673  g_signal_connect (* font_btn, "font_set", (GCallback) on_font_btn_font_set, (void *) widget);
674 }
675 
676 static void create_entry (const PreferencesWidget * widget, GtkWidget * * label,
677  GtkWidget * * entry, const char * domain)
678 {
679  *entry = gtk_entry_new();
680  gtk_entry_set_visibility(GTK_ENTRY(*entry), !widget->data.entry.password);
681  gtk_widget_set_hexpand(*entry, TRUE);
682 
683  if (widget->label)
684  * label = gtk_label_new (dgettext (domain, widget->label));
685 
686  if (widget->tooltip)
687  gtk_widget_set_tooltip_text (* entry, dgettext (domain, widget->tooltip));
688 
689  if (widget->cfg_type == VALUE_STRING)
690  {
691  char * value = widget_get_string (widget);
692  if (value)
693  {
694  gtk_entry_set_text ((GtkEntry *) * entry, value);
695  g_free (value);
696  }
697 
698  g_signal_connect (* entry, "changed", (GCallback) on_entry_changed, (void *) widget);
699  }
700 }
701 
702 static void create_label (const PreferencesWidget * widget, GtkWidget * * label,
703  GtkWidget * * icon, const char * domain)
704 {
705  if (widget->data.label.stock_id)
706  *icon = gtk_image_new_from_stock(widget->data.label.stock_id, GTK_ICON_SIZE_BUTTON);
707 
708  * label = gtk_label_new_with_mnemonic (dgettext (domain, widget->label));
709  gtk_label_set_use_markup(GTK_LABEL(*label), TRUE);
710 
711  if (widget->data.label.single_line == FALSE)
712  gtk_label_set_line_wrap(GTK_LABEL(*label), TRUE);
713 
714  gtk_misc_set_alignment(GTK_MISC(*label), 0, 0.5);
715 }
716 
717 static void create_cbox (const PreferencesWidget * widget, GtkWidget * * label,
718  GtkWidget * * combobox, const char * domain)
719 {
720  * combobox = gtk_combo_box_text_new ();
721 
722  if (widget->label) {
723  * label = gtk_label_new (dgettext (domain, widget->label));
724  }
725 
726  fill_cbox (* combobox, widget, domain);
727 }
728 
729 static void fill_grid (GtkWidget * grid, const PreferencesWidget * elements, int
730  amt, const char * domain)
731 {
732  int x;
733  GtkWidget *widget_left, *widget_middle, *widget_right;
734 
735  for (x = 0; x < amt; ++x) {
736  widget_left = widget_middle = widget_right = NULL;
737  switch (elements[x].type) {
738  case WIDGET_SPIN_BTN:
739  create_spin_button (& elements[x], & widget_left,
740  & widget_middle, & widget_right, domain);
741  break;
742  case WIDGET_LABEL:
743  create_label (& elements[x], & widget_middle, & widget_left,
744  domain);
745  break;
746  case WIDGET_FONT_BTN:
747  create_font_btn (& elements[x], & widget_left, & widget_middle,
748  domain);
749  break;
750  case WIDGET_ENTRY:
751  create_entry (& elements[x], & widget_left, & widget_middle,
752  domain);
753  break;
754  case WIDGET_COMBO_BOX:
755  create_cbox (& elements[x], & widget_left, & widget_middle,
756  domain);
757  break;
758  default:
759  g_warning("Unsupported widget type %d in table", elements[x].type);
760  }
761 
762  if (widget_left)
763  gtk_grid_attach(GTK_GRID(grid), widget_left, 0, x, 1, 1);
764 
765  if (widget_middle)
766  gtk_grid_attach(GTK_GRID(grid), widget_middle, 1, x, 1, 1);
767 
768  if (widget_right)
769  gtk_grid_attach(GTK_GRID(grid), widget_right, 2, x, 1, 1);
770  }
771 }
772 
773 /* box: a GtkBox */
774 void create_widgets_with_domain (void * box, const PreferencesWidget * widgets,
775  int amt, const char * domain)
776 {
777  GtkWidget *alignment = NULL, *widget = NULL;
778  GtkWidget *child_box = NULL;
779  GSList *radio_btn_group = NULL;
780 
781  for (int x = 0; x < amt; x ++)
782  {
783  GtkWidget * label = NULL;
784 
785  if (widget && widgets[x].child)
786  {
787  if (!child_box) {
788  child_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
789  g_object_set_data(G_OBJECT(widget), "child", child_box);
790  alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
791  gtk_box_pack_start(box, alignment, FALSE, FALSE, 0);
792  gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 12, 0);
793  gtk_container_add (GTK_CONTAINER (alignment), child_box);
794 
795  if (GTK_IS_TOGGLE_BUTTON (widget))
796  gtk_widget_set_sensitive (child_box, gtk_toggle_button_get_active ((GtkToggleButton *) widget));
797  }
798  } else
799  child_box = NULL;
800 
801  alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
802  gtk_alignment_set_padding ((GtkAlignment *) alignment, 6, 0, 12, 0);
803  gtk_box_pack_start(child_box ? GTK_BOX(child_box) : box, alignment, FALSE, FALSE, 0);
804 
805  if (radio_btn_group && widgets[x].type != WIDGET_RADIO_BTN)
806  radio_btn_group = NULL;
807 
808  switch(widgets[x].type) {
809  case WIDGET_CHK_BTN:
810  widget = gtk_check_button_new_with_mnemonic (dgettext (domain, widgets[x].label));
811  init_toggle_button (widget, & widgets[x]);
812  break;
813  case WIDGET_LABEL:
814  if (strstr (widgets[x].label, "<b>"))
815  gtk_alignment_set_padding ((GtkAlignment *) alignment,
816  (x == 0) ? 0 : 12, 0, 0, 0);
817 
818  GtkWidget * icon = NULL;
819  create_label (& widgets[x], & label, & icon, domain);
820 
821  if (icon == NULL)
822  widget = label;
823  else {
824  widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
825  gtk_box_pack_start(GTK_BOX(widget), icon, FALSE, FALSE, 0);
826  gtk_box_pack_start(GTK_BOX(widget), label, FALSE, FALSE, 0);
827  }
828  break;
829  case WIDGET_RADIO_BTN:
830  widget = gtk_radio_button_new_with_mnemonic (radio_btn_group,
831  dgettext (domain, widgets[x].label));
832  radio_btn_group = gtk_radio_button_get_group ((GtkRadioButton *) widget);
833  init_radio_button (widget, & widgets[x]);
834  break;
835  case WIDGET_SPIN_BTN:
836  widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
837 
838  GtkWidget *label_pre = NULL, *spin_btn = NULL, *label_past = NULL;
839  create_spin_button (& widgets[x], & label_pre, & spin_btn,
840  & label_past, domain);
841 
842  if (label_pre)
843  gtk_box_pack_start(GTK_BOX(widget), label_pre, FALSE, FALSE, 0);
844  if (spin_btn)
845  gtk_box_pack_start(GTK_BOX(widget), spin_btn, FALSE, FALSE, 0);
846  if (label_past)
847  gtk_box_pack_start(GTK_BOX(widget), label_past, FALSE, FALSE, 0);
848 
849  break;
850  case WIDGET_CUSTOM: /* custom widget. --nenolod */
851  if (widgets[x].data.populate)
852  widget = widgets[x].data.populate();
853  else
854  widget = NULL;
855 
856  break;
857  case WIDGET_FONT_BTN:
858  widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
859 
860  GtkWidget *font_btn = NULL;
861  create_font_btn (& widgets[x], & label, & font_btn, domain);
862 
863  if (label)
864  gtk_box_pack_start(GTK_BOX(widget), label, FALSE, FALSE, 0);
865  if (font_btn)
866  gtk_box_pack_start(GTK_BOX(widget), font_btn, FALSE, FALSE, 0);
867  break;
868  case WIDGET_TABLE:
869  widget = gtk_grid_new();
870  fill_grid(widget, widgets[x].data.table.elem,
871  widgets[x].data.table.rows, domain);
872  gtk_grid_set_column_spacing(GTK_GRID(widget), 6);
873  gtk_grid_set_row_spacing(GTK_GRID(widget), 6);
874  break;
875  case WIDGET_ENTRY:
876  widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
877 
878  GtkWidget *entry = NULL;
879  create_entry (& widgets[x], & label, & entry, domain);
880 
881  if (label)
882  gtk_box_pack_start(GTK_BOX(widget), label, FALSE, FALSE, 0);
883  if (entry)
884  gtk_box_pack_start(GTK_BOX(widget), entry, TRUE, TRUE, 0);
885  break;
886  case WIDGET_COMBO_BOX:
887  widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
888 
889  GtkWidget *combo = NULL;
890  create_cbox (& widgets[x], & label, & combo, domain);
891 
892  if (label)
893  gtk_box_pack_start(GTK_BOX(widget), label, FALSE, FALSE, 0);
894  if (combo)
895  gtk_box_pack_start(GTK_BOX(widget), combo, FALSE, FALSE, 0);
896  break;
897  case WIDGET_BOX:
898  gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 0, 0, 0, 0);
899 
900  if (widgets[x].data.box.horizontal) {
901  widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
902  } else {
903  widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
904  }
905 
906  create_widgets_with_domain ((GtkBox *) widget,
907  widgets[x].data.box.elem, widgets[x].data.box.n_elem, domain);
908 
909  if (widgets[x].data.box.frame) {
910  GtkWidget *tmp;
911  tmp = widget;
912 
913  widget = gtk_frame_new (dgettext (domain, widgets[x].label));
914  gtk_container_add(GTK_CONTAINER(widget), tmp);
915  }
916  break;
917  case WIDGET_NOTEBOOK:
918  gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
919 
920  widget = gtk_notebook_new();
921 
922  int i;
923  for (i = 0; i<widgets[x].data.notebook.n_tabs; i++) {
924  GtkWidget *vbox;
925  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
926  create_widgets_with_domain ((GtkBox *) vbox,
927  widgets[x].data.notebook.tabs[i].widgets,
928  widgets[x].data.notebook.tabs[i].n_widgets, domain);
929 
930  gtk_notebook_append_page (GTK_NOTEBOOK (widget), vbox,
931  gtk_label_new (dgettext (domain,
932  widgets[x].data.notebook.tabs[i].name)));
933  }
934  break;
935  case WIDGET_SEPARATOR:
936  gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 6, 0, 0);
937 
938  if (widgets[x].data.separator.horizontal == TRUE) {
939  widget = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
940  } else {
941  widget = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
942  }
943  break;
944  default:
945  break;
946  }
947 
948  if (widget && !gtk_widget_get_parent(widget))
949  gtk_container_add(GTK_CONTAINER(alignment), widget);
950  if (widget && widgets[x].tooltip && widgets[x].type != WIDGET_SPIN_BTN)
951  gtk_widget_set_tooltip_text (widget, dgettext (domain,
952  widgets[x].tooltip));
953  }
954 
955 }
956 
957 static GtkWidget *
959 {
960  GtkWidget *titlestring_tag_menu, *menu_item;
961  unsigned int i;
962 
963  titlestring_tag_menu = gtk_menu_new();
964  for(i = 0; i < n_title_field_tags; i++) {
965  menu_item = gtk_menu_item_new_with_label(_(title_field_tags[i].name));
966  gtk_menu_shell_append(GTK_MENU_SHELL(titlestring_tag_menu), menu_item);
967  g_signal_connect(menu_item, "activate",
968  G_CALLBACK(titlestring_tag_menu_callback),
969  GINT_TO_POINTER(i));
970  };
971  gtk_widget_show_all(titlestring_tag_menu);
972 
973  return titlestring_tag_menu;
974 }
975 
976 static void show_numbers_cb (GtkToggleButton * numbers, void * unused)
977 {
978  set_bool (NULL, "show_numbers_in_pl", gtk_toggle_button_get_active (numbers));
980  hook_call ("title change", NULL);
981 }
982 
983 static void leading_zero_cb (GtkToggleButton * leading)
984 {
985  set_bool (NULL, "leading_zero", gtk_toggle_button_get_active (leading));
987  hook_call ("title change", NULL);
988 }
989 
990 static void create_titlestring_widgets (GtkWidget * * cbox, GtkWidget * * entry)
991 {
992  * cbox = gtk_combo_box_text_new ();
993  for (int i = 0; i < TITLESTRING_NPRESETS; i ++)
994  gtk_combo_box_text_append_text ((GtkComboBoxText *) * cbox, _(titlestring_preset_names[i]));
995  gtk_combo_box_text_append_text ((GtkComboBoxText *) * cbox, _("Custom"));
996 
997  * entry = gtk_entry_new ();
998 
999  char * format = get_string (NULL, "generic_title_format");
1000  update_titlestring_cbox ((GtkComboBox *) * cbox, format);
1001  gtk_entry_set_text ((GtkEntry *) * entry, format);
1002  g_free (format);
1003 
1004  g_signal_connect (* cbox, "changed", (GCallback) on_titlestring_cbox_changed, * entry);
1005  g_signal_connect (* entry, "changed", (GCallback) on_titlestring_entry_changed, * cbox);
1006 }
1007 
1008 static void
1010 {
1011  GtkWidget *vbox5;
1012  GtkWidget *alignment55;
1013  GtkWidget *label60;
1014  GtkWidget *alignment56;
1015  GtkWidget *grid6;
1016  GtkWidget *titlestring_help_button;
1017  GtkWidget *image1;
1018  GtkWidget *label62;
1019  GtkWidget *label61;
1020  GtkWidget *titlestring_tag_menu = create_titlestring_tag_menu();
1021  GtkWidget * numbers_alignment, * numbers;
1022 
1023  vbox5 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1024  gtk_container_add ((GtkContainer *) category_notebook, vbox5);
1025 
1026  create_widgets(GTK_BOX(vbox5), playlist_page_widgets, G_N_ELEMENTS(playlist_page_widgets));
1027 
1028  alignment55 = gtk_alignment_new (0.5, 0.5, 1, 1);
1029  gtk_box_pack_start (GTK_BOX (vbox5), alignment55, FALSE, FALSE, 0);
1030  gtk_alignment_set_padding ((GtkAlignment *) alignment55, 12, 3, 0, 0);
1031 
1032  label60 = gtk_label_new (_("<b>Song Display</b>"));
1033  gtk_container_add (GTK_CONTAINER (alignment55), label60);
1034  gtk_label_set_use_markup (GTK_LABEL (label60), TRUE);
1035  gtk_misc_set_alignment (GTK_MISC (label60), 0, 0.5);
1036 
1037  numbers_alignment = gtk_alignment_new (0, 0, 0, 0);
1038  gtk_alignment_set_padding ((GtkAlignment *) numbers_alignment, 0, 0, 12, 0);
1039  gtk_box_pack_start ((GtkBox *) vbox5, numbers_alignment, 0, 0, 3);
1040 
1041  numbers = gtk_check_button_new_with_label (_("Show song numbers"));
1042  gtk_toggle_button_set_active ((GtkToggleButton *) numbers,
1043  get_bool (NULL, "show_numbers_in_pl"));
1044  g_signal_connect ((GObject *) numbers, "toggled", (GCallback)
1045  show_numbers_cb, 0);
1046  gtk_container_add ((GtkContainer *) numbers_alignment, numbers);
1047 
1048  numbers_alignment = gtk_alignment_new (0, 0, 0, 0);
1049  gtk_alignment_set_padding ((GtkAlignment *) numbers_alignment, 0, 0, 12, 0);
1050  gtk_box_pack_start ((GtkBox *) vbox5, numbers_alignment, 0, 0, 3);
1051 
1052  numbers = gtk_check_button_new_with_label (_("Show leading zeroes (02:00 "
1053  "instead of 2:00)"));
1054  gtk_toggle_button_set_active ((GtkToggleButton *) numbers, get_bool (NULL, "leading_zero"));
1055  g_signal_connect ((GObject *) numbers, "toggled", (GCallback)
1056  leading_zero_cb, 0);
1057  gtk_container_add ((GtkContainer *) numbers_alignment, numbers);
1058 
1059  alignment56 = gtk_alignment_new (0.5, 0.5, 1, 1);
1060  gtk_box_pack_start (GTK_BOX (vbox5), alignment56, FALSE, FALSE, 0);
1061  gtk_alignment_set_padding (GTK_ALIGNMENT (alignment56), 0, 0, 12, 0);
1062 
1063  grid6 = gtk_grid_new ();
1064  gtk_container_add (GTK_CONTAINER (alignment56), grid6);
1065  gtk_grid_set_row_spacing (GTK_GRID (grid6), 4);
1066  gtk_grid_set_column_spacing (GTK_GRID (grid6), 12);
1067 
1068  titlestring_help_button = gtk_button_new ();
1069  gtk_grid_attach (GTK_GRID (grid6), titlestring_help_button, 2, 1, 1, 1);
1070 
1071  gtk_widget_set_can_focus (titlestring_help_button, FALSE);
1072  gtk_widget_set_tooltip_text (titlestring_help_button, _("Show information about titlestring format"));
1073  gtk_button_set_relief (GTK_BUTTON (titlestring_help_button), GTK_RELIEF_HALF);
1074  gtk_button_set_focus_on_click (GTK_BUTTON (titlestring_help_button), FALSE);
1075 
1076  image1 = gtk_image_new_from_stock ("gtk-index", GTK_ICON_SIZE_BUTTON);
1077  gtk_container_add (GTK_CONTAINER (titlestring_help_button), image1);
1078 
1079  GtkWidget * titlestring_cbox;
1080  create_titlestring_widgets (& titlestring_cbox, & titlestring_entry);
1081  gtk_widget_set_hexpand (titlestring_cbox, TRUE);
1082  gtk_widget_set_hexpand (titlestring_entry, TRUE);
1083  gtk_grid_attach (GTK_GRID (grid6), titlestring_cbox, 1, 0, 1, 1);
1084  gtk_grid_attach (GTK_GRID (grid6), titlestring_entry, 1, 1, 1, 1);
1085 
1086  label62 = gtk_label_new (_("Custom string:"));
1087  gtk_grid_attach (GTK_GRID (grid6), label62, 0, 1, 1, 1);
1088  gtk_label_set_justify (GTK_LABEL (label62), GTK_JUSTIFY_RIGHT);
1089  gtk_misc_set_alignment (GTK_MISC (label62), 1, 0.5);
1090 
1091  label61 = gtk_label_new (_("Title format:"));
1092  gtk_grid_attach (GTK_GRID (grid6), label61, 0, 0, 1, 1);
1093  gtk_label_set_justify (GTK_LABEL (label61), GTK_JUSTIFY_RIGHT);
1094  gtk_misc_set_alignment (GTK_MISC (label61), 1, 0.5);
1095 
1096  g_signal_connect(titlestring_help_button, "clicked",
1098  titlestring_tag_menu);
1099 }
1100 
1101 static void create_song_info_category (void)
1102 {
1103  GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1104  gtk_container_add ((GtkContainer *) category_notebook, vbox);
1105  create_widgets ((GtkBox *) vbox, song_info_page_widgets,
1106  G_N_ELEMENTS (song_info_page_widgets));
1107 }
1108 
1110 
1111 static bool_t output_enum_cb (PluginHandle * plugin, GList * * list)
1112 {
1113  * list = g_list_prepend (* list, plugin);
1114  return TRUE;
1115 }
1116 
1117 static GList * output_get_list (void)
1118 {
1119  static GList * list = NULL;
1120 
1121  if (list == NULL)
1122  {
1124  & list);
1125  list = g_list_reverse (list);
1126  }
1127 
1128  return list;
1129 }
1130 
1131 static void output_combo_update (GtkComboBox * combo)
1132 {
1134  gtk_combo_box_set_active (combo, g_list_index (output_get_list (), plugin));
1135  gtk_widget_set_sensitive (output_config_button, plugin_has_configure (plugin));
1136  gtk_widget_set_sensitive (output_about_button, plugin_has_about (plugin));
1137 }
1138 
1139 static void output_combo_changed (GtkComboBox * combo)
1140 {
1141  PluginHandle * plugin = g_list_nth_data (output_get_list (),
1142  gtk_combo_box_get_active (combo));
1143  g_return_if_fail (plugin != NULL);
1144 
1145  plugin_enable (plugin, TRUE);
1146  output_combo_update (combo);
1147 }
1148 
1149 static void output_combo_fill (GtkComboBox * combo)
1150 {
1151  for (GList * node = output_get_list (); node != NULL; node = node->next)
1152  gtk_combo_box_text_append_text ((GtkComboBoxText *) combo,
1153  plugin_get_name (node->data));
1154 }
1155 
1156 static void output_bit_depth_changed (void)
1157 {
1159 }
1160 
1161 static void output_do_config (void)
1162 {
1164  g_return_if_fail (plugin != NULL);
1165  plugin_do_configure (plugin);
1166 }
1167 
1168 static void output_do_about (void)
1169 {
1171  g_return_if_fail (plugin != NULL);
1172  plugin_do_about (plugin);
1173 }
1174 
1175 static void * create_output_plugin_box (void)
1176 {
1177  GtkWidget * hbox1 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
1178  gtk_box_pack_start ((GtkBox *) hbox1, gtk_label_new (_("Output plugin:")), FALSE, FALSE, 0);
1179 
1180  GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
1181  gtk_box_pack_start ((GtkBox *) hbox1, vbox, FALSE, FALSE, 0);
1182 
1183  GtkWidget * hbox2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
1184  gtk_box_pack_start ((GtkBox *) vbox, hbox2, FALSE, FALSE, 0);
1185 
1186  GtkWidget * output_plugin_cbox = gtk_combo_box_text_new ();
1187  gtk_box_pack_start ((GtkBox *) hbox2, output_plugin_cbox, FALSE, FALSE, 0);
1188 
1189  GtkWidget * hbox3 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
1190  gtk_box_pack_start ((GtkBox *) vbox, hbox3, FALSE, FALSE, 0);
1191 
1192  output_config_button = gtk_button_new_from_stock (GTK_STOCK_PREFERENCES);
1193  gtk_box_pack_start ((GtkBox *) hbox3, output_config_button, FALSE, FALSE, 0);
1194 
1195  output_about_button = gtk_button_new_from_stock (GTK_STOCK_ABOUT);
1196  gtk_box_pack_start ((GtkBox *) hbox3, output_about_button, FALSE, FALSE, 0);
1197 
1198  output_combo_fill ((GtkComboBox *) output_plugin_cbox);
1199  output_combo_update ((GtkComboBox *) output_plugin_cbox);
1200 
1201  g_signal_connect (output_plugin_cbox, "changed", (GCallback) output_combo_changed, NULL);
1202  g_signal_connect (output_config_button, "clicked", (GCallback) output_do_config, NULL);
1203  g_signal_connect (output_about_button, "clicked", (GCallback) output_do_about, NULL);
1204 
1205  return hbox1;
1206 }
1207 
1208 static void create_audio_category (void)
1209 {
1210  GtkWidget * audio_page_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1211  create_widgets ((GtkBox *) audio_page_vbox, audio_page_widgets, G_N_ELEMENTS (audio_page_widgets));
1212  gtk_container_add ((GtkContainer *) category_notebook, audio_page_vbox);
1213 }
1214 
1215 static void
1217 {
1218  GtkWidget *connectivity_page_vbox;
1219  GtkWidget *vbox29;
1220 
1221  connectivity_page_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1222  gtk_container_add (GTK_CONTAINER (category_notebook), connectivity_page_vbox);
1223 
1224  vbox29 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1225  gtk_box_pack_start (GTK_BOX (connectivity_page_vbox), vbox29, TRUE, TRUE, 0);
1226 
1227  create_widgets(GTK_BOX(vbox29), connectivity_page_widgets, G_N_ELEMENTS(connectivity_page_widgets));
1228 }
1229 
1230 static void create_plugin_category (void)
1231 {
1232  GtkWidget * notebook = gtk_notebook_new ();
1233  gtk_container_add ((GtkContainer *) category_notebook, notebook);
1234 
1237  const char * names[] = {N_("Transport"), N_("Playlist"), N_("Input"),
1238  N_("Effect"), N_("Visualization"), N_("General")};
1239 
1240  for (int i = 0; i < G_N_ELEMENTS (types); i ++)
1241  gtk_notebook_append_page ((GtkNotebook *) notebook, plugin_view_new
1242  (types[i]), gtk_label_new (_(names[i])));
1243 }
1244 
1245 static bool_t
1246 prefswin_destroy(GtkWidget *window, GdkEvent *event, void * data)
1247 {
1248  prefswin = NULL;
1250  gtk_widget_destroy(window);
1251  return TRUE;
1252 }
1253 
1254 /* GtkWidget * * create_prefs_window (void) */
1255 void * * create_prefs_window (void)
1256 {
1257  char *aud_version_string;
1258 
1259  GtkWidget *vbox;
1260  GtkWidget *hbox1;
1261  GtkWidget *scrolledwindow6;
1262  GtkWidget *hseparator1;
1263  GtkWidget *hbox4;
1264  GtkWidget *audversionlabel;
1265  GtkWidget *prefswin_button_box;
1266  GtkWidget *hbox11;
1267  GtkWidget *image10;
1268  GtkWidget *close;
1269  GtkAccelGroup *accel_group;
1270 
1271  accel_group = gtk_accel_group_new ();
1272 
1273  prefswin = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1274  gtk_window_set_type_hint (GTK_WINDOW (prefswin), GDK_WINDOW_TYPE_HINT_DIALOG);
1275  gtk_container_set_border_width (GTK_CONTAINER (prefswin), 12);
1276  gtk_window_set_title (GTK_WINDOW (prefswin), _("Audacious Preferences"));
1277  gtk_window_set_position (GTK_WINDOW (prefswin), GTK_WIN_POS_CENTER);
1278  gtk_window_set_default_size (GTK_WINDOW (prefswin), 680, 400);
1279 
1280  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1281  gtk_container_add (GTK_CONTAINER (prefswin), vbox);
1282 
1283  hbox1 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8);
1284  gtk_box_pack_start (GTK_BOX (vbox), hbox1, TRUE, TRUE, 0);
1285 
1286  scrolledwindow6 = gtk_scrolled_window_new (NULL, NULL);
1287  gtk_box_pack_start (GTK_BOX (hbox1), scrolledwindow6, FALSE, FALSE, 0);
1288  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow6), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1289  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow6), GTK_SHADOW_IN);
1290 
1291  category_treeview = gtk_tree_view_new ();
1292  gtk_container_add (GTK_CONTAINER (scrolledwindow6), category_treeview);
1293  gtk_widget_set_size_request (scrolledwindow6, 168, -1);
1294  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (category_treeview), FALSE);
1295 
1296  category_notebook = gtk_notebook_new ();
1297  gtk_box_pack_start (GTK_BOX (hbox1), category_notebook, TRUE, TRUE, 0);
1298 
1299  gtk_widget_set_can_focus (category_notebook, FALSE);
1300  gtk_notebook_set_show_tabs (GTK_NOTEBOOK (category_notebook), FALSE);
1301  gtk_notebook_set_show_border (GTK_NOTEBOOK (category_notebook), FALSE);
1302  gtk_notebook_set_scrollable (GTK_NOTEBOOK (category_notebook), TRUE);
1303 
1309 
1310  hseparator1 = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
1311  gtk_box_pack_start (GTK_BOX (vbox), hseparator1, FALSE, FALSE, 6);
1312 
1313  hbox4 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1314  gtk_box_pack_start (GTK_BOX (vbox), hbox4, FALSE, FALSE, 0);
1315 
1316  audversionlabel = gtk_label_new ("");
1317  gtk_box_pack_start (GTK_BOX (hbox4), audversionlabel, FALSE, FALSE, 0);
1318  gtk_label_set_use_markup (GTK_LABEL (audversionlabel), TRUE);
1319 
1320  prefswin_button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
1321  gtk_box_pack_start (GTK_BOX (hbox4), prefswin_button_box, TRUE, TRUE, 0);
1322  gtk_button_box_set_layout (GTK_BUTTON_BOX (prefswin_button_box), GTK_BUTTONBOX_END);
1323  gtk_box_set_spacing (GTK_BOX (prefswin_button_box), 6);
1324 
1325  hbox11 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
1326 
1327  image10 = gtk_image_new_from_stock ("gtk-refresh", GTK_ICON_SIZE_BUTTON);
1328  gtk_box_pack_start (GTK_BOX (hbox11), image10, FALSE, FALSE, 0);
1329 
1330  close = gtk_button_new_from_stock ("gtk-close");
1331  gtk_container_add (GTK_CONTAINER (prefswin_button_box), close);
1332  gtk_widget_set_can_default(close, TRUE);
1333  gtk_widget_add_accelerator (close, "clicked", accel_group,
1334  GDK_KEY_Escape, (GdkModifierType) 0,
1335  GTK_ACCEL_VISIBLE);
1336 
1337 
1338  gtk_window_add_accel_group (GTK_WINDOW (prefswin), accel_group);
1339 
1340  /* connect signals */
1341  g_signal_connect(G_OBJECT(prefswin), "delete_event",
1342  G_CALLBACK(prefswin_destroy),
1343  NULL);
1344  g_signal_connect_swapped(G_OBJECT(close), "clicked",
1345  G_CALLBACK(prefswin_destroy),
1346  prefswin);
1347 
1348  /* create category view */
1349  fill_category_list ((GtkTreeView *) category_treeview, (GtkNotebook *) category_notebook);
1350 
1351  /* audacious version label */
1352 
1353  aud_version_string = g_strdup_printf
1354  ("<span size='small'>%s (%s)</span>", "Audacious " VERSION, BUILDSTAMP);
1355 
1356  gtk_label_set_markup( GTK_LABEL(audversionlabel) , aud_version_string );
1357  g_free(aud_version_string);
1358  gtk_widget_show_all(vbox);
1359 
1360  return & prefswin;
1361 }
1362 
1363 void
1365 {
1367 }
1368 
1370 {
1371  if (! prefswin)
1373 
1374  gtk_window_present ((GtkWindow *) prefswin);
1375 }
1376 
1377 void
1379 {
1380  g_return_if_fail(prefswin);
1381  gtk_widget_hide(GTK_WIDGET(prefswin));
1382 }
1383 
1384 static void prefswin_page_queue_new (GtkWidget * container, const char * name,
1385  const char * imgurl)
1386 {
1387  CategoryQueueEntry *ent = g_new0(CategoryQueueEntry, 1);
1388 
1389  ent->container = container;
1390  ent->pg_name = name;
1391  ent->img_url = imgurl;
1392 
1393  if (category_queue)
1394  ent->next = category_queue;
1395 
1396  category_queue = ent;
1397 }
1398 
1399 static void
1401 {
1402  category_queue = ent->next;
1403  g_free(ent);
1404 }
1405 
1406 /*
1407  * Public APIs for adding new pages to the prefs window.
1408  *
1409  * Basically, the concept here is that third party components can register themselves in the root
1410  * preferences window.
1411  *
1412  * From a usability standpoint this makes the application look more "united", instead of cluttered
1413  * and malorganised. Hopefully this option will be used further in the future.
1414  *
1415  * - nenolod
1416  */
1417 /* int prefswin_page_new (GtkWidget * container, const char * name,
1418  const char * imgurl) */
1419 int prefswin_page_new (void * container, const char * name, const char *
1420  imgurl)
1421 {
1422  GtkTreeModel *model;
1423  GtkTreeIter iter;
1424  GdkPixbuf *img = NULL;
1425  GtkTreeView *treeview = GTK_TREE_VIEW(category_treeview);
1426  int id;
1427 
1428  if (treeview == NULL || category_notebook == NULL)
1429  {
1430  prefswin_page_queue_new(container, name, imgurl);
1431  return -1;
1432  }
1433 
1434  model = gtk_tree_view_get_model(treeview);
1435 
1436  if (model == NULL)
1437  {
1438  prefswin_page_queue_new(container, name, imgurl);
1439  return -1;
1440  }
1441 
1442  /* Make sure the widgets are visible. */
1443  gtk_widget_show(container);
1444  id = gtk_notebook_append_page(GTK_NOTEBOOK(category_notebook), container, NULL);
1445 
1446  if (id == -1)
1447  return -1;
1448 
1449  if (imgurl != NULL)
1450  img = gdk_pixbuf_new_from_file(imgurl, NULL);
1451 
1452  gtk_list_store_append(GTK_LIST_STORE(model), &iter);
1453  gtk_list_store_set(GTK_LIST_STORE(model), &iter,
1456  name, CATEGORY_VIEW_COL_ID, id, -1);
1457 
1458  if (img != NULL)
1459  g_object_unref(img);
1460 
1461  return id;
1462 }
1463 
1464 void
1465 prefswin_page_destroy(GtkWidget *container)
1466 {
1467  GtkTreeModel *model;
1468  GtkTreeIter iter;
1469  GtkTreeView *treeview = GTK_TREE_VIEW(category_treeview);
1470  bool_t ret;
1471  int id;
1472  int index = -1;
1473 
1474  if (category_notebook == NULL || treeview == NULL || container == NULL)
1475  return;
1476 
1477  id = gtk_notebook_page_num(GTK_NOTEBOOK(category_notebook), container);
1478 
1479  if (id == -1)
1480  return;
1481 
1482  gtk_notebook_remove_page(GTK_NOTEBOOK(category_notebook), id);
1483 
1484  model = gtk_tree_view_get_model(treeview);
1485 
1486  if (model == NULL)
1487  return;
1488 
1489  ret = gtk_tree_model_get_iter_first(model, &iter);
1490 
1491  while (ret == TRUE)
1492  {
1493  gtk_tree_model_get(model, &iter, CATEGORY_VIEW_COL_ID, &index, -1);
1494 
1495  if (index == id)
1496  {
1497  gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1498  ret = gtk_tree_model_get_iter_first(model, &iter);
1499  continue;
1500  }
1501 
1502  if (index > id)
1503  {
1504  index--;
1505  gtk_list_store_set(GTK_LIST_STORE(model), &iter, CATEGORY_VIEW_COL_ID, index, -1);
1506  }
1507 
1508  ret = gtk_tree_model_iter_next(model, &iter);
1509  }
1510 }