1 /*                                                     -*- linux-c -*-
2     Copyright (C) 2004 Tom Szilagyi
3 
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 
18     $Id: playlist.c 1293 2014-05-04 22:06:57Z tszilagyi $
19 */
20 
21 #include <config.h>
22 
23 #include <stddef.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <dirent.h>
28 #include <unistd.h>
29 #include <sys/stat.h>
30 #include <glib.h>
31 #include <glib/gstdio.h>
32 #include <glib-object.h>
33 #include <gdk/gdkkeysyms.h>
34 #include <gdk-pixbuf/gdk-pixbuf.h>
35 #include <gtk/gtk.h>
36 #include <libxml/globals.h>
37 #include <libxml/parser.h>
38 #include <libxml/tree.h>
39 
40 #ifdef HAVE_IFP
41 #include "ifp_device.h"
42 #endif /* HAVE_IFP */
43 
44 #ifdef HAVE_TRANSCODING
45 #include "export.h"
46 #endif /* HAVE_TRANSCODING */
47 
48 #include "athread.h"
49 #include "ext_lua.h"
50 #include "common.h"
51 #include "utils.h"
52 #include "utils_gui.h"
53 #include "core.h"
54 #include "httpc.h"
55 #include "rb.h"
56 #include "gui_main.h"
57 #include "music_browser.h"
58 #include "file_info.h"
59 #include "decoder/file_decoder.h"
60 #include "metadata_api.h"
61 #include "volume.h"
62 #include "options.h"
63 #include "i18n.h"
64 #include "search_playlist.h"
65 #include "playlist.h"
66 
67 
68 extern options_t options;
69 
70 extern char pl_color_active[14];
71 extern char pl_color_inactive[14];
72 
73 extern PangoFontDescription *fd_playlist;
74 extern PangoFontDescription *fd_statusbar;
75 
76 extern GtkWidget * main_window;
77 
78 extern GtkTreeSelection * music_select;
79 
80 extern int is_file_loaded;
81 extern int is_paused;
82 extern int allow_seeks;
83 
84 extern rb_t * rb_gui2disk;
85 
86 extern GtkWidget * playlist_toggle;
87 
88 
89 GtkWidget * playlist_window;
90 GtkWidget * playlist_color_indicator;
91 
92 gint playlist_scroll_up_tag = -1;
93 gint playlist_scroll_dn_tag = -1;
94 
95 GtkWidget * statusbar_total;
96 GtkWidget * statusbar_selected;
97 GtkWidget * statusbar_total_label;
98 GtkWidget * statusbar_selected_label;
99 
100 
101 /* popup menus */
102 GtkWidget * add_menu;
103 GtkWidget * add__files;
104 GtkWidget * add__dir;
105 GtkWidget * add__url;
106 GtkWidget * sel_menu;
107 GtkWidget * sel__none;
108 GtkWidget * sel__all;
109 GtkWidget * sel__inv;
110 GtkWidget * rem_menu;
111 GtkWidget * cut__sel;
112 GtkWidget * rem__all;
113 GtkWidget * rem__dead;
114 GtkWidget * rem__sel;
115 GtkWidget * plist_menu;
116 GtkWidget * plist__tab_new;
117 GtkWidget * plist__save;
118 GtkWidget * plist__save_all;
119 GtkWidget * plist__load;
120 GtkWidget * plist__enqueue;
121 GtkWidget * plist__load_tab;
122 GtkWidget * plist__rva;
123 GtkWidget * plist__rva_menu;
124 GtkWidget * plist__rva_separate;
125 GtkWidget * plist__rva_average;
126 GtkWidget * plist__reread_file_meta;
127 GtkWidget * plist__fileinfo;
128 GtkWidget * plist__search;
129 GtkWidget * plist__roll;
130 #ifdef HAVE_IFP
131 GtkWidget * plist__send_songs_to_iriver;
132 #endif /* HAVE_IFP */
133 #ifdef HAVE_TRANSCODING
134 GtkWidget * plist__export;
135 #endif /* HAVE_TRANSCODING */
136 
137 
138 GtkTreeIter * fileinfo_iter = NULL;
139 
140 int playlist_dirty;
141 int playlist_auto_save_tag = -1;
142 
143 typedef struct {
144 	GtkTreeStore * store;
145 	int has_album_node;
146 } clipboard_t;
147 
148 clipboard_t * clipboard;
149 
150 enum {
151 	PLAYLIST_INVALID,
152 	PLAYLIST_XML_SINGLE,
153 	PLAYLIST_XML_MULTI,
154 	PLAYLIST_M3U,
155 	PLAYLIST_PLS
156 };
157 
158 void create_playlist_gui(playlist_t * pl);
159 
160 void playlist_notebook_prev_page(void);
161 void playlist_notebook_next_page(void);
162 void playlist_tab_close(GtkButton * button, gpointer data);
163 void playlist_tab_close_undo(void);
164 void playlist_progress_bar_hide(playlist_t * pl);
165 
166 playlist_transfer_t * playlist_transfer_get(int mode, char * tab_name, int start_playback);
167 
168 
169 playlist_data_t * playlist_filemeta_get(char * filename);
170 
171 void playlist_unlink_files(playlist_t * pl);
172 void set_cursor_in_playlist(playlist_t * pl, GtkTreeIter *iter, gboolean scroll);
173 void select_active_position_in_playlist(playlist_t * pl);
174 
175 void playlist_selection_changed(playlist_t * pl);
176 void playlist_selection_changed_cb(GtkTreeSelection * select, gpointer data);
177 
178 void sel__all_cb(gpointer data);
179 void sel__none_cb(gpointer data);
180 void rem__all_cb(gpointer data);
181 void rem__sel_cb(gpointer data);
182 void cut__sel_cb(gpointer data);
183 void plist__search_cb(gpointer data);
184 void plist__roll_cb(gpointer data);
185 void rem_all(playlist_t * pl);
186 void add_files(GtkWidget * widget, gpointer data);
187 
188 void tab__rename_cb(gpointer data);
189 
190 void add_directory(GtkWidget * widget, gpointer data);
191 void init_plist_menu(GtkWidget *append_menu);
192 void show_active_position_in_playlist(playlist_t * pl);
193 void show_active_position_in_playlist_toggle(playlist_t * pl);
194 void expand_collapse_album_node(playlist_t * pl);
195 
196 void playlist_save(playlist_t * pl, char * filename);
197 
198 int playlist_get_type(char * filename);
199 void playlist_load_xml_multi(char * filename, int start_playback);
200 void playlist_load_xml_single(char * filename, playlist_transfer_t * pt);
201 void * playlist_load_m3u_thread(void * arg);
202 void * playlist_load_pls_thread(void * arg);
203 void roll_to_active_track(playlist_t * pl, GtkTreeIter *piter);
204 
205 
206 GtkWidget * playlist_notebook;
207 
208 GList * playlists;
209 GList * playlists_closed;
210 
211 
212 playlist_data_t *
playlist_data_new()213 playlist_data_new() {
214 
215 	playlist_data_t * data;
216 
217 	if ((data = (playlist_data_t *)calloc(1, sizeof(playlist_data_t))) == NULL) {
218 		fprintf(stderr, "playlist_data_new(): calloc error\n");
219 		return NULL;
220 	}
221 
222 	data->voladj = 0.0f;
223 	data->duration = 0.0f;
224 	data->size = 0;
225 	data->flags = 0;
226 	data->artist = NULL;
227 	data->album = NULL;
228 	data->title = NULL;
229 	data->display = NULL;
230 	data->file = NULL;
231 
232 	return data;
233 }
234 
235 void
playlist_data_copy_noalloc(playlist_data_t * dest,playlist_data_t * src)236 playlist_data_copy_noalloc(playlist_data_t * dest, playlist_data_t * src) {
237 
238 	if (src->artist) {
239 		free_strdup(&dest->artist, src->artist);
240 	}
241 	if (src->album) {
242 		free_strdup(&dest->album, src->album);
243 	}
244 	if (src->title) {
245 		free_strdup(&dest->title, src->title);
246 	}
247 	if (src->display) {
248 		free_strdup(&dest->display, src->display);
249 	}
250 	if (src->file) {
251 		free_strdup(&dest->file, src->file);
252 	}
253 
254 	dest->voladj = src->voladj;
255 	dest->duration = src->duration;
256 	dest->size = src->size;
257 
258 	dest->ntracks = src->ntracks;
259 	dest->actrack = src->actrack;
260 	dest->flags = src->flags;
261 }
262 
263 playlist_data_t *
playlist_data_copy(playlist_data_t * src)264 playlist_data_copy(playlist_data_t * src) {
265 
266 	playlist_data_t * dest;
267 
268 	if ((dest = playlist_data_new()) == NULL) {
269 		return NULL;
270 	}
271 
272 	playlist_data_copy_noalloc(dest, src);
273 
274 	return dest;
275 }
276 
277 void
playlist_data_free(playlist_data_t * data)278 playlist_data_free(playlist_data_t * data) {
279 
280 	if (data->artist) {
281 		free(data->artist);
282 		data->artist = NULL;
283 	}
284 	if (data->album) {
285 		free(data->album);
286 		data->album = NULL;
287 	}
288 	if (data->title) {
289 		free(data->title);
290 		data->title = NULL;
291 	}
292 	if (data->display) {
293 		free(data->display);
294 		data->display = NULL;
295 	}
296 	if (data->file) {
297 		free(data->file);
298 		data->file = NULL;
299 	}
300 	free(data);
301 }
302 
303 gboolean
playlist_remove_track(GtkTreeStore * store,GtkTreeIter * iter)304 playlist_remove_track(GtkTreeStore * store, GtkTreeIter * iter) {
305 
306 	playlist_data_t * data;
307 
308 	gtk_tree_model_get(GTK_TREE_MODEL(store), iter, PL_COL_DATA, &data, -1);
309 	playlist_data_free(data);
310 
311 	return gtk_tree_store_remove(store, iter);
312 }
313 
314 void
playlist_clear(GtkTreeStore * store)315 playlist_clear(GtkTreeStore * store) {
316 
317 	GtkTreeIter iter;
318 	GtkTreeIter iter_child;
319 	playlist_data_t * data;
320 	int i, j;
321 
322 	i = 0;
323 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter, NULL, i++)) {
324 		gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, PL_COL_DATA, &data, -1);
325 		playlist_data_free(data);
326 
327 		j = 0;
328 		while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter_child, &iter, j++)) {
329 			gtk_tree_model_get(GTK_TREE_MODEL(store), &iter_child, PL_COL_DATA, &data, -1);
330 			playlist_data_free(data);
331 		}
332 	}
333 
334 	gtk_tree_store_clear(store);
335 }
336 
337 GtkTreeStore *
playlist_store_new()338 playlist_store_new() {
339 
340 	return gtk_tree_store_new(PL_COL_COUNT,
341 				  G_TYPE_STRING,    /* title string */
342 				  G_TYPE_STRING,    /* volume adj. displayed */
343 				  G_TYPE_STRING,    /* duration displayed */
344 				  G_TYPE_STRING,    /* color */
345 				  G_TYPE_INT,       /* font weight */
346 				  G_TYPE_POINTER);  /* pointer to struct playlist_data_t */
347 }
348 
349 playlist_t *
playlist_new(char * name)350 playlist_new(char * name) {
351 
352 	playlist_t * pl = NULL;
353 
354 	if ((pl = (playlist_t *)calloc(1, sizeof(playlist_t))) == NULL) {
355 		fprintf(stderr, "playlist_new(): calloc error\n");
356 		return NULL;
357 	}
358 
359 	playlists = g_list_append(playlists, pl);
360 
361 	AQUALUNG_COND_INIT(pl->thread_wait);
362 
363 #ifndef HAVE_LIBPTHREAD
364 	pl->thread_mutex = g_mutex_new();
365 	pl->wait_mutex = g_mutex_new();
366 	pl->thread_wait = g_cond_new();
367 #endif /* !HAVE_LIBPTHREAD */
368 
369 	if (name != NULL) {
370 		strncpy(pl->name, name, MAXLEN-1);
371 		pl->name_set = 1;
372 	} else {
373 		strncpy(pl->name, _("(Untitled)"), MAXLEN-1);
374 	}
375 
376 	pl->index = -1;
377 
378 	pl->store = playlist_store_new();
379 
380 	return pl;
381 }
382 
383 void
playlist_free(playlist_t * pl)384 playlist_free(playlist_t * pl) {
385 
386 	playlists = g_list_remove(playlists, pl);
387 
388 #ifndef HAVE_LIBPTHREAD
389 	g_mutex_free(pl->thread_mutex);
390 	g_mutex_free(pl->wait_mutex);
391 	g_cond_free(pl->thread_wait);
392 #endif /* !HAVE_LIBPTHREAD */
393 
394 	free(pl);
395 }
396 
397 
398 gint
playlist_compare_widget(gconstpointer list,gconstpointer widget)399 playlist_compare_widget(gconstpointer list, gconstpointer widget) {
400 
401 	return ((playlist_t *)list)->widget != widget;
402 }
403 
404 gint
playlist_compare_playing(gconstpointer list,gconstpointer dummy)405 playlist_compare_playing(gconstpointer list, gconstpointer dummy) {
406 
407 	return ((playlist_t *)list)->playing == 0;
408 }
409 
410 gint
playlist_compare_name(gconstpointer list,gconstpointer name)411 playlist_compare_name(gconstpointer list, gconstpointer name) {
412 
413 	return strcmp(((playlist_t *)list)->name, (char *)name);
414 }
415 
416 gint
playlist_compare_index(gconstpointer p1,gconstpointer p2)417 playlist_compare_index(gconstpointer p1, gconstpointer p2) {
418 
419 	int i1 = ((playlist_t *)p1)->index;
420 	int i2 = ((playlist_t *)p2)->index;
421 
422 	if (i1 == -1) {
423 		i1 = 1000;
424 	}
425 
426 	if (i2 == -1) {
427 		i2 = 1000;
428 	}
429 
430 	if (i1 < i2) {
431 		return -1;
432 	} else if (i1 > i2) {
433 		return 1;
434 	}
435 
436 	return 0;
437 }
438 
439 playlist_t *
playlist_find(gconstpointer data,GCompareFunc func)440 playlist_find(gconstpointer data, GCompareFunc func) {
441 
442 	GList * find = g_list_find_custom(playlists, data, func);
443 
444 	if (find != NULL) {
445 		return (playlist_t *)(find->data);
446 	}
447 
448 	return NULL;
449 }
450 
451 
452 playlist_t *
playlist_get_current()453 playlist_get_current() {
454 
455 	int idx = gtk_notebook_get_current_page(GTK_NOTEBOOK(playlist_notebook));
456 	GtkWidget * widget = gtk_notebook_get_nth_page(GTK_NOTEBOOK(playlist_notebook), idx);
457 
458 	return playlist_find(widget, playlist_compare_widget);
459 }
460 
461 playlist_t *
playlist_get_playing()462 playlist_get_playing() {
463 
464 	return playlist_find(NULL/*dummy*/, playlist_compare_playing);
465 }
466 
467 playlist_t *
playlist_get_by_page_num(int num)468 playlist_get_by_page_num(int num) {
469 
470 	GtkWidget * widget = gtk_notebook_get_nth_page(GTK_NOTEBOOK(playlist_notebook), num);
471 
472 	playlist_t * pl = playlist_find(widget, playlist_compare_widget);
473 
474 	if (pl == NULL) {
475 		fprintf(stderr, "WARNING: playlist_get_by_page_num() == NULL\n");
476 	}
477 
478 	return pl;
479 }
480 
481 
482 playlist_t *
playlist_get_by_name(char * name)483 playlist_get_by_name(char * name) {
484 
485 	return playlist_find(name, playlist_compare_name);
486 }
487 
488 
489 void
playlist_set_current(playlist_t * pl)490 playlist_set_current(playlist_t * pl) {
491 
492 	int n = gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook));
493 	int i;
494 
495 	for (i = 0; i < n; i++) {
496 		if (pl->widget == gtk_notebook_get_nth_page(GTK_NOTEBOOK(playlist_notebook), i)) {
497 			gtk_notebook_set_current_page(GTK_NOTEBOOK(playlist_notebook), i);
498 			return;
499 		}
500 	}
501 }
502 
503 int
playlist_is_empty(playlist_t * pl)504 playlist_is_empty(playlist_t * pl) {
505 
506 	GtkTreeIter dummy;
507 
508 	if (!pl->name_set &&
509 	    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(pl->store), &dummy) == FALSE) {
510 		return 1;
511 	}
512 
513 	return 0;
514 }
515 
516 void
playlist_set_active(GtkTreeStore * store,GtkTreeIter * piter)517 playlist_set_active(GtkTreeStore * store, GtkTreeIter * piter) {
518 
519 	playlist_data_t * data;
520 
521 	gtk_tree_model_get(GTK_TREE_MODEL(store), piter, PL_COL_DATA, &data, -1);
522 	PL_SET_FLAG(data, PL_FLAG_ACTIVE);
523 
524 	gtk_tree_store_set(store, piter, PL_COL_COLO, pl_color_active, -1);
525 	if (options.show_active_track_name_in_bold) {
526 		gtk_tree_store_set(store, piter, PL_COL_FONT, PANGO_WEIGHT_BOLD, -1);
527 	}
528 }
529 
530 void
playlist_set_inactive(GtkTreeStore * store,GtkTreeIter * piter)531 playlist_set_inactive(GtkTreeStore * store, GtkTreeIter * piter) {
532 
533 	playlist_data_t * data;
534 
535 	gtk_tree_model_get(GTK_TREE_MODEL(store), piter, PL_COL_DATA, &data, -1);
536 	PL_UNSET_FLAG(data, PL_FLAG_ACTIVE);
537 
538 	gtk_tree_store_set(store, piter,
539 			   PL_COL_COLO, pl_color_inactive,
540 			   PL_COL_FONT, PANGO_WEIGHT_NORMAL, -1);
541 }
542 
543 void
playlist_node_copy(GtkTreeStore * sstore,GtkTreeIter * siter,GtkTreeStore * tstore,GtkTreeIter * titer,GtkTreeIter * iter,int mode)544 playlist_node_copy(GtkTreeStore * sstore, GtkTreeIter * siter,
545 		   GtkTreeStore * tstore, GtkTreeIter * titer,
546 		   GtkTreeIter * iter, int mode) {
547 
548 	gchar * name;
549 	gchar * vadj;
550 	gchar * dura;
551 
552 	playlist_data_t * sdata;
553 	playlist_data_t * tdata;
554 
555 	gtk_tree_model_get(GTK_TREE_MODEL(sstore), siter,
556 			   PL_COL_NAME, &name,
557 			   PL_COL_VADJ, &vadj,
558 			   PL_COL_DURA, &dura,
559 			   PL_COL_DATA, &sdata, -1);
560 
561 	if ((tdata = playlist_data_copy(sdata)) == NULL) {
562 		return;
563 	}
564 
565 	if (mode == 0) {
566 		gtk_tree_store_insert_after(tstore, iter, NULL, titer);
567 	} else if (mode == 1) {
568 		gtk_tree_store_insert_before(tstore, iter, NULL, titer);
569 	} else {
570 		gtk_tree_store_append(tstore, iter, titer);
571 	}
572 
573 	if (tdata->file) {
574 		GtkTreeIter parent;
575 		int name_set = 0;
576 		if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(tstore), &parent, iter)) {
577 			playlist_data_t * pdata;
578 			gtk_tree_model_get(GTK_TREE_MODEL(tstore), &parent, PL_COL_DATA, &pdata, -1);
579 			if (pdata->artist && tdata->artist && pdata->album && tdata->album && tdata->title &&
580 			    !strcmp(pdata->artist, tdata->artist) && !strcmp(pdata->album, tdata->album)) {
581 				gtk_tree_store_set(tstore, iter, PL_COL_NAME, tdata->title, -1);
582 				name_set = 1;
583 			}
584 		}
585 		if (!name_set) {
586 			char list_str[MAXLEN];
587 			playlist_data_get_display_name(list_str, tdata);
588 			gtk_tree_store_set(tstore, iter, PL_COL_NAME, list_str, -1);
589 		}
590 	} else {
591 		gtk_tree_store_set(tstore, iter, PL_COL_NAME, name, -1);
592 	}
593 
594 	gtk_tree_store_set(tstore, iter,
595 			   PL_COL_VADJ, vadj,
596 			   PL_COL_DURA, dura,
597 			   PL_COL_COLO, IS_PL_ACTIVE(tdata) ? pl_color_active : pl_color_inactive,
598 			   PL_COL_FONT, (IS_PL_ACTIVE(tdata) && options.show_active_track_name_in_bold) ?
599 			                 PANGO_WEIGHT_BOLD :
600 			                 PANGO_WEIGHT_NORMAL,
601 			   PL_COL_DATA, tdata, -1);
602 
603 	if (sstore != tstore && IS_PL_ACTIVE(sdata)) {
604 
605 		playlist_set_inactive(tstore, iter);
606 
607 		if (IS_PL_ALBUM_NODE(tdata)) {
608 			char name[MAXLEN];
609 			snprintf(name, MAXLEN-1, "%s: %s", tdata->artist, tdata->album);
610 			gtk_tree_store_set(tstore, iter, PL_COL_NAME, name, -1);
611 		}
612 	}
613 
614 	g_free(name);
615 	g_free(vadj);
616 	g_free(dura);
617 }
618 
619 void
playlist_node_deep_copy(GtkTreeStore * sstore,GtkTreeIter * siter,GtkTreeStore * tstore,GtkTreeIter * titer,int mode)620 playlist_node_deep_copy(GtkTreeStore * sstore, GtkTreeIter * siter,
621 			GtkTreeStore * tstore, GtkTreeIter * titer,
622 			int mode) {
623 
624 	GtkTreeIter iter;
625 	GtkTreeIter dummy;
626 	GtkTreeIter child_siter;
627 	int i;
628 
629 
630 	playlist_node_copy(sstore, siter, tstore, titer, &iter, mode);
631 
632 	i = 0;
633 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(sstore), &child_siter, siter, i++)) {
634 		playlist_node_copy(sstore, &child_siter, tstore, &iter, &dummy, 2/*append*/);
635 	}
636 }
637 
638 int
all_tracks_selected(playlist_t * pl,GtkTreeIter * piter)639 all_tracks_selected(playlist_t * pl, GtkTreeIter * piter) {
640 
641 	gint j = 0;
642 	GtkTreeIter iter_child;
643 
644 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter_child, piter, j++)) {
645 		if (!gtk_tree_selection_iter_is_selected(pl->select, &iter_child)) {
646 			return 0;
647 		}
648 	}
649 	return 1;
650 }
651 
652 void
playlist_copy(playlist_t * pl)653 playlist_copy(playlist_t * pl) {
654 
655 	GtkTreeIter iter;
656 	gint i = 0;
657 
658 	if (clipboard == NULL) {
659 		if ((clipboard = (clipboard_t *)calloc(1, sizeof(clipboard_t))) == NULL) {
660 			fprintf(stderr, "playlist_copy(): calloc error\n");
661 			return;
662 		}
663 		clipboard->store = playlist_store_new();
664 	} else {
665 		playlist_clear(clipboard->store);
666 	}
667 
668 	clipboard->has_album_node = 0;
669 
670 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
671 
672 		if (gtk_tree_selection_iter_is_selected(pl->select, &iter)) {
673 			playlist_node_deep_copy(pl->store, &iter, clipboard->store, NULL, 2);
674 			clipboard->has_album_node = 1;
675 			continue;
676 		}
677 
678 		if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pl->store), &iter) == 0) {
679 			continue;
680 		}
681 
682 		if (all_tracks_selected(pl, &iter)) {
683 			playlist_node_deep_copy(pl->store, &iter, clipboard->store, NULL, 2);
684 			clipboard->has_album_node = 1;
685 		} else {
686 			int j = 0;
687 			GtkTreeIter iter_child;
688 			GtkTreeIter dummy;
689 
690 			while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter_child, &iter, j++)) {
691 				if (gtk_tree_selection_iter_is_selected(pl->select, &iter_child)) {
692 					playlist_node_copy(pl->store, &iter_child,
693 							   clipboard->store, NULL, &dummy, 2);
694 				}
695 			}
696 		}
697 	}
698 }
699 
700 void
playlist_cut(playlist_t * pl)701 playlist_cut(playlist_t * pl) {
702 
703 	playlist_copy(pl);
704 	rem__sel_cb(pl);
705 }
706 
707 void
playlist_paste(playlist_t * pl,int before)708 playlist_paste(playlist_t * pl, int before) {
709 
710 	gint i = 0;
711 	GtkTreeIter iter;
712 	GtkTreeIter titer;
713 	GtkTreePath * path;
714 
715 	if (clipboard == NULL) {
716 		return;
717 	}
718 
719 	gtk_tree_view_get_cursor(GTK_TREE_VIEW(pl->view), &path, NULL);
720 
721 	if (path != NULL) {
722 
723 		if (clipboard->has_album_node && gtk_tree_path_get_depth(path) > 1) {
724 			gtk_tree_path_free(path);
725 			return;
726 		}
727 
728 		gtk_tree_model_get_iter(GTK_TREE_MODEL(pl->store), &titer, path);
729 	}
730 
731 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(clipboard->store), &iter, NULL, i++)) {
732 
733 		if (path == NULL) {
734 			playlist_node_deep_copy(clipboard->store, &iter, pl->store, NULL, 2);
735 		} else {
736 			if (before) {
737 				playlist_node_deep_copy(clipboard->store, &iter, pl->store, &titer, 1);
738 			} else {
739 				playlist_node_deep_copy(clipboard->store, &iter, pl->store, &titer, 0);
740 				gtk_tree_model_iter_next(GTK_TREE_MODEL(pl->store), &titer);
741 			}
742 		}
743 	}
744 
745 	if (path != NULL) {
746 		gtk_tree_path_free(path);
747 	}
748 
749 	playlist_content_changed(pl);
750 }
751 
752 
753 playlist_transfer_t *
playlist_transfer_new(playlist_t * pl)754 playlist_transfer_new(playlist_t * pl) {
755 
756 	playlist_transfer_t * pt = NULL;
757 
758 	if ((pt = (playlist_transfer_t *)calloc(1, sizeof(playlist_transfer_t))) == NULL) {
759 		fprintf(stderr, "playlist_transfer_new(): calloc error\n");
760 		return NULL;
761 	}
762 
763 	if (pl == NULL) {
764 		if ((pt->pl = playlist_get_current()) == NULL) {
765 			free(pt);
766 			return NULL;
767 		}
768 	} else {
769 		pt->pl = pl;
770 	}
771 
772 	pt->threshold = 1;
773 
774 	return pt;
775 }
776 
777 
778 void
playlist_transfer_free(playlist_transfer_t * pt)779 playlist_transfer_free(playlist_transfer_t * pt) {
780 
781 	if (pt->xml_ref != NULL) {
782 		*(pt->xml_ref) -= 1;
783 		if (*(pt->xml_ref) == 0) {
784 			xmlFreeDoc((xmlDocPtr)pt->xml_doc);
785 			free(pt->xml_ref);
786 		}
787 	} else if (pt->xml_doc != NULL) {
788 		xmlFreeDoc((xmlDocPtr)pt->xml_doc);
789 	}
790 
791 	if (pt->filename) {
792 		free(pt->filename);
793 	}
794 
795 	free(pt);
796 }
797 
798 void
playlist_reset_display_names(void)799 playlist_reset_display_names(void) {
800 
801 	GList * node = NULL;
802 	playlist_data_t * data = NULL;
803 	char list_str[MAXLEN];
804 
805 	for (node = playlists; node; node = node->next) {
806 		playlist_t * pl = (playlist_t *)node->data;
807 		GtkTreeIter iter;
808 		int i = 0;
809 		while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
810 			if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(pl->store), &iter)) {
811 				gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter, PL_COL_DATA, &data, -1);
812 				if (!httpc_is_url(data->file)) {
813 					playlist_data_get_display_name(list_str, data);
814 					gtk_tree_store_set(pl->store, &iter, PL_COL_NAME, list_str, -1);
815 				}
816 			}
817 		}
818 	}
819 }
820 
821 void
playlist_disable_bold_font_foreach(gpointer data,gpointer user_data)822 playlist_disable_bold_font_foreach(gpointer data, gpointer user_data) {
823 
824         GtkTreeIter iter;
825 	gint i = 0;
826 
827 	GtkTreeStore * store = ((playlist_t *)data)->store;
828 
829 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter, NULL, i++)) {
830 		gint j = 0;
831 		GtkTreeIter iter_child;
832 
833 		gtk_tree_store_set(store, &iter, PL_COL_FONT, PANGO_WEIGHT_NORMAL, -1);
834 		while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter_child, &iter, j++)) {
835 			gtk_tree_store_set(store, &iter_child, PL_COL_FONT, PANGO_WEIGHT_NORMAL, -1);
836 		}
837 	}
838 }
839 
840 void
playlist_disable_bold_font(void)841 playlist_disable_bold_font(void) {
842 
843 	g_list_foreach(playlists, playlist_disable_bold_font_foreach, NULL);
844 }
845 
846 void
playlist_set_font_foreach(gpointer data,gpointer user_data)847 playlist_set_font_foreach(gpointer data, gpointer user_data) {
848 
849 	gtk_widget_modify_font(((playlist_t *)data)->view, fd_playlist);
850 }
851 
852 void
playlist_set_font(int cond)853 playlist_set_font(int cond) {
854 
855         if (cond) {
856 		gtk_widget_modify_font(statusbar_selected, fd_statusbar);
857 		gtk_widget_modify_font(statusbar_selected_label, fd_statusbar);
858 		gtk_widget_modify_font(statusbar_total, fd_statusbar);
859 		gtk_widget_modify_font(statusbar_total_label, fd_statusbar);
860 
861 		g_list_foreach(playlists, playlist_set_font_foreach, NULL);
862 	}
863 }
864 
865 void
playlist_set_markup(playlist_t * pl)866 playlist_set_markup(playlist_t * pl) {
867 
868 	if (pl->playing) {
869 		gchar * str = g_markup_printf_escaped("<b>%s</b>", pl->name);
870 		gtk_label_set_markup(GTK_LABEL(pl->label), str);
871 		g_free(str);
872 		gtk_widget_modify_fg(pl->label, GTK_STATE_NORMAL,
873 				     &playlist_color_indicator->style->fg[GTK_STATE_ACTIVE]);
874 		gtk_widget_modify_fg(pl->label, GTK_STATE_ACTIVE,
875 				     &playlist_color_indicator->style->fg[GTK_STATE_ACTIVE]);
876 	} else {
877 		gtk_label_set_text(GTK_LABEL(pl->label), pl->name);
878 		gtk_widget_modify_fg(pl->label, GTK_STATE_NORMAL,
879 				     &playlist_color_indicator->style->fg[GTK_STATE_NORMAL]);
880 		gtk_widget_modify_fg(pl->label, GTK_STATE_ACTIVE,
881 				     &playlist_color_indicator->style->fg[GTK_STATE_NORMAL]);
882 	}
883 }
884 
885 void
playlist_set_playing(playlist_t * pl,int playing)886 playlist_set_playing(playlist_t * pl, int playing) {
887 
888 	pl->playing = playing;
889 	playlist_set_markup(pl);
890 }
891 
892 void
adjust_playlist_item_color(GtkTreeStore * store,GtkTreeIter * iter,char * active,char * inactive)893 adjust_playlist_item_color(GtkTreeStore * store, GtkTreeIter * iter,
894 			   char * active, char * inactive) {
895 
896 	playlist_data_t * data;
897 
898 	gtk_tree_model_get(GTK_TREE_MODEL(store), iter, PL_COL_DATA, &data, -1);
899 
900 	if (IS_PL_ACTIVE(data)) {
901 		gtk_tree_store_set(store, iter, PL_COL_COLO, active, -1);
902 		if (options.show_active_track_name_in_bold) {
903 			gtk_tree_store_set(store, iter, PL_COL_FONT, PANGO_WEIGHT_BOLD, -1);
904 		}
905 	} else {
906 		gtk_tree_store_set(store, iter,
907 				   PL_COL_COLO, inactive,
908 				   PL_COL_FONT, PANGO_WEIGHT_NORMAL, -1);
909 	}
910 }
911 
912 
913 void
adjust_playlist_color(playlist_t * pl,char * active,char * inactive)914 adjust_playlist_color(playlist_t * pl, char * active, char * inactive) {
915 
916 	int i = 0;
917 	GtkTreeIter iter;
918 
919 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
920 		int j = 0;
921 		GtkTreeIter iter_child;
922 
923 		adjust_playlist_item_color(pl->store, &iter, active, inactive);
924 		while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store),
925 						     &iter_child, &iter, j++)) {
926 			adjust_playlist_item_color(pl->store, &iter_child,
927 						   active, inactive);
928 		}
929 	}
930 }
931 
932 void
playlist_set_color(void)933 playlist_set_color(void) {
934 
935 	GList * node = NULL;
936 
937 	char active[14];
938 	char inactive[14];
939 
940 	GdkColor color;
941 
942 	int rs = 0, gs = 0, bs = 0;
943 	int ri = 0, gi = 0, bi = 0;
944 
945 
946         if (options.override_skin_settings &&
947 	    (gdk_color_parse(options.activesong_color, &color) == TRUE)) {
948 
949                 rs = color.red;
950                 gs = color.green;
951                 bs = color.blue;
952 
953                 if (rs == 0 && gs == 0 && bs == 0) {
954                         rs = 1;
955 		}
956         } else {
957 		rs = playlist_color_indicator->style->fg[GTK_STATE_SELECTED].red;
958 		gs = playlist_color_indicator->style->fg[GTK_STATE_SELECTED].green;
959 		bs = playlist_color_indicator->style->fg[GTK_STATE_SELECTED].blue;
960 	}
961 
962         if (options.override_skin_settings &&
963 	    (gdk_color_parse(options.song_color, &color) == TRUE)) {
964 
965                 ri = color.red;
966                 gi = color.green;
967                 bi = color.blue;
968 
969         } else {
970         	ri = playlist_color_indicator->style->fg[GTK_STATE_INSENSITIVE].red;
971         	gi = playlist_color_indicator->style->fg[GTK_STATE_INSENSITIVE].green;
972         	bi = playlist_color_indicator->style->fg[GTK_STATE_INSENSITIVE].blue;
973         }
974 
975         sprintf(active, "#%04X%04X%04X", rs, gs, bs);
976 	sprintf(inactive, "#%04X%04X%04X", ri, gi, bi);
977 
978 	for (node = playlists; node; node = node->next) {
979 		playlist_t * pl = (playlist_t *)node->data;
980 		adjust_playlist_color(pl, active, inactive);
981 		playlist_set_playing(pl, pl->playing);
982 	}
983 
984 	for (node = playlists_closed; node; node = node->next) {
985 		playlist_t * pl = (playlist_t *)node->data;
986 		adjust_playlist_color(pl, active, inactive);
987 	}
988 
989         strcpy(pl_color_active, active);
990 	strcpy(pl_color_inactive, inactive);
991 }
992 
993 void
playlist_foreach_selected(playlist_t * pl,int (* foreach)(playlist_t *,GtkTreeIter *,void *),void * data)994 playlist_foreach_selected(playlist_t * pl, int (* foreach)(playlist_t *, GtkTreeIter *, void *),
995 			  void * data) {
996 
997 	GtkTreeIter iter_top;
998 	GtkTreeIter iter;
999 	gint i;
1000 	gint j;
1001 
1002 	i = 0;
1003 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter_top, NULL, i++)) {
1004 
1005 		gboolean topsel = gtk_tree_selection_iter_is_selected(pl->select, &iter_top);
1006 
1007 		if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(pl->store), &iter_top)) {
1008 
1009 			j = 0;
1010 			while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, &iter_top, j++)) {
1011 				if (topsel || gtk_tree_selection_iter_is_selected(pl->select, &iter)) {
1012 					if (foreach(pl, &iter, data)) {
1013 						return;
1014 					}
1015 				}
1016 			}
1017 
1018 		} else if (topsel) {
1019 			if (foreach(pl, &iter_top, data)) {
1020 				return;
1021 			}
1022 		}
1023 	}
1024 }
1025 
1026 int
playlist_selection_file_types_foreach(playlist_t * pl,GtkTreeIter * iter,void * user_data)1027 playlist_selection_file_types_foreach(playlist_t * pl, GtkTreeIter * iter, void * user_data) {
1028 
1029 	playlist_data_t * pldata;
1030 	int * i = (int *)user_data;
1031 
1032 	gtk_tree_model_get(GTK_TREE_MODEL(pl->store), iter, PL_COL_DATA, &pldata, -1);
1033 
1034 	if (g_str_has_prefix(pldata->file, "CDDA ")) {
1035 		*i = 1;
1036 	} else if (!httpc_is_url(pldata->file)) {
1037 		*i = 0;
1038 	}
1039 
1040 	return *i == 0;
1041 }
1042 
1043 /* return: 0: selection has audio file; 1: has cdda but no audio; 2: has only http or empty */
1044 int
playlist_selection_file_types(playlist_t * pl)1045 playlist_selection_file_types(playlist_t * pl) {
1046 
1047 	int type = 2;
1048 
1049 	playlist_foreach_selected(pl, playlist_selection_file_types_foreach, &type);
1050 
1051 	return type;
1052 }
1053 
1054 GtkTreePath *
playlist_get_playing_path(playlist_t * pl)1055 playlist_get_playing_path(playlist_t * pl) {
1056 
1057 	playlist_data_t * data;
1058 	GtkTreeIter iter;
1059 	int i = 0;
1060 
1061 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
1062 
1063 		gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter, PL_COL_DATA, &data, -1);
1064 
1065 		if (IS_PL_ACTIVE(data)) {
1066 			if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pl->store), &iter) > 0) {
1067 
1068 				playlist_data_t * data_child;
1069 				GtkTreeIter iter_child;
1070 				int j = 0;
1071 
1072 				while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store),
1073 								     &iter_child, &iter, j++)) {
1074 
1075 					gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter_child,
1076 							   PL_COL_DATA, &data_child, -1);
1077 
1078 					if (IS_PL_ACTIVE(data_child)) {
1079 						return gtk_tree_model_get_path(GTK_TREE_MODEL(pl->store), &iter_child);
1080 					}
1081 				}
1082 			} else {
1083 				return gtk_tree_model_get_path(GTK_TREE_MODEL(pl->store), &iter);
1084 			}
1085 		}
1086 	}
1087 
1088 	return NULL;
1089 }
1090 
1091 void
mark_track(playlist_t * pl,GtkTreeIter * piter)1092 mark_track(playlist_t * pl, GtkTreeIter * piter) {
1093 
1094         int j, n;
1095         char * name;
1096         char counter[MAXLEN];
1097 	char tmpname[MAXLEN];
1098 
1099 	GtkTreeModel * model = GTK_TREE_MODEL(pl->store);
1100 	playlist_data_t * data;
1101 
1102 
1103 	gtk_tree_model_get(model, piter, PL_COL_DATA, &data, -1);
1104 	if (IS_PL_ACTIVE(data)) {
1105 		return;
1106 	}
1107 
1108 
1109         n = gtk_tree_model_iter_n_children(model, piter);
1110 
1111         if (n) {
1112 		GtkTreeIter iter_child;
1113 		playlist_data_t * data_child;
1114 
1115                 gtk_tree_model_get(model, piter,  PL_COL_NAME, &name, -1);
1116                 strncpy(tmpname, name, MAXLEN-1);
1117 
1118                 j = 0;
1119 		while (gtk_tree_model_iter_nth_child(model, &iter_child, piter, j++)) {
1120 
1121 			gtk_tree_model_get(model, &iter_child, PL_COL_DATA, &data_child, -1);
1122 			if (IS_PL_ACTIVE(data_child)) {
1123 				break;
1124 			}
1125 		}
1126 
1127 		if (j > n) {
1128 			return;
1129 		}
1130 
1131                 sprintf(counter, _(" (%d/%d)"), j, n);
1132                 strncat(tmpname, counter, MAXLEN-1);
1133 		gtk_tree_store_set(pl->store, piter, PL_COL_NAME, tmpname, -1);
1134 		data->ntracks = n;
1135 		data->actrack = j;
1136                 g_free(name);
1137 	}
1138 
1139 	playlist_set_active(pl->store, piter);
1140 
1141 	if (gtk_tree_store_iter_depth(pl->store, piter)) { /* track node of album */
1142 		GtkTreeIter iter_parent;
1143 
1144 		gtk_tree_model_iter_parent(model, &iter_parent, piter);
1145 		mark_track(pl, &iter_parent);
1146 	}
1147 
1148 	if (options.auto_roll_to_active_track) {
1149 		roll_to_active_track(pl, piter);
1150 	}
1151 }
1152 
1153 
1154 void
unmark_track(playlist_t * pl,GtkTreeIter * piter)1155 unmark_track(playlist_t * pl, GtkTreeIter * piter) {
1156 
1157 	int n;
1158 	GtkTreeModel * model = GTK_TREE_MODEL(pl->store);
1159 
1160 	playlist_set_inactive(pl->store, piter);
1161 
1162         n = gtk_tree_model_iter_n_children(model, piter);
1163 
1164         if (n) {
1165 		char name[MAXLEN];
1166 		playlist_data_t * data;
1167 
1168 		gtk_tree_model_get(model, piter, PL_COL_DATA, &data, -1);
1169 		snprintf(name, MAXLEN-1, "%s: %s", data->artist, data->album);
1170 		gtk_tree_store_set(pl->store, piter, PL_COL_NAME, name, -1);
1171 	}
1172 
1173 	if (gtk_tree_store_iter_depth(pl->store, piter)) { /* track node of album */
1174 		GtkTreeIter iter_parent;
1175 
1176 		gtk_tree_model_iter_parent(model, &iter_parent, piter);
1177 		unmark_track(pl, &iter_parent);
1178 	}
1179 }
1180 
1181 
1182 void
playlist_start_playback_at_path(playlist_t * pl,GtkTreePath * path)1183 playlist_start_playback_at_path(playlist_t * pl, GtkTreePath * path) {
1184 
1185 	GtkTreeIter iter;
1186 	GtkTreePath * p;
1187 	gchar cmd;
1188 	cue_t cue;
1189 
1190 	playlist_t * plist = NULL;
1191 	playlist_data_t * pldata = NULL;
1192 	GtkTreeModel * model = GTK_TREE_MODEL(pl->store);
1193 
1194 
1195 	if (!allow_seeks) {
1196 		return;
1197 	}
1198 
1199 	while ((plist = playlist_get_playing()) != NULL) {
1200 		playlist_set_playing(plist, 0);
1201 	}
1202 
1203 	playlist_set_playing(pl, 1);
1204 
1205 	while ((p = playlist_get_playing_path(pl)) != NULL) {
1206  		gtk_tree_model_get_iter(model, &iter, p);
1207  		gtk_tree_path_free(p);
1208 		unmark_track(pl, &iter);
1209  	}
1210 
1211 	gtk_tree_model_get_iter(model, &iter, path);
1212 
1213 	if (gtk_tree_model_iter_n_children(model, &iter) > 0) {
1214 		GtkTreeIter iter_child;
1215 		gtk_tree_model_iter_children(model, &iter_child, &iter);
1216 		mark_track(pl, &iter_child);
1217 	} else {
1218 		mark_track(pl, &iter);
1219 	}
1220 
1221 	p = playlist_get_playing_path(pl);
1222 	gtk_tree_model_get_iter(model, &iter, p);
1223 	gtk_tree_path_free(p);
1224 
1225 	gtk_tree_model_get(model, &iter, PL_COL_DATA, &pldata, -1);
1226 	cue_track_for_playback(pl->store, &iter, &cue);
1227 
1228 	is_file_loaded = 1;
1229 
1230         if (options.show_sn_title) {
1231 		refresh_displays();
1232         }
1233 
1234 	toggle_noeffect(PLAY, TRUE);
1235 
1236 	if (is_paused) {
1237 		is_paused = 0;
1238 		toggle_noeffect(PAUSE, FALSE);
1239 	}
1240 
1241 	cmd = CMD_CUE;
1242 	flush_rb_disk2gui();
1243 	rb_write(rb_gui2disk, &cmd, sizeof(char));
1244 	rb_write(rb_gui2disk, (void *)&cue, sizeof(cue_t));
1245 	try_waking_disk_thread();
1246 }
1247 
1248 
1249 static gboolean
playlist_window_close(GtkWidget * widget,GdkEvent * event,gpointer data)1250 playlist_window_close(GtkWidget * widget, GdkEvent * event, gpointer data) {
1251 
1252 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(playlist_toggle), FALSE);
1253 
1254 	return TRUE;
1255 }
1256 
1257 
1258 gint
playlist_window_key_pressed(GtkWidget * widget,GdkEventKey * kevent)1259 playlist_window_key_pressed(GtkWidget * widget, GdkEventKey * kevent) {
1260 
1261 	GtkTreePath * path;
1262 	GtkTreeIter iter;
1263 	playlist_t * pl;
1264 
1265 	if ((pl = playlist_get_current()) == NULL) {
1266 		return TRUE;
1267 	}
1268 
1269         switch (kevent->keyval) {
1270 
1271         case GDK_Insert:
1272 	case GDK_KP_Insert:
1273                 if (kevent->state & GDK_SHIFT_MASK) {  /* SHIFT + Insert */
1274                         add_directory(NULL, NULL);
1275                 } else {
1276                         add_files(NULL, NULL);
1277                 }
1278                 return TRUE;
1279         case GDK_q:
1280 	case GDK_Q:
1281 	case GDK_Escape:
1282                 if (!options.playlist_is_embedded) {
1283                         playlist_window_close(NULL, NULL, NULL);
1284 		}
1285 		return TRUE;
1286 	case GDK_F1:
1287 	case GDK_i:
1288 	case GDK_I:
1289 		gtk_tree_view_get_cursor(GTK_TREE_VIEW(pl->view), &path, NULL);
1290 
1291 		if (path &&
1292 		    gtk_tree_model_get_iter(GTK_TREE_MODEL(pl->store), &iter, path) &&
1293 		    !gtk_tree_model_iter_has_child(GTK_TREE_MODEL(pl->store), &iter)) {
1294 			playlist_data_t * data;
1295 			gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter, PL_COL_DATA, &data, -1);
1296 			show_file_info(GTK_TREE_MODEL(pl->store), iter, playlist_model_func,
1297 				       0, FALSE, IS_PL_COVER(data));
1298 		}
1299 
1300 		if (path) {
1301 			gtk_tree_path_free(path);
1302 		}
1303 
1304 		return TRUE;
1305 	case GDK_Return:
1306 	case GDK_KP_Enter:
1307 		gtk_tree_view_get_cursor(GTK_TREE_VIEW(pl->view), &path, NULL);
1308 
1309 		if (path) {
1310 			playlist_start_playback_at_path(pl, path);
1311 			gtk_tree_path_free(path);
1312 		}
1313 		return TRUE;
1314 	case GDK_u:
1315 	case GDK_U:
1316 		cut__sel_cb(NULL);
1317 		return TRUE;
1318 	case GDK_f:
1319 	case GDK_F:
1320 		plist__search_cb(pl);
1321 		return TRUE;
1322         case GDK_a:
1323         case GDK_A:
1324 		if (kevent->state & GDK_CONTROL_MASK) {
1325 			if (kevent->state & GDK_SHIFT_MASK) {
1326 				sel__none_cb(NULL);
1327 			} else {
1328 				sel__all_cb(NULL);
1329 			}
1330 		} else {
1331                         if (kevent->state & GDK_MOD1_MASK) {  /* ALT + a */
1332                                 playlist_set_current(playlist_get_playing());
1333                                 show_active_position_in_playlist(playlist_get_playing());
1334                         } else {
1335 				plist__roll_cb(pl);
1336                         }
1337 		}
1338                 return TRUE;
1339         case GDK_w:
1340         case GDK_W:
1341 		if (kevent->state & GDK_CONTROL_MASK) {
1342 			playlist_tab_close(NULL, pl);
1343 		} else {
1344 			gtk_tree_view_collapse_all(GTK_TREE_VIEW(pl->view));
1345 			show_active_position_in_playlist(pl);
1346 		}
1347 		return TRUE;
1348         case GDK_Delete:
1349 	case GDK_KP_Delete:
1350                 if (kevent->state & GDK_SHIFT_MASK) {  /* SHIFT + Delete */
1351 			rem__all_cb(NULL);
1352                 } else if (kevent->state & GDK_CONTROL_MASK) {  /* CTRL + Delete */
1353                         playlist_unlink_files(pl);
1354                 } else {
1355 			rem__sel_cb(NULL);
1356                 }
1357 		return TRUE;
1358 	case GDK_t:
1359 	case GDK_T:
1360 		if (kevent->state & GDK_CONTROL_MASK) {
1361 			if (kevent->state & GDK_SHIFT_MASK) {   /* CTRL + SHIFT T */
1362 				playlist_tab_close_undo();
1363 			} else {   /* CTRL + T */
1364 				playlist_tab_new(NULL);
1365 			}
1366 		}
1367 #ifdef HAVE_IFP
1368 		else {
1369 			if (kevent->state & GDK_SHIFT_MASK) {   /* SHIFT T */
1370                                 aifp_transfer_files(DOWNLOAD_MODE);
1371                         } else {
1372                                 aifp_transfer_files(UPLOAD_MODE);
1373                         }
1374                 }
1375 #endif /* HAVE_IFP */
1376 		return TRUE;
1377 	case GDK_r:
1378 	case GDK_R:
1379 		if (kevent->state & GDK_CONTROL_MASK) {  /* CTRL + R */
1380                         tab__rename_cb(playlist_get_current());
1381                 }
1382 		return TRUE;
1383         case GDK_grave:
1384                 expand_collapse_album_node(pl);
1385                 return TRUE;
1386 	case GDK_Page_Up:
1387 		if (kevent->state & GDK_CONTROL_MASK) {
1388 			playlist_notebook_prev_page();
1389 			return TRUE;
1390 		}
1391 		break;
1392 	case GDK_Page_Down:
1393 		if (kevent->state & GDK_CONTROL_MASK) {
1394 			playlist_notebook_next_page();
1395 			return TRUE;
1396 		}
1397 		break;
1398 	case GDK_ISO_Left_Tab: /* it is usually mapped to SHIFT + TAB */
1399 		if (kevent->state & GDK_CONTROL_MASK) {
1400 			playlist_notebook_prev_page();
1401 		}
1402 		return TRUE;
1403 	case GDK_Tab:
1404 		if (kevent->state & GDK_CONTROL_MASK) {
1405 			if (kevent->state & GDK_SHIFT_MASK) {
1406 				playlist_notebook_prev_page();
1407 			} else {
1408 				playlist_notebook_next_page();
1409 			}
1410 		}
1411 		return TRUE;
1412 	case GDK_c:
1413 	case GDK_C:
1414 		if (kevent->state & GDK_CONTROL_MASK) {
1415 			playlist_copy(playlist_get_current());
1416 			return TRUE;
1417 		}
1418 		break;
1419 	case GDK_x:
1420 	case GDK_X:
1421 		if (kevent->state & GDK_CONTROL_MASK) {
1422 			playlist_cut(playlist_get_current());
1423 			return TRUE;
1424 		}
1425 		break;
1426 	case GDK_v:
1427 	case GDK_V:
1428 		if (kevent->state & GDK_CONTROL_MASK) {
1429 			if (kevent->state & GDK_SHIFT_MASK) {
1430 				playlist_paste(playlist_get_current(), 0/*after*/);
1431 			} else {
1432 				playlist_paste(playlist_get_current(), 1/*before*/);
1433 			}
1434 			return TRUE;
1435 		}
1436 		break;
1437 	}
1438 
1439 	return FALSE;
1440 }
1441 
1442 gint
playlist_notebook_key_pressed(GtkWidget * widget,GdkEventKey * kevent)1443 playlist_notebook_key_pressed(GtkWidget * widget, GdkEventKey * kevent) {
1444 
1445 	if ((kevent->state & GDK_CONTROL_MASK) &&
1446 	    (kevent->keyval == GDK_Page_Up || kevent->keyval == GDK_Page_Down)) {
1447 		/* ignore default tab switching key handler */
1448 		return TRUE;
1449 	}
1450 
1451 	if (kevent->keyval == GDK_Tab ||
1452 	    kevent->keyval == GDK_ISO_Left_Tab) {
1453 		/* prevent focus traversal */
1454 		return TRUE;
1455 	}
1456 
1457 	return FALSE;
1458 }
1459 
1460 gint
playlist_key_pressed(GtkWidget * widget,GdkEventKey * kevent)1461 playlist_key_pressed(GtkWidget * widget, GdkEventKey * kevent) {
1462 
1463 	if ((kevent->state & GDK_CONTROL_MASK) &&
1464 	    (kevent->keyval == GDK_Page_Up || kevent->keyval == GDK_Page_Down)) {
1465 		/* ignore default key handler for PageUp/Down when
1466 		   CTRL is pressed to prevent unwanted cursor motion */
1467 		return TRUE;
1468 	}
1469 
1470 	if (kevent->keyval == GDK_Tab ||
1471 	    kevent->keyval == GDK_ISO_Left_Tab) {
1472 		/* prevent focus traversal */
1473 		return TRUE;
1474 	}
1475 
1476 	return FALSE;
1477 }
1478 
1479 void
playlist_menu_set_popup_sensitivity(playlist_t * pl)1480 playlist_menu_set_popup_sensitivity(playlist_t * pl) {
1481 
1482 	int file_types = 0;
1483 	int has_selection = (gtk_tree_selection_count_selected_rows(pl->select) != 0);
1484 
1485 	if (has_selection) {
1486 		file_types = playlist_selection_file_types(pl);
1487 	}
1488 
1489 	gtk_widget_set_sensitive(plist__reread_file_meta, has_selection && (file_types == 0));
1490 	gtk_widget_set_sensitive(plist__rva, has_selection && (file_types <= 1));
1491 #ifdef HAVE_TRANSCODING
1492 	gtk_widget_set_sensitive(plist__export, has_selection && (file_types == 0));
1493 #endif /* HAVE_TRANSCODING */
1494 #ifdef HAVE_IFP
1495 	gtk_widget_set_sensitive(plist__send_songs_to_iriver, has_selection && (file_types == 0));
1496 #endif  /* HAVE_IFP */
1497 }
1498 
1499 gint
playlist_window_button_pressed(GtkWidget * widget,GdkEventButton * event,gpointer data)1500 playlist_window_button_pressed(GtkWidget * widget, GdkEventButton * event, gpointer data) {
1501 
1502 	GtkTreePath * path;
1503 	GtkTreeIter iter;
1504 	playlist_t * pl = (playlist_t *)data;
1505 
1506 	if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
1507 
1508 		if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(pl->view), event->x, event->y,
1509 						  &path, NULL, NULL, NULL)) {
1510 			playlist_start_playback_at_path(pl, path);
1511 			gtk_tree_path_free(path);
1512 		}
1513 		return TRUE;
1514 	}
1515 
1516 	if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
1517 		if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(pl->view), event->x, event->y,
1518 						  &path, NULL, NULL, NULL) &&
1519 		    gtk_tree_model_get_iter(GTK_TREE_MODEL(pl->store), &iter, path)) {
1520 			if (gtk_tree_selection_count_selected_rows(pl->select) == 0) {
1521 				gtk_tree_view_set_cursor(GTK_TREE_VIEW(pl->view), path, NULL, FALSE);
1522 			}
1523 			if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(pl->store), &iter)) {
1524 				gtk_widget_set_sensitive(plist__fileinfo, FALSE);
1525 			} else {
1526 				gtk_widget_set_sensitive(plist__fileinfo, TRUE);
1527 				fileinfo_iter = gtk_tree_iter_copy(&iter);
1528 			}
1529 		} else {
1530 			gtk_widget_set_sensitive(plist__fileinfo, TRUE);
1531 		}
1532 
1533 		playlist_menu_set_popup_sensitivity(pl);
1534 
1535 		gtk_menu_popup(GTK_MENU(plist_menu), NULL, NULL, NULL, NULL,
1536 			       event->button, event->time);
1537 		return TRUE;
1538 	}
1539 	return FALSE;
1540 }
1541 
1542 
1543 static int
filter(const struct dirent * de)1544 filter(const struct dirent * de) {
1545 
1546 	return de->d_name[0] != '.';
1547 }
1548 
1549 
1550 gboolean
finalize_add_to_playlist(gpointer data)1551 finalize_add_to_playlist(gpointer data) {
1552 
1553 	playlist_transfer_t * pt = (playlist_transfer_t *)data;
1554 
1555 	pt->pl->progbar_semaphore--;
1556 
1557 	if (pt->pl->progbar_semaphore != 0) {
1558 		return FALSE;
1559 	}
1560 
1561 	if (playlist_window == NULL) {
1562 		return FALSE;
1563 	}
1564 
1565 	if (pt->pl == playlist_get_current()) {
1566 		playlist_content_changed(pt->pl);
1567 		if (pt->start_playback) {
1568 			GtkTreeModel * model = GTK_TREE_MODEL(pt->pl->store);
1569 			GtkTreePath * path = NULL;
1570 			GtkTreeIter iter;
1571 
1572 			if (gtk_tree_model_get_iter_first(model, &iter)) {
1573 				if (gtk_tree_model_iter_has_child(model, &iter)) {
1574 					GtkTreeIter iter_child;
1575 					gtk_tree_model_iter_children(model, &iter_child, &iter);
1576 					path = gtk_tree_model_get_path(model, &iter_child);
1577 				} else {
1578 					path = gtk_tree_model_get_path(model, &iter);
1579 				}
1580 			}
1581 			if (path != NULL) {
1582 				playlist_start_playback_at_path(pt->pl, path);
1583 				gtk_tree_path_free(path);
1584 			}
1585 		}
1586 	}
1587 
1588 	playlist_progress_bar_hide(pt->pl);
1589 
1590 	if (pt->xml_ref != NULL) {
1591 		select_active_position_in_playlist(pt->pl);
1592 	}
1593 
1594 	return FALSE;
1595 }
1596 
1597 void
playlist_data_get_display_name(char * list_str,playlist_data_t * pldata)1598 playlist_data_get_display_name(char * list_str, playlist_data_t * pldata) {
1599 
1600 	if (pldata->display) {
1601 		strncpy(list_str, pldata->display, MAXLEN-1);
1602 	} else if (pldata->artist || pldata->album || pldata->title) {
1603 		make_title_string(list_str, options.title_format,
1604 				  pldata->artist, pldata->album, pldata->title);
1605 	} else {
1606 		gchar * tmp = g_filename_display_name(pldata->file);
1607 		if (options.meta_use_basename_only) {
1608 			char * bname = g_path_get_basename(tmp);
1609 			strncpy(list_str, bname, MAXLEN-1);
1610 			g_free(bname);
1611 		} else {
1612 			strncpy(list_str, tmp, MAXLEN-1);
1613 		}
1614 		g_free(tmp);
1615 
1616 		if (options.meta_us_to_space) {
1617 			int i;
1618 			for (i = 0; list_str[i]; i++) {
1619 				if (list_str[i] == '_') {
1620 					list_str[i] = ' ';
1621 				}
1622 			}
1623 		}
1624 		if (options.meta_rm_extension) {
1625 			char * c = NULL;
1626 			if ((c = strrchr(list_str, '.')) != NULL) {
1627 				*c = '\0';
1628 			}
1629 		}
1630 	}
1631 }
1632 
1633 gboolean
add_file_to_playlist(gpointer data)1634 add_file_to_playlist(gpointer data) {
1635 
1636 	playlist_transfer_t * pt = (playlist_transfer_t *)data;
1637 	GList * node;
1638 	int finish = 0;
1639 
1640 	AQUALUNG_MUTEX_LOCK(pt->pl->wait_mutex);
1641 
1642 	if (pt->clear) {
1643 		rem_all(pt->pl);
1644 		pt->clear = 0;
1645 	}
1646 
1647 	for (node = pt->pldata_list; node; node = node->next) {
1648 
1649 		GtkTreeIter iter;
1650 		GtkTreePath * path;
1651 		char voladj_str[32];
1652 		char duration_str[MAXLEN];
1653 		char list_str[MAXLEN];
1654 
1655 		playlist_data_t * pldata = (playlist_data_t *)node->data;
1656 
1657 		pt->data_written--;
1658 
1659 		if (pldata == NULL) {
1660 			pt->data_written = 0;
1661 			finish = 1;
1662 			break;
1663 		}
1664 
1665 		voladj2str(pldata->voladj, voladj_str);
1666 		time2time_na(pldata->duration, duration_str);
1667 
1668 		if (IS_PL_TOPLEVEL(pldata)) {
1669 			gtk_tree_store_append(pt->pl->store, &iter, NULL);
1670 		} else {
1671 			GtkTreeIter parent;
1672 			int n = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pt->pl->store), NULL);
1673 
1674 			if (n == 0) {
1675 				/* someone viciously cleared the list while adding tracks to album node;
1676 				   ignore further tracks added to this node */
1677 				playlist_data_free(pldata);
1678 				continue;
1679 			}
1680 
1681 			gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pt->pl->store), &parent, NULL, n-1);
1682 			gtk_tree_store_append(pt->pl->store, &iter, &parent);
1683 		}
1684 
1685 		if (IS_PL_ALBUM_NODE(pldata)) {
1686 			if (IS_PL_ACTIVE(pldata)) {
1687 				snprintf(list_str, MAXLEN-1, "%s: %s (%d/%d)",
1688 					 pldata->artist, pldata->album,
1689 					 pldata->actrack, pldata->ntracks);
1690 			} else {
1691 				snprintf(list_str, MAXLEN-1, "%s: %s",
1692 					 pldata->artist, pldata->album);
1693 			}
1694 		} else if (IS_PL_ALBUM_CHILD(pldata)) {
1695 			GtkTreeIter parent;
1696 			playlist_data_t * pdata;
1697 			if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(pt->pl->store), &parent, &iter)) {
1698 				gtk_tree_model_get(GTK_TREE_MODEL(pt->pl->store), &parent, PL_COL_DATA, &pdata, -1);
1699 				if (pdata->artist && pdata->album && pldata->artist && pldata->album && pldata->title &&
1700 				    !strcmp(pdata->artist, pldata->artist) && !strcmp(pdata->album, pldata->album)) {
1701 					strncpy(list_str, pldata->title, MAXLEN-1);
1702 				} else {
1703 					playlist_data_get_display_name(list_str, pldata);
1704 				}
1705 			}
1706 		} else {
1707 			playlist_data_get_display_name(list_str, pldata);
1708 		}
1709 
1710 		gtk_tree_store_set(pt->pl->store, &iter,
1711 				   PL_COL_NAME, list_str,
1712 				   PL_COL_VADJ, IS_PL_ALBUM_NODE(pldata) ? "" : voladj_str,
1713 				   PL_COL_DURA, duration_str,
1714 				   PL_COL_COLO, IS_PL_ACTIVE(pldata) ? pl_color_active : pl_color_inactive,
1715 				   PL_COL_FONT, (IS_PL_ACTIVE(pldata) && options.show_active_track_name_in_bold) ?
1716 				                 PANGO_WEIGHT_BOLD :
1717 				                 PANGO_WEIGHT_NORMAL,
1718 				   PL_COL_DATA, pldata,
1719 				   -1);
1720 
1721 		if (!pt->start_playback && IS_PL_ACTIVE(pldata) && !IS_PL_ALBUM_NODE(pldata) &&
1722 		    (path = playlist_get_playing_path(pt->pl)) != NULL) {
1723 			/* deactivate item if playing path already exists */
1724 			GtkTreePath * p = gtk_tree_model_get_path(GTK_TREE_MODEL(pt->pl->store), &iter);
1725 			if (gtk_tree_path_compare(path, p) != 0) {
1726 				unmark_track(pt->pl, &iter);
1727 			}
1728 			gtk_tree_path_free(path);
1729 			gtk_tree_path_free(p);
1730 		}
1731 
1732 		if (pt->start_playback && IS_PL_ACTIVE(pldata) && !IS_PL_ALBUM_NODE(pldata)) {
1733 			GtkTreePath * p = gtk_tree_model_get_path(GTK_TREE_MODEL(pt->pl->store), &iter);
1734 			GtkTreeIter parent;
1735 
1736 			/* save/restore the displayed name and ntracks of an album node before/after starting
1737 			   playback at one of its childlen, since the album node may not yet be fully loaded */
1738 			if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(pt->pl->store), &parent, &iter)) {
1739 				gchar * name;
1740 				unsigned short ntracks;
1741 				playlist_data_t * pdata;
1742 
1743 				gtk_tree_model_get(GTK_TREE_MODEL(pt->pl->store), &parent,
1744 						   PL_COL_NAME, &name,
1745 						   PL_COL_DATA, &pdata, -1);
1746 				ntracks = pdata->ntracks;
1747 
1748 				playlist_start_playback_at_path(pt->pl, p);
1749 
1750 				gtk_tree_store_set(pt->pl->store, &parent, PL_COL_NAME, name, -1);
1751 				pdata->ntracks = ntracks;
1752 				g_free(name);
1753 			} else {
1754 				playlist_start_playback_at_path(pt->pl, p);
1755 			}
1756 
1757 			gtk_tree_path_free(p);
1758 			pt->start_playback = 0;
1759 		}
1760 	}
1761 
1762 	g_list_free(pt->pldata_list);
1763 	pt->pldata_list = NULL;
1764 
1765 	if (finish) {
1766 		finalize_add_to_playlist(pt);
1767 	}
1768 
1769 	AQUALUNG_MUTEX_UNLOCK(pt->pl->wait_mutex);
1770 	AQUALUNG_COND_SIGNAL(pt->pl->thread_wait);
1771 
1772 	return FALSE;
1773 }
1774 
1775 
1776 void
playlist_thread_add_to_list(playlist_transfer_t * pt,playlist_data_t * pldata)1777 playlist_thread_add_to_list(playlist_transfer_t * pt, playlist_data_t * pldata) {
1778 
1779 	AQUALUNG_MUTEX_LOCK(pt->pl->wait_mutex);
1780 
1781 	pt->pldata_list = g_list_append(pt->pldata_list, pldata);
1782 	pt->data_written++;
1783 
1784 	if (pt->data_written >= pt->threshold || pldata == NULL) {
1785 
1786 		if (pt->threshold < 64) {
1787 			pt->threshold *= 2;
1788 		}
1789 
1790 		aqualung_idle_add(add_file_to_playlist, pt);
1791 
1792 		while (pt->data_written > 0) {
1793 			AQUALUNG_COND_WAIT(pt->pl->thread_wait, pt->pl->wait_mutex);
1794 		}
1795 	}
1796 
1797 	AQUALUNG_MUTEX_UNLOCK(pt->pl->wait_mutex);
1798 }
1799 
1800 void *
add_files_to_playlist_thread(void * arg)1801 add_files_to_playlist_thread(void * arg) {
1802 
1803 	playlist_transfer_t * pt = (playlist_transfer_t *)arg;
1804 	GSList * node = NULL;
1805 
1806 	AQUALUNG_THREAD_DETACH();
1807 
1808 	AQUALUNG_MUTEX_LOCK(pt->pl->thread_mutex);
1809 
1810 	for (node = pt->list; node; node = node->next) {
1811 
1812 		if (!pt->pl->thread_stop) {
1813 			playlist_data_t * pldata = NULL;
1814 
1815 			if ((pldata = playlist_filemeta_get((char *)node->data)) != NULL) {
1816 				if (pt->start_playback) {
1817 					PL_SET_FLAG(pldata, PL_FLAG_ACTIVE);
1818 				}
1819 				playlist_thread_add_to_list(pt, pldata);
1820 			}
1821 		}
1822 
1823 		g_free(node->data);
1824 	}
1825 
1826 	playlist_thread_add_to_list(pt, NULL);
1827 
1828 	g_slist_free(pt->list);
1829 
1830 	AQUALUNG_MUTEX_UNLOCK(pt->pl->thread_mutex);
1831 
1832 	playlist_transfer_free(pt);
1833 
1834 	return NULL;
1835 }
1836 
1837 
1838 void
add_files(GtkWidget * widget,gpointer data)1839 add_files(GtkWidget * widget, gpointer data) {
1840 
1841         GSList * files = file_chooser(_("Select files"),
1842 				      options.playlist_is_embedded ? main_window : playlist_window,
1843 				      GTK_FILE_CHOOSER_ACTION_OPEN,
1844 				      FILE_CHOOSER_FILTER_AUDIO,
1845 				      TRUE,
1846 				      options.audiodir);
1847         if (files != NULL) {
1848 
1849 		playlist_transfer_t * pt = playlist_transfer_get(PLAYLIST_ENQUEUE, NULL, 0);
1850 
1851 		if (pt != NULL) {
1852 			pt->list = files;
1853 			playlist_progress_bar_show(pt->pl);
1854 			AQUALUNG_THREAD_CREATE(pt->pl->thread_id,
1855 					       NULL,
1856 					       add_files_to_playlist_thread,
1857 					       pt);
1858 		}
1859         }
1860 }
1861 
1862 
1863 void
add_dir_to_playlist(playlist_transfer_t * pt,char * dirname)1864 add_dir_to_playlist(playlist_transfer_t * pt, char * dirname) {
1865 
1866 	gint i, n;
1867 	struct dirent ** ent;
1868 	gchar path[MAXLEN];
1869 
1870 
1871 	if (pt->pl->thread_stop) {
1872 		return;
1873 	}
1874 
1875 	n = scandir(dirname, &ent, filter, alphasort);
1876 	for (i = 0; i < n; i++) {
1877 
1878 		if (pt->pl->thread_stop) {
1879 			break;
1880 		}
1881 
1882 		snprintf(path, MAXLEN-1, "%s/%s", dirname, ent[i]->d_name);
1883 
1884 		if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
1885 			free(ent[i]);
1886 			continue;
1887 		}
1888 
1889 		if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
1890 			add_dir_to_playlist(pt, path);
1891 		} else {
1892 			playlist_data_t * pldata = NULL;
1893 
1894 			if ((pldata = playlist_filemeta_get(path)) != NULL) {
1895 				if (pt->start_playback) {
1896 					PL_SET_FLAG(pldata, PL_FLAG_ACTIVE);
1897 				}
1898 				playlist_thread_add_to_list(pt, pldata);
1899 			}
1900 		}
1901 
1902 		free(ent[i]);
1903 	}
1904 
1905 	while (i < n) {
1906 		free(ent[i]);
1907 		++i;
1908 	}
1909 
1910 	if (n > 0) {
1911 		free(ent);
1912 	}
1913 }
1914 
1915 
1916 void *
add_dir_to_playlist_thread(void * arg)1917 add_dir_to_playlist_thread(void * arg) {
1918 
1919 
1920 	playlist_transfer_t * pt = (playlist_transfer_t *)arg;
1921 	GSList * node = NULL;
1922 
1923 	AQUALUNG_THREAD_DETACH();
1924 
1925 	AQUALUNG_MUTEX_LOCK(pt->pl->thread_mutex);
1926 
1927 	for (node = pt->list; node; node = node->next) {
1928 
1929 		add_dir_to_playlist(pt, (char *)node->data);
1930 		g_free(node->data);
1931 	}
1932 
1933 	playlist_thread_add_to_list(pt, NULL);
1934 
1935 	g_slist_free(pt->list);
1936 
1937 	AQUALUNG_MUTEX_UNLOCK(pt->pl->thread_mutex);
1938 
1939 	playlist_transfer_free(pt);
1940 
1941 	return NULL;
1942 }
1943 
1944 
1945 void
add_directory(GtkWidget * widget,gpointer data)1946 add_directory(GtkWidget * widget, gpointer data) {
1947 
1948         GSList * dirs = file_chooser(_("Select directory"),
1949 				     options.playlist_is_embedded ? main_window : playlist_window,
1950 				     GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1951 				     FILE_CHOOSER_FILTER_NONE,
1952 				     TRUE,
1953 				     options.audiodir);
1954         if (dirs != NULL) {
1955 
1956 		playlist_transfer_t * pt = playlist_transfer_get(PLAYLIST_ENQUEUE, NULL, 0);
1957 
1958 		if (pt != NULL) {
1959 			pt->list = dirs;
1960 			playlist_progress_bar_show(pt->pl);
1961 			AQUALUNG_THREAD_CREATE(pt->pl->thread_id,
1962 					       NULL,
1963 					       add_dir_to_playlist_thread, pt);
1964 		}
1965         }
1966 }
1967 
1968 gint
add_url_key_press_cb(GtkWidget * widget,GdkEventKey * event,gpointer data)1969 add_url_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data) {
1970 
1971         if (event->keyval == GDK_Return) {
1972                 gtk_dialog_response(GTK_DIALOG(widget), GTK_RESPONSE_ACCEPT);
1973                 return TRUE;
1974         }
1975 
1976         return FALSE;
1977 }
1978 
1979 void
add_url(GtkWidget * widget,gpointer data)1980 add_url(GtkWidget * widget, gpointer data) {
1981 
1982 	GtkWidget * dialog;
1983 	GtkWidget * table;
1984 	GtkWidget * hbox;
1985 	GtkWidget * hbox2;
1986 	GtkWidget * url_entry;
1987 	GtkWidget * url_label;
1988 	char url[MAXLEN];
1989 
1990         dialog = gtk_dialog_new_with_buttons(_("Add URL"),
1991                                              options.playlist_is_embedded ? GTK_WINDOW(main_window) : GTK_WINDOW(playlist_window),
1992 					     GTK_DIALOG_DESTROY_WITH_PARENT,
1993 					     GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1994 					     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1995 					     NULL);
1996 
1997         gtk_widget_set_size_request(GTK_WIDGET(dialog), 400, -1);
1998 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
1999         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_REJECT);
2000         g_signal_connect (G_OBJECT (dialog), "key_press_event",
2001                         G_CALLBACK (add_url_key_press_cb), NULL);
2002 
2003 	table = gtk_table_new(2, 2, FALSE);
2004 	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
2005 			   table, FALSE, TRUE, 2);
2006 
2007 	url_label = gtk_label_new(_("URL:"));
2008         hbox = gtk_hbox_new(FALSE, 0);
2009         gtk_box_pack_start(GTK_BOX(hbox), url_label, FALSE, FALSE, 0);
2010 	gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 5, 5);
2011 
2012 	hbox2 = gtk_hbox_new(FALSE, 0);
2013 	gtk_table_attach(GTK_TABLE(table), hbox2, 1, 2, 0, 1,
2014 			 GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 5);
2015 
2016         url_entry = gtk_entry_new();
2017         gtk_entry_set_max_length(GTK_ENTRY(url_entry), MAXLEN - 1);
2018 	gtk_entry_set_text(GTK_ENTRY(url_entry), "http://");
2019         gtk_box_pack_start(GTK_BOX(hbox2), url_entry, TRUE, TRUE, 0);
2020 
2021 	gtk_widget_grab_focus(url_entry);
2022 
2023 	gtk_widget_show_all(dialog);
2024 
2025  display:
2026 	url[0] = '\0';
2027         if (aqualung_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
2028 
2029 		GtkTreeIter iter;
2030 		gchar voladj_str[32];
2031 		gchar duration_str[MAXLEN];
2032 		playlist_t * pl = playlist_get_current();
2033 		playlist_data_t * data;
2034 
2035                 strncpy(url, gtk_entry_get_text(GTK_ENTRY(url_entry)), MAXLEN-1);
2036 
2037 		if (url[0] == '\0' || strstr(url, "http://") != url || strlen(url) <= strlen("http://")) {
2038 			gtk_widget_grab_focus(url_entry);
2039 			goto display;
2040 		}
2041 
2042 		if ((data = playlist_data_new()) == NULL) {
2043 			return;
2044 		}
2045 
2046 		data->file = strdup(url);
2047 		data->duration = 0.0f;
2048 		data->voladj = options.rva_no_rva_voladj;
2049 
2050 		voladj2str(data->voladj, voladj_str);
2051 		time2time_na(data->duration, duration_str);
2052 
2053 		gtk_tree_store_append(pl->store, &iter, NULL);
2054 		gtk_tree_store_set(pl->store, &iter,
2055 				   PL_COL_NAME, url,
2056 				   PL_COL_VADJ, voladj_str,
2057 				   PL_COL_DURA, duration_str,
2058 				   PL_COL_COLO, pl_color_inactive,
2059 				   PL_COL_FONT, PANGO_WEIGHT_NORMAL,
2060 				   PL_COL_DATA, data, -1);
2061 
2062 		playlist_content_changed(pl);
2063         }
2064 
2065         gtk_widget_destroy(dialog);
2066 }
2067 
2068 
2069 void
plist__save_cb(gpointer data)2070 plist__save_cb(gpointer data) {
2071 
2072 	GSList * file;
2073 	playlist_t * pl;
2074 
2075 	if ((pl = playlist_get_current()) == NULL) {
2076 		return;
2077 	}
2078 
2079         file = file_chooser(_("Please specify the file to save the playlist to."),
2080 			    options.playlist_is_embedded ? main_window : playlist_window,
2081 			    GTK_FILE_CHOOSER_ACTION_SAVE,
2082 			    FILE_CHOOSER_FILTER_NONE,
2083 			    FALSE,
2084 			    options.plistdir);
2085 
2086         if (file != NULL) {
2087 		if (g_str_has_suffix((gchar *)file->data, ".m3u")) {
2088 			playlist_save_m3u(pl, (char *)file->data);
2089 		}
2090 		else {
2091 			playlist_save(pl, (char *)file->data);
2092 		}
2093 		g_free(file->data);
2094 		g_slist_free(file);
2095         }
2096 }
2097 
2098 
2099 void
plist__save_all_cb(gpointer data)2100 plist__save_all_cb(gpointer data) {
2101 
2102         GSList * file;
2103 	playlist_t * pl;
2104 
2105 	if ((pl = playlist_get_current()) == NULL) {
2106 		return;
2107 	}
2108 
2109         file = file_chooser(_("Please specify the file to save the playlist to."),
2110 			    options.playlist_is_embedded ? main_window : playlist_window,
2111 			    GTK_FILE_CHOOSER_ACTION_SAVE,
2112 			    FILE_CHOOSER_FILTER_NONE,
2113 			    FALSE,
2114 			    options.plistdir);
2115         if (file != NULL) {
2116 		playlist_save_all((char *)file->data);
2117                 g_free(file->data);
2118 		g_slist_free(file);
2119         }
2120 }
2121 
2122 
2123 playlist_transfer_t *
playlist_transfer_get(int mode,char * tab_name,int start_playback)2124 playlist_transfer_get(int mode, char * tab_name, int start_playback) {
2125 
2126 	playlist_t * pl = NULL;
2127 	playlist_transfer_t * pt = NULL;
2128 
2129 	if (mode == PLAYLIST_LOAD_TAB) {
2130 		pl = playlist_tab_new(tab_name);
2131 		pt = playlist_transfer_new(pl);
2132 	} else {
2133 		if (tab_name == NULL) {
2134 			pl = playlist_get_current();
2135 		} else {
2136 			pl = playlist_get_by_name(tab_name);
2137 		}
2138 		if (pl == NULL) {
2139 			pl = playlist_tab_new(tab_name);
2140 		}
2141 		pt = playlist_transfer_new(pl);
2142 	}
2143 
2144 	if (mode == PLAYLIST_LOAD) {
2145 		pt->clear = 1;
2146 	}
2147 
2148 	pt->start_playback = start_playback;
2149 
2150 	return pt;
2151 }
2152 
2153 
2154 void
playlist_load(GSList * list,int mode,char * tab_name,int start_playback)2155 playlist_load(GSList * list, int mode, char * tab_name, int start_playback) {
2156 
2157 	GSList * node;
2158 	playlist_t * pl = NULL;
2159 	playlist_transfer_t * pt = NULL;
2160 	char fullname[MAXLEN];
2161 	int type;
2162 
2163 	for (node = list; node; node = node->next) {
2164 
2165 		normalize_filename((char *)node->data, fullname);
2166 		type = playlist_get_type(fullname);
2167 
2168 		if (type != PLAYLIST_XML_MULTI) {
2169 			/* content goes to the same playlist */
2170 			if (pl == NULL) {
2171 				pt = playlist_transfer_get(mode, tab_name, start_playback);
2172 				pl = pt->pl;
2173 				start_playback = 0;
2174 			} else {
2175 				pt = playlist_transfer_new(pl);
2176 			}
2177 		}
2178 
2179 		switch (type) {
2180 		case PLAYLIST_INVALID:
2181 			/* not a playlist, trying to load as audio file */
2182 			pt->list = g_slist_append(NULL, strdup(fullname));
2183 			playlist_progress_bar_show(pt->pl);
2184 
2185 			if (g_file_test(fullname, G_FILE_TEST_IS_DIR)) {
2186 				AQUALUNG_THREAD_CREATE(pt->pl->thread_id,
2187 						       NULL,
2188 						       add_dir_to_playlist_thread, pt);
2189 			} else {
2190 				AQUALUNG_THREAD_CREATE(pt->pl->thread_id,
2191 						       NULL,
2192 						       add_files_to_playlist_thread, pt);
2193 			}
2194 
2195 			break;
2196 
2197 		case PLAYLIST_XML_MULTI:
2198 			playlist_load_xml_multi(fullname, start_playback);
2199 			start_playback = 0;
2200 			break;
2201 
2202 		case PLAYLIST_XML_SINGLE:
2203 			playlist_load_xml_single(fullname, pt);
2204 			break;
2205 
2206 		case PLAYLIST_M3U:
2207 			playlist_progress_bar_show(pt->pl);
2208 			pt->filename = strdup(fullname);
2209 			AQUALUNG_THREAD_CREATE(pt->pl->thread_id,
2210 					       NULL,
2211 					       playlist_load_m3u_thread,
2212 					       pt);
2213 			break;
2214 
2215 		case PLAYLIST_PLS:
2216 			playlist_progress_bar_show(pt->pl);
2217 			pt->filename = strdup(fullname);
2218 			AQUALUNG_THREAD_CREATE(pt->pl->thread_id,
2219 					       NULL,
2220 					       playlist_load_pls_thread,
2221 					       pt);
2222 			break;
2223 		}
2224 
2225 		free(node->data);
2226 	}
2227 
2228 	g_slist_free(list);
2229 }
2230 
2231 void
playlist_load_dialog(int mode)2232 playlist_load_dialog(int mode) {
2233 
2234 	GSList * files;
2235 
2236         files = file_chooser(_("Please specify the file to load the playlist from."),
2237 			     options.playlist_is_embedded ? main_window : playlist_window,
2238 			     GTK_FILE_CHOOSER_ACTION_OPEN,
2239 			     FILE_CHOOSER_FILTER_PLAYLIST,
2240 			     FALSE,
2241 			     options.plistdir);
2242         if (files != NULL) {
2243 		playlist_load(files, mode, NULL, 0);
2244         }
2245 }
2246 
2247 void
plist__load_cb(gpointer data)2248 plist__load_cb(gpointer data) {
2249 
2250 	playlist_load_dialog(PLAYLIST_LOAD);
2251 }
2252 
2253 void
plist__enqueue_cb(gpointer data)2254 plist__enqueue_cb(gpointer data) {
2255 
2256 	playlist_load_dialog(PLAYLIST_ENQUEUE);
2257 }
2258 
2259 void
plist__load_tab_cb(gpointer data)2260 plist__load_tab_cb(gpointer data) {
2261 
2262 	playlist_load_dialog(PLAYLIST_LOAD_TAB);
2263 }
2264 
2265 
2266 int
plist_setup_vol_foreach(playlist_t * pl,GtkTreeIter * iter,void * data)2267 plist_setup_vol_foreach(playlist_t * pl, GtkTreeIter * iter, void * data) {
2268 
2269 	volume_t * vol = (volume_t *)data;
2270 	playlist_data_t * pldata;
2271 
2272 	gtk_tree_model_get(GTK_TREE_MODEL(pl->store), iter, PL_COL_DATA, &pldata, -1);
2273 
2274 	if (!httpc_is_url(pldata->file)) {
2275 		volume_push(vol, pldata->file, *iter);
2276 	}
2277 
2278 	return 0;
2279 }
2280 
2281 void
plist_setup_vol_calc(playlist_t * pl,int type)2282 plist_setup_vol_calc(playlist_t * pl, int type) {
2283 
2284 	volume_t * vol;
2285 
2286 	if (!options.rva_is_enabled) {
2287 
2288 		int ret = message_dialog(_("Warning"),
2289 					 options.playlist_is_embedded ? main_window : playlist_window,
2290 					 GTK_MESSAGE_WARNING,
2291 					 GTK_BUTTONS_YES_NO,
2292 					 NULL,
2293 					 _("Playback RVA is currently disabled.\n"
2294 					   "Do you want to enable it now?"));
2295 
2296 		if (ret != GTK_RESPONSE_YES) {
2297 			return;
2298 		}
2299 
2300 		options.rva_is_enabled = 1;
2301 	}
2302 
2303 	if ((vol = volume_new(pl->store, type)) == NULL) {
2304 		return;
2305 	}
2306 
2307 	playlist_foreach_selected(pl, plist_setup_vol_foreach, vol);
2308 
2309 	volume_start(vol);
2310 }
2311 
2312 void
plist__rva_separate_cb(gpointer data)2313 plist__rva_separate_cb(gpointer data) {
2314 
2315 	playlist_t * pl;
2316 
2317 	if ((pl = playlist_get_current()) == NULL) {
2318 		return;
2319 	}
2320 
2321 	plist_setup_vol_calc(pl, VOLUME_SEPARATE);
2322 }
2323 
2324 void
plist__rva_average_cb(gpointer data)2325 plist__rva_average_cb(gpointer data) {
2326 
2327 	playlist_t * pl;
2328 
2329 	if ((pl = playlist_get_current()) == NULL) {
2330 		return;
2331 	}
2332 
2333 	plist_setup_vol_calc(pl, VOLUME_AVERAGE);
2334 }
2335 
2336 int
plist__reread_file_meta_foreach(playlist_t * pl,GtkTreeIter * iter,void * user_data)2337 plist__reread_file_meta_foreach(playlist_t * pl, GtkTreeIter * iter, void * user_data) {
2338 
2339 	GtkTreeIter parent;
2340 	int name_set = 0;
2341 
2342 	char voladj_str[32];
2343 	char duration_str[MAXLEN];
2344 	playlist_data_t * data;
2345 	playlist_data_t * tmp;
2346 
2347 	gtk_tree_model_get(GTK_TREE_MODEL(pl->store), iter, PL_COL_DATA, &data, -1);
2348 
2349 	if (!g_file_test(data->file, G_FILE_TEST_EXISTS)) {
2350 		return 0;
2351 	}
2352 
2353 	if ((tmp = playlist_filemeta_get(data->file)) == NULL) {
2354 		fprintf(stderr, "plist__reread_file_meta_foreach(): "
2355 			"playlist_filemeta_get() returned NULL\n");
2356 		return 0;
2357 	}
2358 
2359 	free_strdup(&data->artist, tmp->artist);
2360 	free_strdup(&data->album, tmp->album);
2361 	free_strdup(&data->title, tmp->title);
2362 	free_strdup(&data->display, tmp->display);
2363 
2364 	data->voladj = tmp->voladj;
2365 	data->duration = tmp->duration;
2366 	data->size = tmp->size;
2367 
2368 	playlist_data_free(tmp);
2369 
2370 	voladj2str(data->voladj, voladj_str);
2371 	time2time_na(data->duration, duration_str);
2372 
2373 	gtk_tree_store_set(pl->store, iter,
2374 			   PL_COL_VADJ, voladj_str,
2375 			   PL_COL_DURA, duration_str,
2376 			   -1);
2377 
2378 	if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(pl->store), &parent, iter)) {
2379 		playlist_data_t * pdata;
2380 		gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &parent, PL_COL_DATA, &pdata, -1);
2381 		if (pdata->artist && pdata->album && data->artist && data->album && data->title &&
2382 		    !strcmp(pdata->artist, data->artist) && !strcmp(pdata->album, data->album)) {
2383 			gtk_tree_store_set(pl->store, iter, PL_COL_NAME, data->title, -1);
2384 			name_set = 1;
2385 		}
2386 	}
2387 
2388 	if (!name_set) {
2389 		char list_str[MAXLEN];
2390 		playlist_data_get_display_name(list_str, data);
2391 		gtk_tree_store_set(pl->store, iter, PL_COL_NAME, list_str, -1);
2392 	}
2393 
2394 	return 0;
2395 }
2396 
2397 
2398 void
plist__reread_file_meta_cb(gpointer data)2399 plist__reread_file_meta_cb(gpointer data) {
2400 
2401 	GtkTreeIter iter;
2402 	GtkTreeIter iter_child;
2403 	gint i = 0;
2404 	gint j = 0;
2405 	gint reread = 0;
2406 	playlist_t * pl;
2407 
2408 	if ((pl = playlist_get_current()) == NULL) {
2409 		return;
2410 	}
2411 
2412 	playlist_foreach_selected(pl, plist__reread_file_meta_foreach, NULL);
2413 
2414 	i = 0;
2415 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
2416 
2417 		if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(pl->store), &iter)) {
2418 
2419 			reread = 0;
2420 
2421 			if (gtk_tree_selection_iter_is_selected(pl->select, &iter)) {
2422 				reread = 1;
2423 			} else {
2424 				j = 0;
2425 				while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store),
2426 								     &iter_child, &iter, j++)) {
2427 					if (gtk_tree_selection_iter_is_selected(pl->select,
2428 										&iter_child)) {
2429 						reread = 1;
2430 						break;
2431 					}
2432 				}
2433 			}
2434 
2435 			if (reread) {
2436 				recalc_album_node(pl, &iter);
2437 			}
2438 		}
2439 	}
2440 
2441 	playlist_content_changed(pl);
2442 }
2443 
2444 void
playlist_update_metadata(void)2445 playlist_update_metadata(void) {
2446 
2447 	GList * node = NULL;
2448 
2449 	for (node = playlists; node; node = node->next) {
2450 		playlist_t * pl = (playlist_t *)node->data;
2451 		GtkTreeIter iter;
2452 		gint i = 0;
2453 		while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
2454 			plist__reread_file_meta_foreach(pl, &iter, NULL);
2455 		}
2456 		playlist_content_changed(pl);
2457 	}
2458 }
2459 
2460 #ifdef HAVE_TRANSCODING
2461 int
plist__export_foreach(playlist_t * pl,GtkTreeIter * iter,void * data)2462 plist__export_foreach(playlist_t * pl, GtkTreeIter * iter, void * data) {
2463 
2464 	export_t * export = (export_t *)data;
2465 	playlist_data_t * pldata;
2466 	file_decoder_t * fdec;
2467 
2468 	char artist[MAXLEN];
2469 	char album[MAXLEN];
2470 	char title[MAXLEN];
2471 	int year = 0;
2472 	int no = 0;
2473 
2474 	artist[0] = '\0';
2475 	album[0] = '\0';
2476 	title[0] = '\0';
2477 
2478 	gtk_tree_model_get(GTK_TREE_MODEL(pl->store), iter, PL_COL_DATA, &pldata, -1);
2479 
2480 	if (!g_file_test(pldata->file, G_FILE_TEST_EXISTS)) {
2481 		return 0;
2482 	}
2483 
2484 	fdec = file_decoder_new();
2485 	if (fdec == NULL) {
2486 		fprintf(stderr, "plist__export_foreach: file_decoder_new() failed\n");
2487 		return 0;
2488 	}
2489 
2490 	if (file_decoder_open(fdec, pldata->file) == 0) {
2491 
2492 		char * tmp;
2493 
2494 		if (pldata->artist) {
2495 			strcpy(artist, pldata->artist);
2496 		} else if (metadata_get_artist(fdec->meta, &tmp)) {
2497 			strncpy(artist, tmp, MAXLEN-1);
2498 		}
2499 
2500 		if (pldata->album) {
2501 			strcpy(album, pldata->album);
2502 		} else if (metadata_get_album(fdec->meta, &tmp)) {
2503 			strncpy(album, tmp, MAXLEN-1);
2504 		}
2505 
2506 		if (pldata->title) {
2507 			strcpy(title, pldata->title);
2508 		} else if (metadata_get_title(fdec->meta, &tmp)) {
2509 			strncpy(title, tmp, MAXLEN-1);
2510 		}
2511 
2512 		if (metadata_get_date(fdec->meta, &tmp)) {
2513 			year = atoi(tmp);
2514 		}
2515 
2516 		if (!metadata_get_tracknum(fdec->meta, &no)) {
2517 			GtkTreePath * path = gtk_tree_model_get_path(GTK_TREE_MODEL(pl->store), iter);
2518 			int depth = gtk_tree_path_get_depth(path);
2519 			gint * indices = gtk_tree_path_get_indices(path);
2520 			no = indices[depth-1] + 1;
2521 			gtk_tree_path_free(path);
2522 		}
2523 
2524 		file_decoder_close(fdec);
2525 	}
2526 	file_decoder_delete(fdec);
2527 
2528 	export_append_item(export, pldata->file, artist, album, title, year, no);
2529 
2530 	return 0;
2531 }
2532 
2533 void
plist__export_cb(gpointer data)2534 plist__export_cb(gpointer data) {
2535 
2536 	export_t * export;
2537 	playlist_t * pl;
2538 
2539 	if ((pl = playlist_get_current()) == NULL) {
2540 		return;
2541 	}
2542 
2543 	if ((export = export_new()) == NULL) {
2544 		return;
2545 	}
2546 
2547 	playlist_foreach_selected(pl, plist__export_foreach, export);
2548 
2549 	export_start(export);
2550 }
2551 #endif /* HAVE_TRANSCODING */
2552 
2553 #ifdef HAVE_IFP
2554 void
plist__send_songs_to_iriver_cb(gpointer data)2555 plist__send_songs_to_iriver_cb(gpointer data) {
2556 
2557         aifp_transfer_files(UPLOAD_MODE);
2558 
2559 }
2560 #endif /* HAVE_IFP */
2561 
2562 void
plist__fileinfo_cb(gpointer user_data)2563 plist__fileinfo_cb(gpointer user_data) {
2564 
2565 	playlist_t * pl = playlist_get_current();
2566 	playlist_data_t * pldata;
2567 	GtkTreeIter iter;
2568 
2569 	if (!pl) return;
2570 	if (fileinfo_iter) {
2571 		gtk_tree_model_get(GTK_TREE_MODEL(pl->store), fileinfo_iter, PL_COL_DATA, &pldata, -1);
2572 		iter = *fileinfo_iter;
2573 		gtk_tree_iter_free(fileinfo_iter);
2574 		fileinfo_iter = NULL; // mark it as consumed
2575 	} else {
2576 		GtkTreePath * p = playlist_get_playing_path(pl);
2577 		if (p == NULL) return;
2578 		gtk_tree_model_get_iter(GTK_TREE_MODEL(pl->store), &iter, p);
2579 		gtk_tree_path_free(p);
2580 	}
2581 	show_file_info(GTK_TREE_MODEL(pl->store), iter, playlist_model_func, 0,
2582 		       FALSE, IS_PL_COVER(pldata));
2583 }
2584 
2585 void
plist__search_cb(gpointer data)2586 plist__search_cb(gpointer data) {
2587 
2588 	search_playlist_dialog();
2589 }
2590 
2591 void
plist__roll_cb(gpointer data)2592 plist__roll_cb(gpointer data) {
2593 
2594 	playlist_t * pl;
2595 
2596 	if ((pl = playlist_get_current()) == NULL) {
2597 		return;
2598 	}
2599 
2600 	show_active_position_in_playlist_toggle(pl);
2601 }
2602 
2603 
2604 static gboolean
add_cb(GtkWidget * widget,GdkEvent * event)2605 add_cb(GtkWidget * widget, GdkEvent * event) {
2606 
2607         if (event->type == GDK_BUTTON_PRESS) {
2608                 GdkEventButton * bevent = (GdkEventButton *) event;
2609 
2610                 if (bevent->button == 3) {
2611 
2612 			gtk_menu_popup(GTK_MENU(add_menu), NULL, NULL, NULL, NULL,
2613 				       bevent->button, bevent->time);
2614 
2615 			return TRUE;
2616 		}
2617 		return FALSE;
2618 	}
2619 	return FALSE;
2620 }
2621 
2622 
2623 static gboolean
sel_cb(GtkWidget * widget,GdkEvent * event)2624 sel_cb(GtkWidget * widget, GdkEvent * event) {
2625 
2626         if (event->type == GDK_BUTTON_PRESS) {
2627                 GdkEventButton * bevent = (GdkEventButton *) event;
2628 
2629                 if (bevent->button == 3) {
2630 			gtk_menu_popup(GTK_MENU(sel_menu), NULL, NULL, NULL, NULL,
2631 				       bevent->button, bevent->time);
2632 			return TRUE;
2633 		}
2634 		return FALSE;
2635 	}
2636 	return FALSE;
2637 }
2638 
2639 
2640 static gboolean
rem_cb(GtkWidget * widget,GdkEvent * event)2641 rem_cb(GtkWidget * widget, GdkEvent * event) {
2642 
2643         if (event->type == GDK_BUTTON_PRESS) {
2644                 GdkEventButton * bevent = (GdkEventButton *) event;
2645 
2646                 if (bevent->button == 3) {
2647 			gtk_menu_popup(GTK_MENU(rem_menu), NULL, NULL, NULL, NULL,
2648 				       bevent->button, bevent->time);
2649 			return TRUE;
2650 		}
2651 		return FALSE;
2652 	}
2653 	return FALSE;
2654 }
2655 
2656 
2657 playlist_data_t *
playlist_filemeta_get(char * filename)2658 playlist_filemeta_get(char * filename) {
2659 
2660 	playlist_data_t * data;
2661 	file_decoder_t * fdec;
2662 	struct stat statbuf;
2663 	char * tmp;
2664 
2665 	if ((data = playlist_data_new()) == NULL) {
2666 		return NULL;
2667 	}
2668 
2669 	fdec = file_decoder_new();
2670 	if (fdec == NULL) {
2671 		fprintf(stderr, "playlist_filemeta_get: file_decoder_new() failed\n");
2672 		playlist_data_free(data);
2673 		return NULL;
2674 	}
2675 	if (file_decoder_open(fdec, filename) != 0) {
2676 		file_decoder_delete(fdec);
2677 		playlist_data_free(data);
2678 		return NULL;
2679 	}
2680 
2681 	if (g_stat(data->file, &statbuf) != -1) {
2682 		data->size = statbuf.st_size;
2683 	}
2684 
2685 	data->duration = (float)fdec->fileinfo.total_samples / fdec->fileinfo.sample_rate;
2686 
2687 	if (options.rva_is_enabled) {
2688 		if (!metadata_get_rva(fdec->meta, &data->voladj)) {
2689 			data->voladj = options.rva_no_rva_voladj;
2690 		}
2691 	} else {
2692 		data->voladj = 0.0f;
2693 	}
2694 
2695 	if (metadata_get_artist(fdec->meta, &tmp) && !is_all_wspace(tmp)) {
2696 		free_strdup(&data->artist, tmp);
2697 	}
2698 	if (metadata_get_album(fdec->meta, &tmp) && !is_all_wspace(tmp)) {
2699 		free_strdup(&data->album, tmp);
2700 	}
2701 	if (metadata_get_title(fdec->meta, &tmp) && !is_all_wspace(tmp)) {
2702 		free_strdup(&data->title, tmp);
2703 	}
2704 
2705 	data->display = extended_title_format(fdec);
2706 
2707 	file_decoder_close(fdec);
2708 	file_decoder_delete(fdec);
2709 
2710 	data->file = strdup(filename);
2711 
2712 	return data;
2713 }
2714 
2715 
2716 void
add__files_cb(gpointer data)2717 add__files_cb(gpointer data) {
2718 
2719 	add_files(NULL, NULL);
2720 }
2721 
2722 
2723 void
add__dir_cb(gpointer data)2724 add__dir_cb(gpointer data) {
2725 
2726 	add_directory(NULL, NULL);
2727 }
2728 
2729 
2730 void
add__url_cb(gpointer data)2731 add__url_cb(gpointer data) {
2732 
2733 	add_url(NULL, NULL);
2734 }
2735 
2736 
2737 void
select_all(GtkWidget * widget,gpointer data)2738 select_all(GtkWidget * widget, gpointer data) {
2739 
2740 	playlist_t * pl = playlist_get_current();
2741 
2742 	if (pl != NULL) {
2743 		gtk_tree_selection_select_all(pl->select);
2744 	}
2745 }
2746 
2747 
2748 void
sel__all_cb(gpointer data)2749 sel__all_cb(gpointer data) {
2750 
2751 	select_all(NULL, data);
2752 }
2753 
2754 
2755 void
sel__none_cb(gpointer data)2756 sel__none_cb(gpointer data) {
2757 
2758 	playlist_t * pl;
2759 
2760 	if ((pl = playlist_get_current()) != NULL) {
2761 		gtk_tree_selection_unselect_all(pl->select);
2762 	}
2763 }
2764 
2765 
2766 void
sel__inv_foreach(GtkTreePath * path,gpointer data)2767 sel__inv_foreach(GtkTreePath * path, gpointer data) {
2768 
2769 	gtk_tree_selection_unselect_path((GtkTreeSelection *)data, path);
2770 	gtk_tree_path_free(path);
2771 }
2772 
2773 
2774 void
sel__inv_cb(gpointer data)2775 sel__inv_cb(gpointer data) {
2776 
2777 
2778 	GList * list = NULL;
2779 	playlist_t * pl = NULL;
2780 
2781 	if ((pl = playlist_get_current()) == NULL) {
2782 		return;
2783 	}
2784 
2785 	list = gtk_tree_selection_get_selected_rows(pl->select, NULL);
2786 
2787 	g_signal_handlers_block_by_func(G_OBJECT(pl->select), playlist_selection_changed_cb, pl);
2788 
2789 	gtk_tree_selection_select_all(pl->select);
2790 	g_list_foreach(list, (GFunc)sel__inv_foreach, pl->select);
2791 	g_list_free(list);
2792 
2793 	g_signal_handlers_unblock_by_func(G_OBJECT(pl->select), playlist_selection_changed_cb, pl);
2794 
2795 	playlist_selection_changed(pl);
2796 }
2797 
2798 
2799 void
rem_all(playlist_t * pl)2800 rem_all(playlist_t * pl) {
2801 
2802 	g_signal_handlers_block_by_func(G_OBJECT(pl->select), playlist_selection_changed_cb, pl);
2803 
2804 	playlist_clear(pl->store);
2805 
2806 	g_signal_handlers_unblock_by_func(G_OBJECT(pl->select), playlist_selection_changed_cb, pl);
2807 
2808 	if (pl == playlist_get_current()) {
2809 		playlist_selection_changed(pl);
2810 		playlist_content_changed(pl);
2811 	}
2812 }
2813 
2814 void
rem__all_cb(gpointer data)2815 rem__all_cb(gpointer data) {
2816 
2817 	playlist_t * pl;
2818 
2819 	if ((pl = playlist_get_current()) == NULL) {
2820 		return;
2821 	}
2822 
2823 	rem_all(pl);
2824 }
2825 
2826 void
rem__sel_cb(gpointer data)2827 rem__sel_cb(gpointer data) {
2828 
2829 	GtkTreeModel * model;
2830 	GtkTreeIter iter;
2831 	gint i = 0;
2832 	playlist_t * pl;
2833 	GtkTreePath * path = NULL;
2834 
2835 
2836 	if (data != NULL) {
2837 		pl = (playlist_t *)data;
2838 	} else if ((pl = playlist_get_current()) == NULL) {
2839 		return;
2840 	}
2841 
2842 	g_signal_handlers_block_by_func(G_OBJECT(pl->select), playlist_selection_changed_cb, pl);
2843 
2844 	model = GTK_TREE_MODEL(pl->store);
2845 
2846 	while (gtk_tree_model_iter_nth_child(model, &iter, NULL, i++)) {
2847 
2848 		if (gtk_tree_selection_iter_is_selected(pl->select, &iter)) {
2849 			if (path == NULL) {
2850 				path = gtk_tree_model_get_path(model, &iter);
2851 			}
2852 			playlist_remove_track(pl->store, &iter);
2853 			i--;
2854 			continue;
2855 		}
2856 
2857 		if (gtk_tree_model_iter_n_children(model, &iter) == 0) {
2858 			continue;
2859 		}
2860 
2861 		if (all_tracks_selected(pl, &iter)) {
2862 			if (path == NULL) {
2863 				path = gtk_tree_model_get_path(model, &iter);
2864 			}
2865 			playlist_remove_track(pl->store, &iter);
2866 			i--;
2867 		} else {
2868 			int j = 0;
2869 			int recalc = 0;
2870 			GtkTreeIter iter_child;
2871 
2872 			while (gtk_tree_model_iter_nth_child(model, &iter_child, &iter, j++)) {
2873 				if (gtk_tree_selection_iter_is_selected(pl->select, &iter_child)) {
2874 					if (path == NULL) {
2875 						path = gtk_tree_model_get_path(model, &iter_child);
2876 					}
2877 					playlist_remove_track(pl->store, &iter_child);
2878 					recalc = 1;
2879 					j--;
2880 				}
2881 			}
2882 
2883 			if (recalc) {
2884 				recalc_album_node(pl, &iter);
2885 			}
2886 		}
2887 	}
2888 
2889 	if (path != NULL) {
2890 		if (gtk_tree_model_get_iter(model, &iter, path)) {
2891 			set_cursor_in_playlist(pl, &iter, TRUE);
2892 		} else {
2893 			if (gtk_tree_path_get_depth(path) == 1) {
2894 				int n = gtk_tree_model_iter_n_children(model, NULL);
2895 				if (n > 0) {
2896 					gtk_tree_model_iter_nth_child(model, &iter, NULL, n-1);
2897 					set_cursor_in_playlist(pl, &iter, TRUE);
2898 				}
2899 			} else {
2900 				GtkTreeIter iter_child;
2901 
2902 				gtk_tree_path_up(path);
2903 				gtk_tree_model_get_iter(model, &iter, path);
2904 
2905 				gtk_tree_model_iter_nth_child(model, &iter_child, &iter,
2906 						gtk_tree_model_iter_n_children(model, &iter)-1);
2907 				set_cursor_in_playlist(pl, &iter_child, TRUE);
2908 			}
2909 		}
2910 
2911 		gtk_tree_path_free(path);
2912 	}
2913 
2914 	g_signal_handlers_unblock_by_func(G_OBJECT(pl->select), playlist_selection_changed_cb, pl);
2915 
2916 
2917 	playlist_content_changed(pl);
2918 	playlist_selection_changed(pl);
2919 }
2920 
2921 
2922 int
rem__dead_skip(char * filename)2923 rem__dead_skip(char * filename) {
2924 
2925 	return (filename != NULL &&
2926 		!g_str_has_prefix(filename, "CDDA ") &&
2927 		!httpc_is_url(filename) &&
2928 		(g_file_test(filename, G_FILE_TEST_IS_REGULAR) == FALSE));
2929 }
2930 
2931 void
rem__dead_cb(gpointer user_data)2932 rem__dead_cb(gpointer user_data) {
2933 
2934 	GtkTreeIter iter;
2935 	gint i = 0;
2936 	playlist_t * pl;
2937 	playlist_data_t * data;
2938 
2939 	if ((pl = playlist_get_current()) == NULL) {
2940 		return;
2941 	}
2942 
2943 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
2944 
2945 		GtkTreeIter iter_child;
2946 		gint j = 0;
2947 
2948 
2949                 gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter, PL_COL_DATA, &data, -1);
2950 
2951                 gint n = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pl->store), &iter);
2952 
2953                 if (n == 0 && rem__dead_skip(data->file)) {
2954                         playlist_remove_track(pl->store, &iter);
2955                         --i;
2956                         continue;
2957                 }
2958 
2959                 if (n) {
2960                         while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter_child, &iter, j++)) {
2961                                 gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter_child, PL_COL_DATA, &data, -1);
2962 
2963                                 if (rem__dead_skip(data->file)) {
2964                                         playlist_remove_track(pl->store, &iter_child);
2965                                         --j;
2966                                 }
2967                         }
2968 
2969                         /* remove album node if empty */
2970                         if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pl->store), &iter) == 0) {
2971 				playlist_remove_track(pl->store, &iter);
2972                                 --i;
2973                         } else {
2974                                 recalc_album_node(pl, &iter);
2975                         }
2976                 }
2977 
2978         }
2979 
2980 	playlist_content_changed(pl);
2981 }
2982 
2983 void
remove_sel(GtkWidget * widget,gpointer data)2984 remove_sel(GtkWidget * widget, gpointer data) {
2985 
2986 	rem__sel_cb(data);
2987 }
2988 
2989 
2990 /* playlist item is selected -> keep, else -> remove
2991  * ret: 0 if kept, 1 if removed track
2992  */
2993 int
cut_track_item(playlist_t * pl,GtkTreeIter * piter)2994 cut_track_item(playlist_t * pl, GtkTreeIter * piter) {
2995 
2996 	if (!gtk_tree_selection_iter_is_selected(pl->select, piter)) {
2997 		playlist_remove_track(pl->store, piter);
2998 		return 1;
2999 	}
3000 	return 0;
3001 }
3002 
3003 
3004 /* ret: 1 if at least one of album node's children are selected; else 0 */
3005 int
any_tracks_selected(playlist_t * pl,GtkTreeIter * piter)3006 any_tracks_selected(playlist_t * pl, GtkTreeIter * piter) {
3007 
3008 	gint j = 0;
3009 	GtkTreeIter iter_child;
3010 
3011 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter_child, piter, j++)) {
3012 		if (gtk_tree_selection_iter_is_selected(pl->select, &iter_child)) {
3013 			return 1;
3014 		}
3015 	}
3016 	return 0;
3017 }
3018 
3019 
3020 /* cut selected callback */
3021 void
cut__sel_cb(gpointer data)3022 cut__sel_cb(gpointer data) {
3023 
3024 	GtkTreeIter iter;
3025 	gint i = 0;
3026 	playlist_t * pl;
3027 
3028 	if ((pl = playlist_get_current()) == NULL) {
3029 		return;
3030 	}
3031 
3032 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
3033 
3034 		gint n = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pl->store), &iter);
3035 
3036 		if (n) { /* album node */
3037 			if (any_tracks_selected(pl, &iter)) {
3038 				gint j = 0;
3039 				GtkTreeIter iter_child;
3040 				while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store),
3041 								     &iter_child, &iter, j++)) {
3042 					j -= cut_track_item(pl, &iter_child);
3043 				}
3044 
3045 				recalc_album_node(pl, &iter);
3046 			} else {
3047 				i -= cut_track_item(pl, &iter);
3048 			}
3049 		} else { /* track node */
3050 			i -= cut_track_item(pl, &iter);
3051 		}
3052 	}
3053 
3054 	gtk_tree_selection_unselect_all(pl->select);
3055 	playlist_content_changed(pl);
3056 }
3057 
3058 
3059 void
playlist_reorder_columns_foreach(gpointer data,gpointer user_data)3060 playlist_reorder_columns_foreach(gpointer data, gpointer user_data) {
3061 
3062 	playlist_t * pl = (playlist_t *)data;
3063 	int * order = (int *)user_data;
3064 	GtkTreeViewColumn * cols[3];
3065 	int i;
3066 
3067 	cols[0] = pl->track_column;
3068 	cols[1] = pl->rva_column;
3069 	cols[2] = pl->length_column;
3070 
3071         for (i = 2; i >= 0; i--) {
3072 		gtk_tree_view_move_column_after(GTK_TREE_VIEW(pl->view),
3073 						cols[order[i]], NULL);
3074 	}
3075 
3076 	gtk_tree_view_column_set_visible(GTK_TREE_VIEW_COLUMN(pl->rva_column),
3077 					 options.show_rva_in_playlist);
3078 	gtk_tree_view_column_set_visible(GTK_TREE_VIEW_COLUMN(pl->length_column),
3079 					 options.show_length_in_playlist);
3080 	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(pl->view), options.enable_pl_rules_hint);
3081 }
3082 
3083 void
playlist_reorder_columns_all(int * order)3084 playlist_reorder_columns_all(int * order) {
3085 
3086 	g_list_foreach(playlists, playlist_reorder_columns_foreach, order);
3087 
3088 	if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook)) == 1) {
3089 		gtk_notebook_set_show_tabs(GTK_NOTEBOOK(playlist_notebook),
3090 					   options.playlist_always_show_tabs);
3091 	}
3092 }
3093 
3094 void
playlist_stats_set_busy()3095 playlist_stats_set_busy() {
3096 
3097 	gtk_label_set_text(GTK_LABEL(statusbar_total), _("counting..."));
3098 }
3099 
3100 void
playlist_child_stats(playlist_t * pl,GtkTreeIter * iter,int * ntrack,float * length,double * size,int selected)3101 playlist_child_stats(playlist_t * pl, GtkTreeIter * iter,
3102 		     int * ntrack, float * length, double * size, int selected) {
3103 
3104 	int j = 0;
3105 	GtkTreeIter iter_child;
3106 	playlist_data_t * data;
3107 
3108 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter_child, iter, j++)) {
3109 
3110 		if (!selected || gtk_tree_selection_iter_is_selected(pl->select, &iter_child)) {
3111 
3112 			gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter_child, PL_COL_DATA, &data, -1);
3113 
3114 			if (data->size == 0) {
3115 				struct stat statbuf;
3116 				if (g_stat(data->file, &statbuf) != -1) {
3117 					data->size = statbuf.st_size;
3118 				}
3119 			}
3120 
3121 			*size += data->size / 1024.0;
3122 			*length += data->duration;
3123 			(*ntrack)++;
3124 		}
3125 	}
3126 }
3127 
3128 
3129 /* if selected == true -> stats for selected tracks; else: all tracks */
3130 void
playlist_stats(playlist_t * pl,int selected)3131 playlist_stats(playlist_t * pl, int selected) {
3132 
3133 	GtkTreeIter iter;
3134 	int i = 0;
3135 
3136 	int ntrack = 0;
3137 	float length = 0;
3138         double size = 0;
3139 
3140 	char str[MAXLEN];
3141 	char length_str[MAXLEN];
3142 	char tmp[MAXLEN];
3143 
3144 	playlist_data_t * data;
3145 
3146 
3147 	if (!options.enable_playlist_statusbar) {
3148 		return;
3149 	}
3150 
3151 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
3152 
3153 		gint n = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pl->store), &iter);
3154 		if (n > 0) {
3155 			if (gtk_tree_selection_iter_is_selected(pl->select, &iter)) {
3156 				playlist_child_stats(pl, &iter, &ntrack, &length, &size, 0/*false*/);
3157 			} else {
3158 				playlist_child_stats(pl, &iter, &ntrack, &length, &size, selected);
3159 			}
3160 		} else {
3161 			if (!selected || gtk_tree_selection_iter_is_selected(pl->select, &iter)) {
3162 
3163 				gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter, PL_COL_DATA, &data, -1);
3164 
3165 				if (data->size == 0) {
3166 					struct stat statbuf;
3167 					if (g_stat(data->file, &statbuf) != -1) {
3168 						data->size = statbuf.st_size;
3169 					}
3170 				}
3171 
3172 				size += data->size / 1024.0;
3173 				length += data->duration;
3174 				ntrack++;
3175 			}
3176                 }
3177 	}
3178 
3179 	sprintf(str, " %d %s ", ntrack, (ntrack == 1) ? _("track") : _("tracks"));
3180 
3181 	if (length > 0.0f || ntrack == 0) {
3182 		time2time(length, length_str);
3183 		sprintf(tmp, " [%s] ", length_str);
3184 		strcat(str, tmp);
3185 	}
3186 
3187 	if (options.pl_statusbar_show_size) {
3188 		if (size > 1024 * 1024) {
3189 			sprintf(tmp, " (%.1f GB) ", size / (1024 * 1024));
3190 	        	strcat(str, tmp);
3191 		} else if (size > (1 << 10)){
3192 			sprintf(tmp, " (%.1f MB) ", size / 1024);
3193         		strcat(str, tmp);
3194 		} else if (size > 0 || ntrack == 0) {
3195 			sprintf(tmp, " (%.1f KB) ", size);
3196 		        strcat(str, tmp);
3197 		}
3198 	}
3199 
3200 	if (selected) {
3201 		gtk_label_set_text(GTK_LABEL(statusbar_selected), str);
3202 	} else {
3203 		gtk_label_set_text(GTK_LABEL(statusbar_total), str);
3204 	}
3205 }
3206 
3207 
3208 void
recalc_album_node(playlist_t * pl,GtkTreeIter * iter)3209 recalc_album_node(playlist_t * pl, GtkTreeIter * iter) {
3210 
3211 	gint ntrack = 0;
3212 	gfloat length = 0;
3213 	double size = 0.0;
3214 	gchar time[MAXLEN];
3215 	playlist_data_t * data;
3216 
3217 	gtk_tree_model_get(GTK_TREE_MODEL(pl->store), iter, PL_COL_DATA, &data, -1);
3218 
3219 	playlist_child_stats(pl, iter, &ntrack, &length, &size, 0/*false*/);
3220 
3221 	time2time(length, time);
3222 	gtk_tree_store_set(pl->store, iter, PL_COL_DURA, time, -1);
3223 	data->duration = length;
3224 
3225 	if (IS_PL_ACTIVE(data)) {
3226 		unmark_track(pl, iter);
3227 		mark_track(pl, iter);
3228 	}
3229 }
3230 
3231 
3232 void
playlist_selection_changed(playlist_t * pl)3233 playlist_selection_changed(playlist_t * pl) {
3234 
3235 	playlist_stats(pl, 1/* true */);
3236 }
3237 
3238 
3239 void
playlist_content_changed(playlist_t * pl)3240 playlist_content_changed(playlist_t * pl) {
3241 
3242 	if (pl->progbar_semaphore == 0 && pl->ms_semaphore == 0) {
3243 		playlist_stats(pl, 0/*false*/);
3244 		playlist_dirty = 1;
3245 	} else {
3246 		playlist_stats_set_busy();
3247 	}
3248 }
3249 
3250 void
playlist_selection_changed_cb(GtkTreeSelection * select,gpointer data)3251 playlist_selection_changed_cb(GtkTreeSelection * select, gpointer data) {
3252 
3253 	playlist_t * pl = (playlist_t *)data;
3254 
3255 	if (pl == playlist_get_current()) {
3256 		playlist_selection_changed(pl);
3257 	}
3258 }
3259 
3260 void
playlist_drag_data_get(GtkWidget * widget,GdkDragContext * drag_context,GtkSelectionData * data,guint info,guint time,gpointer user_data)3261 playlist_drag_data_get(GtkWidget * widget, GdkDragContext * drag_context,
3262 		      GtkSelectionData * data, guint info, guint time, gpointer user_data) {
3263 
3264 	char tmp[32];
3265 	sprintf(tmp, "%p", user_data);
3266 	gtk_selection_data_set(data, gtk_selection_data_get_target(data), 8,
3267 			       (guchar *)tmp, strlen(tmp));
3268 }
3269 
3270 void
playlist_perform_drag(playlist_t * spl,GtkTreeIter * siter,GtkTreePath * spath,playlist_t * tpl,GtkTreeIter * titer,GtkTreePath * tpath)3271 playlist_perform_drag(playlist_t * spl, GtkTreeIter * siter, GtkTreePath * spath,
3272 		      playlist_t * tpl, GtkTreeIter * titer, GtkTreePath * tpath) {
3273 
3274 	GtkTreeIter sparent;
3275 	GtkTreeIter tparent;
3276 	int recalc_sparent = 0;
3277 	int recalc_tparent = 0;
3278 	gint cmp = 2/*append*/;
3279 
3280 
3281 	if (tpath != NULL) {
3282 		cmp = gtk_tree_path_compare(spath, tpath);
3283 	}
3284 
3285 	if (spl == tpl && cmp == 0) {
3286 		return;
3287 	}
3288 
3289 	if (titer && tpath &&
3290 	    gtk_tree_model_iter_has_child(GTK_TREE_MODEL(spl->store), siter) &&
3291 	    !gtk_tree_model_iter_has_child(GTK_TREE_MODEL(tpl->store), titer) &&
3292 	    gtk_tree_path_get_depth(tpath) == 2) {
3293 		return;
3294 	}
3295 
3296 	if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(spl->store), &sparent, siter)) {
3297 		recalc_sparent = 1;
3298 	}
3299 
3300 	if (titer && gtk_tree_model_iter_parent(GTK_TREE_MODEL(tpl->store), &tparent, titer)) {
3301 		recalc_tparent = 1;
3302 	}
3303 
3304 	playlist_node_deep_copy(spl->store, siter, tpl->store, titer, titer ? (cmp == 1) : 2);
3305 
3306 	gtk_tree_store_remove(spl->store, siter);
3307 
3308         if (tpath) {
3309 		gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW (tpl->view), tpath, NULL, FALSE, 0, 0);
3310         }
3311 
3312 	if (recalc_sparent) {
3313 		if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(spl->store), &sparent)) {
3314 			recalc_album_node(spl, &sparent);
3315 			unmark_track(spl, &sparent);
3316 			mark_track(spl, &sparent);
3317 		} else {
3318 			gtk_tree_store_remove(spl->store, &sparent);
3319 		}
3320 	}
3321 
3322 	if (recalc_tparent) {
3323 		recalc_album_node(tpl, &tparent);
3324 		unmark_track(tpl, &tparent);
3325 		mark_track(tpl, &tparent);
3326 	}
3327 
3328 	playlist_content_changed(tpl);
3329 }
3330 
3331 
3332 void
playlist_remove_scroll_tags()3333 playlist_remove_scroll_tags() {
3334 
3335 	if (playlist_scroll_up_tag > 0) {
3336 		g_source_remove(playlist_scroll_up_tag);
3337 		playlist_scroll_up_tag = -1;
3338 	}
3339 	if (playlist_scroll_dn_tag > 0) {
3340 		g_source_remove(playlist_scroll_dn_tag);
3341 		playlist_scroll_dn_tag = -1;
3342 	}
3343 }
3344 
3345 gint
playlist_drag_data_received(GtkWidget * widget,GdkDragContext * drag_context,gint x,gint y,GtkSelectionData * data,guint info,guint time,gpointer user_data)3346 playlist_drag_data_received(GtkWidget * widget, GdkDragContext * drag_context, gint x, gint y,
3347 			    GtkSelectionData * data, guint info, guint time, gpointer user_data) {
3348 
3349 	playlist_t * tpl = (playlist_t *)user_data;
3350 
3351 	if (info == 0) { /* drag and drop inside or between playlists */
3352 
3353 		GtkTreeIter siter;
3354 		GtkTreeIter titer;
3355 		GtkTreePath * spath = NULL;
3356 		GtkTreePath * tpath = NULL;
3357 		playlist_t * spl = NULL;
3358 
3359 
3360 		sscanf((char *)gtk_selection_data_get_data(data), "%p", &spl);
3361 		gtk_tree_selection_set_mode(spl->select, GTK_SELECTION_SINGLE);
3362 
3363 		if (gtk_tree_selection_get_selected(spl->select, NULL, &siter)) {
3364 
3365 			spath = gtk_tree_model_get_path(GTK_TREE_MODEL(spl->store), &siter);
3366 
3367 			if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tpl->view),
3368 							  x, y, &tpath, NULL, NULL, NULL)) {
3369 
3370 				gtk_tree_model_get_iter(GTK_TREE_MODEL(tpl->store), &titer, tpath);
3371 				playlist_perform_drag(spl, &siter, spath, tpl, &titer, tpath);
3372 			} else {
3373 				playlist_perform_drag(spl, &siter, spath, tpl, NULL, NULL);
3374 			}
3375 		}
3376 
3377 		if (spath) {
3378 			gtk_tree_path_free(spath);
3379 		}
3380 
3381 		if (tpath) {
3382 			gtk_tree_path_free(tpath);
3383 		}
3384 
3385 		gtk_tree_selection_set_mode(spl->select, GTK_SELECTION_MULTIPLE);
3386 
3387 	} else if (info == 1) { /* drag and drop from music store to playlist */
3388 
3389 		GtkTreePath * path = NULL;
3390 		GtkTreeIter * piter = NULL;
3391 		GtkTreeIter ms_iter;
3392 		GtkTreeIter pl_iter;
3393 		GtkTreeIter parent;
3394 		GtkTreeModel * model;
3395 
3396 		if (!gtk_tree_selection_get_selected(music_select, &model, &ms_iter)) {
3397 			return FALSE;
3398 		}
3399 
3400 		if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tpl->view),
3401 						  x, y, &path, NULL, NULL, NULL)) {
3402 
3403 			if (!music_store_iter_is_track(&ms_iter)) {
3404 				while (gtk_tree_path_get_depth(path) > 1) {
3405 					gtk_tree_path_up(path);
3406 				}
3407 			}
3408 
3409 			gtk_tree_model_get_iter(GTK_TREE_MODEL(tpl->store), &pl_iter, path);
3410 			piter = &pl_iter;
3411 		}
3412 
3413 		music_store_iter_addlist_defmode(&ms_iter, piter, 0/*new_tab*/);
3414 
3415 		if (piter && gtk_tree_model_iter_parent(GTK_TREE_MODEL(tpl->store), &parent, piter) &&
3416 		    music_store_iter_is_track(&ms_iter)) {
3417 			recalc_album_node(tpl, &parent);
3418 			unmark_track(tpl, &parent);
3419 			mark_track(tpl, &parent);
3420 		}
3421 
3422 		if (path) {
3423 			gtk_tree_path_free(path);
3424 		}
3425 
3426 	} else if (info == 2) { /* drag and drop from external app */
3427 
3428 		GSList * list = NULL;
3429 		gchar ** uri_list;
3430 		gchar * str = NULL;
3431 		int i;
3432 		char file[MAXLEN];
3433 
3434 		uri_list = g_strsplit_set((gchar *)gtk_selection_data_get_data(data),
3435 					  "\r\n", 0);
3436 
3437 		for (i = 0; uri_list[i]; i++) {
3438 
3439 			if (*(uri_list[i]) == '\0') {
3440 				continue;
3441 			}
3442 
3443 			if ((str = g_filename_from_uri(uri_list[i], NULL, NULL)) != NULL) {
3444 				strncpy(file, str, MAXLEN-1);
3445 				g_free(str);
3446 			} else {
3447 				int off = 0;
3448 
3449 				if (strstr(uri_list[i], "file:") == uri_list[i] ||
3450 				    strstr(uri_list[i], "FILE:") == uri_list[i]) {
3451 					off = 5;
3452 				}
3453 
3454 				while (uri_list[i][off] == '/' && uri_list[i][off+1] == '/') {
3455 					off++;
3456 				}
3457 
3458 				strncpy(file, uri_list[i] + off, MAXLEN-1);
3459 			}
3460 
3461 			if ((str = g_filename_from_utf8(file, -1, NULL, NULL, NULL)) != NULL) {
3462 				strncpy(file, str, MAXLEN-1);
3463 				g_free(str);
3464 			}
3465 
3466 			if (g_file_test(file, G_FILE_TEST_EXISTS) || httpc_is_url(file)) {
3467 				list = g_slist_append(list, strdup(file));
3468 			}
3469 		}
3470 
3471 		if (list != NULL) {
3472 			playlist_load(list, PLAYLIST_ENQUEUE, NULL, 0);
3473 		}
3474 
3475 		g_strfreev(uri_list);
3476 
3477 		playlist_remove_scroll_tags();
3478 	}
3479 
3480 	return FALSE;
3481 }
3482 
3483 gint
playlist_scroll_up(gpointer data)3484 playlist_scroll_up(gpointer data) {
3485 
3486 	playlist_t * pl = (playlist_t *)data;
3487 
3488 	gboolean dummy;
3489 	GtkRange * range = GTK_RANGE(gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(pl->scroll)));
3490 
3491 	g_signal_emit_by_name(G_OBJECT(range), "move-slider", GTK_SCROLL_STEP_UP, &dummy);
3492 
3493 	return TRUE;
3494 }
3495 
3496 gint
playlist_scroll_dn(gpointer data)3497 playlist_scroll_dn(gpointer data) {
3498 
3499 	playlist_t * pl = (playlist_t *)data;
3500 
3501 	gboolean dummy;
3502 	GtkRange * range = GTK_RANGE(gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(pl->scroll)));
3503 
3504 	g_signal_emit_by_name(G_OBJECT(range), "move-slider", GTK_SCROLL_STEP_DOWN, &dummy);
3505 
3506 	return TRUE;
3507 }
3508 
3509 
3510 void
playlist_drag_leave(GtkWidget * widget,GdkDragContext * drag_context,guint time,gpointer data)3511 playlist_drag_leave(GtkWidget * widget, GdkDragContext * drag_context,
3512 		    guint time, gpointer data) {
3513 
3514 	playlist_remove_scroll_tags();
3515 }
3516 
3517 
3518 gboolean
playlist_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer data)3519 playlist_drag_motion(GtkWidget * widget, GdkDragContext * context,
3520 		     gint x, gint y, guint time, gpointer data) {
3521 
3522 	if (y < 30) {
3523 		if (playlist_scroll_up_tag == -1) {
3524 			playlist_scroll_up_tag = aqualung_timeout_add(100, playlist_scroll_up, data);
3525 		}
3526 	} else {
3527 		if (playlist_scroll_up_tag > 0) {
3528 			g_source_remove(playlist_scroll_up_tag);
3529 			playlist_scroll_up_tag = -1;
3530 		}
3531 	}
3532 
3533 	if (y > widget->allocation.height - 30) {
3534 		if (playlist_scroll_dn_tag == -1) {
3535 			playlist_scroll_dn_tag = aqualung_timeout_add(100, playlist_scroll_dn, data);
3536 		}
3537 	} else {
3538 		if (playlist_scroll_dn_tag > 0) {
3539 			g_source_remove(playlist_scroll_dn_tag);
3540 			playlist_scroll_dn_tag = -1;
3541 		}
3542 	}
3543 
3544 	return TRUE;
3545 }
3546 
3547 void
playlist_drag_end(GtkWidget * widget,GdkDragContext * drag_context,gpointer data)3548 playlist_drag_end(GtkWidget * widget, GdkDragContext * drag_context, gpointer data) {
3549 
3550 	playlist_remove_scroll_tags();
3551 }
3552 
3553 
3554 void
playlist_rename(playlist_t * pl,char * name)3555 playlist_rename(playlist_t * pl, char * name) {
3556 
3557 	if (name != NULL) {
3558 		strncpy(pl->name, name, MAXLEN-1);
3559 		pl->name_set = 1;
3560 		playlist_set_markup(pl);
3561 	}
3562 }
3563 
3564 void
playlist_notebook_prev_page(void)3565 playlist_notebook_prev_page(void) {
3566 
3567 	if (gtk_notebook_get_current_page(GTK_NOTEBOOK(playlist_notebook)) == 0) {
3568 		gtk_notebook_set_current_page(GTK_NOTEBOOK(playlist_notebook), -1);
3569 	} else {
3570 		gtk_notebook_prev_page(GTK_NOTEBOOK(playlist_notebook));
3571 	}
3572 }
3573 
3574 void
playlist_notebook_next_page(void)3575 playlist_notebook_next_page(void) {
3576 
3577 	if (gtk_notebook_get_current_page(GTK_NOTEBOOK(playlist_notebook)) ==
3578 	    gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook)) - 1) {
3579 		gtk_notebook_set_current_page(GTK_NOTEBOOK(playlist_notebook), 0);
3580 	} else {
3581 		gtk_notebook_next_page(GTK_NOTEBOOK(playlist_notebook));
3582 	}
3583 }
3584 
3585 
3586 void
playlist_tab_close(GtkButton * button,gpointer data)3587 playlist_tab_close(GtkButton * button, gpointer data) {
3588 
3589 	playlist_t * pl = (playlist_t *)data;
3590 	int n = gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook));
3591 	int i;
3592 
3593 	if (pl->progbar_semaphore > 0 || pl->ms_semaphore > 0) {
3594 		return;
3595 	}
3596 
3597 	for (i = 0; i < n; i++) {
3598 		if (pl->widget == gtk_notebook_get_nth_page(GTK_NOTEBOOK(playlist_notebook), i)) {
3599 			gtk_notebook_remove_page(GTK_NOTEBOOK(playlist_notebook), i);
3600 
3601 			if (playlist_is_empty(pl)) {
3602 				playlist_free(pl);
3603 			} else {
3604 				pl->closed = 1;
3605 				pl->index = i;
3606 				playlists_closed = g_list_prepend(playlists_closed, pl);
3607 				playlists = g_list_remove(playlists, pl);
3608 			}
3609 
3610 			if (!options.playlist_always_show_tabs &&
3611 			    gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook)) == 1) {
3612 				gtk_notebook_set_show_tabs(GTK_NOTEBOOK(playlist_notebook), FALSE);
3613 			}
3614 
3615 			break;
3616 		}
3617 	}
3618 
3619 	if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook)) == 0) {
3620 		playlist_tab_new(NULL);
3621 	}
3622 }
3623 
3624 void
playlist_tab_close_undo(void)3625 playlist_tab_close_undo(void) {
3626 
3627 	GList * first = g_list_first(playlists_closed);
3628 
3629 	if (first != NULL) {
3630 		playlist_t * pl = (playlist_t *)first->data;
3631 
3632 		if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook)) == 1) {
3633 			playlist_t * pl = (playlist_t *)playlists->data;
3634 			if (playlist_is_empty(pl)) {
3635 				gtk_notebook_remove_page(GTK_NOTEBOOK(playlist_notebook), 0);
3636 				playlist_free(pl);
3637 			}
3638 		}
3639 
3640 		if (playlist_get_playing() != NULL) {
3641 			pl->playing = 0;
3642 		}
3643 
3644 		pl->closed = 0;
3645 		playlists_closed = g_list_delete_link(playlists_closed, first);
3646 		playlists = g_list_insert(playlists, pl, pl->index);
3647 		create_playlist_gui(pl);
3648 	}
3649 }
3650 
3651 gint
playlist_notebook_clicked(GtkWidget * widget,GdkEventButton * event,gpointer data)3652 playlist_notebook_clicked(GtkWidget * widget, GdkEventButton * event, gpointer data) {
3653 
3654 	if (event->type == GDK_2BUTTON_PRESS && event->button == 1 &&
3655 	    event->y < 25 && event->x > 25 && event->x < widget->allocation.width - 25) {
3656 		playlist_tab_new(NULL);
3657 		return TRUE;
3658 	}
3659 
3660 	return FALSE;
3661 }
3662 
3663 gint
playlist_tab_label_clicked(GtkWidget * widget,GdkEventButton * event,gpointer data)3664 playlist_tab_label_clicked(GtkWidget * widget, GdkEventButton * event, gpointer data) {
3665 
3666 	playlist_t * pl = (playlist_t *)data;
3667 
3668 	if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
3669 
3670 		GtkTreePath * path = playlist_get_playing_path(pl);
3671 
3672 		if (path != NULL) {
3673 			playlist_start_playback_at_path(pl, path);
3674 			gtk_tree_path_free(path);
3675 		} else {
3676 			GtkTreeIter iter;
3677 			if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(pl->store), &iter)) {
3678 				path = gtk_tree_model_get_path(GTK_TREE_MODEL(pl->store), &iter);
3679 				playlist_start_playback_at_path(pl, path);
3680 				gtk_tree_path_free(path);
3681 			}
3682 		}
3683 
3684 		return TRUE;
3685 	}
3686 
3687 	if (event->button == 2) {
3688 		playlist_tab_close(NULL, pl);
3689 		return TRUE;
3690 	}
3691 
3692 	if (event->button == 3) {
3693 		if (g_list_length(playlists_closed) > 0) {
3694 			gtk_widget_set_sensitive(pl->tab__close_undo, TRUE);
3695 		} else {
3696 			gtk_widget_set_sensitive(pl->tab__close_undo, FALSE);
3697 		}
3698 		gtk_menu_popup(GTK_MENU(pl->tab_menu), NULL, NULL, NULL, NULL,
3699 			       event->button, event->time);
3700 		return TRUE;
3701 	}
3702 
3703 	return FALSE;
3704 }
3705 
3706 gint
tab__rename_key_press_cb(GtkWidget * widget,GdkEventKey * event,gpointer data)3707 tab__rename_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer data) {
3708 
3709         if (event->keyval == GDK_Return) {
3710                 gtk_dialog_response (GTK_DIALOG(widget), GTK_RESPONSE_ACCEPT);
3711                 return TRUE;
3712         }
3713 
3714         return FALSE;
3715 }
3716 
3717 void
tab__rename_cb(gpointer data)3718 tab__rename_cb(gpointer data) {
3719 
3720 	GtkWidget * dialog;
3721 	GtkWidget * table;
3722 	GtkWidget * hbox;
3723 	GtkWidget * hbox2;
3724 	GtkWidget * entry;
3725 	GtkWidget * label;
3726 
3727 	char name[MAXLEN];
3728 	playlist_t * pl = (playlist_t *)data;
3729 
3730 
3731         dialog = gtk_dialog_new_with_buttons(_("Rename playlist"),
3732                                              options.playlist_is_embedded ? GTK_WINDOW(main_window) : GTK_WINDOW(playlist_window),
3733 					     GTK_DIALOG_DESTROY_WITH_PARENT,
3734 					     GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
3735 					     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
3736 					     NULL);
3737 
3738         gtk_widget_set_size_request(GTK_WIDGET(dialog), 400, -1);
3739 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
3740         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_REJECT);
3741         g_signal_connect (G_OBJECT (dialog), "key_press_event",
3742                         G_CALLBACK (tab__rename_key_press_cb), NULL);
3743 
3744 	table = gtk_table_new(2, 2, FALSE);
3745 	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
3746 			   table, FALSE, TRUE, 2);
3747 
3748 	label = gtk_label_new(_("Name:"));
3749         hbox = gtk_hbox_new(FALSE, 0);
3750         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
3751 	gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 5, 5);
3752 
3753 	hbox2 = gtk_hbox_new(FALSE, 0);
3754 	gtk_table_attach(GTK_TABLE(table), hbox2, 1, 2, 0, 1,
3755 			 GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 5);
3756 
3757         entry = gtk_entry_new();
3758         gtk_entry_set_max_length(GTK_ENTRY(entry), MAXLEN - 1);
3759 	gtk_entry_set_text(GTK_ENTRY(entry), pl->name);
3760         gtk_box_pack_start(GTK_BOX(hbox2), entry, TRUE, TRUE, 0);
3761 
3762 	gtk_widget_grab_focus(entry);
3763 
3764 	gtk_widget_show_all(dialog);
3765 
3766  display:
3767 	name[0] = '\0';
3768         if (aqualung_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
3769 
3770                 strncpy(name, gtk_entry_get_text(GTK_ENTRY(entry)), MAXLEN-1);
3771 
3772 		if (name[0] == '\0') {
3773 			gtk_widget_grab_focus(entry);
3774 			goto display;
3775 		}
3776 
3777 		playlist_rename(pl, name);
3778         }
3779 
3780         gtk_widget_destroy(dialog);
3781 }
3782 
3783 
3784 void
tab__new_cb(gpointer data)3785 tab__new_cb(gpointer data) {
3786 
3787 	playlist_tab_new(NULL);
3788 }
3789 
3790 void
tab__close_cb(gpointer data)3791 tab__close_cb(gpointer data) {
3792 
3793 	playlist_t * pl = (playlist_t *)data;
3794 
3795 	playlist_tab_close(NULL, pl);
3796 }
3797 
3798 void
tab__close_undo_cb(gpointer data)3799 tab__close_undo_cb(gpointer data) {
3800 
3801 	playlist_tab_close_undo();
3802 }
3803 
3804 void
tab__close_other_cb(gpointer data)3805 tab__close_other_cb(gpointer data) {
3806 
3807 	playlist_t * pl = (playlist_t *)data;
3808 	GList * node;
3809 
3810 	for (node = playlists; node && node->data != pl; node = playlists) {
3811 		playlist_tab_close(NULL, node->data);
3812 	}
3813 
3814 	for (node = playlists->next; node && node->data != pl; node = playlists->next) {
3815 		playlist_tab_close(NULL, node->data);
3816 	}
3817 }
3818 
3819 void
playlist_show_hide_close_buttons(gboolean state)3820 playlist_show_hide_close_buttons(gboolean state) {
3821 
3822     	GList * list;
3823 
3824 	for (list = playlists; list; list = list->next) {
3825 		playlist_t * pl = (playlist_t *)list->data;
3826                 if (state == TRUE) {
3827                         gtk_widget_show(pl->tab_close_button);
3828                 } else {
3829                         gtk_widget_hide(pl->tab_close_button);
3830                 }
3831 	}
3832 }
3833 
3834 GtkWidget *
create_playlist_tab_label(playlist_t * pl)3835 create_playlist_tab_label(playlist_t * pl) {
3836 
3837 	GtkWidget * hbox;
3838 	GtkWidget * event_box;
3839         GtkWidget * image;
3840         GdkPixbuf * pixbuf;
3841 
3842 	GtkWidget * separator;
3843 	GtkWidget * tab__new;
3844 	GtkWidget * tab__rename;
3845 	GtkWidget * tab__close;
3846 	GtkWidget * tab__close_other;
3847 
3848 	char path[MAXLEN];
3849 
3850 
3851 	hbox = gtk_hbox_new(FALSE, 4);
3852 	gtk_widget_set_can_focus(hbox, FALSE);
3853 
3854 	event_box = gtk_event_box_new();
3855 	pl->label = gtk_label_new(pl->name);
3856 	gtk_widget_set_name(pl->label, "playlist_tab_label");
3857 	gtk_container_add(GTK_CONTAINER(event_box), hbox);
3858 
3859 	pl->tab_close_button = gtk_button_new();
3860 	gtk_widget_set_name(pl->tab_close_button, "playlist_tab_close_button");
3861 	sprintf(path, "%s/tab-close.png", AQUALUNG_DATADIR);
3862 	if ((pixbuf = gdk_pixbuf_new_from_file(path, NULL)) != NULL) {
3863 		image = gtk_image_new_from_pixbuf(pixbuf);
3864 		gtk_container_add(GTK_CONTAINER(pl->tab_close_button), image);
3865 	}
3866 
3867 	gtk_button_set_relief(GTK_BUTTON(pl->tab_close_button), GTK_RELIEF_NONE);
3868 	gtk_widget_set_can_focus(pl->tab_close_button, FALSE);
3869 	gtk_widget_set_size_request(pl->tab_close_button, 16, 16);
3870 
3871         gtk_box_pack_start(GTK_BOX(hbox), pl->label, TRUE, TRUE, 0);
3872         gtk_box_pack_start(GTK_BOX(hbox), pl->tab_close_button, FALSE, FALSE, 0);
3873 
3874 	gtk_widget_show_all(hbox);
3875 
3876         g_signal_connect(G_OBJECT(pl->tab_close_button), "clicked", G_CALLBACK(playlist_tab_close), pl);
3877 
3878 	g_signal_connect(G_OBJECT(event_box), "button_press_event",
3879 			 G_CALLBACK(playlist_tab_label_clicked), pl);
3880 
3881 	/* popup menu for tabs */
3882 
3883 	pl->tab_menu = gtk_menu_new();
3884         tab__new = gtk_menu_item_new_with_label(_("New tab"));
3885         tab__rename = gtk_menu_item_new_with_label(_("Rename"));
3886         tab__close = gtk_menu_item_new_with_label(_("Close tab"));
3887         pl->tab__close_undo = gtk_menu_item_new_with_label(_("Undo close tab"));
3888 	tab__close_other = gtk_menu_item_new_with_label(_("Close other tabs"));
3889 
3890         gtk_menu_shell_append(GTK_MENU_SHELL(pl->tab_menu), tab__new);
3891         gtk_menu_shell_append(GTK_MENU_SHELL(pl->tab_menu), tab__rename);
3892 
3893 	separator = gtk_separator_menu_item_new();
3894         gtk_menu_shell_append(GTK_MENU_SHELL(pl->tab_menu), separator);
3895 	gtk_widget_show(separator);
3896 
3897         gtk_menu_shell_append(GTK_MENU_SHELL(pl->tab_menu), tab__close);
3898         gtk_menu_shell_append(GTK_MENU_SHELL(pl->tab_menu), pl->tab__close_undo);
3899 
3900 	separator = gtk_separator_menu_item_new();
3901         gtk_menu_shell_append(GTK_MENU_SHELL(pl->tab_menu), separator);
3902 	gtk_widget_show(separator);
3903 
3904         gtk_menu_shell_append(GTK_MENU_SHELL(pl->tab_menu), tab__close_other);
3905 
3906         g_signal_connect_swapped(G_OBJECT(tab__new), "activate", G_CALLBACK(tab__new_cb), pl);
3907         g_signal_connect_swapped(G_OBJECT(tab__rename), "activate", G_CALLBACK(tab__rename_cb), pl);
3908         g_signal_connect_swapped(G_OBJECT(tab__close), "activate", G_CALLBACK(tab__close_cb), pl);
3909         g_signal_connect_swapped(G_OBJECT(pl->tab__close_undo), "activate", G_CALLBACK(tab__close_undo_cb), pl);
3910         g_signal_connect_swapped(G_OBJECT(tab__close_other), "activate", G_CALLBACK(tab__close_other_cb), pl);
3911 
3912         gtk_widget_show(tab__rename);
3913         gtk_widget_show(tab__new);
3914 	gtk_widget_show(tab__close);
3915 	gtk_widget_show(pl->tab__close_undo);
3916         gtk_widget_show(tab__close_other);
3917 
3918         if (options.playlist_show_close_button_in_tab == FALSE) {
3919                 gtk_widget_hide(pl->tab_close_button);
3920         }
3921 
3922 	return event_box;
3923 }
3924 
3925 void
create_playlist_gui(playlist_t * pl)3926 create_playlist_gui(playlist_t * pl) {
3927 
3928 	GtkWidget * viewport;
3929 	GtkCellRenderer * track_renderer;
3930 	GtkCellRenderer * rva_renderer;
3931 	GtkCellRenderer * length_renderer;
3932 
3933 	int i;
3934 
3935 	GdkPixbuf * pixbuf;
3936 	gchar path[MAXLEN];
3937 
3938 	GtkTargetEntry source_table[] = {
3939 		{ "aqualung-list-list", GTK_TARGET_SAME_APP, 0 }
3940 	};
3941 
3942 	GtkTargetEntry target_table[] = {
3943 		{ "aqualung-list-list", GTK_TARGET_SAME_APP, 0 },
3944 		{ "aqualung-browser-list", GTK_TARGET_SAME_APP, 1 },
3945 		{ "STRING", 0, 2 },
3946 		{ "text/plain", 0, 2 },
3947 		{ "text/uri-list", 0, 2 }
3948 	};
3949 
3950         pl->view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(pl->store));
3951 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(pl->view), FALSE);
3952 	gtk_widget_set_name(pl->view, "play_list");
3953 	gtk_widget_set_size_request(pl->view, 100, 100);
3954 
3955 	g_signal_connect(G_OBJECT(pl->view), "key_press_event",
3956 			 G_CALLBACK(playlist_key_pressed), NULL);
3957 
3958 	if (options.override_skin_settings) {
3959                 gtk_widget_modify_font(pl->view, fd_playlist);
3960 	}
3961 
3962 	if (options.enable_pl_rules_hint) {
3963 		gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(pl->view), TRUE);
3964 	}
3965 
3966 	for (i = 0; i < 3; i++) {
3967 		switch (options.plcol_idx[i]) {
3968 		case 0:
3969 			track_renderer = gtk_cell_renderer_text_new();
3970 			g_object_set((gpointer)track_renderer, "xalign", 0.0, NULL);
3971 			pl->track_column = gtk_tree_view_column_new_with_attributes("Tracks",
3972 										track_renderer,
3973 										"text", PL_COL_NAME,
3974 										"foreground", PL_COL_COLO,
3975                                                                                 "weight", PL_COL_FONT,
3976 										NULL);
3977 			gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(pl->track_column),
3978 							GTK_TREE_VIEW_COLUMN_AUTOSIZE);
3979 			gtk_tree_view_column_set_spacing(GTK_TREE_VIEW_COLUMN(pl->track_column), 3);
3980 			gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(pl->track_column), FALSE);
3981                         g_object_set(G_OBJECT(track_renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
3982 			gtk_tree_view_column_set_expand(GTK_TREE_VIEW_COLUMN(pl->track_column), TRUE);
3983 			gtk_tree_view_append_column(GTK_TREE_VIEW(pl->view), pl->track_column);
3984 			break;
3985 
3986 		case 1:
3987 			rva_renderer = gtk_cell_renderer_text_new();
3988 			g_object_set((gpointer)rva_renderer, "xalign", 1.0, NULL);
3989 			pl->rva_column = gtk_tree_view_column_new_with_attributes("RVA",
3990 									      rva_renderer,
3991 									      "text", PL_COL_VADJ,
3992 									      "foreground", PL_COL_COLO,
3993                                                                               "weight", PL_COL_FONT,
3994 									      NULL);
3995 			gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(pl->rva_column),
3996 							GTK_TREE_VIEW_COLUMN_AUTOSIZE);
3997 			gtk_tree_view_column_set_spacing(GTK_TREE_VIEW_COLUMN(pl->rva_column), 3);
3998 			if (options.show_rva_in_playlist) {
3999 				gtk_tree_view_column_set_visible(GTK_TREE_VIEW_COLUMN(pl->rva_column), TRUE);
4000 			} else {
4001 				gtk_tree_view_column_set_visible(GTK_TREE_VIEW_COLUMN(pl->rva_column), FALSE);
4002 			}
4003 			gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(pl->rva_column), FALSE);
4004 			gtk_tree_view_append_column(GTK_TREE_VIEW(pl->view), pl->rva_column);
4005 			break;
4006 
4007 		case 2:
4008 			length_renderer = gtk_cell_renderer_text_new();
4009 			g_object_set((gpointer)length_renderer, "xalign", 1.0, NULL);
4010 			pl->length_column = gtk_tree_view_column_new_with_attributes("Length",
4011 										 length_renderer,
4012 										 "text", PL_COL_DURA,
4013 										 "foreground", PL_COL_COLO,
4014                                                                                  "weight", PL_COL_FONT,
4015 										 NULL);
4016 			gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(pl->length_column),
4017 							GTK_TREE_VIEW_COLUMN_AUTOSIZE);
4018 			gtk_tree_view_column_set_spacing(GTK_TREE_VIEW_COLUMN(pl->length_column), 3);
4019 			if (options.show_length_in_playlist) {
4020 				gtk_tree_view_column_set_visible(GTK_TREE_VIEW_COLUMN(pl->length_column), TRUE);
4021 			} else {
4022 				gtk_tree_view_column_set_visible(GTK_TREE_VIEW_COLUMN(pl->length_column), FALSE);
4023 			}
4024 			gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(pl->length_column), FALSE);
4025 			gtk_tree_view_append_column(GTK_TREE_VIEW(pl->view), pl->length_column);
4026 		}
4027 	}
4028 
4029         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(pl->view), FALSE);
4030 
4031 	g_signal_connect(G_OBJECT(pl->view), "button_press_event",
4032 			 G_CALLBACK(playlist_window_button_pressed), pl);
4033 
4034 
4035 	/* setup drag and drop */
4036 
4037 	gtk_drag_source_set(pl->view,
4038 			    GDK_BUTTON1_MASK,
4039 			    source_table,
4040 			    sizeof(source_table) / sizeof(GtkTargetEntry),
4041 			    GDK_ACTION_MOVE);
4042 
4043 	gtk_drag_dest_set(pl->view,
4044 			  GTK_DEST_DEFAULT_ALL,
4045 			  target_table,
4046 			  sizeof(target_table) / sizeof(GtkTargetEntry),
4047 			  GDK_ACTION_MOVE | GDK_ACTION_COPY);
4048 
4049 	snprintf(path, MAXLEN-1, "%s/drag.png", AQUALUNG_DATADIR);
4050 	if ((pixbuf = gdk_pixbuf_new_from_file(path, NULL)) != NULL) {
4051 		gtk_drag_source_set_icon_pixbuf(pl->view, pixbuf);
4052 	}
4053 
4054 	g_signal_connect(G_OBJECT(pl->view), "drag_end",
4055 			 G_CALLBACK(playlist_drag_end), pl);
4056 
4057 	g_signal_connect(G_OBJECT(pl->view), "drag_data_get",
4058 			 G_CALLBACK(playlist_drag_data_get), pl);
4059 
4060 	g_signal_connect(G_OBJECT(pl->view), "drag_leave",
4061 			 G_CALLBACK(playlist_drag_leave), pl);
4062 
4063 	g_signal_connect(G_OBJECT(pl->view), "drag_motion",
4064 			 G_CALLBACK(playlist_drag_motion), pl);
4065 
4066 	g_signal_connect(G_OBJECT(pl->view), "drag_data_received",
4067 			 G_CALLBACK(playlist_drag_data_received), pl);
4068 
4069 
4070         pl->select = gtk_tree_view_get_selection(GTK_TREE_VIEW(pl->view));
4071         gtk_tree_selection_set_mode(pl->select, GTK_SELECTION_MULTIPLE);
4072 
4073 	g_signal_connect(G_OBJECT(pl->select), "changed",
4074 			 G_CALLBACK(playlist_selection_changed_cb), pl);
4075 
4076 
4077         pl->widget = gtk_vbox_new(FALSE, 2);
4078 
4079 	viewport = gtk_viewport_new(NULL, NULL);
4080         gtk_box_pack_start(GTK_BOX(pl->widget), viewport, TRUE, TRUE, 0);
4081 
4082 	pl->scroll = gtk_scrolled_window_new(NULL, NULL);
4083 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(pl->scroll),
4084 				       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
4085         gtk_container_add(GTK_CONTAINER(viewport), pl->scroll);
4086 
4087 	gtk_container_add(GTK_CONTAINER(pl->scroll), pl->view);
4088 
4089 
4090 	pl->progbar_container = gtk_hbox_new(FALSE, 4);
4091         gtk_box_pack_start(GTK_BOX(pl->widget), pl->progbar_container, FALSE, FALSE, 0);
4092 
4093 	if (pl->progbar_semaphore > 0) {
4094 		pl->progbar_semaphore--;
4095 		playlist_progress_bar_show(pl);
4096 	}
4097 
4098 	gtk_widget_show_all(pl->widget);
4099 
4100 	gtk_notebook_insert_page(GTK_NOTEBOOK(playlist_notebook),
4101 				 pl->widget,
4102 				 create_playlist_tab_label(pl),
4103 				 pl->index);
4104 
4105 	gtk_notebook_set_current_page(GTK_NOTEBOOK(playlist_notebook), pl->index);
4106 
4107 	playlist_set_playing(pl, pl->playing);
4108 
4109 	if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook)) > 1) {
4110 		gtk_notebook_set_show_tabs(GTK_NOTEBOOK(playlist_notebook), TRUE);
4111 	}
4112 
4113 	gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(playlist_notebook), pl->widget, TRUE);
4114 
4115 	gtk_widget_grab_focus(pl->view);
4116 }
4117 
4118 playlist_t *
playlist_tab_new(char * name)4119 playlist_tab_new(char * name) {
4120 
4121 	playlist_t * pl = NULL;
4122 
4123 	pl = playlist_new(name);
4124 	create_playlist_gui(pl);
4125 	return pl;
4126 }
4127 
4128 playlist_t *
playlist_tab_new_if_nonempty(char * name)4129 playlist_tab_new_if_nonempty(char * name) {
4130 
4131 	playlist_t * pl = (playlist_t *)playlists->data;
4132 
4133 	if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook)) == 1 &&
4134 	    playlist_is_empty(pl)) {
4135 		playlist_rename(pl, name);
4136 		return pl;
4137 	}
4138 
4139 	return playlist_tab_new(name);
4140 }
4141 
4142 void
notebook_switch_page(GtkNotebook * notebook,GtkNotebookPage * page,guint page_num,gpointer user_data)4143 notebook_switch_page(GtkNotebook * notebook, GtkNotebookPage * page,
4144 		     guint page_num, gpointer user_data) {
4145 
4146 	playlist_t * pl = playlist_get_by_page_num(page_num);
4147 
4148 	if (pl == NULL) {
4149 		return;
4150 	}
4151 
4152 	playlist_content_changed(pl);
4153 	playlist_selection_changed(pl);
4154 }
4155 
4156 void
notebook_page_reordered(GtkNotebook * notebook,GtkWidget * child,guint page_num,gpointer user_data)4157 notebook_page_reordered(GtkNotebook * notebook, GtkWidget * child,
4158 			guint page_num, gpointer user_data) {
4159 
4160 	GList * node;
4161 
4162 	for (node = playlists; node; node = node->next) {
4163 		playlist_t * pl = (playlist_t *)node->data;
4164 		pl->index = gtk_notebook_page_num(notebook, pl->widget);
4165 	}
4166 
4167 	playlists = g_list_sort(playlists, playlist_compare_index);
4168 }
4169 
4170 void
create_playlist(void)4171 create_playlist(void) {
4172 
4173 	GtkWidget * vbox;
4174 
4175 	GtkWidget * statusbar;
4176 	GtkWidget * statusbar_scrolledwin;
4177 	GtkWidget * statusbar_viewport;
4178 
4179 	GtkWidget * hbox_bottom;
4180 	GtkWidget * add_button;
4181 	GtkWidget * sel_button;
4182 	GtkWidget * rem_button;
4183 
4184 
4185 	if (pl_color_active[0] == '\0') {
4186 		strcpy(pl_color_active, "#000080");
4187 	}
4188 
4189 	if (pl_color_inactive[0] == '\0') {
4190 		strcpy(pl_color_inactive, "#010101");
4191 	}
4192 
4193 
4194         /* window creating stuff */
4195 	if (!options.playlist_is_embedded) {
4196 		playlist_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
4197 		gtk_window_set_title(GTK_WINDOW(playlist_window), _("Playlist"));
4198 		gtk_container_set_border_width(GTK_CONTAINER(playlist_window), 2);
4199 		g_signal_connect(G_OBJECT(playlist_window), "delete_event",
4200 				 G_CALLBACK(playlist_window_close), NULL);
4201 		gtk_widget_set_size_request(playlist_window, 300, 200);
4202 	} else {
4203 		gtk_widget_set_size_request(playlist_window, 200, 200);
4204 	}
4205 
4206         if (!options.playlist_is_embedded) {
4207                 g_signal_connect(G_OBJECT(playlist_window), "key_press_event",
4208                                  G_CALLBACK(playlist_window_key_pressed), NULL);
4209         }
4210 
4211 	gtk_widget_set_events(playlist_window, GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
4212 
4213 
4214         if (!options.playlist_is_embedded) {
4215                 plist_menu = gtk_menu_new();
4216 		register_toplevel_window(plist_menu, TOP_WIN_SKIN);
4217                 init_plist_menu(plist_menu);
4218         }
4219 
4220         vbox = gtk_vbox_new(FALSE, 2);
4221         gtk_container_add(GTK_CONTAINER(playlist_window), vbox);
4222 
4223 	/* this widget should not be visually noticeable, it is only used for
4224 	   getting active and inactive playlist colors from the skin/rc file */
4225 	playlist_color_indicator = gtk_hbox_new(FALSE, 0);
4226 	gtk_widget_set_name(playlist_color_indicator, "playlist_color_indicator");
4227         gtk_box_pack_start(GTK_BOX(vbox), playlist_color_indicator, FALSE, FALSE, 0);
4228 
4229 	/* create notebook */
4230 	playlist_notebook = gtk_notebook_new();
4231 	gtk_notebook_set_show_border(GTK_NOTEBOOK(playlist_notebook), FALSE);
4232 	gtk_notebook_set_scrollable(GTK_NOTEBOOK(playlist_notebook), TRUE);
4233 	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(playlist_notebook),
4234 				   options.playlist_always_show_tabs);
4235         gtk_box_pack_start(GTK_BOX(vbox), playlist_notebook, TRUE, TRUE, 0);
4236 
4237 	g_signal_connect(G_OBJECT(playlist_notebook), "key_press_event",
4238 			 G_CALLBACK(playlist_notebook_key_pressed), NULL);
4239 
4240 	g_signal_connect(G_OBJECT(playlist_notebook), "button_press_event",
4241 			       G_CALLBACK(playlist_notebook_clicked), NULL);
4242 
4243 	g_signal_connect(G_OBJECT(playlist_notebook), "switch_page",
4244 			 G_CALLBACK(notebook_switch_page), NULL);
4245 
4246 	g_signal_connect(G_OBJECT(playlist_notebook), "page_reordered",
4247 			 G_CALLBACK(notebook_page_reordered), NULL);
4248 
4249 	if (playlists != NULL) {
4250 		GList * list;
4251 		for (list = playlists; list; list = list->next) {
4252 			playlist_t * pl = (playlist_t *)list->data;
4253 			if (!pl->closed) {
4254 				create_playlist_gui(pl);
4255 			}
4256 		}
4257 	}
4258 
4259 
4260 	/* statusbar */
4261 	if (options.enable_playlist_statusbar) {
4262 
4263 		statusbar_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
4264 		gtk_widget_set_can_focus(statusbar_scrolledwin, FALSE);
4265 		gtk_widget_set_size_request(statusbar_scrolledwin, 1, -1);    /* MAGIC */
4266 		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(statusbar_scrolledwin),
4267 					       GTK_POLICY_NEVER, GTK_POLICY_NEVER);
4268 
4269 		statusbar_viewport = gtk_viewport_new(NULL, NULL);
4270 		gtk_widget_set_name(statusbar_viewport, "info_viewport");
4271 
4272 		gtk_container_add(GTK_CONTAINER(statusbar_scrolledwin), statusbar_viewport);
4273 		gtk_box_pack_start(GTK_BOX(vbox), statusbar_scrolledwin, FALSE, TRUE, 2);
4274 
4275 		gtk_widget_set_events(statusbar_viewport,
4276 				      GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
4277 
4278 		g_signal_connect(G_OBJECT(statusbar_viewport), "button_press_event",
4279 				 G_CALLBACK(scroll_btn_pressed), NULL);
4280 		g_signal_connect(G_OBJECT(statusbar_viewport), "button_release_event",
4281 				 G_CALLBACK(scroll_btn_released), (gpointer)statusbar_scrolledwin);
4282 		g_signal_connect(G_OBJECT(statusbar_viewport), "motion_notify_event",
4283 				 G_CALLBACK(scroll_motion_notify), (gpointer)statusbar_scrolledwin);
4284 
4285 		statusbar = gtk_hbox_new(FALSE, 0);
4286 		gtk_container_set_border_width(GTK_CONTAINER(statusbar), 1);
4287 		gtk_container_add(GTK_CONTAINER(statusbar_viewport), statusbar);
4288 
4289 		statusbar_selected = gtk_label_new("");
4290 		gtk_widget_set_name(statusbar_selected, "label_info");
4291 		gtk_box_pack_end(GTK_BOX(statusbar), statusbar_selected, FALSE, TRUE, 0);
4292 
4293 		statusbar_selected_label = gtk_label_new(_(" Selected: "));
4294 		gtk_widget_set_name(statusbar_selected_label, "label_info");
4295 		gtk_box_pack_end(GTK_BOX(statusbar), statusbar_selected_label, FALSE, TRUE, 0);
4296 
4297 		gtk_box_pack_end(GTK_BOX(statusbar), gtk_vseparator_new(), FALSE, TRUE, 5);
4298 
4299 		statusbar_total = gtk_label_new("");
4300 		gtk_widget_set_name(statusbar_total, "label_info");
4301 		gtk_box_pack_end(GTK_BOX(statusbar), statusbar_total, FALSE, TRUE, 0);
4302 
4303 		statusbar_total_label = gtk_label_new(_("Total: "));
4304 		gtk_widget_set_name(statusbar_total_label, "label_info");
4305 		gtk_box_pack_end(GTK_BOX(statusbar), statusbar_total_label, FALSE, TRUE, 0);
4306 	}
4307 
4308 
4309 	playlist_set_font(options.override_skin_settings);
4310 
4311 	/* bottom area of playlist window */
4312         hbox_bottom = gtk_hbox_new(FALSE, 0);
4313         gtk_box_pack_start(GTK_BOX(vbox), hbox_bottom, FALSE, TRUE, 0);
4314 
4315 	add_button = gtk_button_new_with_label(_("Add files"));
4316 	gtk_widget_set_can_focus(add_button, FALSE);
4317         aqualung_widget_set_tooltip_text(add_button, _("Add files to playlist\n(Press right mouse button for menu)"));
4318         gtk_box_pack_start(GTK_BOX(hbox_bottom), add_button, TRUE, TRUE, 0);
4319         g_signal_connect(G_OBJECT(add_button), "clicked", G_CALLBACK(add_files), NULL);
4320 
4321 	sel_button = gtk_button_new_with_label(_("Select all"));
4322 	gtk_widget_set_can_focus(sel_button, FALSE);
4323         aqualung_widget_set_tooltip_text(sel_button, _("Select all songs in playlist\n(Press right mouse button for menu)"));
4324         gtk_box_pack_start(GTK_BOX(hbox_bottom), sel_button, TRUE, TRUE, 0);
4325         g_signal_connect(G_OBJECT(sel_button), "clicked", G_CALLBACK(select_all), NULL);
4326 
4327 	rem_button = gtk_button_new_with_label(_("Remove selected"));
4328 	gtk_widget_set_can_focus(rem_button, FALSE);
4329         aqualung_widget_set_tooltip_text(rem_button, _("Remove selected songs from playlist\n(Press right mouse button for menu)"));
4330         gtk_box_pack_start(GTK_BOX(hbox_bottom), rem_button, TRUE, TRUE, 0);
4331         g_signal_connect(G_OBJECT(rem_button), "clicked", G_CALLBACK(remove_sel), NULL);
4332 
4333 
4334         add_menu = gtk_menu_new();
4335 	register_toplevel_window(add_menu, TOP_WIN_SKIN);
4336 
4337         add__dir = gtk_menu_item_new_with_label(_("Add directory"));
4338         gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), add__dir);
4339         g_signal_connect_swapped(G_OBJECT(add__dir), "activate", G_CALLBACK(add__dir_cb), NULL);
4340 	gtk_widget_show(add__dir);
4341 
4342         add__url = gtk_menu_item_new_with_label(_("Add URL"));
4343         gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), add__url);
4344         g_signal_connect_swapped(G_OBJECT(add__url), "activate", G_CALLBACK(add__url_cb), NULL);
4345 	gtk_widget_show(add__url);
4346 
4347         add__files = gtk_menu_item_new_with_label(_("Add files"));
4348         gtk_menu_shell_append(GTK_MENU_SHELL(add_menu), add__files);
4349         g_signal_connect_swapped(G_OBJECT(add__files), "activate", G_CALLBACK(add__files_cb), NULL);
4350 	gtk_widget_show(add__files);
4351 
4352         g_signal_connect_swapped(G_OBJECT(add_button), "event", G_CALLBACK(add_cb), NULL);
4353 
4354 
4355         sel_menu = gtk_menu_new();
4356 	register_toplevel_window(sel_menu, TOP_WIN_SKIN);
4357 
4358         sel__none = gtk_menu_item_new_with_label(_("Select none"));
4359         gtk_menu_shell_append(GTK_MENU_SHELL(sel_menu), sel__none);
4360         g_signal_connect_swapped(G_OBJECT(sel__none), "activate", G_CALLBACK(sel__none_cb), NULL);
4361 	gtk_widget_show(sel__none);
4362 
4363         sel__inv = gtk_menu_item_new_with_label(_("Invert selection"));
4364         gtk_menu_shell_append(GTK_MENU_SHELL(sel_menu), sel__inv);
4365         g_signal_connect_swapped(G_OBJECT(sel__inv), "activate", G_CALLBACK(sel__inv_cb), NULL);
4366 	gtk_widget_show(sel__inv);
4367 
4368         sel__all = gtk_menu_item_new_with_label(_("Select all"));
4369         gtk_menu_shell_append(GTK_MENU_SHELL(sel_menu), sel__all);
4370         g_signal_connect_swapped(G_OBJECT(sel__all), "activate", G_CALLBACK(sel__all_cb), NULL);
4371 	gtk_widget_show(sel__all);
4372 
4373         g_signal_connect_swapped(G_OBJECT(sel_button), "event", G_CALLBACK(sel_cb), NULL);
4374 
4375 
4376         rem_menu = gtk_menu_new();
4377 	register_toplevel_window(rem_menu, TOP_WIN_SKIN);
4378 
4379         rem__all = gtk_menu_item_new_with_label(_("Remove all"));
4380         gtk_menu_shell_append(GTK_MENU_SHELL(rem_menu), rem__all);
4381         g_signal_connect_swapped(G_OBJECT(rem__all), "activate", G_CALLBACK(rem__all_cb), NULL);
4382     	gtk_widget_show(rem__all);
4383 
4384         rem__dead = gtk_menu_item_new_with_label(_("Remove dead"));
4385         gtk_menu_shell_append(GTK_MENU_SHELL(rem_menu), rem__dead);
4386         g_signal_connect_swapped(G_OBJECT(rem__dead), "activate", G_CALLBACK(rem__dead_cb), NULL);
4387     	gtk_widget_show(rem__dead);
4388 
4389         cut__sel = gtk_menu_item_new_with_label(_("Cut selected"));
4390         gtk_menu_shell_append(GTK_MENU_SHELL(rem_menu), cut__sel);
4391         g_signal_connect_swapped(G_OBJECT(cut__sel), "activate", G_CALLBACK(cut__sel_cb), NULL);
4392     	gtk_widget_show(cut__sel);
4393 
4394         rem__sel = gtk_menu_item_new_with_label(_("Remove selected"));
4395         gtk_menu_shell_append(GTK_MENU_SHELL(rem_menu), rem__sel);
4396         g_signal_connect_swapped(G_OBJECT(rem__sel), "activate", G_CALLBACK(rem__sel_cb), NULL);
4397     	gtk_widget_show(rem__sel);
4398 
4399         g_signal_connect_swapped(G_OBJECT(rem_button), "event", G_CALLBACK(rem_cb), NULL);
4400 }
4401 
4402 
4403 void
playlist_ensure_tab_exists()4404 playlist_ensure_tab_exists() {
4405 
4406 	if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook)) == 0) {
4407 		playlist_tab_new(NULL);
4408 	}
4409 }
4410 
4411 
4412 void
show_playlist(void)4413 show_playlist(void) {
4414 
4415 	options.playlist_on = 1;
4416 
4417 	if (!options.playlist_is_embedded) {
4418 		gtk_window_move(GTK_WINDOW(playlist_window), options.playlist_pos_x, options.playlist_pos_y);
4419 		gtk_window_resize(GTK_WINDOW(playlist_window), options.playlist_size_x, options.playlist_size_y);
4420 		register_toplevel_window(playlist_window, TOP_WIN_SKIN | TOP_WIN_TRAY);
4421 	} else {
4422 		gtk_window_get_size(GTK_WINDOW(main_window), &options.main_size_x, &options.main_size_y);
4423 		gtk_window_resize(GTK_WINDOW(main_window), options.main_size_x, options.main_size_y + options.playlist_size_y + 6);
4424 	}
4425 
4426         gtk_widget_show_all(playlist_window);
4427 }
4428 
4429 void
hide_playlist(void)4430 hide_playlist(void) {
4431 
4432 	options.playlist_on = 0;
4433 	if (!options.playlist_is_embedded) {
4434 		gtk_window_get_position(GTK_WINDOW(playlist_window), &options.playlist_pos_x, &options.playlist_pos_y);
4435 		gtk_window_get_size(GTK_WINDOW(playlist_window), &options.playlist_size_x, &options.playlist_size_y);
4436 		register_toplevel_window(playlist_window, TOP_WIN_SKIN);
4437 	} else {
4438 		options.playlist_size_x = playlist_window->allocation.width;
4439 		options.playlist_size_y = playlist_window->allocation.height;
4440 		gtk_window_get_size(GTK_WINDOW(main_window), &options.main_size_x, &options.main_size_y);
4441 	}
4442 
4443         gtk_widget_hide(playlist_window);
4444 
4445 	if (options.playlist_is_embedded) {
4446 		gtk_window_resize(GTK_WINDOW(main_window), options.main_size_x, options.main_size_y - options.playlist_size_y - 6);
4447 	}
4448 
4449 }
4450 
4451 
4452 xmlNodePtr
save_track_node(playlist_t * pl,GtkTreeIter * piter,xmlNodePtr root,char * nodeID)4453 save_track_node(playlist_t * pl, GtkTreeIter * piter, xmlNodePtr root, char * nodeID) {
4454 
4455         gchar str[32];
4456         xmlNodePtr node;
4457 	playlist_data_t * data;
4458 
4459 
4460 	gtk_tree_model_get(GTK_TREE_MODEL(pl->store), piter, PL_COL_DATA, &data, -1);
4461 
4462 	node = xmlNewTextChild(root, NULL, (const xmlChar*) nodeID, NULL);
4463 	xmlNewTextChild(node, NULL, (const xmlChar*) "artist", (const xmlChar*) data->artist);
4464 	xmlNewTextChild(node, NULL, (const xmlChar*) "album", (const xmlChar*) data->album);
4465 
4466 	if (strcmp(nodeID, "record")) {
4467 
4468 		char * file = NULL;
4469 
4470 		xmlNewTextChild(node, NULL, (const xmlChar*) "title", (const xmlChar*) data->title);
4471 		xmlNewTextChild(node, NULL, (const xmlChar*) "display", (const xmlChar*) data->display);
4472 
4473 		if (g_str_has_prefix(data->file, "CDDA ") ||
4474 		    httpc_is_url(data->file)) {
4475 			file = g_strdup(data->file);
4476 		} else {
4477 			file = g_filename_to_uri(data->file, NULL, NULL);
4478 		}
4479 		xmlNewTextChild(node, NULL, (const xmlChar*) "file", (const xmlChar*) file);
4480 		g_free(file);
4481 
4482 		snprintf(str, 31, "%f", data->voladj);
4483 		xmlNewTextChild(node, NULL, (const xmlChar*) "voladj", (const xmlChar*) str);
4484 
4485 		snprintf(str, 31, "%u", data->size);
4486 		xmlNewTextChild(node, NULL, (const xmlChar*) "size", (const xmlChar*) str);
4487 
4488 	} else if (IS_PL_ACTIVE(data)){
4489 
4490 		snprintf(str, 31, "%u", data->ntracks);
4491 		xmlNewTextChild(node, NULL, (const xmlChar*) "ntracks", (const xmlChar*) str);
4492 
4493 		snprintf(str, 31, "%u", data->actrack);
4494 		xmlNewTextChild(node, NULL, (const xmlChar*) "actrack", (const xmlChar*) str);
4495 	}
4496 
4497 	snprintf(str, 31, "%f", data->duration);
4498 	xmlNewTextChild(node, NULL, (const xmlChar*) "duration", (const xmlChar*) str);
4499 
4500 	if (IS_PL_ACTIVE(data)) {
4501 		xmlNewTextChild(node, NULL, (const xmlChar*) "active", NULL);
4502 	}
4503 
4504 	if (IS_PL_COVER(data)) {
4505 		xmlNewTextChild(node, NULL, (const xmlChar*) "cover", NULL);
4506 	}
4507 
4508 	return node;
4509 }
4510 
4511 void
playlist_save_data(playlist_t * pl,xmlNodePtr dest,int current)4512 playlist_save_data(playlist_t * pl, xmlNodePtr dest, int current) {
4513 
4514         gint i = 0;
4515         GtkTreeIter iter;
4516 	GtkTreePath * p = NULL;
4517 
4518 	if (pl->name_set) {
4519 		xmlNewTextChild(dest, NULL, (const xmlChar*) "name", (const xmlChar*) pl->name);
4520 	}
4521 	if (pl->playing) {
4522 		xmlNewTextChild(dest, NULL, (const xmlChar*) "active", NULL);
4523 	}
4524 	if (current) {
4525 		xmlNewTextChild(dest, NULL, (const xmlChar*) "current", NULL);
4526 	}
4527 	if ((p = playlist_get_playing_path(pl)) != NULL) {
4528 		xmlNewTextChild(dest, NULL, (const xmlChar*) "has_active_track", NULL);
4529 		gtk_tree_path_free(p);
4530 	}
4531 
4532         while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
4533 
4534 		gint n = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pl->store), &iter);
4535 
4536 		if (n) { /* album node */
4537 			gint j = 0;
4538 			GtkTreeIter iter_child;
4539 			xmlNodePtr node;
4540 
4541 			node = save_track_node(pl, &iter, dest, "record");
4542 			while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store),
4543 							     &iter_child, &iter, j++)) {
4544 				save_track_node(pl, &iter_child, node, "track");
4545 			}
4546 		} else { /* track node */
4547 			save_track_node(pl, &iter, dest, "track");
4548 		}
4549         }
4550 }
4551 
4552 
4553 void
playlist_save(playlist_t * pl,char * filename)4554 playlist_save(playlist_t * pl, char * filename) {
4555 
4556         xmlDocPtr doc;
4557         xmlNodePtr root;
4558 
4559         doc = xmlNewDoc((const xmlChar*) "1.0");
4560         root = xmlNewNode(NULL, (const xmlChar*) "aqualung_playlist");
4561         xmlDocSetRootElement(doc, root);
4562 
4563 	playlist_save_data(pl, root, 0);
4564 
4565         xmlSaveFormatFile(filename, doc, 1);
4566 	xmlFreeDoc(doc);
4567 }
4568 
4569 int
playlist_save_m3u_node(playlist_t * pl,GtkTreeIter * iter,FILE * f)4570 playlist_save_m3u_node(playlist_t * pl, GtkTreeIter * iter, FILE * f) {
4571 
4572 	playlist_data_t * data;
4573 	size_t fn_len;
4574 
4575 	gtk_tree_model_get(GTK_TREE_MODEL(pl->store), iter, PL_COL_DATA, &data, -1);
4576 	fn_len = strlen(data->file);
4577 	if (fwrite(data->file, 1, fn_len, f) != fn_len) {
4578 		g_warning("Error writing the playlist file");
4579 		return -1;
4580 	}
4581 	if (fwrite("\n", 1, 1, f) != 1) {
4582 		g_warning("Error writing the playlist file");
4583 		return -1;
4584 	}
4585 	return 0;
4586 }
4587 
4588 void
playlist_save_m3u(playlist_t * pl,char * filename)4589 playlist_save_m3u(playlist_t * pl, char * filename) {
4590 
4591 	FILE * f;
4592         gint i = 0;
4593         GtkTreeIter iter;
4594 
4595 	if ((f = g_fopen(filename, "wb")) == NULL) {
4596 		g_warning("Unable to open playlist file %s for writing", filename);
4597 		return;
4598 	}
4599 
4600         while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
4601 
4602 		gint n = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pl->store), &iter);
4603 
4604 		if (n) { /* album node */
4605 			gint j = 0;
4606 			GtkTreeIter iter_child;
4607 
4608 			while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store),
4609 							     &iter_child, &iter, j++)) {
4610 				if (playlist_save_m3u_node(pl, &iter_child, f) != 0) {
4611 					goto playlist_save_m3u_cleanup;
4612 				 }
4613 			}
4614 		} else { /* track node */
4615 			if (playlist_save_m3u_node(pl, &iter, f) != 0) {
4616 				goto playlist_save_m3u_cleanup;
4617 			}
4618 		}
4619         }
4620 
4621 	playlist_save_m3u_cleanup:
4622 	if (fclose(f) != 0) {
4623 		g_warning("Unable to close playlist file %s", filename);
4624 	}
4625 }
4626 
4627 void
playlist_save_all(char * filename)4628 playlist_save_all(char * filename) {
4629 
4630         xmlDocPtr doc;
4631         xmlNodePtr root;
4632 	xmlNodePtr tab;
4633 
4634     	GList * list = NULL;
4635 	int current = gtk_notebook_get_current_page(GTK_NOTEBOOK(playlist_notebook));
4636 	int i = 0;
4637 
4638         doc = xmlNewDoc((const xmlChar*) "1.0");
4639         root = xmlNewNode(NULL, (const xmlChar*) "aqualung_playlist_tabs");
4640         xmlDocSetRootElement(doc, root);
4641 
4642 	for (list = playlists; list; list = list->next) {
4643 		playlist_t * pl = (playlist_t *)list->data;
4644 
4645 		tab = xmlNewTextChild(root, NULL, (const xmlChar*) "tab", NULL);
4646 		playlist_save_data(pl, tab, (i == current));
4647 		i++;
4648 	}
4649 
4650         xmlSaveFormatFile(filename, doc, 1);
4651 	xmlFreeDoc(doc);
4652 }
4653 
4654 gboolean
playlist_auto_save_cb(gpointer data)4655 playlist_auto_save_cb(gpointer data) {
4656 
4657 	if (playlist_dirty) {
4658 		char playlist_name[MAXLEN];
4659 		snprintf(playlist_name, MAXLEN-1, "%s/%s", options.confdir, "playlist.xml");
4660 		playlist_save_all(playlist_name);
4661 		playlist_dirty = 0;
4662 	}
4663 
4664 	return TRUE;
4665 }
4666 
4667 void
playlist_auto_save_reset(void)4668 playlist_auto_save_reset(void) {
4669 
4670 	if (playlist_auto_save_tag != -1) {
4671 		g_source_remove(playlist_auto_save_tag);
4672 	}
4673 	if (options.playlist_auto_save) {
4674 		playlist_auto_save_tag = aqualung_timeout_add(options.playlist_auto_save_int * 60000,
4675 						       playlist_auto_save_cb, NULL);
4676 	}
4677 }
4678 
4679 xmlChar *
parse_playlist_name(xmlDocPtr doc,xmlNodePtr _cur)4680 parse_playlist_name(xmlDocPtr doc, xmlNodePtr _cur) {
4681 
4682 	xmlNodePtr cur;
4683 
4684         for (cur = _cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
4685                 if (!xmlStrcmp(cur->name, (const xmlChar *)"name")) {
4686                         return xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
4687                 }
4688 	}
4689 
4690 	return NULL;
4691 }
4692 
4693 void
parse_playlist_status(xmlDocPtr doc,xmlNodePtr _cur,int * active,int * current,int * has_active)4694 parse_playlist_status(xmlDocPtr doc, xmlNodePtr _cur, int * active, int * current, int * has_active) {
4695 
4696 	xmlNodePtr cur;
4697 
4698 	if (active) {
4699 		*active = 0;
4700 	}
4701 	if (current) {
4702 		*current = 0;
4703 	}
4704 	if (has_active) {
4705 		*has_active = 0;
4706 	}
4707 
4708         for (cur = _cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
4709                 if (active && !xmlStrcmp(cur->name, (const xmlChar *)"active")) {
4710 			*active = 1;
4711                 }
4712                 if (current && !xmlStrcmp(cur->name, (const xmlChar *)"current")) {
4713 			*current = 1;
4714                 }
4715                 if (has_active && !xmlStrcmp(cur->name, (const xmlChar *)"has_active_track")) {
4716 			*has_active = 1;
4717                 }
4718 	}
4719 }
4720 
4721 void
parse_playlist_track(playlist_transfer_t * pt,xmlDocPtr doc,xmlNodePtr _cur,int flags)4722 parse_playlist_track(playlist_transfer_t * pt, xmlDocPtr doc, xmlNodePtr _cur, int flags) {
4723 
4724         xmlChar * key;
4725 	xmlNodePtr cur;
4726 	playlist_data_t * data = NULL;
4727 
4728 	if ((data = playlist_data_new()) == NULL) {
4729 		return;
4730 	}
4731 
4732 	data->flags = flags;
4733 
4734         for (cur = _cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
4735 
4736                 if (!xmlStrcmp(cur->name, (const xmlChar *)"artist")) {
4737                         if ((key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
4738 				data->artist = strndup((char *)key, MAXLEN-1);
4739 				xmlFree(key);
4740 			}
4741 		} else if (!xmlStrcmp(cur->name, (const xmlChar *)"album")) {
4742                         if ((key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
4743 				data->album = strndup((char *)key, MAXLEN-1);
4744 				xmlFree(key);
4745 			}
4746 		} else if (!xmlStrcmp(cur->name, (const xmlChar *)"title")) {
4747                         if ((key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
4748 				data->title = strndup((char *)key, MAXLEN-1);
4749 				xmlFree(key);
4750 			}
4751 		} else if (!xmlStrcmp(cur->name, (const xmlChar *)"display")) {
4752                         if ((key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
4753 				data->display = strndup((char *)key, MAXLEN-1);
4754 				xmlFree(key);
4755 			}
4756                 } else if (!xmlStrcmp(cur->name, (const xmlChar *)"file")) {
4757                         if ((key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
4758 				gchar * tmp;
4759 				if ((tmp = g_filename_from_uri((char *) key, NULL, NULL))) {
4760 					data->file = strndup(tmp, MAXLEN-1);
4761 					g_free(tmp);
4762 				} else {
4763 					data->file = strndup((char *)key, MAXLEN-1);
4764 				}
4765 				xmlFree(key);
4766 			}
4767                 } else if (!xmlStrcmp(cur->name, (const xmlChar *)"voladj")) {
4768                         if ((key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
4769 				data->voladj = convf((char *)key);
4770 				xmlFree(key);
4771 			}
4772                 } else if (!xmlStrcmp(cur->name, (const xmlChar *)"duration")) {
4773                         if ((key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
4774 				data->duration = convf((char *)key);
4775 				xmlFree(key);
4776 			}
4777                 } else if (!xmlStrcmp(cur->name, (const xmlChar *)"size")) {
4778                         if ((key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
4779 				sscanf((char *)key, "%u", &data->size);
4780 				xmlFree(key);
4781 			}
4782                 } else if (!xmlStrcmp(cur->name, (const xmlChar *)"ntracks")) {
4783                         if ((key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
4784 				sscanf((char *)key, "%hu", &data->ntracks);
4785 				xmlFree(key);
4786 			}
4787                 } else if (!xmlStrcmp(cur->name, (const xmlChar *)"actrack")) {
4788                         if ((key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
4789 				sscanf((char *)key, "%hu", &data->actrack);
4790 				xmlFree(key);
4791 			}
4792                 } else if (!xmlStrcmp(cur->name, (const xmlChar *)"active")) {
4793 			PL_SET_FLAG(data, PL_FLAG_ACTIVE);
4794                 } else if (!xmlStrcmp(cur->name, (const xmlChar *)"cover")) {
4795 			PL_SET_FLAG(data, PL_FLAG_COVER);
4796 		}
4797 	}
4798 
4799 	if (IS_PL_ALBUM_NODE(data)) {
4800 		int act_flag = 0;
4801 		if (flags & PL_FLAG_ACTIVE) {
4802 			/* ACTIVE flag has been forced by playlist_load_xml_thread */
4803 			act_flag = PL_FLAG_ACTIVE;
4804 			data->actrack = 1;
4805 			for (cur = _cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
4806 				if (!xmlStrcmp(cur->name, (const xmlChar *)"track")) {
4807 					data->ntracks++;
4808 				}
4809 			}
4810 		}
4811 
4812 		playlist_thread_add_to_list(pt, data);
4813 
4814 		for (cur = _cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
4815 			if (!xmlStrcmp(cur->name, (const xmlChar *)"track")) {
4816 				parse_playlist_track(pt, doc, cur, PL_FLAG_ALBUM_CHILD | act_flag);
4817 				act_flag = 0;
4818 			}
4819 		}
4820 	} else if (data->file != NULL) {
4821 		playlist_thread_add_to_list(pt, data);
4822 	}
4823 }
4824 
4825 void *
playlist_load_xml_thread(void * arg)4826 playlist_load_xml_thread(void * arg) {
4827 
4828 	playlist_transfer_t * pt = (playlist_transfer_t *)arg;
4829         xmlDocPtr doc = (xmlDocPtr)pt->xml_doc;
4830         xmlNodePtr cur = (xmlNodePtr)pt->xml_node;
4831 	int act_flag = 0;
4832 
4833 	AQUALUNG_THREAD_DETACH();
4834 
4835 	AQUALUNG_MUTEX_LOCK(pt->pl->thread_mutex);
4836 
4837 	if (pt->start_playback) {
4838 		int has_active;
4839 		parse_playlist_status(doc, cur, NULL, NULL, &has_active);
4840 		if (!has_active) {
4841 			act_flag = PL_FLAG_ACTIVE;
4842 		}
4843 	}
4844 
4845 	for (cur = cur->xmlChildrenNode; cur != NULL && !pt->pl->thread_stop; cur = cur->next) {
4846 
4847 		if (!xmlStrcmp(cur->name, (const xmlChar *)"track")) {
4848 			parse_playlist_track(pt, doc, cur, act_flag);
4849 			act_flag = 0;
4850 		}
4851 		if (!xmlStrcmp(cur->name, (const xmlChar *)"record")) {
4852 			parse_playlist_track(pt, doc, cur, PL_FLAG_ALBUM_NODE | act_flag);
4853 			act_flag = 0;
4854 		}
4855 	}
4856 
4857 	playlist_thread_add_to_list(pt, NULL);
4858 
4859 	AQUALUNG_MUTEX_UNLOCK(pt->pl->thread_mutex);
4860 
4861 	playlist_transfer_free(pt);
4862 
4863 	return NULL;
4864 }
4865 
4866 void
playlist_load_xml_single(char * filename,playlist_transfer_t * pt)4867 playlist_load_xml_single(char * filename, playlist_transfer_t * pt) {
4868 
4869         xmlDocPtr doc;
4870         xmlNodePtr cur;
4871 
4872         doc = xmlParseFile(filename);
4873         if (doc == NULL) {
4874                 fprintf(stderr, "An XML error occured while parsing %s\n", filename);
4875                 return;
4876         }
4877 
4878         cur = xmlDocGetRootElement(doc);
4879         if (cur == NULL) {
4880                 fprintf(stderr, "empty XML document\n");
4881                 xmlFreeDoc(doc);
4882                 return;
4883         }
4884 
4885 	if (xmlStrcmp(cur->name, (const xmlChar *)"aqualung_playlist")) {
4886 		fprintf(stderr, "XML document of the wrong type\n");
4887 		xmlFreeDoc(doc);
4888 		return;
4889 	}
4890 
4891 	if (!pt->pl->name_set || pt->clear) {
4892 		xmlChar * pl_name = parse_playlist_name(doc, cur);
4893 		if (pl_name != NULL) {
4894 			playlist_rename(pt->pl, (char *)pl_name);
4895 			xmlFree(pl_name);
4896 		}
4897 	}
4898 
4899 	if (playlist_get_playing() == NULL) {
4900 		int pl_act;
4901 		parse_playlist_status(doc, cur, &pl_act, NULL, NULL);
4902 		playlist_set_playing(pt->pl, pl_act);
4903 	}
4904 
4905 	pt->xml_doc = doc;
4906 	pt->xml_node = cur;
4907 
4908 	playlist_progress_bar_show(pt->pl);
4909 	AQUALUNG_THREAD_CREATE(pt->pl->thread_id, NULL,
4910 			       playlist_load_xml_thread, pt);
4911 }
4912 
4913 void
playlist_load_xml_multi(char * filename,int start_playback)4914 playlist_load_xml_multi(char * filename, int start_playback) {
4915 
4916         xmlDocPtr doc;
4917         xmlNodePtr root;
4918         xmlNodePtr cur;
4919 	int * ref = NULL;
4920 	int current = -1;
4921 	int force_active = start_playback;
4922 	int i = gtk_notebook_get_n_pages(GTK_NOTEBOOK(playlist_notebook));
4923 
4924         doc = xmlParseFile(filename);
4925         if (doc == NULL) {
4926                 fprintf(stderr, "An XML error occured while parsing %s\n", filename);
4927                 return;
4928         }
4929 
4930         root = xmlDocGetRootElement(doc);
4931         if (root == NULL) {
4932                 fprintf(stderr, "empty XML document\n");
4933                 xmlFreeDoc(doc);
4934                 return;
4935         }
4936 
4937 	if (xmlStrcmp(root->name, (const xmlChar *)"aqualung_playlist_tabs")) {
4938 		fprintf(stderr, "XML document of the wrong type\n");
4939 		xmlFreeDoc(doc);
4940 		return;
4941 	}
4942 
4943 	if ((ref = (int *)malloc(sizeof(int *))) == NULL) {
4944 		fprintf(stderr, "playlist_load_xml_multi(): malloc error\n");
4945 		return;
4946 	}
4947 
4948 	*ref = 1;
4949 
4950 	if (start_playback) {
4951 		for (cur = root->xmlChildrenNode; cur != NULL; cur = cur->next) {
4952 			if (!xmlStrcmp(cur->name, (const xmlChar *)"tab")) {
4953 				int pl_act;
4954 				parse_playlist_status(doc, cur, &pl_act, NULL, NULL);
4955 				if (pl_act) {
4956 					force_active = 0;
4957 					break;
4958 				}
4959 			}
4960 		}
4961 	}
4962 
4963 	for (cur = root->xmlChildrenNode; cur != NULL; cur = cur->next) {
4964 
4965 		if (!xmlStrcmp(cur->name, (const xmlChar *)"tab")) {
4966 
4967 			playlist_t * pl = NULL;
4968 			playlist_transfer_t * pt = NULL;
4969 			xmlChar * pl_name = NULL;
4970 			int pl_act, pl_cur;
4971 
4972 			pl_name = parse_playlist_name(doc, cur);
4973 			pl = playlist_tab_new((char *)pl_name);
4974 			if (pl_name != NULL) {
4975 				xmlFree(pl_name);
4976 			}
4977 
4978 			parse_playlist_status(doc, cur, &pl_act, &pl_cur, NULL);
4979 			if (playlist_get_playing() == NULL) {
4980 				playlist_set_playing(pl, pl_act);
4981 			}
4982 
4983 			pt = playlist_transfer_new(pl);
4984 
4985 			pt->xml_doc = doc;
4986 			pt->xml_node = cur;
4987 			pt->xml_ref = ref;
4988 
4989 			*ref += 1;
4990 
4991 			if (pl_act || (pl_cur && force_active)) {
4992 				pt->start_playback = start_playback;
4993 				start_playback = 0;
4994 				force_active = 0;
4995 			}
4996 			if (pl_cur) {
4997 				current = i;
4998 			}
4999 
5000 			++i;
5001 
5002 			playlist_progress_bar_show(pt->pl);
5003 			AQUALUNG_THREAD_CREATE(pt->pl->thread_id, NULL,
5004 					       playlist_load_xml_thread, pt);
5005 		}
5006 	}
5007 
5008 	*ref -= 1;
5009 
5010 	gtk_notebook_set_current_page(GTK_NOTEBOOK(playlist_notebook), current);
5011 }
5012 
5013 void *
playlist_load_m3u_thread(void * arg)5014 playlist_load_m3u_thread(void * arg) {
5015 
5016 	FILE * f;
5017 	gint c;
5018 	gint i = 0;
5019 	gint n;
5020 	gchar * str;
5021 	gchar line[MAXLEN];
5022 	gchar name[MAXLEN];
5023 	gchar path[MAXLEN];
5024 	gchar tmp[MAXLEN];
5025 	gchar pl_dir[MAXLEN];
5026 	gint have_name = 0;
5027 	gint end_of_file_reached = 0;
5028 
5029 	playlist_transfer_t * pt = (playlist_transfer_t *)arg;
5030 	playlist_data_t * data = NULL;
5031 
5032 
5033 	AQUALUNG_THREAD_DETACH();
5034 
5035 	AQUALUNG_MUTEX_LOCK(pt->pl->thread_mutex);
5036 
5037 	if ((f = fopen(pt->filename, "rb")) == NULL) {
5038 		fprintf(stderr, "unable to open .m3u playlist: %s\n", pt->filename);
5039 		goto finish;
5040 	}
5041 
5042 	str = strrchr(pt->filename, '/');
5043 	for (i = 0; (i < (str - pt->filename)) && (i < MAXLEN-1); i++) {
5044  		pl_dir[i] = pt->filename[i];
5045  	}
5046  	pl_dir[i] = '\0';
5047 
5048 	i = 0;
5049 	while (!end_of_file_reached && !pt->pl->thread_stop) {
5050 		c = fgetc(f);
5051 		if ((c != EOF) && (c != '\n') && (c != '\r') && (i < MAXLEN-1)) {
5052 			if ((i > 0) || ((c != ' ') && (c != '\t'))) {
5053 				line[i++] = c;
5054 			}
5055 		} else {
5056 			end_of_file_reached = (c == EOF);
5057 			line[i] = '\0';
5058 			if (i == 0) {
5059 				continue;
5060 			}
5061 			i = 0;
5062 
5063 			if (strstr(line, "#EXTM3U") == line) {
5064 				continue;
5065 			}
5066 
5067 			if (strstr(line, "#EXTINF:") == line) {
5068 				int cnt = 0;
5069 				while ((line[cnt+8] >= '0') && (line[cnt+8] <= '9') && cnt < 63) {
5070 					++cnt;
5071 				}
5072 				snprintf(name, MAXLEN-1, "%s", line+cnt+9);
5073 				have_name = 1;
5074 			} else {
5075                                 if (!httpc_is_url(line)) {
5076                                         /* safeguard against C:\ stuff */
5077                                         if ((line[1] == ':') && (line[2] == '\\')) {
5078                                                 fprintf(stderr, "Ignoring playlist item: %s\n", line);
5079                                                 i = 0;
5080                                                 have_name = 0;
5081                                                 continue;
5082                                         }
5083 
5084                                         snprintf(path, MAXLEN-1, "%s", line);
5085 
5086                                         /* path curing: turn \-s into /-s */
5087                                         for (n = 0; n < strlen(path); n++) {
5088                                                 if (path[n] == '\\')
5089                                                         path[n] = '/';
5090                                         }
5091 
5092                                         if (path[0] != '/') {
5093                                                 strncpy(tmp, path, MAXLEN-1);
5094                                                 snprintf(path, MAXLEN-1, "%s/%s", pl_dir, tmp);
5095                                         }
5096 
5097                                         if (!have_name) {
5098                                                 gchar * ch;
5099                                                 if ((ch = strrchr(path, '/')) != NULL) {
5100                                                         ++ch;
5101                                                         snprintf(name, MAXLEN-1, "%s", ch);
5102                                                 } else {
5103                                                         fprintf(stderr,
5104                                                                 "warning: ain't this a directory? : %s\n", path);
5105                                                         snprintf(name, MAXLEN-1, "%s", path);
5106                                                 }
5107                                         }
5108 
5109                                         data = playlist_filemeta_get(path);
5110                                 } else {
5111 
5112                                         data = playlist_filemeta_get(line);
5113                                 }
5114 
5115 				if (have_name && data != NULL) {
5116 					free_strdup(&data->title, name);
5117 				}
5118 
5119 				have_name = 0;
5120 
5121 				if (data == NULL) {
5122 					fprintf(stderr, "load_m3u(): playlist_filemeta_get() returned NULL\n");
5123 				} else {
5124 					playlist_thread_add_to_list(pt, data);
5125 				}
5126 			}
5127 		}
5128 	}
5129 
5130 	playlist_thread_add_to_list(pt, NULL);
5131 
5132  finish:
5133 
5134 	AQUALUNG_MUTEX_UNLOCK(pt->pl->thread_mutex);
5135 
5136 	playlist_transfer_free(pt);
5137 
5138 	return NULL;
5139 }
5140 
5141 
5142 void
load_pls_load(playlist_transfer_t * pt,char * file,char * title,gint * have_file,gint * have_title)5143 load_pls_load(playlist_transfer_t * pt, char * file, char * title, gint * have_file, gint * have_title) {
5144 
5145 	playlist_data_t * data = NULL;
5146 
5147 	if (*have_file == 0) {
5148 		return;
5149 	}
5150 
5151 	data = playlist_filemeta_get(file);
5152 
5153 	if (data && *have_title) {
5154 		free_strdup(&data->title, title);
5155 	}
5156 
5157 	if (data == NULL) {
5158 		fprintf(stderr, "load_pls_load(): playlist_filemeta_get() returned NULL\n");
5159 	} else {
5160 		playlist_thread_add_to_list(pt, data);
5161 	}
5162 
5163 	*have_file = *have_title = 0;
5164 }
5165 
5166 void *
playlist_load_pls_thread(void * arg)5167 playlist_load_pls_thread(void * arg) {
5168 
5169 	FILE * f;
5170 	gint c;
5171 	gint i = 0;
5172 	gint n;
5173 	gchar * str;
5174 	gchar line[MAXLEN];
5175 	gchar file[MAXLEN];
5176 	gchar title[MAXLEN];
5177 	gchar tmp[MAXLEN];
5178 	gchar pl_dir[MAXLEN];
5179 	gint have_file = 0;
5180 	gint have_title = 0;
5181 	gchar numstr_file[10];
5182 	gchar numstr_title[10];
5183 	gint end_of_file_reached = 0;
5184 
5185 	playlist_transfer_t * pt = (playlist_transfer_t *)arg;
5186 
5187 
5188 	AQUALUNG_THREAD_DETACH();
5189 
5190 	AQUALUNG_MUTEX_LOCK(pt->pl->thread_mutex);
5191 
5192 	if ((f = fopen(pt->filename, "rb")) == NULL) {
5193 		fprintf(stderr, "unable to open .pls playlist: %s\n", pt->filename);
5194 		goto finish;
5195 	}
5196 
5197 	str = strrchr(pt->filename, '/');
5198 	for (i = 0; (i < (str - pt->filename)) && (i < MAXLEN-1); i++) {
5199  		pl_dir[i] = pt->filename[i];
5200  	}
5201  	pl_dir[i] = '\0';
5202 
5203 	i = 0;
5204 	while (!end_of_file_reached && !pt->pl->thread_stop) {
5205 		c = fgetc(f);
5206 		if ((c != EOF) && (c != '\n') && (c != '\r') && (i < MAXLEN-1)) {
5207 			if ((i > 0) || ((c != ' ') && (c != '\t'))) {
5208 				line[i++] = c;
5209 			}
5210 		} else {
5211 			end_of_file_reached = (c == EOF);
5212 			line[i] = '\0';
5213 			if (i == 0)
5214 				continue;
5215 			i = 0;
5216 
5217 			if (strstr(line, "[playlist]") == line) {
5218 				continue;
5219 			}
5220 			if (strcasestr(line, "NumberOfEntries") == line) {
5221 				continue;
5222 			}
5223 			if (strstr(line, "Version") == line) {
5224 				continue;
5225 			}
5226 
5227 			if (strstr(line, "File") == line) {
5228 
5229 				char numstr[10];
5230 				char * ch;
5231 				int m;
5232 
5233 				if (have_file) {
5234 					load_pls_load(pt, file, title, &have_file, &have_title);
5235 				}
5236 
5237 				if ((ch = strstr(line, "=")) == NULL) {
5238 					fprintf(stderr, "Syntax error in %s\n", pt->filename);
5239 					goto finish;
5240 				}
5241 
5242 				++ch;
5243 				while ((*ch == ' ') || (*ch == '\t'))
5244 					++ch;
5245 
5246                                 if (!httpc_is_url(ch)) {
5247 
5248                                         m = 0;
5249                                         for (n = 0; (line[n+4] != '=') && (m < sizeof(numstr)); n++) {
5250                                                 if ((line[n+4] != ' ') && (line[n+4] != '\t'))
5251                                                         numstr[m++] = line[n+4];
5252                                         }
5253                                         numstr[m] = '\0';
5254                                         strncpy(numstr_file, numstr, sizeof(numstr_file));
5255 
5256                                         /* safeguard against C:\ stuff */
5257                                         if ((ch[1] == ':') && (ch[2] == '\\')) {
5258                                                 fprintf(stderr, "Ignoring playlist item: %s\n", ch);
5259                                                 i = 0;
5260                                                 have_file = have_title = 0;
5261                                                 continue;
5262                                         }
5263 
5264                                         snprintf(file, MAXLEN-1, "%s", ch);
5265 
5266                                         /* path curing: turn \-s into /-s */
5267 
5268                                         for (n = 0; n < strlen(file); n++) {
5269                                                 if (file[n] == '\\')
5270                                                         file[n] = '/';
5271                                         }
5272 
5273                                         if (file[0] != '/') {
5274                                                 strncpy(tmp, file, MAXLEN-1);
5275                                                 snprintf(file, MAXLEN-1, "%s/%s", pl_dir, tmp);
5276                                         }
5277 
5278                                 } else {
5279                                         snprintf(file, MAXLEN-1, "%s", ch);
5280                                 }
5281 
5282 				have_file = 1;
5283 
5284 			} else if (strstr(line, "Title") == line) {
5285 
5286 				char numstr[10];
5287 				char * ch;
5288 				int m;
5289 
5290 				if ((ch = strstr(line, "=")) == NULL) {
5291 					fprintf(stderr, "Syntax error in %s\n", pt->filename);
5292 					goto finish;
5293 				}
5294 
5295 				++ch;
5296 				while ((*ch == ' ') || (*ch == '\t'))
5297 					++ch;
5298 
5299 				m = 0;
5300 				for (n = 0; (line[n+5] != '=') && (m < sizeof(numstr)); n++) {
5301 					if ((line[n+5] != ' ') && (line[n+5] != '\t'))
5302 						numstr[m++] = line[n+5];
5303 				}
5304 				numstr[m] = '\0';
5305 				strncpy(numstr_title, numstr, sizeof(numstr_title));
5306 
5307 				snprintf(title, MAXLEN-1, "%s", ch);
5308 				have_title = 1;
5309 
5310 
5311 			} else if (strstr(line, "Length") == line) {
5312 
5313 				/* We parse the timing, but throw it away.
5314 				   This may change in the future. */
5315 
5316 				char * ch;
5317 				if ((ch = strstr(line, "=")) == NULL) {
5318 					fprintf(stderr, "Syntax error in %s\n", pt->filename);
5319 					goto finish;
5320 				}
5321 
5322 				++ch;
5323 				while ((*ch == ' ') || (*ch == '\t'))
5324 					++ch;
5325 
5326 			} else {
5327 				fprintf(stderr,
5328 					"Syntax error: invalid line in playlist: %s\n", line);
5329 				goto finish;
5330 			}
5331 
5332 			if (!have_file || !have_title) {
5333 				continue;
5334 			}
5335 
5336 			if (strcmp(numstr_file, numstr_title) != 0) {
5337 				continue;
5338 			}
5339 
5340 			load_pls_load(pt, file, title, &have_file, &have_title);
5341 		}
5342 	}
5343 
5344 	load_pls_load(pt, file, title, &have_file, &have_title);
5345 
5346 	playlist_thread_add_to_list(pt, NULL);
5347 
5348  finish:
5349 
5350 	AQUALUNG_MUTEX_UNLOCK(pt->pl->thread_mutex);
5351 
5352 	playlist_transfer_free(pt);
5353 
5354 	return NULL;
5355 }
5356 
5357 
5358 int
playlist_get_type(char * filename)5359 playlist_get_type(char * filename) {
5360 
5361 	FILE * f;
5362 	gchar buf1[] = "<?xml version=\"1.0\"?>\n<aqualung_playlist_tabs"; /* this must be the 1st */
5363 	gchar buf2[] = "<?xml version=\"1.0\"?>\n<aqualung_playlist";      /* this must be the 2nd */
5364 	gchar inbuf[48];
5365 	gint len;
5366 
5367 	if ((f = fopen(filename, "rb")) == NULL) {
5368 		return 0;
5369 	}
5370 
5371 	len = fread(inbuf, 1, 47, f);
5372 	fclose(f);
5373 
5374 	inbuf[len] = '\0';
5375 
5376 	if (strstr(inbuf, buf1) == inbuf) {
5377 		return PLAYLIST_XML_MULTI;
5378 	}
5379 
5380 	if (strstr(inbuf, buf2) == inbuf) {
5381 		return PLAYLIST_XML_SINGLE;
5382 	}
5383 
5384 	/* not Aqualung playlist, decide by file extension */
5385 
5386 	len = strlen(filename);
5387 	if (len < 5) {
5388 		return PLAYLIST_INVALID;
5389 	}
5390 
5391 	if ((filename[len-4] == '.') &&
5392 	    ((filename[len-3] == 'm') || (filename[len-3] == 'M')) &&
5393 	    (filename[len-2] == '3') &&
5394 	    ((filename[len-1] == 'u') || (filename[len-1] == 'U'))) {
5395 		return PLAYLIST_M3U;
5396 	}
5397 
5398 	if ((filename[len-4] == '.') &&
5399 	    ((filename[len-3] == 'p') || (filename[len-3] == 'P')) &&
5400 	    ((filename[len-2] == 'l') || (filename[len-2] == 'L')) &&
5401 	    ((filename[len-1] == 's') || (filename[len-1] == 'S'))) {
5402 		return PLAYLIST_PLS;
5403 	}
5404 
5405 	return PLAYLIST_INVALID;
5406 }
5407 
5408 
5409 void
init_plist_menu(GtkWidget * append_menu)5410 init_plist_menu(GtkWidget *append_menu) {
5411 
5412 	GtkWidget * separator;
5413 
5414         plist__tab_new = gtk_menu_item_new_with_label(_("New tab"));
5415         plist__save = gtk_menu_item_new_with_label(_("Save playlist"));
5416         plist__save_all = gtk_menu_item_new_with_label(_("Save all playlists"));
5417         plist__load_tab = gtk_menu_item_new_with_label(_("Load playlist in new tab"));
5418         plist__load = gtk_menu_item_new_with_label(_("Load playlist"));
5419         plist__enqueue = gtk_menu_item_new_with_label(_("Enqueue playlist"));
5420 #ifdef HAVE_IFP
5421         plist__send_songs_to_iriver = gtk_menu_item_new_with_label(_("Send to iFP device"));
5422 #endif  /* HAVE_IFP */
5423 #ifdef HAVE_TRANSCODING
5424         plist__export = gtk_menu_item_new_with_label(_("Export files"));
5425 #endif /* HAVE_TRANSCODING */
5426         plist__rva = gtk_menu_item_new_with_label(_("Calculate RVA"));
5427         plist__rva_menu = gtk_menu_new();
5428         plist__rva_separate = gtk_menu_item_new_with_label(_("Separate"));
5429         plist__rva_average = gtk_menu_item_new_with_label(_("Average"));
5430         plist__reread_file_meta = gtk_menu_item_new_with_label(_("Reread file metadata"));
5431         plist__fileinfo = gtk_menu_item_new_with_label(_("File info..."));
5432         plist__search = gtk_menu_item_new_with_label(_("Search..."));
5433 	plist__roll = gtk_menu_item_new_with_label(_("Roll to active song"));
5434 
5435         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__tab_new);
5436 
5437 	separator = gtk_separator_menu_item_new();
5438         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), separator);
5439 	gtk_widget_show(separator);
5440 
5441         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__save);
5442         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__save_all);
5443 
5444 	separator = gtk_separator_menu_item_new();
5445         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), separator);
5446 	gtk_widget_show(separator);
5447 
5448         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__load);
5449         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__load_tab);
5450         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__enqueue);
5451 
5452 	separator = gtk_separator_menu_item_new();
5453         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), separator);
5454 	gtk_widget_show(separator);
5455 
5456 #ifdef HAVE_IFP
5457         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__send_songs_to_iriver);
5458 #endif  /* HAVE_IFP */
5459 #ifdef HAVE_TRANSCODING
5460         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__export);
5461 #endif /* HAVE_TRANSCODING */
5462 
5463 	separator = gtk_separator_menu_item_new();
5464         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), separator);
5465 	gtk_widget_show(separator);
5466 
5467         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__rva);
5468         gtk_menu_item_set_submenu(GTK_MENU_ITEM(plist__rva), plist__rva_menu);
5469         gtk_menu_shell_append(GTK_MENU_SHELL(plist__rva_menu), plist__rva_separate);
5470         gtk_menu_shell_append(GTK_MENU_SHELL(plist__rva_menu), plist__rva_average);
5471         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__reread_file_meta);
5472 
5473 	separator = gtk_separator_menu_item_new();
5474         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), separator);
5475 	gtk_widget_show(separator);
5476 
5477         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__fileinfo);
5478         gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__search);
5479 	gtk_menu_shell_append(GTK_MENU_SHELL(append_menu), plist__roll);
5480 
5481         add_custom_command_menu_to_playlist_menu(append_menu);
5482 
5483         g_signal_connect_swapped(G_OBJECT(plist__tab_new), "activate", G_CALLBACK(tab__new_cb), NULL);
5484         g_signal_connect_swapped(G_OBJECT(plist__save), "activate", G_CALLBACK(plist__save_cb), NULL);
5485         g_signal_connect_swapped(G_OBJECT(plist__save_all), "activate", G_CALLBACK(plist__save_all_cb), NULL);
5486         g_signal_connect_swapped(G_OBJECT(plist__load_tab), "activate", G_CALLBACK(plist__load_tab_cb), NULL);
5487         g_signal_connect_swapped(G_OBJECT(plist__load), "activate", G_CALLBACK(plist__load_cb), NULL);
5488         g_signal_connect_swapped(G_OBJECT(plist__enqueue), "activate", G_CALLBACK(plist__enqueue_cb), NULL);
5489         g_signal_connect_swapped(G_OBJECT(plist__rva_separate), "activate", G_CALLBACK(plist__rva_separate_cb), NULL);
5490         g_signal_connect_swapped(G_OBJECT(plist__rva_average), "activate", G_CALLBACK(plist__rva_average_cb), NULL);
5491         g_signal_connect_swapped(G_OBJECT(plist__reread_file_meta), "activate", G_CALLBACK(plist__reread_file_meta_cb), NULL);
5492 
5493 #ifdef HAVE_IFP
5494         g_signal_connect_swapped(G_OBJECT(plist__send_songs_to_iriver), "activate", G_CALLBACK(plist__send_songs_to_iriver_cb), NULL);
5495 #endif  /* HAVE_IFP */
5496 #ifdef HAVE_TRANSCODING
5497         g_signal_connect_swapped(G_OBJECT(plist__export), "activate", G_CALLBACK(plist__export_cb), NULL);
5498 #endif /* HAVE_TRANSCODING */
5499 
5500         g_signal_connect_swapped(G_OBJECT(plist__fileinfo), "activate", G_CALLBACK(plist__fileinfo_cb), NULL);
5501         g_signal_connect_swapped(G_OBJECT(plist__search), "activate", G_CALLBACK(plist__search_cb), NULL);
5502 	g_signal_connect_swapped(G_OBJECT(plist__roll), "activate", G_CALLBACK(plist__roll_cb), NULL);
5503 
5504         gtk_widget_show(plist__tab_new);
5505         gtk_widget_show(plist__save);
5506         gtk_widget_show(plist__save_all);
5507         gtk_widget_show(plist__load_tab);
5508         gtk_widget_show(plist__load);
5509         gtk_widget_show(plist__enqueue);
5510         gtk_widget_show(plist__rva);
5511         gtk_widget_show(plist__rva_separate);
5512         gtk_widget_show(plist__rva_average);
5513         gtk_widget_show(plist__reread_file_meta);
5514 #ifdef HAVE_IFP
5515         gtk_widget_show(plist__send_songs_to_iriver);
5516 #endif  /* HAVE_IFP */
5517 #ifdef HAVE_TRANSCODING
5518         gtk_widget_show(plist__export);
5519 #endif /* HAVE_TRANSCODING */
5520         gtk_widget_show(plist__fileinfo);
5521         gtk_widget_show(plist__search);
5522 	gtk_widget_show(plist__roll);
5523 }
5524 
5525 
5526 void
show_active_position_in_playlist(playlist_t * pl)5527 show_active_position_in_playlist(playlist_t * pl) {
5528 
5529         GtkTreeIter iter;
5530 	playlist_data_t * data;
5531         int flag = 0;
5532 
5533         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(pl->store), &iter)) {
5534 
5535                 do {
5536 			gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter, PL_COL_DATA, &data, -1);
5537 		        if (IS_PL_ACTIVE(data)) {
5538                                 flag = 1;
5539                                 break;
5540                         }
5541 		} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(pl->store), &iter));
5542 
5543                 if (!flag) {
5544                         gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, 0);
5545                 }
5546 
5547                 set_cursor_in_playlist(pl, &iter, TRUE);
5548         }
5549 }
5550 
5551 void
show_active_position_in_playlist_toggle(playlist_t * pl)5552 show_active_position_in_playlist_toggle(playlist_t * pl) {
5553 
5554         GtkTreeIter iter, iter_child;
5555 	playlist_data_t * data;
5556         int flag, cflag, j;
5557 
5558 
5559         flag = cflag = 0;
5560 
5561         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(pl->store), &iter)) {
5562 
5563                 do {
5564 			gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter, PL_COL_DATA, &data, -1);
5565 		        if (IS_PL_ACTIVE(data)) {
5566                                 flag = 1;
5567 		                if (gtk_tree_selection_iter_is_selected(pl->select, &iter) &&
5568                                     gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pl->store), &iter)) {
5569 
5570 					GtkTreePath * visible_path;
5571 
5572                                         gtk_tree_view_get_cursor(GTK_TREE_VIEW(pl->view), &visible_path, NULL);
5573 
5574                                         if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(pl->view), visible_path)) {
5575                                                 gtk_tree_view_expand_row(GTK_TREE_VIEW(pl->view), visible_path, FALSE);
5576                                         }
5577 
5578 					gtk_tree_path_free(visible_path);
5579 
5580                                         j = 0;
5581                                         while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter_child, &iter, j++)) {
5582                                                 gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter_child,
5583 								   PL_COL_DATA, &data, -1);
5584                                                 if (IS_PL_ACTIVE(data)) {
5585                                                         cflag = 1;
5586                                                         break;
5587                                                 }
5588                                         }
5589                                 }
5590                                 break;
5591                         }
5592 		} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(pl->store), &iter));
5593 
5594                 if (flag) {
5595                         if (cflag) {
5596                                 set_cursor_in_playlist(pl, &iter_child, TRUE);
5597                         } else {
5598                                 set_cursor_in_playlist(pl, &iter, TRUE);
5599                         }
5600                 }
5601         }
5602 }
5603 
5604 
5605 void
expand_collapse_album_node(playlist_t * pl)5606 expand_collapse_album_node(playlist_t * pl) {
5607 
5608 	GtkTreeIter iter, iter_child;
5609 	gint i, j;
5610         GtkTreePath *path;
5611 
5612         i = 0;
5613 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter, NULL, i++)) {
5614 
5615 		if (gtk_tree_selection_iter_is_selected(pl->select, &iter) &&
5616                     gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pl->store), &iter)) {
5617 
5618                         gtk_tree_view_get_cursor(GTK_TREE_VIEW(pl->view), &path, NULL);
5619 
5620                         if (path && gtk_tree_view_row_expanded(GTK_TREE_VIEW(pl->view), path)) {
5621                                 gtk_tree_view_collapse_row(GTK_TREE_VIEW(pl->view), path);
5622                         } else {
5623                                 gtk_tree_view_expand_row(GTK_TREE_VIEW(pl->view), path, FALSE);
5624                         }
5625 
5626                         continue;
5627                 }
5628 
5629                 j = 0;
5630 		while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pl->store), &iter_child, &iter, j++)) {
5631 
5632                         if (gtk_tree_selection_iter_is_selected(pl->select, &iter_child) &&
5633                             gtk_tree_model_iter_parent(GTK_TREE_MODEL(pl->store), &iter, &iter_child)) {
5634 
5635                                 path = gtk_tree_model_get_path (GTK_TREE_MODEL(pl->store), &iter);
5636 
5637                                 if (path) {
5638                                         gtk_tree_view_collapse_row(GTK_TREE_VIEW(pl->view), path);
5639                                         gtk_tree_view_set_cursor(GTK_TREE_VIEW(pl->view), path, NULL, FALSE);
5640                                 }
5641                         }
5642                 }
5643         }
5644 }
5645 
5646 
5647 static gboolean
pl_progress_bar_update(gpointer data)5648 pl_progress_bar_update(gpointer data) {
5649 
5650 	playlist_t * pl = (playlist_t *)data;
5651 
5652 	if (pl->progbar != NULL) {
5653 		gtk_progress_bar_pulse(GTK_PROGRESS_BAR(pl->progbar));
5654 		return TRUE;
5655 	}
5656 
5657 	return FALSE;
5658 }
5659 
5660 static void
pl_progress_bar_stop_clicked(GtkWidget * widget,gpointer data)5661 pl_progress_bar_stop_clicked(GtkWidget * widget, gpointer data) {
5662 
5663 	playlist_t * pl = (playlist_t *)data;
5664 
5665 	pl->thread_stop = 1;
5666 }
5667 
5668 void
playlist_progress_bar_show(playlist_t * pl)5669 playlist_progress_bar_show(playlist_t * pl) {
5670 
5671 	pl->progbar_semaphore++;
5672 
5673 	if (pl->progbar != NULL) {
5674 		return;
5675 	}
5676 
5677 	pl->thread_stop = 0;
5678 
5679 	playlist_stats_set_busy();
5680 
5681 	pl->progbar = gtk_progress_bar_new();
5682         gtk_box_pack_start(GTK_BOX(pl->progbar_container), pl->progbar, TRUE, TRUE, 0);
5683 
5684 	pl->progbar_stop_button = gtk_button_new_with_label(_("Stop adding files"));
5685         gtk_box_pack_start(GTK_BOX(pl->progbar_container), pl->progbar_stop_button, FALSE, FALSE, 0);
5686         g_signal_connect(G_OBJECT(pl->progbar_stop_button), "clicked",
5687 			 G_CALLBACK(pl_progress_bar_stop_clicked), pl);
5688 
5689 	gtk_widget_show_all(pl->progbar_container);
5690 
5691 	aqualung_timeout_add(200, pl_progress_bar_update, pl);
5692 }
5693 
5694 
5695 void
playlist_progress_bar_hide(playlist_t * pl)5696 playlist_progress_bar_hide(playlist_t * pl) {
5697 
5698 	if (pl->progbar != NULL) {
5699 		gtk_widget_destroy(pl->progbar);
5700 		pl->progbar = NULL;
5701 	}
5702 
5703 	if (pl->progbar_stop_button != NULL) {
5704 		gtk_widget_destroy(pl->progbar_stop_button);
5705 		pl->progbar_stop_button = NULL;
5706 	}
5707 }
5708 
5709 void
playlist_progress_bar_hide_all()5710 playlist_progress_bar_hide_all() {
5711 
5712 	GList * list;
5713 
5714 	for (list = playlists; list; list = list->next) {
5715 		playlist_progress_bar_hide((playlist_t *)list->data);
5716 	}
5717 }
5718 
5719 
5720 int
playlist_unlink_files_foreach(playlist_t * pl,GtkTreeIter * iter,void * data)5721 playlist_unlink_files_foreach(playlist_t * pl, GtkTreeIter * iter, void * data) {
5722 
5723 	playlist_data_t * pldata;
5724 
5725 	gtk_tree_model_get(GTK_TREE_MODEL(pl->store), iter, PL_COL_DATA, &pldata, -1);
5726 
5727 	if (g_file_test(pldata->file, G_FILE_TEST_EXISTS)) {
5728 
5729 		GtkListStore * store = (GtkListStore *)data;
5730 		GtkTreeIter i;
5731 
5732 		gtk_list_store_append(store, &i);
5733 		gtk_list_store_set(store, &i, 0, pldata->file, 1, gtk_tree_iter_copy(iter), -1);
5734 	}
5735 
5736 	return 0;
5737 }
5738 
5739 void
playlist_unlink_files(playlist_t * pl)5740 playlist_unlink_files(playlist_t * pl) {
5741 
5742 	GtkWidget * view;
5743 	GtkWidget * scrwin;
5744 	GtkWidget * viewport;
5745 	GtkCellRenderer * cell;
5746 	GtkTreeViewColumn * column;
5747 	GtkListStore * store;
5748 	GtkTreeIter iter;
5749 	int res;
5750 	int i;
5751 	int error = 0;
5752 
5753 
5754 	store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
5755 
5756 	playlist_foreach_selected(pl, playlist_unlink_files_foreach, store);
5757 
5758 	if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL) == 0) {
5759 		return;
5760 	}
5761 
5762 	view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
5763 	cell = gtk_cell_renderer_text_new();
5764 	column = gtk_tree_view_column_new_with_attributes(_("Files selected for removal"), cell, "text", 0, NULL);
5765 	gtk_tree_view_append_column(GTK_TREE_VIEW(view), GTK_TREE_VIEW_COLUMN(column));
5766 
5767 	viewport = gtk_viewport_new(NULL, NULL);
5768 	scrwin = gtk_scrolled_window_new(NULL, NULL);
5769 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
5770         gtk_widget_set_size_request(scrwin, -1, 200);
5771 
5772 	gtk_container_add(GTK_CONTAINER(viewport), scrwin);
5773 	gtk_container_add(GTK_CONTAINER(scrwin), view);
5774 
5775 	res = message_dialog(_("Remove files"),
5776 		       options.playlist_is_embedded ? main_window : playlist_window,
5777 		       GTK_MESSAGE_QUESTION,
5778 		       GTK_BUTTONS_YES_NO,
5779 		       viewport,
5780 		       _("The selected files will be deleted from the filesystem. "
5781 			 "No recovery will be possible after this operation.\n\n"
5782 			 "Do you want to proceed?"));
5783 
5784 	i = 0;
5785 	while (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter, NULL, i++)) {
5786 
5787 		GtkTreeIter * pl_iter;
5788 
5789 		gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 1, &pl_iter, -1);
5790 
5791 		if (res == GTK_RESPONSE_YES) {
5792 
5793 			gchar * file;
5794 			gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &file, -1);
5795 
5796 			if (unlink(file) != 0) {
5797 				fprintf(stderr, "unlink: unable to unlink %s\n", file);
5798 				perror("unlink");
5799 				gtk_tree_selection_unselect_iter(pl->select, pl_iter);
5800 				++error;
5801 			}
5802 
5803 			g_free(file);
5804 		}
5805 
5806 		gtk_tree_iter_free(pl_iter);
5807 	}
5808 
5809 	if (res == GTK_RESPONSE_YES) {
5810 		rem__sel_cb(pl);
5811 	}
5812 
5813 	if (error) {
5814 		message_dialog(_("Remove files"),
5815 			       options.playlist_is_embedded ? main_window : playlist_window,
5816 			       GTK_MESSAGE_WARNING,
5817 			       GTK_BUTTONS_OK,
5818 			       NULL,
5819 			       (error == 1) ? _("Unable to remove %d file.") : _("Unable to remove %d files."),
5820 			       error);
5821 	}
5822 }
5823 
5824 
5825 void
set_cursor_in_playlist(playlist_t * pl,GtkTreeIter * iter,gboolean scroll)5826 set_cursor_in_playlist(playlist_t * pl, GtkTreeIter *iter, gboolean scroll) {
5827 
5828         GtkTreePath * visible_path;
5829 
5830         visible_path = gtk_tree_model_get_path(GTK_TREE_MODEL(pl->store), iter);
5831 
5832         if (visible_path) {
5833                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(pl->view), visible_path, NULL, FALSE);
5834                 if (scroll == TRUE) {
5835                         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW (pl->view), visible_path,
5836 						     NULL, TRUE, 0.5, 0.0);
5837                 }
5838                 gtk_tree_path_free(visible_path);
5839         }
5840 }
5841 
5842 void
select_active_position_in_playlist(playlist_t * pl)5843 select_active_position_in_playlist(playlist_t * pl) {
5844 
5845 	GtkTreeIter iter;
5846 	playlist_data_t * data;
5847 
5848         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(pl->store), &iter)) {
5849 
5850                 do {
5851 			gtk_tree_model_get(GTK_TREE_MODEL(pl->store), &iter, PL_COL_DATA, &data, -1);
5852 		        if (IS_PL_ACTIVE(data)) {
5853                                 set_cursor_in_playlist(pl, &iter, FALSE);
5854 				break;
5855                         }
5856 		} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(pl->store), &iter));
5857         }
5858 }
5859 
5860 void
roll_to_active_track(playlist_t * pl,GtkTreeIter * piter)5861 roll_to_active_track(playlist_t * pl, GtkTreeIter * piter) {
5862 	GtkTreePath * active_path;
5863 
5864 	active_path = gtk_tree_model_get_path(GTK_TREE_MODEL(pl->store), piter);
5865 
5866 	if (active_path) {
5867 		gtk_tree_view_expand_to_path(GTK_TREE_VIEW(pl->view), active_path);
5868 		gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(pl->view), active_path, NULL, TRUE, 0.5, 0.0);
5869 	}
5870 	gtk_tree_path_free(active_path);
5871 }
5872 
5873 /* passed as fileinfo_model_func_t argument to show_file_info */
5874 gboolean
playlist_model_func(GtkTreeModel * model,GtkTreeIter iter,char ** name,char ** file)5875 playlist_model_func(GtkTreeModel * model, GtkTreeIter iter, char**name, char**file) {
5876 	playlist_data_t * data;
5877 	gtk_tree_model_get(model, &iter, PL_COL_NAME, name, -1);
5878 	gtk_tree_model_get(model, &iter, PL_COL_DATA, &data, -1);
5879 	*file = strdup(data->file);
5880 	return TRUE;
5881 }
5882 
5883 // vim: shiftwidth=8:tabstop=8:softtabstop=8 :
5884 
5885