static void
rb_static_playlist_source_add_location_internal (RBStaticPlaylistSource *source,
						 const char *location,
						 gint index)
{
	RhythmDB *db;
	RhythmDBEntry *entry;
	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
	if (rb_playlist_source_location_in_map (psource, location))
		return;

	db = rb_playlist_source_get_db (psource);
	entry = rhythmdb_entry_lookup_by_location (db, location);
	if (entry) {
		RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);

		if (_rb_source_check_entry_type (RB_SOURCE (source), entry)) {
			rhythmdb_entry_ref (entry);
			rhythmdb_query_model_add_entry (priv->base_model, entry, index);
			rhythmdb_entry_unref (entry);
		}
	}

	rb_playlist_source_add_to_map (psource, location);

	rb_playlist_source_mark_dirty (psource);
}
static void
rb_static_playlist_source_add_id_list (RBStaticPlaylistSource *source,
				       GList *list)
{
	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
	GList *i;
	gint id;

	g_return_if_fail (list != NULL);

	for (i = list; i != NULL; i = i->next) {
		RhythmDBEntry *entry;

		id = strtoul ((const char *)i->data, NULL, 0);
		if (id == 0)
			continue;

		entry = rhythmdb_entry_lookup_by_id (rb_playlist_source_get_db (psource), id);
		if (entry == NULL) {
			rb_debug ("received id %d, but can't find the entry", id);
			continue;
		}

		rb_static_playlist_source_add_entry (source, entry, -1);
	}
}
static void
rb_static_playlist_source_add_uri_list (RBStaticPlaylistSource *source,
					GList *list)
{
	GList *i, *uri_list = NULL;
	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
	RhythmDBEntry *entry;

	g_return_if_fail (list != NULL);

	for (i = list; i != NULL; i = g_list_next (i)) {
		char *uri = (char *) i->data;
		uri_list = g_list_prepend (uri_list, rb_canonicalise_uri (uri));
	}

	uri_list = g_list_reverse (uri_list);
	if (uri_list == NULL)
		return;

	for (i = uri_list; i != NULL; i = i->next) {
		char *uri = i->data;
		if (uri != NULL) {
			entry = rhythmdb_entry_lookup_by_location (rb_playlist_source_get_db (psource), uri);
			if (entry == NULL)
				rhythmdb_add_uri (rb_playlist_source_get_db (psource), uri);

			rb_static_playlist_source_add_location (source, uri, -1);
		}

		g_free (uri);
	}
	g_list_free (uri_list);
}
示例#4
0
static void
rb_playlist_source_drop_cb (GtkWidget *widget,
			    GdkDragContext *context,
			    gint x,
			    gint y,
			    GtkSelectionData *data,
			    guint info,
			    guint time,
			    gpointer user_data)
{
	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (user_data);
	GtkTargetList *tlist;
	GdkAtom target;

	tlist = gtk_target_list_new (target_uri, G_N_ELEMENTS (target_uri));
	target = gtk_drag_dest_find_target (widget, context, tlist);
	gtk_target_list_unref (tlist);

	if (target == GDK_NONE)
		return;

	rb_display_page_receive_drag (RB_DISPLAY_PAGE (source), data);

	gtk_drag_finish (context, TRUE, FALSE, time);
}
示例#5
0
static void
rb_playlist_source_dispose (GObject *object)
{
	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (object);

	if (source->priv->dispose_has_run) {
		/* If dispose did already run, return. */
		rb_debug ("Dispose has already run for playlist source %p", object);
		return;
	}
	/* Make sure dispose does not run twice. */
	source->priv->dispose_has_run = TRUE;

	rb_debug ("Disposing playlist source %p", source);

	if (source->priv->db != NULL) {
		g_object_unref (source->priv->db);
		source->priv->db = NULL;
	}

	if (source->priv->model != NULL) {
		g_object_unref (source->priv->model);
		source->priv->model = NULL;
	}

	G_OBJECT_CLASS (rb_playlist_source_parent_class)->dispose (object);
}
示例#6
0
static RBEntryView *
impl_get_entry_view (RBSource *asource)
{
	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (asource);

	return source->priv->songs;
}
static void
rb_static_playlist_source_rows_reordered (GtkTreeModel *model,
					  GtkTreePath *path,
					  GtkTreeIter *iter,
					  gint *order_map,
					  RBStaticPlaylistSource *source)
{
	rb_playlist_source_mark_dirty (RB_PLAYLIST_SOURCE (source));
}
static GPtrArray *
construct_query_from_selection (RBStaticPlaylistSource *source)
{
	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
	RhythmDB *db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (psource));
	GPtrArray *query = NULL;

	query = g_ptr_array_new();

	if (priv->search_query != NULL) {
		rhythmdb_query_append (db,
				       query,
				       RHYTHMDB_QUERY_SUBQUERY, priv->search_query,
				       RHYTHMDB_QUERY_END);
	}

	return query;
}
static void
rb_static_playlist_source_non_entry_dropped (GtkTreeModel *model,
					     const char *uri,
					     int position,
					     RBStaticPlaylistSource *source)
{
	g_assert (g_utf8_strlen (uri, -1) > 0);

	rhythmdb_add_uri (rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source)), uri);
	rb_static_playlist_source_add_location (source, uri, position);
}
示例#10
0
/**
 * rb_static_playlist_source_move_entry:
 * @source: an #RBStaticPlaylistSource
 * @entry: the entry to move
 * @index: new location for the entry
 *
 * Moves an entry within the playlist.
 */
