1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /***************************************************************************
3 *            play-list.c
4 *
5 *  mer mai 25 22:22:53 2005
6 *  Copyright  2005  Philippe Rouquier
7 *  brasero-app@wanadoo.fr
8 ****************************************************************************/
9 
10 /*
11  *  Brasero is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2 of the License, or
14  *  (at your option) any later version.
15  *
16  *  Brasero is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU Library General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to:
23  * 	The Free Software Foundation, Inc.,
24  * 	51 Franklin Street, Fifth Floor
25  * 	Boston, MA  02110-1301, USA.
26  */
27 
28 
29 #ifdef HAVE_CONFIG_H
30 #  include <config.h>
31 #endif
32 
33 #include <string.h>
34 
35 #include <glib.h>
36 #include <glib/gi18n-lib.h>
37 #include <glib-object.h>
38 
39 #include <gio/gio.h>
40 
41 #include <gtk/gtk.h>
42 
43 #include <totem-pl-parser.h>
44 
45 #include "brasero-misc.h"
46 
47 #include "brasero-units.h"
48 
49 #include "brasero-app.h"
50 #include "brasero-playlist.h"
51 #include "brasero-search-engine.h"
52 #include "brasero-utils.h"
53 #include "brasero-metadata.h"
54 #include "brasero-io.h"
55 #include "eggtreemultidnd.h"
56 
57 #include "brasero-uri-container.h"
58 #include "brasero-layout-object.h"
59 
60 
61 struct BraseroPlaylistPrivate {
62 	BraseroSearchEngine *engine;
63 	int id;
64 
65 	GtkWidget *tree;
66 	GtkWidget *button_add;
67 	GtkWidget *button_remove;
68 	guint activity_counter;
69 
70 	BraseroIOJobBase *parse_type;
71 
72 	gint searched:1;
73 };
74 
75 enum {
76 	BRASERO_PLAYLIST_DISPLAY_COL,
77 	BRASERO_PLAYLIST_NB_SONGS_COL,
78 	BRASERO_PLAYLIST_LEN_COL,
79 	BRASERO_PLAYLIST_GENRE_COL,
80 	BRASERO_PLAYLIST_URI_COL,
81 	BRASERO_PLAYLIST_DSIZE_COL,
82 	BRASERO_PLAYLIST_NB_COL,
83 };
84 
85 enum {
86 	TARGET_URIS_LIST,
87 };
88 
89 static GtkTargetEntry ntables[] = {
90 	{"text/uri-list", 0, TARGET_URIS_LIST}
91 };
92 static guint nb_ntables = sizeof (ntables) / sizeof (ntables[0]);
93 
94 static void brasero_playlist_iface_uri_container_init (BraseroURIContainerIFace *iface);
95 static void brasero_playlist_iface_layout_object_init (BraseroLayoutObjectIFace *iface);
96 
97 G_DEFINE_TYPE_WITH_CODE (BraseroPlaylist,
98 			 brasero_playlist,
99 			 GTK_TYPE_BOX,
100 			 G_IMPLEMENT_INTERFACE (BRASERO_TYPE_URI_CONTAINER,
101 					        brasero_playlist_iface_uri_container_init)
102 			 G_IMPLEMENT_INTERFACE (BRASERO_TYPE_LAYOUT_OBJECT,
103 					        brasero_playlist_iface_layout_object_init));
104 
105 #define BRASERO_PLAYLIST_SPACING 6
106 
107 static void
brasero_playlist_get_proportion(BraseroLayoutObject * object,gint * header,gint * center,gint * footer)108 brasero_playlist_get_proportion (BraseroLayoutObject *object,
109 				 gint *header,
110 				 gint *center,
111 				 gint *footer)
112 {
113 	GtkRequisition requisition;
114 
115 	gtk_widget_get_preferred_size (gtk_widget_get_parent (BRASERO_PLAYLIST (object)->priv->button_add),
116 				 &requisition, NULL);
117 	(*footer) = requisition.height + BRASERO_PLAYLIST_SPACING;
118 }
119 
120 static void
brasero_playlist_increase_activity_counter(BraseroPlaylist * playlist)121 brasero_playlist_increase_activity_counter (BraseroPlaylist *playlist)
122 {
123 	GdkWindow *window;
124 
125 	window = gtk_widget_get_window (GTK_WIDGET (playlist->priv->tree));
126 	if (!window)
127 		return;
128 
129 	if (playlist->priv->activity_counter == 0) {
130 		GdkCursor *cursor;
131 
132 		cursor = gdk_cursor_new (GDK_WATCH);
133 		gdk_window_set_cursor (window,
134 				       cursor);
135 		g_object_unref (cursor);
136 	}
137 	playlist->priv->activity_counter++;
138 }
139 
140 static void
brasero_playlist_decrease_activity_counter(BraseroPlaylist * playlist)141 brasero_playlist_decrease_activity_counter (BraseroPlaylist *playlist)
142 {
143 	GdkWindow *window;
144 
145 	if (playlist->priv->activity_counter > 0)
146 		playlist->priv->activity_counter--;
147 
148 	window = gtk_widget_get_window (GTK_WIDGET (playlist->priv->tree));
149 	if (!window)
150 		return;
151 
152 	if (playlist->priv->activity_counter == 0)
153 		gdk_window_set_cursor (window, NULL);
154 }
155 
156 static void
brasero_playlist_search_playlists_rhythmbox(BraseroPlaylist * playlist)157 brasero_playlist_search_playlists_rhythmbox (BraseroPlaylist *playlist)
158 {
159 /*	RBSource *source;
160 
161 	manager = rb_playlist_manager_new ();
162 	lists = rb_playlist_manager_get_playlists (manager);
163 */
164 }
165 
166 static void
brasero_playlist_search(BraseroPlaylist * playlist)167 brasero_playlist_search (BraseroPlaylist *playlist)
168 {
169 	const gchar* mimes [] = {"audio/x-ms-asx",
170 		"audio/x-mpegurl",
171 		"audio/x-scpls",
172 		"audio/x-mp3-playlist",
173 		NULL};
174 
175 	brasero_search_engine_new_query (playlist->priv->engine, NULL);
176 	brasero_search_engine_set_query_mime (playlist->priv->engine, mimes);
177 	brasero_search_engine_start_query (playlist->priv->engine);
178 	brasero_playlist_increase_activity_counter (playlist);
179 }
180 
181 static gboolean
brasero_playlist_try_again(BraseroPlaylist * playlist)182 brasero_playlist_try_again (BraseroPlaylist *playlist)
183 {
184 	if (!brasero_search_engine_is_available (playlist->priv->engine))
185 		return TRUE;
186 
187 	brasero_playlist_search (playlist);
188 
189 	playlist->priv->id = 0;
190 	return FALSE;
191 }
192 
193 static void
brasero_playlist_start_search(BraseroPlaylist * playlist)194 brasero_playlist_start_search (BraseroPlaylist *playlist)
195 {
196 	if (!playlist->priv->engine)
197 		return;
198 
199 	if (!brasero_search_engine_is_available (playlist->priv->engine)) {
200 		/* we will retry in 10 seconds */
201 		playlist->priv->id = g_timeout_add_seconds (10,
202 							    (GSourceFunc) brasero_playlist_try_again,
203 							    playlist);
204 		return;
205 	}
206 
207 	brasero_playlist_search (playlist);
208 }
209 
210 static gboolean
brasero_playlist_draw_cb(GtkWidget * widget,cairo_t * cr,gpointer null_data)211 brasero_playlist_draw_cb (GtkWidget *widget,
212 			  cairo_t *cr,
213 			  gpointer null_data)
214 {
215 	BraseroPlaylist *playlist = BRASERO_PLAYLIST (widget);
216 
217 	/* we only want to load playlists if the user is going to use them that
218 	 * is if they become apparent. That will avoid overhead */
219 	if (!playlist->priv->searched) {
220 		playlist->priv->searched = 1;
221 		brasero_playlist_start_search (playlist);
222 		brasero_playlist_search_playlists_rhythmbox (playlist);
223 	}
224 
225 	return FALSE;
226 }
227 
228 static gchar **
brasero_playlist_get_selected_uris_real(BraseroPlaylist * playlist)229 brasero_playlist_get_selected_uris_real (BraseroPlaylist *playlist)
230 {
231 	GtkTreeSelection *selection;
232 	GtkTreeModel *model;
233 	GtkTreePath *path;
234 	GList *rows, *iter;
235 	GtkTreeIter row;
236 	gchar **uris = NULL, *uri;
237 	GPtrArray *array;
238 	gboolean valid;
239 
240 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (playlist->priv->tree));
241 	rows = gtk_tree_selection_get_selected_rows (selection, &model);
242 
243 	if (rows == NULL)
244 		return NULL;
245 
246 	array = g_ptr_array_sized_new (g_list_length (rows) + 1);
247 	for (iter = rows; iter; iter = iter->next) {
248 		path = iter->data;
249 		valid = gtk_tree_model_get_iter (model, &row, path);
250 		gtk_tree_path_free (path);
251 
252 		if (valid == FALSE)
253 			continue;
254 
255 		/* FIXME : we must find a way to reverse the list */
256 		/* check if it is a list or not */
257 		if (gtk_tree_model_iter_has_child (model, &row)) {
258 			GtkTreeIter child;
259 
260 			if (gtk_tree_model_iter_children (model, &child, &row) == FALSE)
261 				continue;
262 
263 			do {
264 				/* first check if the row is selected to prevent to put it in the list twice */
265 				if (gtk_tree_selection_iter_is_selected (selection, &child) == TRUE)
266 					continue;
267 
268 				gtk_tree_model_get (model, &child,
269 						    BRASERO_PLAYLIST_URI_COL, &uri,
270 						    -1);
271 				g_ptr_array_add (array, uri);
272 			} while (gtk_tree_model_iter_next (model, &child));
273 
274 			continue;
275 		}
276 
277 		gtk_tree_model_get (model, &row,
278 				    BRASERO_PLAYLIST_URI_COL, &uri,
279 				    -1);
280 		g_ptr_array_add (array, uri);
281 	}
282 
283 	g_list_free (rows);
284 
285 	g_ptr_array_set_size (array, array->len + 1);
286 	uris = (gchar **) array->pdata;
287 	g_ptr_array_free (array, FALSE);
288 	return uris;
289 }
290 
291 static gchar **
brasero_playlist_get_selected_uris(BraseroURIContainer * container)292 brasero_playlist_get_selected_uris (BraseroURIContainer *container)
293 {
294 	BraseroPlaylist *playlist;
295 
296 	playlist = BRASERO_PLAYLIST (container);
297 	return brasero_playlist_get_selected_uris_real (playlist);
298 }
299 
300 static gchar *
brasero_playlist_get_selected_uri(BraseroURIContainer * container)301 brasero_playlist_get_selected_uri (BraseroURIContainer *container)
302 {
303 	BraseroPlaylist *playlist;
304 	gchar **uris = NULL;
305 	gchar *uri;
306 
307 	playlist = BRASERO_PLAYLIST (container);
308 	uris = brasero_playlist_get_selected_uris_real (playlist);
309 
310 	if (uris) {
311 		uri = g_strdup (uris [0]);
312 		g_strfreev (uris);
313 		return uri;
314 	}
315 
316 	return NULL;
317 }
318 
319 static void
brasero_playlist_remove_cb(GtkButton * button,BraseroPlaylist * playlist)320 brasero_playlist_remove_cb (GtkButton *button, BraseroPlaylist *playlist)
321 {
322 	GtkTreeSelection *selection;
323 	GtkTreeModel *model;
324 	GtkTreePath *path;
325 	GtkTreeIter row;
326 	GList *rows, *iter;
327 	gboolean valid;
328 
329 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (playlist->priv->tree));
330 	rows = gtk_tree_selection_get_selected_rows (selection, &model);
331 
332 	if (rows == NULL)
333 		return;
334 
335 	/* we just remove the lists removing particular songs would be a nonsense */
336 	/* we must reverse the list otherwise the last paths wouldn't be valid */
337 	for (iter = g_list_last (rows); iter; iter = iter->prev) {
338 		path = iter->data;
339 		valid = gtk_tree_model_get_iter (model, &row, path);
340 		gtk_tree_path_free (path);
341 
342 		if (valid == FALSE)	/* if we remove the whole list it could happen that we try to remove twice a song */
343 			continue;
344 
345 		if (gtk_tree_model_iter_has_child (model, &row)) {
346 			GtkTreeIter child;
347 
348 			/* we remove the songs if it's a list */
349 			gtk_tree_model_iter_children (model, &child, &row);
350 			while (gtk_tree_store_remove (GTK_TREE_STORE (model), &child) == TRUE);
351 			gtk_tree_store_remove (GTK_TREE_STORE (model),
352 					       &row);
353 		}
354 	}
355 
356 	g_list_free (rows);
357 }
358 
359 static void
brasero_playlist_drag_data_get_cb(GtkTreeView * tree,GdkDragContext * drag_context,GtkSelectionData * selection_data,guint info,guint time,BraseroPlaylist * playlist)360 brasero_playlist_drag_data_get_cb (GtkTreeView *tree,
361 				   GdkDragContext *drag_context,
362 				   GtkSelectionData *selection_data,
363 				   guint info,
364 				   guint time,
365 				   BraseroPlaylist *playlist)
366 {
367 	gchar **uris;
368 
369 	uris = brasero_playlist_get_selected_uris_real (playlist);
370 	gtk_selection_data_set_uris (selection_data, uris);
371 	g_strfreev (uris);
372 }
373 
374 struct _BraseroPlaylistParseData {
375 	GtkTreeRowReference *reference;
376 
377 	guint title:1;
378 	guint quiet:1;
379 };
380 typedef struct _BraseroPlaylistParseData BraseroPlaylistParseData;
381 
382 static void
brasero_playlist_dialog_error(BraseroPlaylist * playlist,const gchar * uri)383 brasero_playlist_dialog_error (BraseroPlaylist *playlist, const gchar *uri)
384 {
385 	gchar *name;
386 	gchar *primary;
387 
388 	BRASERO_GET_BASENAME_FOR_DISPLAY (uri, name);
389 
390 	primary = g_strdup_printf (_("Error parsing playlist \"%s\"."), name);
391 	brasero_app_alert (brasero_app_get_default (),
392 			   primary,
393 			   _("An unknown error occurred"),
394 			   GTK_MESSAGE_ERROR);
395 	g_free (primary);
396 	g_free (name);
397 }
398 
399 static void
brasero_playlist_parse_end(GObject * object,gboolean cancelled,gpointer callback_data)400 brasero_playlist_parse_end (GObject *object,
401 			    gboolean cancelled,
402 			    gpointer callback_data)
403 {
404 	BraseroPlaylistParseData *data = callback_data;
405 	BraseroPlaylist *playlist = BRASERO_PLAYLIST (object);
406 
407 	brasero_playlist_decrease_activity_counter (playlist);
408 
409 	gtk_tree_row_reference_free (data->reference);
410 	g_free (data);
411 }
412 
413 static void
brasero_playlist_parse_result(GObject * object,GError * error,const gchar * uri,GFileInfo * info,gpointer callback_data)414 brasero_playlist_parse_result (GObject *object,
415 			       GError *error,
416 			       const gchar *uri,
417 			       GFileInfo *info,
418 			       gpointer callback_data)
419 {
420 	gint num;
421 	gint64 total_length;
422 	GtkTreeModel *model;
423 	GtkTreePath *treepath;
424 	GtkTreeIter parent, row;
425 	gchar *len_string, *num_string;
426 	BraseroPlaylistParseData *data = callback_data;
427 	BraseroPlaylist *playlist = BRASERO_PLAYLIST (object);
428 
429 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (playlist->priv->tree));
430 	treepath = gtk_tree_row_reference_get_path (data->reference);
431 	gtk_tree_model_get_iter (model, &parent, treepath);
432 	gtk_tree_path_free (treepath);
433 
434 	if (info && g_file_info_get_attribute_boolean (info, BRASERO_IO_IS_PLAYLIST)) {
435 		const gchar *playlist_title = NULL;
436 
437 		/* The first entry returned is always the playlist as a whole:
438 		 * if it was successfully parsed uri is the title if any. If not
439 		 * it's simply the URI */
440 
441 		/* this is for the playlist as a whole */
442 		if (error) {
443 			if (!data->quiet)
444 				brasero_playlist_dialog_error (playlist, uri);
445 
446 			gtk_list_store_remove (GTK_LIST_STORE (model), &parent);
447 			data->title = 1;
448 			return;
449 		}
450 
451 		playlist_title = g_file_info_get_attribute_string (info, BRASERO_IO_PLAYLIST_TITLE);
452 		if (playlist_title)
453 			gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
454 					    BRASERO_PLAYLIST_DISPLAY_COL, playlist_title,
455 					    -1);
456 
457 		data->title = 1;
458 		return;
459 	}
460 
461     	/* See if the song can be added */
462 	if (!error && info && g_file_info_get_attribute_boolean (info, BRASERO_IO_HAS_AUDIO)) {
463 		gchar *name;
464 		guint64 len;
465 		const gchar *title;
466 		const gchar *genre;
467 
468 		gtk_tree_store_append (GTK_TREE_STORE (model), &row, &parent);
469 
470 		len = g_file_info_get_attribute_uint64 (info, BRASERO_IO_LEN);
471 		title = g_file_info_get_attribute_string (info, BRASERO_IO_TITLE);
472 		genre = g_file_info_get_attribute_string (info, BRASERO_IO_GENRE);
473 
474 		if (len > 0)
475 			len_string = brasero_units_get_time_string (len, TRUE, FALSE);
476 		else
477 			len_string = NULL;
478 
479 		BRASERO_GET_BASENAME_FOR_DISPLAY (uri, name);
480 		gtk_tree_store_set (GTK_TREE_STORE (model), &row,
481 				    BRASERO_PLAYLIST_DISPLAY_COL, title ? title : name,
482 				    BRASERO_PLAYLIST_LEN_COL, len_string,
483 				    BRASERO_PLAYLIST_GENRE_COL, genre,
484 				    BRASERO_PLAYLIST_URI_COL, uri,
485 				    BRASERO_PLAYLIST_DSIZE_COL, len,
486 				    -1);
487 		g_free (name);
488 		g_free (len_string);
489 
490 		if (len)
491 			total_length += len;
492 	}
493 
494 	/* update the playlist information */
495 	num = gtk_tree_model_iter_n_children (model, &parent);
496 	if (!num)
497 		num_string = g_strdup (_("Empty"));
498 	else	/* Translators: %d is the number of songs */
499 		num_string = g_strdup_printf (ngettext ("%d song", "%d songs", num), num);
500 
501 	/* get total length in time of the playlist */
502 	gtk_tree_model_get (model, &parent,
503 			    BRASERO_PLAYLIST_DSIZE_COL, &total_length,
504 			    -1);
505 
506   	if (total_length > 0)
507 		len_string = brasero_units_get_time_string (total_length, TRUE, FALSE);
508 	else
509 		len_string = NULL;
510 
511 	gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
512 			    BRASERO_PLAYLIST_NB_SONGS_COL, num_string,
513 			    BRASERO_PLAYLIST_LEN_COL, len_string,
514 			    BRASERO_PLAYLIST_DSIZE_COL, total_length,
515 			    -1);
516 	g_free (len_string);
517 	g_free (num_string);
518 }
519 
520 static GtkTreeRowReference *
brasero_playlist_insert(BraseroPlaylist * playlist,const gchar * uri)521 brasero_playlist_insert (BraseroPlaylist *playlist, const gchar *uri)
522 {
523 	gchar *name;
524 	GtkTreeIter parent;
525 	GtkTreeModel *model;
526 	GtkTreePath *treepath;
527 	GtkTreeRowReference *reference;
528 
529 	/* actually add it */
530 	BRASERO_GET_BASENAME_FOR_DISPLAY (uri, name);
531 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (playlist->priv->tree));
532 	gtk_tree_store_append (GTK_TREE_STORE (model), &parent, NULL);
533 	gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
534 			    BRASERO_PLAYLIST_DISPLAY_COL, name,
535 			    BRASERO_PLAYLIST_URI_COL, uri,
536 			    BRASERO_PLAYLIST_NB_SONGS_COL, _("(loading…)"),
537 			    -1);
538 	g_free (name);
539 
540 	treepath = gtk_tree_model_get_path (model, &parent);
541 	reference = gtk_tree_row_reference_new (model, treepath);
542 	gtk_tree_path_free (treepath);
543 
544 	return reference;
545 }
546 
547 static void
brasero_playlist_add_uri_playlist(BraseroPlaylist * playlist,const gchar * uri,gboolean quiet)548 brasero_playlist_add_uri_playlist (BraseroPlaylist *playlist,
549 				   const gchar *uri,
550 				   gboolean quiet)
551 {
552 	BraseroPlaylistParseData *data;
553 
554 	data = g_new0 (BraseroPlaylistParseData, 1);
555 	data->reference = brasero_playlist_insert (playlist, uri);
556 
557 	if (!playlist->priv->parse_type)
558 		playlist->priv->parse_type = brasero_io_register (G_OBJECT (playlist),
559 								  brasero_playlist_parse_result,
560 								  brasero_playlist_parse_end,
561 								  NULL);
562 
563 	brasero_io_parse_playlist (uri,
564 				   playlist->priv->parse_type,
565 				   BRASERO_IO_INFO_PERM|
566 				   BRASERO_IO_INFO_MIME|
567 				   BRASERO_IO_INFO_METADATA,
568 				   data);
569 	brasero_playlist_increase_activity_counter (playlist);
570 }
571 
572 static void
brasero_playlist_add_cb(GtkButton * button,BraseroPlaylist * playlist)573 brasero_playlist_add_cb (GtkButton *button, BraseroPlaylist *playlist)
574 {
575 	GtkWidget *dialog, *toplevel;
576 	gchar *uri;
577 	GSList *uris, *iter;
578 	gint result;
579 
580 	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (playlist));
581 	if (!gtk_widget_is_toplevel (toplevel))
582 		return;
583 
584 	dialog = gtk_file_chooser_dialog_new (_("Select Playlist"),
585 					      GTK_WINDOW (toplevel),
586 					      GTK_FILE_CHOOSER_ACTION_OPEN,
587 					      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
588 					      GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
589 					      NULL);
590 
591 	gtk_dialog_set_default_response (GTK_DIALOG (dialog),
592 					 GTK_RESPONSE_ACCEPT);
593 	gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE);
594 	gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE);
595 	gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog),
596 						 g_get_home_dir ());
597 
598 	gtk_widget_show_all (dialog);
599 	result = gtk_dialog_run (GTK_DIALOG (dialog));
600 	if (result == GTK_RESPONSE_CANCEL) {
601 		gtk_widget_destroy (dialog);
602 		return;
603 	}
604 
605 	uris = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (dialog));
606 	gtk_widget_destroy (dialog);
607 
608 	for (iter = uris; iter; iter = iter->next) {
609 		uri = iter->data;
610 		brasero_playlist_add_uri_playlist (playlist, uri, FALSE);
611 		g_free (uri);
612 	}
613 	g_slist_free (uris);
614 }
615 
616 static void
brasero_playlist_search_hit_added_cb(BraseroSearchEngine * engine,gpointer hit,BraseroPlaylist * playlist)617 brasero_playlist_search_hit_added_cb (BraseroSearchEngine *engine,
618                                       gpointer hit,
619                                       BraseroPlaylist *playlist)
620 {
621 	const char *uri;
622 
623 	uri = brasero_search_engine_uri_from_hit (engine, hit);
624 	brasero_playlist_add_uri_playlist (playlist, uri, TRUE);
625 	brasero_playlist_decrease_activity_counter (playlist);
626 }
627 
628 static void
brasero_playlist_search_hit_substracted_cb(BraseroSearchEngine * engine,gpointer hit,BraseroPlaylist * playlist)629 brasero_playlist_search_hit_substracted_cb (BraseroSearchEngine *engine,
630                                             gpointer hit,
631                                             BraseroPlaylist *playlist)
632 {
633 	GtkTreeModel *model;
634 	GtkTreeIter row;
635 	const gchar *uri;
636 
637 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (playlist->priv->tree));
638 	uri = brasero_search_engine_uri_from_hit (engine, hit);
639 
640 	if (!gtk_tree_model_get_iter_first (model, &row))
641 		return;
642 
643 	do {
644 		char *row_uri;
645 
646 		gtk_tree_model_get (model, &row,
647 				    BRASERO_PLAYLIST_URI_COL, &row_uri,
648 				    -1);
649 
650 		if (!strcmp (row_uri, uri)) {
651 			gtk_tree_store_remove (GTK_TREE_STORE (model), &row);
652 			g_free (row_uri);
653 			return;
654 		}
655 
656 		g_free (row_uri);
657 	} while (gtk_tree_model_iter_next (model, &row));
658 }
659 
660 static void
brasero_playlist_search_finished_cb(BraseroSearchEngine * engine,BraseroPlaylist * playlist)661 brasero_playlist_search_finished_cb (BraseroSearchEngine *engine,
662                                      BraseroPlaylist *playlist)
663 {
664 	brasero_playlist_decrease_activity_counter (playlist);
665 }
666 
667 static void
brasero_playlist_search_error_cb(BraseroSearchEngine * engine,BraseroPlaylist * playlist)668 brasero_playlist_search_error_cb (BraseroSearchEngine *engine,
669                                   BraseroPlaylist *playlist)
670 {
671 
672 }
673 
674 static void
brasero_playlist_row_activated_cb(GtkTreeView * tree,GtkTreeIter * row,GtkTreeViewColumn * column,BraseroPlaylist * playlist)675 brasero_playlist_row_activated_cb (GtkTreeView *tree,
676 				   GtkTreeIter *row,
677 				   GtkTreeViewColumn *column,
678 				   BraseroPlaylist *playlist)
679 {
680 	brasero_uri_container_uri_activated (BRASERO_URI_CONTAINER (playlist));
681 }
682 
683 static void
brasero_playlist_selection_changed_cb(GtkTreeSelection * selection,BraseroPlaylist * playlist)684 brasero_playlist_selection_changed_cb (GtkTreeSelection *selection,
685 				       BraseroPlaylist *playlist)
686 {
687 	brasero_uri_container_uri_selected (BRASERO_URI_CONTAINER (playlist));
688 }
689 
690 static void
brasero_playlist_init(BraseroPlaylist * obj)691 brasero_playlist_init (BraseroPlaylist *obj)
692 {
693 	GtkWidget *hbox, *scroll;
694 	GtkTreeStore *store = NULL;
695 	GtkCellRenderer *renderer;
696 	GtkTreeViewColumn *column;
697 
698 	obj->priv = g_new0 (BraseroPlaylistPrivate, 1);
699 	gtk_box_set_spacing (GTK_BOX (obj), BRASERO_PLAYLIST_SPACING);
700 	gtk_orientable_set_orientation (GTK_ORIENTABLE (obj), GTK_ORIENTATION_VERTICAL);
701 
702 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8);
703 	gtk_widget_show (hbox);
704 
705 	obj->priv->button_add = gtk_button_new_from_stock (GTK_STOCK_ADD);
706 	gtk_widget_show (obj->priv->button_add);
707 	gtk_box_pack_end (GTK_BOX (hbox),
708 			  obj->priv->button_add,
709 			  FALSE,
710 			  FALSE,
711 			  0);
712 	g_signal_connect (G_OBJECT (obj->priv->button_add),
713 			  "clicked",
714 			  G_CALLBACK (brasero_playlist_add_cb),
715 			  obj);
716 
717 	obj->priv->button_remove = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
718 	gtk_widget_show (obj->priv->button_remove);
719 	gtk_box_pack_end (GTK_BOX (hbox),
720 			  obj->priv->button_remove,
721 			  FALSE,
722 			  FALSE,
723 			  0);
724 	g_signal_connect (G_OBJECT (obj->priv->button_remove),
725 			  "clicked",
726 			  G_CALLBACK (brasero_playlist_remove_cb),
727 			  obj);
728 
729 	gtk_box_pack_end (GTK_BOX (obj), hbox, FALSE, FALSE, 0);
730 
731 	store = gtk_tree_store_new (BRASERO_PLAYLIST_NB_COL,
732 				    G_TYPE_STRING,
733 				    G_TYPE_STRING,
734 				    G_TYPE_STRING,
735 				    G_TYPE_STRING,
736 				    G_TYPE_STRING,
737 				    G_TYPE_INT64);
738 
739 	obj->priv->tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
740 	egg_tree_multi_drag_add_drag_support (GTK_TREE_VIEW (obj->priv->tree));
741 
742 	gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (obj->priv->tree), TRUE);
743 	gtk_tree_view_set_rubber_banding (GTK_TREE_VIEW (obj->priv->tree), TRUE);
744 	gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW (obj->priv->tree), TRUE);
745 	gtk_tree_view_set_enable_search (GTK_TREE_VIEW (obj->priv->tree), TRUE);
746 	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (obj->priv->tree), TRUE);
747 	gtk_tree_view_set_expander_column (GTK_TREE_VIEW (obj->priv->tree),
748 					   BRASERO_PLAYLIST_DISPLAY_COL);
749 
750 	renderer = gtk_cell_renderer_text_new ();
751 	column = gtk_tree_view_column_new_with_attributes (_("Playlists"),
752 							   renderer, "text",
753 							   BRASERO_PLAYLIST_DISPLAY_COL,
754 							   NULL);
755 	gtk_tree_view_column_set_sort_column_id (column,
756 						 BRASERO_PLAYLIST_DISPLAY_COL);
757 	gtk_tree_view_append_column (GTK_TREE_VIEW (obj->priv->tree),
758 				     column);
759 	gtk_tree_view_column_set_expand (column, TRUE);
760 
761 	renderer = gtk_cell_renderer_text_new ();
762 	column = gtk_tree_view_column_new_with_attributes (_("Number of Songs"),
763 							   renderer, "text",
764 							   BRASERO_PLAYLIST_NB_SONGS_COL,
765 							   NULL);
766 	gtk_tree_view_column_set_sort_column_id (column,
767 						 BRASERO_PLAYLIST_NB_SONGS_COL);
768 	gtk_tree_view_append_column (GTK_TREE_VIEW (obj->priv->tree),
769 				     column);
770 
771 	renderer = gtk_cell_renderer_text_new ();
772 	column = gtk_tree_view_column_new_with_attributes (_("Length"),
773 							   renderer, "text",
774 							   BRASERO_PLAYLIST_LEN_COL,
775 							   NULL);
776 	gtk_tree_view_column_set_sort_column_id (column,
777 						 BRASERO_PLAYLIST_LEN_COL);
778 	gtk_tree_view_append_column (GTK_TREE_VIEW (obj->priv->tree),
779 				     column);
780 
781 	renderer = gtk_cell_renderer_text_new ();
782 	column = gtk_tree_view_column_new_with_attributes (_("Genre"), renderer,
783 							   "text",
784 							   BRASERO_PLAYLIST_GENRE_COL,
785 							   NULL);
786 	gtk_tree_view_column_set_sort_column_id (column,
787 						 BRASERO_PLAYLIST_GENRE_COL);
788 	gtk_tree_view_append_column (GTK_TREE_VIEW (obj->priv->tree),
789 				     column);
790 
791 	gtk_tree_view_set_search_column (GTK_TREE_VIEW (obj->priv->tree),
792 					 BRASERO_PLAYLIST_DISPLAY_COL);
793 	gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (obj->priv->tree),
794 						GDK_BUTTON1_MASK, ntables,
795 						nb_ntables,
796 						GDK_ACTION_COPY |
797 						GDK_ACTION_MOVE);
798 
799 	g_signal_connect (G_OBJECT (obj->priv->tree), "drag_data_get",
800 			  G_CALLBACK (brasero_playlist_drag_data_get_cb),
801 			  obj);
802 
803 	g_signal_connect (G_OBJECT (obj->priv->tree), "row_activated",
804 			  G_CALLBACK (brasero_playlist_row_activated_cb),
805 			  obj);
806 
807 	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->tree))),
808 			  "changed",
809 			  G_CALLBACK (brasero_playlist_selection_changed_cb),
810 			  obj);
811 
812 	gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->tree)),
813 				     GTK_SELECTION_MULTIPLE);
814 
815 	scroll = gtk_scrolled_window_new (NULL, NULL);
816 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
817 					GTK_POLICY_AUTOMATIC,
818 					GTK_POLICY_AUTOMATIC);
819 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll),
820 					     GTK_SHADOW_IN);
821 
822 	gtk_container_add (GTK_CONTAINER (scroll), obj->priv->tree);
823 	gtk_box_pack_start (GTK_BOX (obj), scroll, TRUE, TRUE, 0);
824 
825 	obj->priv->engine = brasero_search_engine_new_default ();
826 	if (!obj->priv->engine)
827 		return;
828 
829 	g_signal_connect (G_OBJECT (obj->priv->engine), "hit-added",
830 			  G_CALLBACK (brasero_playlist_search_hit_added_cb),
831 			  obj);
832 	g_signal_connect (G_OBJECT (obj->priv->engine), "hit-removed",
833 			  G_CALLBACK (brasero_playlist_search_hit_substracted_cb),
834 			  obj);
835 	g_signal_connect (G_OBJECT (obj->priv->engine), "search-finished",
836 			  G_CALLBACK (brasero_playlist_search_finished_cb),
837 			  obj);
838 	g_signal_connect (G_OBJECT (obj->priv->engine), "search-error",
839 			  G_CALLBACK (brasero_playlist_search_error_cb),
840 			  obj);
841 }
842 
843 static void
brasero_playlist_destroy(GtkWidget * object)844 brasero_playlist_destroy (GtkWidget *object)
845 {
846 	BraseroPlaylist *playlist = BRASERO_PLAYLIST (object);
847 
848 	if (playlist->priv->id) {
849 		g_source_remove (playlist->priv->id);
850 		playlist->priv->id = 0;
851 	}
852 
853 	if (playlist->priv->engine) {
854 		g_object_unref (playlist->priv->engine);
855 		playlist->priv->engine = NULL;
856 	}
857 
858 	/* NOTE: we must do it here since cancel could call brasero_playlist_end
859 	 * itself calling decrease_activity_counter. In finalize the latter will
860 	 * raise problems since the GdkWindow has been destroyed */
861 	if (playlist->priv->parse_type) {
862 		brasero_io_cancel_by_base (playlist->priv->parse_type);
863 		brasero_io_job_base_free (playlist->priv->parse_type);
864 		playlist->priv->parse_type = NULL;
865 	}
866 
867 	if (GTK_WIDGET_CLASS (brasero_playlist_parent_class)->destroy)
868 		GTK_WIDGET_CLASS (brasero_playlist_parent_class)->destroy (object);
869 }
870 
871 static void
brasero_playlist_finalize(GObject * object)872 brasero_playlist_finalize (GObject *object)
873 {
874 	BraseroPlaylist *cobj;
875 
876 	cobj = BRASERO_PLAYLIST (object);
877 
878 	g_free (cobj->priv);
879 
880 	G_OBJECT_CLASS (brasero_playlist_parent_class)->finalize (object);
881 }
882 
883 static void
brasero_playlist_class_init(BraseroPlaylistClass * klass)884 brasero_playlist_class_init (BraseroPlaylistClass *klass)
885 {
886 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
887 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
888 
889 	object_class->finalize = brasero_playlist_finalize;
890 	widget_class->destroy = brasero_playlist_destroy;
891 }
892 
893 static void
brasero_playlist_iface_uri_container_init(BraseroURIContainerIFace * iface)894 brasero_playlist_iface_uri_container_init (BraseroURIContainerIFace *iface)
895 {
896 	iface->get_selected_uri = brasero_playlist_get_selected_uri;
897 	iface->get_selected_uris = brasero_playlist_get_selected_uris;
898 }
899 
900 static void
brasero_playlist_iface_layout_object_init(BraseroLayoutObjectIFace * iface)901 brasero_playlist_iface_layout_object_init (BraseroLayoutObjectIFace *iface)
902 {
903 	iface->get_proportion = brasero_playlist_get_proportion;
904 }
905 
906 GtkWidget *
brasero_playlist_new()907 brasero_playlist_new ()
908 {
909 	BraseroPlaylist *obj;
910 
911 	obj = BRASERO_PLAYLIST (g_object_new (BRASERO_TYPE_PLAYLIST, NULL));
912 
913 	g_signal_connect (obj,
914 			  "draw",
915 			  G_CALLBACK (brasero_playlist_draw_cb),
916 			  NULL);
917 
918 	return GTK_WIDGET (obj);
919 }
920