hythmbox-2.98/sources/rb-static-playlist-source.c

No issues found

   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
   2  *
   3  *  Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
   4  *  Copyright (C) 2003 Colin Walters <walters@gnome.org>
   5  *
   6  *  This program is free software; you can redistribute it and/or modify
   7  *  it under the terms of the GNU General Public License as published by
   8  *  the Free Software Foundation; either version 2 of the License, or
   9  *  (at your option) any later version.
  10  *
  11  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
  12  *  GStreamer plugins to be used and distributed together with GStreamer
  13  *  and Rhythmbox. This permission is above and beyond the permissions granted
  14  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  15  *  you may extend this exception to your version of the code, but you are not
  16  *  obligated to do so. If you do not wish to do so, delete this exception
  17  *  statement from your version.
  18  *
  19  *  This program is distributed in the hope that it will be useful,
  20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22  *  GNU General Public License for more details.
  23  *
  24  *  You should have received a copy of the GNU General Public License
  25  *  along with this program; if not, write to the Free Software
  26  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  27  *
  28  */
  29 
  30 /**
  31  * SECTION:rb-static-playlist-source
  32  * @short_description: Manually defined playlist class
  33  *
  34  * Static playlists are not defined by a query, but instead by manually selected
  35  * and ordered tracks.
  36  *
  37  * This class is used for static playlists built from the user's library, and is
  38  * also a base class for the play queue and for playlists on devices and network
  39  * shares.
  40  *
  41  * It has some ability to track locations that are not yet present in the database
  42  * and to add them to the playlist once they are added.
  43  */
  44 
  45 #include "config.h"
  46 
  47 #include <string.h>
  48 
  49 #include <libxml/tree.h>
  50 #include <glib/gi18n.h>
  51 #include <gtk/gtk.h>
  52 
  53 #include "rb-static-playlist-source.h"
  54 #include "rb-library-browser.h"
  55 #include "rb-util.h"
  56 #include "rb-debug.h"
  57 #include "rb-stock-icons.h"
  58 #include "rb-file-helpers.h"
  59 #include "rb-playlist-xml.h"
  60 #include "rb-source-search-basic.h"
  61 #include "rb-source-toolbar.h"
  62 
  63 static void rb_static_playlist_source_constructed (GObject *object);
  64 static void rb_static_playlist_source_dispose (GObject *object);
  65 static void rb_static_playlist_source_finalize (GObject *object);
  66 static void rb_static_playlist_source_set_property (GObject *object,
  67 						    guint prop_id,
  68 						    const GValue *value,
  69 						    GParamSpec *pspec);
  70 static void rb_static_playlist_source_get_property (GObject *object,
  71 						    guint prop_id,
  72 						    GValue *value,
  73 						    GParamSpec *pspec);
  74 
  75 /* source methods */
  76 static GList * impl_cut (RBSource *source);
  77 static RBTrackTransferBatch *impl_paste (RBSource *asource, GList *entries);
  78 static void impl_delete (RBSource *source);
  79 static void impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text);
  80 static void impl_reset_filters (RBSource *asource);
  81 static gboolean impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data);
  82 static guint impl_want_uri (RBSource *source, const char *uri);
  83 
  84 static GPtrArray *construct_query_from_selection (RBStaticPlaylistSource *source);
  85 
  86 /* playlist methods */
  87 static void impl_save_contents_to_xml (RBPlaylistSource *source,
  88 				       xmlNodePtr node);
  89 
  90 /* browser stuff */
  91 static GList *impl_get_property_views (RBSource *source);
  92 void rb_static_playlist_source_browser_views_activated_cb (GtkWidget *widget,
  93 							 RBStaticPlaylistSource *source);
  94 static void rb_static_playlist_source_browser_changed_cb (RBLibraryBrowser *entry,
  95 							  GParamSpec *pspec,
  96 							  RBStaticPlaylistSource *source);
  97 
  98 static void rb_static_playlist_source_do_query (RBStaticPlaylistSource *source);
  99 
 100 static void rb_static_playlist_source_add_id_list (RBStaticPlaylistSource *source,
 101 						   GList *list);
 102 static void rb_static_playlist_source_add_uri_list (RBStaticPlaylistSource *source,
 103 						    GList *list);
 104 static void rb_static_playlist_source_row_inserted (GtkTreeModel *model,
 105 						    GtkTreePath *path,
 106 						    GtkTreeIter *iter,
 107 						    RBStaticPlaylistSource *source);
 108 static gboolean rb_static_playlist_source_filter_entry_drop (RhythmDBQueryModel *model,
 109 							     RhythmDBEntry *entry,
 110 							     RBStaticPlaylistSource *source);
 111 static void rb_static_playlist_source_non_entry_dropped (GtkTreeModel *model,
 112 							 const char *uri,
 113 							 int position,
 114 							 RBStaticPlaylistSource *source);
 115 static void rb_static_playlist_source_rows_reordered (GtkTreeModel *model,
 116 						      GtkTreePath *path,
 117 						      GtkTreeIter *iter,
 118 						      gint *order_map,
 119 						      RBStaticPlaylistSource *source);
 120 
 121 static GtkRadioActionEntry rb_static_playlist_source_radio_actions [] =
 122 {
 123 	{ "StaticPlaylistSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
 124 	{ "StaticPlaylistSearchArtists", NULL, N_("Search artists"), NULL, NULL, RHYTHMDB_PROP_ARTIST_FOLDED },
 125 	{ "StaticPlaylistSearchAlbums", NULL, N_("Search albums"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
 126 	{ "StaticPlaylistSearchTitles", NULL, N_("Search titles"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
 127 };
 128 
 129 enum
 130 {
 131 	PROP_0,
 132 	PROP_BASE_QUERY_MODEL,
 133 	PROP_SHOW_BROWSER
 134 };
 135 
 136 G_DEFINE_TYPE (RBStaticPlaylistSource, rb_static_playlist_source, RB_TYPE_PLAYLIST_SOURCE)
 137 #define RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), \
 138 								RB_TYPE_STATIC_PLAYLIST_SOURCE, \
 139 								RBStaticPlaylistSourcePrivate))
 140 
 141 typedef struct
 142 {
 143 	RhythmDBQueryModel *base_model;
 144 	RhythmDBQueryModel *filter_model;
 145 
 146 	RBSourceToolbar *toolbar;
 147 	RBLibraryBrowser *browser;
 148 
 149 	RBSourceSearch *default_search;
 150 	RhythmDBQuery *search_query;
 151 
 152 	gboolean dispose_has_run;
 153 } RBStaticPlaylistSourcePrivate;
 154 
 155 static gpointer playlist_pixbuf = NULL;
 156 
 157 static void
 158 rb_static_playlist_source_class_init (RBStaticPlaylistSourceClass *klass)
 159 {
 160 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 161 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 162 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 163 	RBPlaylistSourceClass *playlist_class = RB_PLAYLIST_SOURCE_CLASS (klass);
 164 
 165 	object_class->constructed = rb_static_playlist_source_constructed;
 166 	object_class->dispose = rb_static_playlist_source_dispose;
 167 	object_class->finalize = rb_static_playlist_source_finalize;
 168 	object_class->set_property = rb_static_playlist_source_set_property;
 169 	object_class->get_property = rb_static_playlist_source_get_property;
 170 
 171 	page_class->receive_drag = impl_receive_drag;
 172 
 173 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_true_function;
 174 	source_class->impl_can_paste = (RBSourceFeatureFunc) rb_true_function;
 175 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
 176 	source_class->impl_cut = impl_cut;
 177 	source_class->impl_paste = impl_paste;
 178 	source_class->impl_delete = impl_delete;
 179 	source_class->impl_search = impl_search;
 180 	source_class->impl_reset_filters = impl_reset_filters;
 181 	source_class->impl_get_property_views = impl_get_property_views;
 182 	source_class->impl_want_uri = impl_want_uri;
 183 
 184 	playlist_class->impl_save_contents_to_xml = impl_save_contents_to_xml;
 185 
 186 	g_object_class_override_property (object_class,
 187 					  PROP_BASE_QUERY_MODEL,
 188 					  "base-query-model");
 189 	g_object_class_override_property (object_class,
 190 					  PROP_SHOW_BROWSER,
 191 					  "show-browser");
 192 
 193 	g_type_class_add_private (klass, sizeof (RBStaticPlaylistSourcePrivate));
 194 }
 195 
 196 void
 197 rb_static_playlist_source_create_actions (RBShell *shell)
 198 {
 199 	RBStaticPlaylistSourceClass *klass;
 200 	GtkUIManager *uimanager;
 201 
 202 	klass = RB_STATIC_PLAYLIST_SOURCE_CLASS (g_type_class_ref (RB_TYPE_STATIC_PLAYLIST_SOURCE));
 203 
 204 	klass->action_group = gtk_action_group_new ("StaticPlaylistActions");
 205 	gtk_action_group_set_translation_domain (klass->action_group, GETTEXT_PACKAGE);
 206 
 207 	g_object_get (shell, "ui-manager", &uimanager, NULL);
 208 	gtk_ui_manager_insert_action_group (uimanager, klass->action_group, 0);
 209 	g_object_unref (uimanager);
 210 
 211 	gtk_action_group_add_radio_actions (klass->action_group,
 212 					    rb_static_playlist_source_radio_actions,
 213 					    G_N_ELEMENTS (rb_static_playlist_source_radio_actions),
 214 					    0,
 215 					    NULL,
 216 					    NULL);
 217 	rb_source_search_basic_create_for_actions (klass->action_group,
 218 						   rb_static_playlist_source_radio_actions,
 219 						   G_N_ELEMENTS (rb_static_playlist_source_radio_actions));
 220 
 221 	g_type_class_unref (klass);
 222 }
 223 
 224 static void
 225 set_playlist_pixbuf (RBStaticPlaylistSource *source)
 226 {
 227 	if (playlist_pixbuf == NULL) {
 228 		gint size;
 229 		gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
 230 		playlist_pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
 231 							    RB_STOCK_PLAYLIST,
 232 							    size,
 233 							    0, NULL);
 234 		if (playlist_pixbuf) {
 235 			g_object_add_weak_pointer (playlist_pixbuf,
 236 					   (gpointer *) &playlist_pixbuf);
 237 
 238 			g_object_set (source, "pixbuf", playlist_pixbuf, NULL);
 239 
 240 			/* drop the initial reference to the icon */
 241 			g_object_unref (playlist_pixbuf);
 242 		}
 243 	} else {
 244 		g_object_set (source, "pixbuf", playlist_pixbuf, NULL);
 245 	}
 246 }
 247 
 248 static void
 249 rb_static_playlist_source_init (RBStaticPlaylistSource *source)
 250 {
 251 }
 252 
 253 static void
 254 rb_static_playlist_source_dispose (GObject *object)
 255 {
 256 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (object);
 257 
 258 	if (priv->dispose_has_run) {
 259 		/* If dispose did already run, return. */
 260 		rb_debug ("Dispose has already run for static playlist source %p", object);
 261 		return;
 262 	}
 263 	/* Make sure dispose does not run twice. */
 264 	priv->dispose_has_run = TRUE;
 265 
 266 	rb_debug ("Disposing static playlist source %p", object);
 267 
 268 	if (priv->base_model != NULL) {
 269 		g_object_unref (priv->base_model);
 270 		priv->base_model = NULL;
 271 	}
 272 
 273 	if (priv->filter_model != NULL) {
 274 		g_object_unref (priv->filter_model);
 275 		priv->filter_model = NULL;
 276 	}
 277 
 278 	if (priv->default_search != NULL) {
 279 		g_object_unref (priv->default_search);
 280 		priv->default_search = NULL;
 281 	}
 282 
 283 	G_OBJECT_CLASS (rb_static_playlist_source_parent_class)->dispose (object);
 284 }
 285 
 286 static void
 287 rb_static_playlist_source_finalize (GObject *object)
 288 {
 289 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (object);
 290 
 291 	rb_debug ("Finalizing static playlist source %p", object);
 292 
 293 	if (priv->search_query != NULL) {
 294 		rhythmdb_query_free (priv->search_query);
 295 		priv->search_query = NULL;
 296 	}
 297 
 298 	G_OBJECT_CLASS (rb_static_playlist_source_parent_class)->finalize (object);
 299 }
 300 
 301 static void
 302 rb_static_playlist_source_constructed (GObject *object)
 303 {
 304 	RBStaticPlaylistSource *source;
 305 	RBStaticPlaylistSourcePrivate *priv;
 306 	RBPlaylistSource *psource;
 307 	RBEntryView *songs;
 308 	RBShell *shell;
 309 	RhythmDBEntryType *entry_type;
 310 	GtkUIManager *ui_manager;
 311 	GtkWidget *grid;
 312 	GtkWidget *paned;
 313 
 314 	RB_CHAIN_GOBJECT_METHOD (rb_static_playlist_source_parent_class, constructed, object);
 315 
 316 	source = RB_STATIC_PLAYLIST_SOURCE (object);
 317 	priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
 318 	psource = RB_PLAYLIST_SOURCE (source);
 319 
 320 	set_playlist_pixbuf (source);
 321 
 322 	priv->base_model = rb_playlist_source_get_query_model (RB_PLAYLIST_SOURCE (psource));
 323 	g_object_set (priv->base_model, "show-hidden", TRUE, NULL);
 324 	g_object_ref (priv->base_model);
 325 	g_signal_connect_object (priv->base_model,
 326 				 "filter-entry-drop",
 327 				 G_CALLBACK (rb_static_playlist_source_filter_entry_drop),
 328 				 source, 0);
 329 
 330 	paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
 331 	gtk_widget_set_hexpand (paned, TRUE);
 332 	gtk_widget_set_vexpand (paned, TRUE);
 333 
 334 	priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
 335 
 336 	g_object_get (source, "shell", &shell, NULL);
 337 	g_object_get (shell, "ui-manager", &ui_manager, NULL);
 338 	g_object_unref (shell);
 339 
 340 	g_object_get (source, "entry-type", &entry_type, NULL);
 341 	priv->browser = rb_library_browser_new (rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source)),
 342 						entry_type);
 343 	if (entry_type != NULL) {
 344 		g_object_unref (entry_type);
 345 	}
 346 
 347 	gtk_paned_pack1 (GTK_PANED (paned), GTK_WIDGET (priv->browser), TRUE, FALSE);
 348 	g_signal_connect_object (priv->browser, "notify::output-model",
 349 				 G_CALLBACK (rb_static_playlist_source_browser_changed_cb),
 350 				 source, 0);
 351 
 352 	rb_library_browser_set_model (priv->browser, priv->base_model, FALSE);
 353 	rb_static_playlist_source_do_query (source);
 354 
 355 	/* reparent the entry view */
 356 	songs = rb_source_get_entry_view (RB_SOURCE (source));
 357 	g_object_ref (songs);
 358 	gtk_container_remove (GTK_CONTAINER (source), GTK_WIDGET (songs));
 359 	gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (songs), TRUE, FALSE);
 360 
 361 	/* set up search box / toolbar */
 362 	priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
 363 	rb_source_toolbar_add_search_entry (priv->toolbar, "/StaticPlaylistSourceSearchMenu", NULL);
 364 	g_object_unref (ui_manager);
 365 
 366 	/* put it all together */
 367 	grid = gtk_grid_new ();
 368 	gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
 369 	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
 370 	gtk_widget_set_margin_top (GTK_WIDGET (grid), 6);
 371 	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (priv->toolbar), 0, 0, 1, 1);
 372 	gtk_grid_attach (GTK_GRID (grid), paned, 0, 1, 1, 1);
 373 	gtk_container_add (GTK_CONTAINER (source), grid);
 374 
 375 	rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (songs), paned, GTK_WIDGET (priv->browser));
 376 	g_object_unref (songs);
 377 
 378 	/* watch these to find out when things are dropped into the entry view */
 379 	g_signal_connect_object (priv->base_model, "row-inserted",
 380 				 G_CALLBACK (rb_static_playlist_source_row_inserted),
 381 				 source, 0);
 382 	g_signal_connect_object (priv->base_model, "non-entry-dropped",
 383 				 G_CALLBACK (rb_static_playlist_source_non_entry_dropped),
 384 				 source, 0);
 385 	g_signal_connect_object (priv->base_model, "rows-reordered",
 386 				 G_CALLBACK (rb_static_playlist_source_rows_reordered),
 387 				 source, 0);
 388 
 389 	gtk_widget_show_all (GTK_WIDGET (source));
 390 }
 391 
 392 /**
 393  * rb_static_playlist_source_new:
 394  * @shell: the #RBShell
 395  * @name: the playlist name
 396  * @settings_name: the settings name for the playlist (GSettings path friendly)
 397  * @local: if %TRUE, the playlist is local to the library
 398  * @entry_type: type of database entries that can be added to the playlist.
 399  *
 400  * Creates a new static playlist source.
 401  *
 402  * Return value: new playlist.
 403  */
 404 RBSource *
 405 rb_static_playlist_source_new (RBShell *shell, const char *name, const char *settings_name, gboolean local, RhythmDBEntryType *entry_type)
 406 {
 407 	GSettings *settings;
 408 
 409 	if (name == NULL)
 410 		name = "";
 411 
 412 	if (settings_name != NULL) {
 413 		char *path;
 414 		path = g_strdup_printf ("/org/gnome/rhythmbox/playlist/%s/", settings_name);
 415 		settings = g_settings_new_with_path ("org.gnome.rhythmbox.source", path);
 416 		g_free (path);
 417 	} else {
 418 		settings = NULL;
 419 	}
 420 
 421 	return RB_SOURCE (g_object_new (RB_TYPE_STATIC_PLAYLIST_SOURCE,
 422 					"name", name,
 423 					"settings", settings,
 424 					"shell", shell,
 425 					"is-local", local,
 426 					"entry-type", entry_type,
 427 					"toolbar-path", "/StaticPlaylistSourceToolBar",
 428 					NULL));
 429 }
 430 
 431 static void
 432 rb_static_playlist_source_set_property (GObject *object,
 433 					guint prop_id,
 434 					const GValue *value,
 435 					GParamSpec *pspec)
 436 {
 437 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (object);
 438 
 439 	switch (prop_id) {
 440 	case PROP_SHOW_BROWSER:
 441 		if (g_value_get_boolean (value))
 442 			gtk_widget_show (GTK_WIDGET (priv->browser));
 443 		else
 444 			gtk_widget_hide (GTK_WIDGET (priv->browser));
 445 		break;
 446 	default:
 447 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 448 		break;
 449 	}
 450 }
 451 
 452 static void
 453 rb_static_playlist_source_get_property (GObject *object,
 454 					guint prop_id,
 455 					GValue *value,
 456 					GParamSpec *pspec)
 457 {
 458 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (object);
 459 
 460 	switch (prop_id) {
 461 	case PROP_BASE_QUERY_MODEL:
 462 		g_value_set_object (value, priv->base_model);
 463 		break;
 464 	case PROP_SHOW_BROWSER:
 465 		g_value_set_boolean (value, gtk_widget_get_visible (GTK_WIDGET (priv->browser)));
 466 		break;
 467 	default:
 468 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 469 		break;
 470 	}
 471 }
 472 
 473 /**
 474  * rb_static_playlist_source_load_from_xml:
 475  * @source: an #RBStaticPlaylistSource
 476  * @node: XML node to load from
 477  *
 478  * Loads the playlist contents from the specified XML document node.
 479  */
 480 void
 481 rb_static_playlist_source_load_from_xml (RBStaticPlaylistSource *source, xmlNodePtr node)
 482 {
 483 	xmlNodePtr child;
 484 
 485 	for (child = node->children; child; child = child->next) {
 486 		xmlChar *location;
 487 
 488 		if (xmlNodeIsText (child))
 489 			continue;
 490 
 491 		if (xmlStrcmp (child->name, RB_PLAYLIST_LOCATION))
 492 			continue;
 493 
 494 		location = xmlNodeGetContent (child);
 495 		rb_static_playlist_source_add_location (source,
 496 						        (char *) location, -1);
 497 		xmlFree (location);
 498 	}
 499 }
 500 
 501 /**
 502  * rb_static_playlist_source_new_from_xml:
 503  * @shell: the #RBShell
 504  * @node: XML node containing playlist entries
 505  *
 506  * Constructs a new playlist from the given XML document node.
 507  *
 508  * Return value: playlist read from XML
 509  */
 510 RBSource *
 511 rb_static_playlist_source_new_from_xml (RBShell *shell, xmlNodePtr node)
 512 {
 513 	RBSource *psource = rb_static_playlist_source_new (shell,
 514 							   NULL,
 515 							   NULL,
 516 							   TRUE,
 517 							   RHYTHMDB_ENTRY_TYPE_SONG);
 518 	RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (psource);
 519 
 520 	rb_static_playlist_source_load_from_xml (source, node);
 521 
 522 	return RB_SOURCE (source);
 523 }
 524 
 525 static GList *
 526 impl_cut (RBSource *asource)
 527 {
 528 	RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (asource);
 529 	RBEntryView *songs = rb_source_get_entry_view (asource);
 530 	GList *sel = rb_entry_view_get_selected_entries (songs);
 531 	GList *tem;
 532 
 533 	for (tem = sel; tem; tem = tem->next)
 534 		rb_static_playlist_source_remove_entry (source, (RhythmDBEntry *) tem->data);
 535 
 536 	return sel;
 537 }
 538 
 539 static RBTrackTransferBatch *
 540 impl_paste (RBSource *asource, GList *entries)
 541 {
 542 	RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (asource);
 543 
 544 	for (; entries; entries = g_list_next (entries))
 545 		rb_static_playlist_source_add_entry (source, entries->data, -1);
 546 
 547 	return NULL;
 548 }
 549 
 550 static void
 551 impl_delete (RBSource *asource)
 552 {
 553 	RBEntryView *songs = rb_source_get_entry_view (asource);
 554 	RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (asource);
 555 	GList *sel, *tem;
 556 
 557 	sel = rb_entry_view_get_selected_entries (songs);
 558 	for (tem = sel; tem != NULL; tem = tem->next) {
 559 		rb_static_playlist_source_remove_entry (source, (RhythmDBEntry *) tem->data);
 560 	}
 561 	g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
 562 	g_list_free (sel);
 563 }
 564 
 565 static void
 566 impl_reset_filters (RBSource *source)
 567 {
 568 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
 569 	gboolean changed = FALSE;
 570 
 571 	if (rb_library_browser_reset (priv->browser))
 572 		changed = TRUE;
 573 
 574 	if (priv->search_query != NULL) {
 575 		changed = TRUE;
 576 		rhythmdb_query_free (priv->search_query);
 577 		priv->search_query = NULL;
 578 	}
 579 
 580 	rb_source_toolbar_clear_search_entry (priv->toolbar);
 581 
 582 	if (changed) {
 583 		rb_static_playlist_source_do_query (RB_STATIC_PLAYLIST_SOURCE (source));
 584 		rb_source_notify_filter_changed (source);
 585 	}
 586 }
 587 
 588 static void
 589 impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text)
 590 {
 591 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
 592 	RhythmDB *db;
 593 
 594 	if (search == NULL) {
 595 		search = priv->default_search;
 596 	}
 597 
 598 	/* replace our search query */
 599 	if (priv->search_query != NULL) {
 600 		rhythmdb_query_free (priv->search_query);
 601 		priv->search_query = NULL;
 602 	}
 603 	db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
 604 	priv->search_query = rb_source_search_create_query (search, db, new_text);
 605 
 606 	rb_static_playlist_source_do_query (RB_STATIC_PLAYLIST_SOURCE (source));
 607 }
 608 
 609 static GList *
 610 impl_get_property_views (RBSource *source)
 611 {
 612 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
 613 	GList *ret;
 614 
 615 	ret =  rb_library_browser_get_property_views (priv->browser);
 616 	return ret;
 617 }
 618 
 619 static GPtrArray *
 620 construct_query_from_selection (RBStaticPlaylistSource *source)
 621 {
 622 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
 623 	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
 624 	RhythmDB *db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (psource));
 625 	GPtrArray *query = NULL;
 626 
 627 	query = g_ptr_array_new();
 628 
 629 	if (priv->search_query != NULL) {
 630 		rhythmdb_query_append (db,
 631 				       query,
 632 				       RHYTHMDB_QUERY_SUBQUERY, priv->search_query,
 633 				       RHYTHMDB_QUERY_END);
 634 	}
 635 
 636 	return query;
 637 }
 638 
 639 static void
 640 rb_static_playlist_source_do_query (RBStaticPlaylistSource *source)
 641 {
 642 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
 643 	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
 644 	RhythmDB *db = rb_playlist_source_get_db (psource);
 645 	GPtrArray *query;
 646 
 647 	if (priv->filter_model != NULL) {
 648 		g_object_unref (priv->filter_model);
 649 	}
 650 	priv->filter_model = rhythmdb_query_model_new_empty (db);
 651 	g_object_set (priv->filter_model, "base-model", priv->base_model, NULL);
 652 
 653 	query = construct_query_from_selection (source);
 654 	g_object_set (priv->filter_model, "query", query, NULL);
 655 	rhythmdb_query_free (query);
 656 
 657 	rhythmdb_query_model_reapply_query (priv->filter_model, TRUE);
 658 	rb_library_browser_set_model (priv->browser, priv->filter_model, FALSE);
 659 }
 660 
 661 static void
 662 rb_static_playlist_source_browser_changed_cb (RBLibraryBrowser *browser,
 663 					      GParamSpec *pspec,
 664 					      RBStaticPlaylistSource *source)
 665 {
 666 	RBEntryView *songs = rb_source_get_entry_view (RB_SOURCE (source));
 667 	RhythmDBQueryModel *query_model;
 668 
 669 	g_object_get (browser, "output-model", &query_model, NULL);
 670 	rb_entry_view_set_model (songs, query_model);
 671 	rb_playlist_source_set_query_model (RB_PLAYLIST_SOURCE (source), query_model);
 672 	g_object_unref (query_model);
 673 
 674 	rb_source_notify_filter_changed (RB_SOURCE (source));
 675 }
 676 
 677 static gboolean
 678 impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
 679 {
 680 	GdkAtom type;
 681 	GList *list;
 682 	RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (page);
 683 
 684 	type = gtk_selection_data_get_data_type (data);
 685 
 686         if (type == gdk_atom_intern ("text/uri-list", TRUE) ||
 687 	    type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE)) {
 688 		list = rb_uri_list_parse ((char *)gtk_selection_data_get_data (data));
 689 		if (list == NULL)
 690 			return FALSE;
 691 
 692 		if (type == gdk_atom_intern ("text/uri-list", TRUE))
 693 			rb_static_playlist_source_add_uri_list (source, list);
 694 		else
 695 			rb_static_playlist_source_add_id_list (source, list);
 696 		rb_list_deep_free (list);
 697 	}
 698 
 699         return TRUE;
 700 }
 701 
 702 static void
 703 impl_save_contents_to_xml (RBPlaylistSource *source,
 704 			   xmlNodePtr node)
 705 {
 706 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
 707 	GtkTreeIter iter;
 708 
 709 	xmlSetProp (node, RB_PLAYLIST_TYPE, RB_PLAYLIST_STATIC);
 710 
 711 	if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->base_model), &iter))
 712 		return;
 713 
 714 	do {
 715 		xmlNodePtr child_node = xmlNewChild (node, NULL, RB_PLAYLIST_LOCATION, NULL);
 716 		RhythmDBEntry *entry;
 717 		xmlChar *encoded;
 718 		const char *location;
 719 
 720 		gtk_tree_model_get (GTK_TREE_MODEL (priv->base_model), &iter, 0, &entry, -1);
 721 
 722 		location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
 723 		encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST location);
 724 
 725 		xmlNodeSetContent (child_node, encoded);
 726 
 727 		g_free (encoded);
 728 		rhythmdb_entry_unref (entry);
 729 	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->base_model), &iter));
 730 }
 731 
 732 static void
 733 rb_static_playlist_source_add_id_list (RBStaticPlaylistSource *source,
 734 				       GList *list)
 735 {
 736 	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
 737 	GList *i;
 738 	gint id;
 739 
 740 	g_return_if_fail (list != NULL);
 741 
 742 	for (i = list; i != NULL; i = i->next) {
 743 		RhythmDBEntry *entry;
 744 
 745 		id = strtoul ((const char *)i->data, NULL, 0);
 746 		if (id == 0)
 747 			continue;
 748 
 749 		entry = rhythmdb_entry_lookup_by_id (rb_playlist_source_get_db (psource), id);
 750 		if (entry == NULL) {
 751 			rb_debug ("received id %d, but can't find the entry", id);
 752 			continue;
 753 		}
 754 
 755 		rb_static_playlist_source_add_entry (source, entry, -1);
 756 	}
 757 }
 758 
 759 static void
 760 rb_static_playlist_source_add_uri_list (RBStaticPlaylistSource *source,
 761 					GList *list)
 762 {
 763 	GList *i, *uri_list = NULL;
 764 	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
 765 	RhythmDBEntry *entry;
 766 
 767 	g_return_if_fail (list != NULL);
 768 
 769 	for (i = list; i != NULL; i = g_list_next (i)) {
 770 		char *uri = (char *) i->data;
 771 		uri_list = g_list_prepend (uri_list, rb_canonicalise_uri (uri));
 772 	}
 773 
 774 	uri_list = g_list_reverse (uri_list);
 775 	if (uri_list == NULL)
 776 		return;
 777 
 778 	for (i = uri_list; i != NULL; i = i->next) {
 779 		char *uri = i->data;
 780 		if (uri != NULL) {
 781 			entry = rhythmdb_entry_lookup_by_location (rb_playlist_source_get_db (psource), uri);
 782 			if (entry == NULL)
 783 				rhythmdb_add_uri (rb_playlist_source_get_db (psource), uri);
 784 
 785 			rb_static_playlist_source_add_location (source, uri, -1);
 786 		}
 787 
 788 		g_free (uri);
 789 	}
 790 	g_list_free (uri_list);
 791 }
 792 
 793 static void
 794 rb_static_playlist_source_add_location_internal (RBStaticPlaylistSource *source,
 795 						 const char *location,
 796 						 gint index)
 797 {
 798 	RhythmDB *db;
 799 	RhythmDBEntry *entry;
 800 	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
 801 	if (rb_playlist_source_location_in_map (psource, location))
 802 		return;
 803 
 804 	db = rb_playlist_source_get_db (psource);
 805 	entry = rhythmdb_entry_lookup_by_location (db, location);
 806 	if (entry) {
 807 		RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
 808 
 809 		if (_rb_source_check_entry_type (RB_SOURCE (source), entry)) {
 810 			rhythmdb_entry_ref (entry);
 811 			rhythmdb_query_model_add_entry (priv->base_model, entry, index);
 812 			rhythmdb_entry_unref (entry);
 813 		}
 814 	}
 815 
 816 	rb_playlist_source_add_to_map (psource, location);
 817 
 818 	rb_playlist_source_mark_dirty (psource);
 819 }
 820 
 821 static gboolean
 822 _add_location_cb (GFile *file,
 823 		  gboolean dir,
 824 		  RBStaticPlaylistSource *source)
 825 {
 826 	if (!dir) {
 827 		char *uri;
 828 
 829 		uri = g_file_get_uri (file);
 830 		rb_static_playlist_source_add_location_internal (source, uri, -1);
 831 		g_free (uri);
 832 	}
 833 	return TRUE;
 834 }
 835 
 836 /**
 837  * rb_static_playlist_source_add_location:
 838  * @source: an #RBStaticPlaylistSource
 839  * @location: location (URI) to add to the playlist
 840  * @index: position at which to add the location (-1 to add at the end)
 841  *
 842  * If the location matches an entry in the database, the entry is added
 843  * to the playlist.  Otherwise, if it identifies a directory, the contents
 844  * of that directory are added.
 845  */
 846 void
 847 rb_static_playlist_source_add_location (RBStaticPlaylistSource *source,
 848 					const char *location,
 849 					gint index)
 850 {
 851 	RhythmDB *db;
 852 	RhythmDBEntry *entry;
 853 
 854 	db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
 855 	entry = rhythmdb_entry_lookup_by_location (db, location);
 856 
 857 	/* if there is an entry, it won't be a directory */
 858 	if (entry == NULL && rb_uri_is_directory (location))
 859 		rb_uri_handle_recursively (location,
 860 					   NULL,
 861 					   (RBUriRecurseFunc) _add_location_cb,
 862 					   source);
 863 	else
 864 		rb_static_playlist_source_add_location_internal (source, location, index);
 865 
 866 }
 867 
 868 /**
 869  * rb_static_playlist_source_add_locations:
 870  * @source: an #RBStaticPlaylistSource
 871  * @locations: (element-type utf8) (transfer none): URI strings to add
 872  *
 873  * Adds the locations specified in @locations to the playlist.
 874  * See @rb_static_playlist_source_add_location for details.
 875  */
 876 void
 877 rb_static_playlist_source_add_locations (RBStaticPlaylistSource *source,
 878 					 GList *locations)
 879 {
 880 	GList *l;
 881 
 882 	for (l = locations; l; l = l->next) {
 883 		const gchar *uri = (const gchar *)l->data;
 884 		rb_static_playlist_source_add_location (source, uri, -1);
 885 	}
 886 }
 887 
 888 /**
 889  * rb_static_playlist_source_remove_location:
 890  * @source: an #RBStaticPlaylistSource
 891  * @location: location to remove
 892  *
 893  * Removes the specified location from the playlist.  This affects both
 894  * the location map and the query model, whether an entry exists for the
 895  * location or not.
 896  */
 897 void
 898 rb_static_playlist_source_remove_location (RBStaticPlaylistSource *source,
 899 					   const char *location)
 900 {
 901 	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
 902 	RhythmDB *db;
 903 	RhythmDBEntry *entry;
 904 
 905 	g_return_if_fail (rb_playlist_source_location_in_map (psource, location));
 906 
 907 	db = rb_playlist_source_get_db (psource);
 908 	entry = rhythmdb_entry_lookup_by_location (db, location);
 909 
 910 	if (entry != NULL) {
 911 		RhythmDBQueryModel *model = rb_playlist_source_get_query_model (psource);
 912 
 913 		/* if this fails, the model and the playlist are out of sync */
 914 		g_assert (rhythmdb_query_model_remove_entry (model, entry));
 915 		rb_playlist_source_mark_dirty (psource);
 916 	}
 917 }
 918 
 919 /**
 920  * rb_static_playlist_source_add_entry:
 921  * @source: an #RBStaticPlaylistSource
 922  * @entry: entry to add to the playlist
 923  * @index: position at which to add it (-1 to add at the end)
 924  *
 925  * Adds the specified entry to the playlist.
 926  */
 927 void
 928 rb_static_playlist_source_add_entry (RBStaticPlaylistSource *source,
 929 				     RhythmDBEntry *entry,
 930 				     gint index)
 931 {
 932 	const char *location;
 933 
 934 	location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
 935 	rb_static_playlist_source_add_location_internal (source, location, index);
 936 }
 937 
 938 /**
 939  * rb_static_playlist_source_remove_entry:
 940  * @source: an #RBStaticPlaylistSource
 941  * @entry: the entry to remove
 942  *
 943  * Removes the specified entry from the playlist.
 944  */
 945 void
 946 rb_static_playlist_source_remove_entry (RBStaticPlaylistSource *source,
 947 					RhythmDBEntry *entry)
 948 {
 949 	const char *location;
 950 
 951 	location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
 952 	rb_static_playlist_source_remove_location (source, location);
 953 }
 954 
 955 /**
 956  * rb_static_playlist_source_move_entry:
 957  * @source: an #RBStaticPlaylistSource
 958  * @entry: the entry to move
 959  * @index: new location for the entry
 960  *
 961  * Moves an entry within the playlist.
 962  */
 963 void
 964 rb_static_playlist_source_move_entry (RBStaticPlaylistSource *source,
 965 				      RhythmDBEntry *entry,
 966 				      gint index)
 967 {
 968 	RBPlaylistSource *psource = RB_PLAYLIST_SOURCE (source);
 969 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
 970 
 971 	rhythmdb_query_model_move_entry (priv->base_model, entry, index);
 972 
 973 	rb_playlist_source_mark_dirty (psource);
 974 }
 975 
 976 static void
 977 rb_static_playlist_source_non_entry_dropped (GtkTreeModel *model,
 978 					     const char *uri,
 979 					     int position,
 980 					     RBStaticPlaylistSource *source)
 981 {
 982 	g_assert (g_utf8_strlen (uri, -1) > 0);
 983 
 984 	rhythmdb_add_uri (rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source)), uri);
 985 	rb_static_playlist_source_add_location (source, uri, position);
 986 }
 987 
 988 static void
 989 rb_static_playlist_source_row_inserted (GtkTreeModel *model,
 990 					GtkTreePath *path,
 991 					GtkTreeIter *iter,
 992 					RBStaticPlaylistSource *source)
 993 {
 994 	RhythmDBEntry *entry;
 995 
 996 	gtk_tree_model_get (model, iter, 0, &entry, -1);
 997 
 998 	rb_static_playlist_source_add_entry (source, entry, -1);
 999 
1000 	rhythmdb_entry_unref (entry);
1001 }
1002 
1003 static void
1004 rb_static_playlist_source_rows_reordered (GtkTreeModel *model,
1005 					  GtkTreePath *path,
1006 					  GtkTreeIter *iter,
1007 					  gint *order_map,
1008 					  RBStaticPlaylistSource *source)
1009 {
1010 	rb_playlist_source_mark_dirty (RB_PLAYLIST_SOURCE (source));
1011 }
1012 
1013 static gboolean
1014 rb_static_playlist_source_filter_entry_drop (RhythmDBQueryModel *model,
1015 					     RhythmDBEntry *entry, 
1016 					     RBStaticPlaylistSource *source)
1017 {
1018 	if (_rb_source_check_entry_type (RB_SOURCE (source), entry)) {
1019 		rb_debug ("allowing drop of entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
1020 		return TRUE;
1021 	}
1022 	rb_debug ("preventing drop of entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
1023 	return FALSE;
1024 }
1025 
1026 static guint
1027 impl_want_uri (RBSource *source, const char *uri)
1028 {
1029 	/* take anything local, on smb, or sftp */
1030 	if (rb_uri_is_local (uri) ||
1031 	    g_str_has_prefix (uri, "smb://") ||
1032 	    g_str_has_prefix (uri, "sftp://"))
1033 		return 25;	/* less than what the library returns */
1034 
1035 	return 0;
1036 }