void
rb_static_playlist_source_move_entry (RBStaticPlaylistSource *source,
				      RhythmDBEntry *entry,
				      gint index)
{
	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);

	rhythmdb_query_model_move_entry (priv->base_model, entry, index);

	rb_playlist_source_mark_dirty (psource);
}
示例#11
0
/**
 * rb_playlist_source_new_from_xml:
 * @shell: the #RBShell instance
 * @node: libxml node containing the playlist
 *
 * Constructs a playlist source instance from the XML serialized
 * format.  This function knows about all the playlist types that
 * can be saved to disk, and it hands off the XML node to the
 * appropriate constructor based on the 'type' attribute of
 * the root node of the playlist.
 *
 * Return value: the playlist
 */
RBSource *
rb_playlist_source_new_from_xml	(RBShell *shell,
				 xmlNodePtr node)
{
	RBSource *source = NULL;
	xmlChar *tmp;
	xmlChar *name;

	g_return_val_if_fail (RB_IS_SHELL (shell), NULL);

	/* Try to get name from XML and remove translated names */
	name = get_playlist_name_from_xml (node);

	tmp = xmlGetProp (node, RB_PLAYLIST_TYPE);

	if (!xmlStrcmp (tmp, RB_PLAYLIST_AUTOMATIC))
		source = rb_auto_playlist_source_new_from_xml (shell, (const char *)name, node);
	else if (!xmlStrcmp (tmp, RB_PLAYLIST_STATIC))
		source = rb_static_playlist_source_new_from_xml (shell, (const char *)name, node);
	else if (!xmlStrcmp (tmp, RB_PLAYLIST_QUEUE)) {
		RBStaticPlaylistSource *queue;

		g_object_get (shell, "queue-source", &queue, NULL);
		rb_static_playlist_source_load_from_xml (queue, node);
		apply_source_settings (RB_PLAYLIST_SOURCE (queue), node);
		g_object_unref (queue);
	} else {
		g_warning ("attempting to load playlist '%s' of unknown type '%s'", name, tmp);
	}

	xmlFree (name);
	xmlFree (tmp);

	if (source != NULL) {
		apply_source_settings (RB_PLAYLIST_SOURCE (source), node);
	}
	return source;
}
示例#12
0
static void
impl_song_properties (RBSource *asource)
{
	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (asource);
	GtkWidget *song_info = NULL;

	g_return_if_fail (source->priv->songs != NULL);

	song_info = rb_song_info_new (asource, NULL);
	if (song_info)
		gtk_widget_show_all (song_info);
	else
		rb_debug ("failed to create dialog, or no selection!");
}
示例#13
0
static void
rb_static_playlist_source_browser_changed_cb (RBLibraryBrowser *browser,
					      GParamSpec *pspec,
					      RBStaticPlaylistSource *source)
{
	RBEntryView *songs = rb_source_get_entry_view (RB_SOURCE (source));
	RhythmDBQueryModel *query_model;

	g_object_get (browser, "output-model", &query_model, NULL);
	rb_entry_view_set_model (songs, query_model);
	rb_playlist_source_set_query_model (RB_PLAYLIST_SOURCE (source), query_model);
	g_object_unref (query_model);

	rb_source_notify_filter_changed (RB_SOURCE (source));
}
示例#14
0
static void
rb_playlist_source_set_property (GObject *object,
				 guint prop_id,
				 const GValue *value,
				 GParamSpec *pspec)
{
	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (object);

	switch (prop_id) {
	case PROP_LOCAL:
		source->priv->is_local = g_value_get_boolean (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
示例#15
0
static void
rb_playlist_source_finalize (GObject *object)
{
	RBPlaylistSource *source;

	g_return_if_fail (object != NULL);
	g_return_if_fail (RB_IS_PLAYLIST_SOURCE (object));

	source = RB_PLAYLIST_SOURCE (object);
	g_return_if_fail (source->priv != NULL);

	rb_debug ("Finalizing playlist source %p", source);

	g_hash_table_destroy (source->priv->entries);

	g_free (source->priv->title);
	source->priv = NULL;

	G_OBJECT_CLASS (rb_playlist_source_parent_class)->finalize (object);
}
示例#16
0
static void
impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text)
{
	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
	RhythmDB *db;

	if (search == NULL) {
		search = priv->default_search;
	}

	/* replace our search query */
	if (priv->search_query != NULL) {
		rhythmdb_query_free (priv->search_query);
		priv->search_query = NULL;
	}
	db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
	priv->search_query = rb_source_search_create_query (search, db, new_text);

	rb_static_playlist_source_do_query (RB_STATIC_PLAYLIST_SOURCE (source));
}
示例#17
0
/**
 * rb_static_playlist_source_remove_location:
 * @source: an #RBStaticPlaylistSource
 * @location: location to remove
 *
 * Removes the specified location from the playlist.  This affects both
 * the location map and the query model, whether an entry exists for the
 * location or not.
 */
void
rb_static_playlist_source_remove_location (RBStaticPlaylistSource *source,
					   const char *location)
{
	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
	RhythmDB *db;
	RhythmDBEntry *entry;

	g_return_if_fail (rb_playlist_source_location_in_map (psource, location));

	db = rb_playlist_source_get_db (psource);
	entry = rhythmdb_entry_lookup_by_location (db, location);

	if (entry != NULL) {
		RhythmDBQueryModel *model = rb_playlist_source_get_query_model (psource);

		/* if this fails, the model and the playlist are out of sync */
		g_assert (rhythmdb_query_model_remove_entry (model, entry));
		rb_playlist_source_mark_dirty (psource);
	}
}
示例#18
0
static void
rb_static_playlist_source_do_query (RBStaticPlaylistSource *source)
{
	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
	RhythmDB *db = rb_playlist_source_get_db (psource);
	GPtrArray *query;

	if (priv->filter_model != NULL) {
		g_object_unref (priv->filter_model);
	}
	priv->filter_model = rhythmdb_query_model_new_empty (db);
	g_object_set (priv->filter_model, "base-model", priv->base_model, NULL);

	query = construct_query_from_selection (source);
	g_object_set (priv->filter_model, "query", query, NULL);
	rhythmdb_query_free (query);

	rhythmdb_query_model_reapply_query (priv->filter_model, TRUE);
	rb_library_browser_set_model (priv->browser, priv->filter_model, FALSE);
}
示例#19
0
/**
 * rb_static_playlist_source_add_location:
 * @source: an #RBStaticPlaylistSource
 * @location: location (URI) to add to the playlist
 * @index: position at which to add the location (-1 to add at the end)
 *
 * If the location matches an entry in the database, the entry is added
 * to the playlist.  Otherwise, if it identifies a directory, the contents
 * of that directory are added.
 */
void
rb_static_playlist_source_add_location (RBStaticPlaylistSource *source,
					const char *location,
					gint index)
{
	RhythmDB *db;
	RhythmDBEntry *entry;

	db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
	entry = rhythmdb_entry_lookup_by_location (db, location);

	/* if there is an entry, it won't be a directory */
	if (entry == NULL && rb_uri_is_directory (location))
		rb_uri_handle_recursively (location,
					   NULL,
					   (RBUriRecurseFunc) _add_location_cb,
					   source);
	else
		rb_static_playlist_source_add_location_internal (source, location, index);

}
static DMAPContainerRecord *
rb_dmap_container_db_adapter_lookup_by_id (DMAPContainerDb *db, guint id)
{
	gchar *name;
	GList *playlists;
	DMAPContainerRecord *fnval = NULL;

	playlists = rb_playlist_manager_get_playlists (RB_DMAP_CONTAINER_DB_ADAPTER (db)->priv->playlist_manager);

	if (playlists != NULL && playlists->data != NULL) {
		GList *result;
		result = g_list_find_custom (playlists, GINT_TO_POINTER (id), (GCompareFunc) find_by_id);
		if (result != NULL && result->data != NULL) {
			RBPlaylistSource *source;
			source = RB_PLAYLIST_SOURCE (result->data);
			g_object_get (source, "name", &name, NULL);
			fnval = DMAP_CONTAINER_RECORD (rb_daap_container_record_new (name, source));
		}
	}

	g_list_free (playlists);

	return fnval;
}
示例#21
0
static gboolean
impl_can_remove (RBDisplayPage *page)
{
	RBPlaylistSource *source = RB_PLAYLIST_SOURCE (page);
	return (source->priv->is_local);
}
示例#22
0
static void
rb_static_playlist_source_constructed (GObject *object)
{
	RBStaticPlaylistSource *source;
	RBStaticPlaylistSourcePrivate *priv;
	RBPlaylistSource *psource;
	RBEntryView *songs;
	RBShell *shell;
	RhythmDBEntryType *entry_type;

	RB_CHAIN_GOBJECT_METHOD (rb_static_playlist_source_parent_class, constructed, object);

	source = RB_STATIC_PLAYLIST_SOURCE (object);
	priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
	psource = RB_PLAYLIST_SOURCE (source);

	priv->base_model = rb_playlist_source_get_query_model (RB_PLAYLIST_SOURCE (psource));
	g_object_set (priv->base_model, "show-hidden", TRUE, NULL);
	g_object_ref (priv->base_model);
	g_signal_connect_object (priv->base_model,
				 "filter-entry-drop",
				 G_CALLBACK (rb_static_playlist_source_filter_entry_drop),
				 source, 0);

	priv->paned = gtk_vpaned_new ();

	g_object_get (source, "shell", &shell, NULL);
	priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
								     "StaticPlaylistActions",
								     NULL, 0,
								     shell);
	if (gtk_action_group_get_action (priv->action_group,
					 rb_static_playlist_source_radio_actions[0].name) == NULL) {
		gtk_action_group_add_radio_actions (priv->action_group,
						    rb_static_playlist_source_radio_actions,
						    G_N_ELEMENTS (rb_static_playlist_source_radio_actions),
						    0,
						    NULL,
						    NULL);
		rb_source_search_basic_create_for_actions (priv->action_group,
							   rb_static_playlist_source_radio_actions,
							   G_N_ELEMENTS (rb_static_playlist_source_radio_actions));
	}
	priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);

	g_object_unref (shell);

	g_object_get (source, "entry-type", &entry_type, NULL);
	priv->browser = rb_library_browser_new (rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source)),
						entry_type);
	if (entry_type != NULL) {
		g_object_unref (entry_type);
	}

	gtk_paned_pack1 (GTK_PANED (priv->paned), GTK_WIDGET (priv->browser), TRUE, FALSE);
	g_signal_connect_object (priv->browser, "notify::output-model",
				 G_CALLBACK (rb_static_playlist_source_browser_changed_cb),
				 source, 0);

	rb_library_browser_set_model (priv->browser, priv->base_model, FALSE);
	rb_static_playlist_source_do_query (source);

	/* reparent the entry view */
	songs = rb_source_get_entry_view (RB_SOURCE (source));
	g_object_ref (songs);
	gtk_container_remove (GTK_CONTAINER (source), GTK_WIDGET (songs));
	gtk_paned_pack2 (GTK_PANED (priv->paned), GTK_WIDGET (songs), TRUE, FALSE);
	gtk_container_add (GTK_CONTAINER (source), priv->paned);

	rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (songs), priv->paned, GTK_WIDGET (priv->browser));
	g_object_unref (songs);

	/* watch these to find out when things are dropped into the entry view */
	g_signal_connect_object (priv->base_model, "row-inserted",
				 G_CALLBACK (rb_static_playlist_source_row_inserted),
				 source, 0);
	g_signal_connect_object (priv->base_model, "non-entry-dropped",
				 G_CALLBACK (rb_static_playlist_source_non_entry_dropped),
				 source, 0);
	g_signal_connect_object (priv->base_model, "rows-reordered",
				 G_CALLBACK (rb_static_playlist_source_rows_reordered),
				 source, 0);

	gtk_widget_show_all (GTK_WIDGET (source));
}
示例#23
0
static void
rb_playlist_source_constructed (GObject *object)
{
	GObject *shell_player;
	RBPlaylistSource *source;
	RBShell *shell;
	RhythmDB *db;
	RhythmDBQueryModel *query_model;
	GtkBuilder *builder;
	GSettings *settings;

	RB_CHAIN_GOBJECT_METHOD (rb_playlist_source_parent_class, constructed, object);
	source = RB_PLAYLIST_SOURCE (object);

	g_object_get (source, "shell", &shell, NULL);
	g_object_get (shell,
		      "db", &db,
		      "shell-player", &shell_player,
		      NULL);
	rb_playlist_source_set_db (source, db);
	g_object_unref (db);

	g_object_unref (shell);

	/* store playlist settings using the memory backend
	 * this means the settings path doesn't have to be consistent,
	 * it just has to be unique, so the address of the source object works.
	 * for local playlists, we write the settings into the playlist file on disk
	 * to make them persistent.
	 */
	g_object_get (source, "settings", &settings, NULL);
	if (settings == NULL) {
		char *path;
		path = g_strdup_printf ("/org/gnome/rhythmbox/playlist/%p/", source);
		settings = g_settings_new_with_backend_and_path ("org.gnome.rhythmbox.source",
								 playlist_settings_backend,
								 path);
		g_free (path);

		g_object_set (source, "settings", settings, NULL);
	}

	g_signal_connect (settings, "changed", G_CALLBACK (playlist_settings_changed_cb), source);
	g_object_unref (settings);

	builder = rb_builder_load ("playlist-popup.ui", NULL);
	source->priv->popup = G_MENU (gtk_builder_get_object (builder, "playlist-popup"));
	rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), source->priv->popup);
	g_object_ref (source->priv->popup);
	g_object_unref (builder);

	source->priv->entries = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
						       (GDestroyNotify)rb_refstring_unref, NULL);

	source->priv->songs = rb_entry_view_new (source->priv->db,
						 shell_player,
						 TRUE, TRUE);
	g_object_unref (shell_player);

	g_signal_connect_object (source->priv->songs,
				 "notify::sort-order",
				 G_CALLBACK (rb_playlist_source_songs_sort_order_changed_cb),
				 source, 0);

	query_model = rhythmdb_query_model_new_empty (source->priv->db);
	rb_playlist_source_set_query_model (source, query_model);
	g_object_unref (query_model);

	{
		const char *title = "";
		const char *strings[3] = {0};

		GtkTreeViewColumn *column = gtk_tree_view_column_new ();
		GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();

		g_object_set(renderer,
			     "style", PANGO_STYLE_OBLIQUE,
			     "weight", PANGO_WEIGHT_LIGHT,
			     "xalign", 1.0,
			     NULL);

		gtk_tree_view_column_pack_start (column, renderer, TRUE);

		gtk_tree_view_column_set_resizable (column, TRUE);
		gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);

		strings[0] = title;
		strings[1] = "9999";
		rb_entry_view_set_fixed_column_width (source->priv->songs, column, renderer,
						      strings);
		gtk_tree_view_column_set_cell_data_func (column, renderer,
							 (GtkTreeCellDataFunc)
							 rb_playlist_source_track_cell_data_func,
							 source, NULL);
		rb_entry_view_insert_column_custom (source->priv->songs, column, title,
						    "PlaylistTrack", NULL, 0, NULL, 0);
	}

	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_TRACK_NUMBER, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_TITLE, TRUE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_GENRE, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_ARTIST, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_COMPOSER, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_ALBUM, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_YEAR, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_DURATION, FALSE);
 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_QUALITY, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_RATING, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_PLAY_COUNT, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_COMMENT, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_LOCATION, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_FIRST_SEEN, FALSE);
	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_BPM, FALSE);
	rb_entry_view_set_columns_clickable (source->priv->songs, FALSE);

	rb_playlist_source_setup_entry_view (source, source->priv->songs);

	gtk_container_add (GTK_CONTAINER (source), GTK_WIDGET (source->priv->songs));

	gtk_widget_show_all (GTK_WIDGET (source));
}