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