1 #include <config.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <errno.h>
5 #include <string.h>
6 #include <glib.h>
7 #include <glib/gi18n.h>
8 #include <gtk/gtk.h>
9 #include <glade/glade.h>
10
11 #include "elist.h"
12 #include "math_util.h"
13 #include "str_util.h"
14 #include "str_convert.h"
15 #include "file_util.h"
16 #include "gtk_util.h"
17 #include "file_list.h"
18 #include "progress_dlg.h"
19 #include "cursor.h"
20 #include "mru.h"
21 #include "prefs.h"
22 #include "audio_file.h"
23 #include "genre.h"
24
25 #include "playlist_tab.h"
26
27
28 enum {
29 APPLY_TO_ALL = 0,
30 APPLY_TO_SELECTED = 1
31 };
32
33
34 /* data needed to sort files by their tag fields */
35 typedef struct {
36 gchar *name;
37 audio_file *af;
38 } file_info;
39
40 /* widgets */
41 static GtkButton *b_playlist_go = NULL;
42 static GtkComboBox *combo_playlist_apply = NULL;
43 static GtkToggleButton *rb_create_dir = NULL;
44 static GtkToggleButton *rb_create_toplevel = NULL;
45 static GtkToggleButton *rb_create_both = NULL;
46 static GtkToggleButton *rb_name_dir = NULL;
47 static GtkToggleButton *rb_name_set = NULL;
48 static GtkToggleButton *rb_sort_name = NULL;
49 static GtkToggleButton *rb_sort_field = NULL;
50 static GtkCheckButton *cb_sort_across_dirs = NULL;
51 static GtkEntry *ent_pl_name = NULL;
52 static GtkEntry *ent_pl_extension = NULL;
53 static GtkComboBox *combo_field = NULL;
54
55 /* preferences */
56 static long* create_in;
57 static gboolean* name_dir;
58 static char* name;
59 static char* extension;
60 static gboolean* sort_by_name;
61 static long* sort_field;
62 static gboolean* sort_across_dirs;
63
64
65 /*** private functions ******************************************************/
66
compare_audio_file(file_info * a,file_info * b)67 static gint compare_audio_file(file_info *a, file_info *b)
68 {
69 const char *field_a, *field_b;
70 gboolean have_a, have_b;
71 int result = 0;
72
73 if (!*sort_across_dirs) {
74 /* if sort_across_dirs is FALSE the file's directory takes
75 precedence over the tag contents */
76 if (!fu_compare_file_paths(a->name, b->name)) {
77 char *dir_a = g_dirname(a->name);
78 char *dir_b = g_dirname(b->name);
79 result = strcoll(dir_a, dir_b);
80 free(dir_a);
81 free(dir_b);
82 return result;
83 }
84 }
85
86 have_a = (audio_file_get_field(a->af, *sort_field, &field_a) == AF_OK);
87 if (have_a) field_a = strdup(field_a);
88 have_b = (audio_file_get_field(b->af, *sort_field, &field_b) == AF_OK);
89
90 if (have_a ^ have_b) {
91 /* files with tags will come before files w/o */
92 if (have_a)
93 result = -1;
94 else
95 result = 1;
96 }
97 else if (have_a & have_b) {
98 /* if both have tags, sort by the selected field */
99 switch (*sort_field) {
100 case AF_TITLE:
101 case AF_ARTIST:
102 case AF_ALBUM:
103 case AF_COMMENT:
104 case AF_GENRE:
105 result = strcoll(field_a, field_b);
106 break;
107
108 case AF_YEAR:
109 case AF_TRACK:
110 result = compare(atoi(field_a), atoi(field_b));
111 break;
112
113 default: /* should never get here */
114 g_warning("compare_file_info: unexpected value for *sort_field");
115 result = 0;
116 break;
117 }
118 }
119
120 /* if all else is equal, still sort by file name */
121 if (result == 0)
122 result = strcoll(a->name, b->name);
123
124 if (have_a)
125 free((char*)field_a);
126
127 return result;
128 }
129
sort_by_field(GEList * file_list)130 static void sort_by_field(GEList *file_list)
131 {
132 file_info *info;
133 GEList *file_info_list;
134 audio_file *af;
135 GList *i, *j;
136 int res;
137
138 file_info_list = g_elist_new();
139
140 /* make a list with the file names and tags */
141 for (i = g_elist_first(file_list); i; i = g_list_next(i)) {
142 res = audio_file_new(&af, i->data, FALSE);
143 if (res == AF_OK) {
144 info = malloc(sizeof(file_info));
145 info->name = i->data;
146 info->af = af;
147 g_elist_append(file_info_list, info);
148 }
149 else {
150 char *temp_utf8 = str_filename_to_utf8(i->data, _("(UTF8 conversion error)"));
151 pd_printf(PD_ICON_WARN, _("Skipping file \"%s\""), temp_utf8);
152 free(temp_utf8);
153 if (res == AF_ERR_FILE)
154 pd_printf(PD_ICON_NONE, _("Couldn't open file for reading"));
155 else if (res == AF_ERR_FORMAT)
156 pd_printf(PD_ICON_NONE, _("Audio format not recognized"));
157 else
158 pd_printf(PD_ICON_NONE, _("Unknown error (%d)"), res);
159 }
160 }
161
162 /* sort the list */
163 g_elist_sort(file_info_list, (GCompareFunc)compare_audio_file);
164
165 /* copy the new sort order to the original list */
166 while (g_elist_length(file_list) > g_elist_length(file_info_list))
167 g_elist_extract(file_list, g_elist_last(file_list));
168
169 i = g_elist_first(file_list);
170 j = g_elist_first(file_info_list);
171 while (i) {
172 i->data = ((file_info *)j->data)->name;
173
174 audio_file_delete(((file_info *)j->data)->af);
175 free(j->data);
176
177 i = g_list_next(i);
178 j = g_list_next(j);
179 }
180
181 g_elist_free(file_info_list);
182 }
183
184
playlist_name(gchar * base_dir)185 static gchar *playlist_name(gchar *base_dir)
186 {
187 static GString *full_name = NULL;
188 gchar *temp_name;
189 gchar *temp_ext;
190
191 if (full_name == NULL)
192 full_name = g_string_sized_new(100);
193
194 if (gtk_toggle_button_get_active(rb_name_set))
195 temp_name = str_filename_from_utf8(name, "utf8_conversion_error");
196 else if (*base_dir == 0 || strcmp(base_dir, ".") == 0)
197 temp_name = fu_last_path_component(fl_get_working_dir());
198 else
199 temp_name = fu_last_path_component(base_dir);
200
201 temp_ext = str_filename_from_utf8(extension, "utf8_conversion_error");
202
203 if (*base_dir)
204 g_string_sprintf(full_name, "%s/%s.%s", base_dir, temp_name, temp_ext);
205 else
206 g_string_sprintf(full_name, "%s.%s", temp_name, temp_ext);
207
208 free(temp_name);
209 free(temp_ext);
210
211 return full_name->str;
212 }
213
214
write_playlist(gchar * base_dir,gchar * file_name,GEList * file_list)215 static gint write_playlist(gchar *base_dir, gchar *file_name, GEList *file_list)
216 {
217 FILE *f;
218 GList *iter;
219 gchar *entry;
220 gchar *aux;
221 gint len = strlen(base_dir);
222 gint count = 0;
223
224 f = fopen(file_name, "w");
225 if (f == NULL)
226 return count;
227
228 /* the list comes sorted by file name... */
229 if (gtk_toggle_button_get_active(rb_sort_field)) {
230 /* ...sort it by tag field if requested */
231 sort_by_field(file_list);
232 }
233
234 for (iter = g_elist_first(file_list); iter; iter = g_list_next(iter)) {
235 aux = iter->data;
236 if (*base_dir && strstr(aux, base_dir) == aux) {
237 aux += len;
238 while (*aux == '/') aux++;
239 }
240
241 entry = strdup(aux);
242 str_ascii_replace_char(entry, '/', '\\');
243 fprintf(f, "%s\n", entry);
244 free(entry);
245
246 count++;
247 }
248
249 fclose(f);
250
251 return count;
252 }
253
254
create_playlist_in_dir(gchar * base_dir,GEList * file_list)255 static gboolean create_playlist_in_dir(gchar *base_dir, GEList *file_list)
256 {
257 gchar *pl_name;
258 gchar *pl_name_display;
259 gint ret, save_errno;
260
261 pl_name = playlist_name(base_dir);
262 pl_name_display = str_filename_to_utf8(g_basename(pl_name), _("(UTF8 conversion error)"));
263
264 ret = write_playlist(base_dir, pl_name, file_list);
265 if (ret == 0) {
266 save_errno = errno;
267 pd_printf(PD_ICON_FAIL, _("Error creating playlist \"%s\""), pl_name_display);
268 pd_printf(PD_ICON_NONE, "%s (%d)", strerror(save_errno), save_errno);
269 free(pl_name_display);
270 return FALSE;
271 }
272 else {
273 pd_printf(PD_ICON_OK, _("Wrote \"%s\" (%d entries)"), pl_name_display, ret);
274 free(pl_name_display);
275 return TRUE;
276 }
277 }
278
279
create_playlists(GEList * file_list)280 static void create_playlists(GEList *file_list)
281 {
282 GEList *aux_list;
283 GList *iter;
284 GString *path;
285 gchar *full_name;
286 gchar *last_full_name = "";
287 gchar *p;
288 gchar *temp_utf8;
289 gboolean create_dir;
290 gboolean create_toplevel;
291 gint count_total, count_written;
292 gboolean ret;
293
294 create_dir = gtk_toggle_button_get_active(rb_create_dir) ||
295 gtk_toggle_button_get_active(rb_create_both);
296 create_toplevel = gtk_toggle_button_get_active(rb_create_toplevel) ||
297 gtk_toggle_button_get_active(rb_create_both);
298
299 pd_start(_("Writing Playlists"));
300 pd_printf(PD_ICON_INFO, _("Starting in directory \"%s\""), fl_get_working_dir_utf8());
301
302 count_total = 0;
303 count_written = 0;
304
305 if (create_toplevel) {
306 /* make a copy of the list because it may need to be sorted,
307 and we don't want to change the original */
308 aux_list = g_elist_copy(file_list);
309
310 count_total++;
311 ret = create_playlist_in_dir("", aux_list);
312 if (ret)
313 count_written++;
314
315 g_elist_free(aux_list);
316 }
317
318 if (create_dir) {
319 aux_list = g_elist_new();
320 path = g_string_sized_new(200);
321 g_string_assign(path, "");
322
323 iter = g_elist_first(file_list);
324 while (iter) {
325 /* flush pending gtk operations so the UI doesn't freeze */
326 pd_scroll_to_bottom();
327 while (gtk_events_pending()) gtk_main_iteration();
328 if (pd_stop_requested()) {
329 pd_printf(PD_ICON_WARN, _("Operation stopped at user's request"));
330 break;
331 }
332
333 full_name = (gchar*)iter->data;
334 if (!fu_compare_file_paths(last_full_name, full_name)) {
335 if (!create_toplevel || *(path->str)) {
336 count_total++;
337 ret = create_playlist_in_dir(path->str, aux_list);
338 if (ret)
339 count_written++;
340 }
341
342 p = strrchr(full_name, '/');
343 g_string_sprintf(path, "%.*s", (gint)(p-full_name), full_name);
344 g_elist_clear(aux_list);
345
346 temp_utf8 = str_filename_to_utf8(path->str, _("(UTF8 conversion error)"));
347 pd_printf(PD_ICON_INFO, _("Entering directory \"%s\""), temp_utf8);
348 free(temp_utf8);
349 }
350 g_elist_append(aux_list, full_name);
351 last_full_name = full_name;
352
353 iter = g_list_next(iter);
354 }
355 if (g_elist_length(aux_list) > 0) {
356 /* write the last one */
357 count_total++;
358 ret = create_playlist_in_dir(path->str, aux_list);
359 if (ret)
360 count_written++;
361 }
362
363 g_elist_free(aux_list);
364 g_string_free(path, TRUE);
365 }
366
367 pd_printf(PD_ICON_INFO, _("Done (wrote %d of %d playlists)"), count_written, count_total);
368 pd_end();
369 }
370
371
372 /* sets the interface state according to the preferences */
from_prefs()373 static void from_prefs()
374 {
375 switch (*create_in) {
376 case 0: gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_create_dir), TRUE);
377 break;
378 case 1: gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_create_toplevel), TRUE);
379 break;
380 case 2: gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_create_both), TRUE);
381 break;
382 }
383
384 if (*name_dir) {
385 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_name_dir), TRUE);
386 gtk_widget_set_sensitive(GTK_WIDGET(ent_pl_name), FALSE);
387 }
388 else {
389 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_name_set), TRUE);
390 gtk_widget_set_sensitive(GTK_WIDGET(ent_pl_name), TRUE);
391 }
392 gtk_entry_set_text(ent_pl_name, name);
393 gtk_entry_set_text(ent_pl_extension, extension);
394
395 if (*sort_by_name) {
396 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_sort_name), TRUE);
397 gtk_widget_set_sensitive(GTK_WIDGET(combo_field), FALSE);
398 gtk_widget_set_sensitive(GTK_WIDGET(cb_sort_across_dirs), FALSE);
399 }
400 else {
401 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_sort_field), TRUE);
402 gtk_widget_set_sensitive(GTK_WIDGET(combo_field), TRUE);
403 gtk_widget_set_sensitive(GTK_WIDGET(cb_sort_across_dirs), TRUE);
404 }
405 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_sort_across_dirs), *sort_across_dirs);
406 gtk_combo_box_set_active(combo_field, *sort_field);
407 }
408
409 /* sets the preferences according to the curent interface state */
to_prefs()410 static void to_prefs()
411 {
412 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rb_create_dir)))
413 *create_in = 0;
414 else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rb_create_toplevel)))
415 *create_in = 1;
416 else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rb_create_both)))
417 *create_in = 2;
418
419 *name_dir = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rb_name_dir));
420 name = pref_set("pt:name", PREF_STRING, (void*)gtk_entry_get_text(ent_pl_name));
421 extension = pref_set("pt:extension", PREF_STRING, (void*)gtk_entry_get_text(ent_pl_extension));
422
423 *sort_by_name = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rb_sort_name));
424 *sort_across_dirs = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_sort_across_dirs));
425 *sort_field = gtk_combo_box_get_active(combo_field);
426 }
427
428
429 /* orchestrates the call to create_playlists() */
start_operation()430 static void start_operation()
431 {
432 GEList *file_list;
433
434 if (gtk_combo_box_get_active(combo_playlist_apply) == APPLY_TO_ALL)
435 file_list = fl_get_all_files();
436 else
437 file_list = fl_get_selected_files();
438
439 if (g_elist_length(file_list) == 0) {
440 pd_start(_("Writing Playlists"));
441 pd_printf(PD_ICON_FAIL, _("No files selected"));
442 pd_end();
443
444 g_elist_free(file_list);
445 return;
446 }
447
448 to_prefs();
449
450 cursor_set_wait();
451 gtk_widget_set_sensitive(GTK_WIDGET(b_playlist_go), FALSE);
452 create_playlists(file_list);
453 gtk_widget_set_sensitive(GTK_WIDGET(b_playlist_go), TRUE);
454 cursor_set_normal();
455
456 g_elist_free(file_list);
457 }
458
459
460 /*** UI callbacks ***********************************************************/
461
cb_playlist_go(GtkButton * button,gpointer user_data)462 void cb_playlist_go(GtkButton *button, gpointer user_data)
463 {
464 start_operation();
465 }
466
cb_name_set(GtkToggleButton * button,gpointer user_data)467 void cb_name_set(GtkToggleButton *button, gpointer user_data)
468 {
469 gtk_widget_set_sensitive(GTK_WIDGET(ent_pl_name),
470 gtk_toggle_button_get_active(button));
471 }
472
cb_sort_field(GtkToggleButton * button,gpointer user_data)473 void cb_sort_field(GtkToggleButton *button, gpointer user_data)
474 {
475 gboolean active = gtk_toggle_button_get_active(button);
476
477 gtk_widget_set_sensitive(GTK_WIDGET(combo_field), active);
478 gtk_widget_set_sensitive(GTK_WIDGET(cb_sort_across_dirs), active);
479 }
480
cb_file_selection_changed(GtkTreeSelection * selection,gpointer data)481 static void cb_file_selection_changed(GtkTreeSelection *selection, gpointer data)
482 {
483 if (fl_count_selected() > 0)
484 gtk_combo_box_set_active(combo_playlist_apply, APPLY_TO_SELECTED);
485 else
486 gtk_combo_box_set_active(combo_playlist_apply, APPLY_TO_ALL);
487 }
488
489
490 /*** public functions *******************************************************/
491
pt_init(GladeXML * xml)492 void pt_init(GladeXML *xml)
493 {
494 GtkStyle *style;
495 GtkWidget *w;
496
497 /*
498 * get the widgets from glade
499 */
500
501 b_playlist_go = GTK_BUTTON(glade_xml_get_widget(xml, "b_playlist_go"));
502 combo_playlist_apply = GTK_COMBO_BOX(glade_xml_get_widget(xml, "combo_playlist_apply"));
503
504 rb_create_dir = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_create_dir"));
505 rb_create_toplevel = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_create_toplevel"));
506 rb_create_both = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_create_both"));
507
508 rb_name_dir = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_name_dir"));
509 rb_name_set = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_name_set"));
510 ent_pl_name = GTK_ENTRY(glade_xml_get_widget(xml, "ent_pl_name"));
511 ent_pl_extension = GTK_ENTRY(glade_xml_get_widget(xml, "ent_pl_extension"));
512
513 rb_sort_name = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_sort_name"));
514 rb_sort_field = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_sort_field"));
515 cb_sort_across_dirs = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_sort_across_dirs"));
516 combo_field = GTK_COMBO_BOX(glade_xml_get_widget(xml, "combo_field"));
517
518 /* initialize some widgets' state */
519 gtk_combo_box_set_active(combo_playlist_apply, APPLY_TO_ALL);
520
521 /* connect signals */
522 g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(glade_xml_get_widget(xml, "tv_files"))),
523 "changed", G_CALLBACK(cb_file_selection_changed), NULL);
524
525
526 /*
527 * set the title colors
528 */
529
530 w = glade_xml_get_widget(xml, "lab_playlist_title");
531 gtk_widget_ensure_style(w);
532 style = gtk_widget_get_style(w);
533
534 gtk_widget_modify_fg(w, GTK_STATE_NORMAL, &style->text[GTK_STATE_SELECTED]);
535
536 w = glade_xml_get_widget(xml, "box_playlist_title");
537 gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &style->base[GTK_STATE_SELECTED]);
538
539
540 /*
541 * get the preference values, or set them to defaults
542 */
543
544 /* create_in */
545 create_in = pref_get_ref("pt:create_in");
546 if (!create_in) {
547 long def = 0;
548 create_in = pref_set("pt:create_in", PREF_INT, &def);
549 }
550
551 /* name_dir */
552 name_dir = pref_get_ref("pt:name_dir");
553 if (!name_dir) {
554 gboolean def = TRUE;
555 name_dir = pref_set("pt:name_dir", PREF_BOOLEAN, &def);
556 }
557
558 /* name */
559 name = pref_get_ref("pt:name");
560 if (!name) {
561 char *def = "playlist";
562 name = pref_set("pt:name", PREF_STRING, def);
563 }
564
565 /* extension */
566 extension = pref_get_ref("pt:extension");
567 if (!extension) {
568 char *def = "m3u";
569 extension = pref_set("pt:extension", PREF_STRING, def);
570 }
571
572 /* sort_by_name */
573 sort_by_name = pref_get_ref("pt:sort_by_name");
574 if (!sort_by_name) {
575 gboolean def = TRUE;
576 sort_by_name = pref_set("pt:sort_by_name", PREF_BOOLEAN, &def);
577 }
578
579 /* sort_field */
580 sort_field = pref_get_ref("pt:sort_field");
581 if (!sort_field) {
582 long def = 0;
583 sort_field = pref_set("pt:sort_field", PREF_INT, &def);
584 }
585
586 /* sort_across_dirs */
587 sort_across_dirs = pref_get_ref("pt:sort_across_dirs");
588 if (!sort_across_dirs) {
589 gboolean def = FALSE;
590 sort_across_dirs = pref_set("pt:sort_across_dirs", PREF_BOOLEAN, &def);
591 }
592
593
594 from_prefs();
595 }
596
597