hythmbox-2.98/sources/rb-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 #include "config.h"
  31 
  32 #include <time.h>
  33 #include <string.h>
  34 
  35 #include <glib/gi18n.h>
  36 #include <gtk/gtk.h>
  37 
  38 #include "rb-source.h"
  39 #include "rb-cut-and-paste-code.h"
  40 #include "rb-debug.h"
  41 #include "rb-dialog.h"
  42 #include "rb-shell.h"
  43 #include "rb-source.h"
  44 #include "rb-util.h"
  45 #include "rb-static-playlist-source.h"
  46 #include "rb-play-order.h"
  47 
  48 static void rb_source_class_init (RBSourceClass *klass);
  49 static void rb_source_init (RBSource *source);
  50 static void rb_source_dispose (GObject *object);
  51 static void rb_source_finalize (GObject *object);
  52 static void rb_source_set_property (GObject *object,
  53 					guint prop_id,
  54 					const GValue *value,
  55 					GParamSpec *pspec);
  56 static void rb_source_get_property (GObject *object,
  57 					guint prop_id,
  58 					GValue *value,
  59 					GParamSpec *pspec);
  60 
  61 static void default_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
  62 static void default_activate (RBDisplayPage *page);
  63 static GList *default_get_property_views (RBSource *source);
  64 static gboolean default_can_rename (RBSource *source);
  65 static GList *default_copy (RBSource *source);
  66 static void default_reset_filters (RBSource *source);
  67 static gboolean default_try_playlist (RBSource *source);
  68 static RBSourceEOFType default_handle_eos (RBSource *source);
  69 static RBEntryView *default_get_entry_view (RBSource *source);
  70 static void default_add_to_queue (RBSource *source, RBSource *queue);
  71 static void default_move_to_trash (RBSource *source);
  72 static char *default_get_delete_action (RBSource *source);
  73 
  74 static void rb_source_post_entry_deleted_cb (GtkTreeModel *model,
  75 					     RhythmDBEntry *entry,
  76 					     RBSource *source);
  77 static void rb_source_row_inserted_cb (GtkTreeModel *model,
  78 				       GtkTreePath *path,
  79 				       GtkTreeIter *iter,
  80 				       RBSource *source);
  81 
  82 G_DEFINE_ABSTRACT_TYPE (RBSource, rb_source, RB_TYPE_DISPLAY_PAGE)
  83 
  84 /**
  85  * SECTION:rb-source
  86  * @short_description: base class for sources
  87  *
  88  * This class provides methods for requesting information
  89  * about the UI capabilities of the source, and defines the
  90  * expectations that apply to all sources - that they will
  91  * provide #RBEntryView and #RhythmDBQueryModel objects, mostly.
  92  *
  93  * Many of the methods on this class come in can_do_x and do_x
  94  * pairs.  When can_do_x always returns FALSE, the class does not
  95  * need to implement the do_x method.
  96  *
  97  * Useful subclasses include #RBBrowserSource, which includes a #RBLibraryBrowser
  98  * and takes care of constructing an #RBEntryView too; #RBRemovableMediaSource,
  99  * which takes care of many aspects of implementing a source that represents a
 100  * removable device; and #RBPlaylistSource, which provides functionality for
 101  * playlist-like sources.
 102  */
 103 
 104 struct _RBSourcePrivate
 105 {
 106 	RhythmDBQueryModel *query_model;
 107 	guint hidden_when_empty : 1;
 108 	guint update_visibility_id;
 109 	guint update_status_id;
 110 	RhythmDBEntryType *entry_type;
 111 	RBSourceLoadStatus load_status;
 112 
 113 	GSettings *settings;
 114 
 115 	char *toolbar_path;
 116 };
 117 
 118 enum
 119 {
 120 	PROP_0,
 121 	PROP_QUERY_MODEL,
 122 	PROP_HIDDEN_WHEN_EMPTY,
 123 	PROP_ENTRY_TYPE,
 124 	PROP_BASE_QUERY_MODEL,
 125 	PROP_PLAY_ORDER,
 126 	PROP_SETTINGS,
 127 	PROP_SHOW_BROWSER,
 128 	PROP_LOAD_STATUS,
 129 	PROP_TOOLBAR_PATH
 130 };
 131 
 132 enum
 133 {
 134 	FILTER_CHANGED,
 135 	LAST_SIGNAL
 136 };
 137 
 138 static guint rb_source_signals[LAST_SIGNAL] = { 0 };
 139 
 140 static void
 141 rb_source_class_init (RBSourceClass *klass)
 142 {
 143 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 144 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 145 
 146 	object_class->dispose = rb_source_dispose;
 147 	object_class->finalize = rb_source_finalize;
 148 	object_class->set_property = rb_source_set_property;
 149 	object_class->get_property = rb_source_get_property;
 150 
 151 	page_class->activate = default_activate;
 152 	page_class->get_status = default_get_status;
 153 
 154 	klass->impl_get_property_views = default_get_property_views;
 155 	klass->impl_can_rename = default_can_rename;
 156 	klass->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 157 	klass->impl_can_paste = (RBSourceFeatureFunc) rb_false_function;
 158 	klass->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
 159 	klass->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
 160 	klass->impl_can_add_to_queue = (RBSourceFeatureFunc) rb_false_function;
 161 	klass->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 162 	klass->impl_can_pause = (RBSourceFeatureFunc) rb_true_function;
 163 	klass->impl_get_entry_view = default_get_entry_view;
 164 	klass->impl_copy = default_copy;
 165 	klass->impl_reset_filters = default_reset_filters;
 166 	klass->impl_handle_eos = default_handle_eos;
 167 	klass->impl_try_playlist = default_try_playlist;
 168 	klass->impl_add_to_queue = default_add_to_queue;
 169 	klass->impl_get_delete_action = default_get_delete_action;
 170 	klass->impl_move_to_trash = default_move_to_trash;
 171 
 172 	/**
 173 	 * RBSource:hidden-when-empty:
 174 	 *
 175 	 * If TRUE, the source will not be displayed in the source list
 176 	 * when it contains no entries.
 177 	 */
 178 	g_object_class_install_property (object_class,
 179 					 PROP_HIDDEN_WHEN_EMPTY,
 180 					 g_param_spec_boolean ("hidden-when-empty",
 181 							       "hidden-when-empty",
 182 							       "Whether the source should be displayed in the source list when it is empty",
 183 							       FALSE,
 184 							       G_PARAM_READWRITE));
 185 
 186 	/**
 187 	 * RBSource:query-model:
 188 	 *
 189 	 * The current query model for the source.  This is used in
 190 	 * various places, including the play order, to find the
 191 	 * set of entries within the source.
 192 	 */
 193 	g_object_class_install_property (object_class,
 194 					 PROP_QUERY_MODEL,
 195 					 g_param_spec_object ("query-model",
 196 							      "RhythmDBQueryModel",
 197 							      "RhythmDBQueryModel object",
 198 							      RHYTHMDB_TYPE_QUERY_MODEL,
 199 							      G_PARAM_READWRITE));
 200 	/**
 201 	 * RBSource:entry-type:
 202 	 *
 203 	 * Entry type for entries in this source.
 204 	 */
 205 	g_object_class_install_property (object_class,
 206 					 PROP_ENTRY_TYPE,
 207 					 g_param_spec_object ("entry-type",
 208 							      "Entry type",
 209 							      "Type of the entries which should be displayed by this source",
 210 							      RHYTHMDB_TYPE_ENTRY_TYPE,
 211 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 212 	/**
 213 	 * RBSource:base-query-model:
 214 	 *
 215 	 * The unfiltered query model for the source, containing all entries in the source.
 216 	 * Source classes should override this if they perform filtering based on the search
 217 	 * box or a browser.
 218 	 */
 219 	g_object_class_install_property (object_class,
 220 					 PROP_BASE_QUERY_MODEL,
 221 					 g_param_spec_object ("base-query-model",
 222 							      "RhythmDBQueryModel",
 223 							      "RhythmDBQueryModel object (unfiltered)",
 224 							      RHYTHMDB_TYPE_QUERY_MODEL,
 225 							      G_PARAM_READABLE));
 226 	/**
 227 	 * RBSource:play-order:
 228 	 *
 229 	 * If the source provides its own play order, it can override this property.
 230 	 */
 231 	g_object_class_install_property (object_class,
 232 					 PROP_PLAY_ORDER,
 233 					 g_param_spec_object ("play-order",
 234 							      "play order",
 235 							      "optional play order specific to the source",
 236 							      RB_TYPE_PLAY_ORDER,
 237 							      G_PARAM_READABLE));
 238 
 239 	/**
 240 	 * RBSource:load-status:
 241 	 *
 242 	 * Indicates whether the source is not loaded, is currently loading data, or is
 243 	 * fully loaded.
 244 	 */
 245 	g_object_class_install_property (object_class,
 246 					 PROP_LOAD_STATUS,
 247 					 g_param_spec_enum ("load-status",
 248 							    "load-status",
 249 							    "load status",
 250 							    RB_TYPE_SOURCE_LOAD_STATUS,
 251 							    RB_SOURCE_LOAD_STATUS_LOADED,
 252 							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 253 
 254 	/**
 255 	 * RBSource:settings:
 256 	 *
 257 	 * The #GSettings instance storing settings for the source.  The instance must
 258 	 * have a schema of org.gnome.Rhythmbox.Source.
 259 	 */
 260 	g_object_class_install_property (object_class,
 261 					 PROP_SETTINGS,
 262 					 g_param_spec_object ("settings",
 263 							      "settings",
 264 							      "GSettings instance",
 265 							      G_TYPE_SETTINGS,
 266 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 267 	/**
 268 	 * RBSource:show-browser:
 269 	 *
 270 	 * Whether the browser widget for the source (if any) should be displayed.
 271 	 * This should be overridden in sources that include a browser widget.
 272 	 */
 273 	g_object_class_install_property (object_class,
 274 					 PROP_SHOW_BROWSER,
 275 					 g_param_spec_boolean ("show-browser",
 276 							       "show browser",
 277 							       "whether the browser widget should be shown",
 278 							       TRUE,
 279 							       G_PARAM_READWRITE));
 280 	/**
 281 	 * RBSource:toolbar-path:
 282 	 *
 283 	 * UI manager path for a toolbar to display at the top of the source.
 284 	 * The #RBSource class doesn't actually display the toolbar anywhere.
 285 	 * Adding the toolbar to a container is the responsibility of a subclass
 286 	 * such as #RBBrowserSource.
 287 	 */
 288 	g_object_class_install_property (object_class,
 289 					 PROP_TOOLBAR_PATH,
 290 					 g_param_spec_string ("toolbar-path",
 291 							      "toolbar path",
 292 							      "toolbar UI path",
 293 							      NULL,
 294 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 295 
 296 	/**
 297 	 * RBSource::filter-changed:
 298 	 * @source: the #RBSource
 299 	 *
 300 	 * Fires when the user changes the filter, either by changing the
 301 	 * contents of the search box or by selecting a different browser
 302 	 * entry.
 303 	 */
 304 	rb_source_signals[FILTER_CHANGED] =
 305 		g_signal_new ("filter_changed",
 306 			      RB_TYPE_SOURCE,
 307 			      G_SIGNAL_RUN_LAST,
 308 			      G_STRUCT_OFFSET (RBSourceClass, filter_changed),
 309 			      NULL, NULL,
 310 			      g_cclosure_marshal_VOID__VOID,
 311 			      G_TYPE_NONE,
 312 			      0);
 313 
 314 	g_type_class_add_private (object_class, sizeof (RBSourcePrivate));
 315 }
 316 
 317 static void
 318 rb_source_init (RBSource *source)
 319 {
 320 	source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source, RB_TYPE_SOURCE, RBSourcePrivate);
 321 }
 322 
 323 static void
 324 rb_source_dispose (GObject *object)
 325 {
 326 	RBSource *source;
 327 
 328 	g_return_if_fail (object != NULL);
 329 	g_return_if_fail (RB_IS_SOURCE (object));
 330 
 331 	source = RB_SOURCE (object);
 332 
 333 	if (source->priv->update_visibility_id != 0) {
 334 		g_source_remove (source->priv->update_visibility_id);
 335 		source->priv->update_visibility_id = 0;
 336 	}
 337 	if (source->priv->update_status_id != 0) {
 338 		g_source_remove (source->priv->update_status_id);
 339 		source->priv->update_status_id = 0;
 340 	}
 341 	if (source->priv->settings != NULL) {
 342 		g_object_unref (source->priv->settings);
 343 		source->priv->settings = NULL;
 344 	}
 345 
 346 	G_OBJECT_CLASS (rb_source_parent_class)->dispose (object);
 347 }
 348 
 349 static void
 350 rb_source_finalize (GObject *object)
 351 {
 352 	RBSource *source;
 353 
 354 	g_return_if_fail (object != NULL);
 355 	g_return_if_fail (RB_IS_SOURCE (object));
 356 
 357 	source = RB_SOURCE (object);
 358 
 359 	if (source->priv->query_model != NULL) {
 360 		rb_debug ("Unreffing model %p count: %d",
 361 			  source->priv->query_model,
 362 			  G_OBJECT (source->priv->query_model)->ref_count);
 363 		g_object_unref (source->priv->query_model);
 364 	}
 365 
 366 	g_free (source->priv->toolbar_path);
 367 
 368 	G_OBJECT_CLASS (rb_source_parent_class)->finalize (object);
 369 }
 370 
 371 static gboolean
 372 update_visibility_idle (RBSource *source)
 373 {
 374 	gint count;
 375 
 376 	GDK_THREADS_ENTER ();
 377 
 378 	count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->query_model), NULL);
 379 	g_object_set (source, "visibility", (count > 0), NULL);
 380 
 381 	source->priv->update_visibility_id = 0;
 382 	GDK_THREADS_LEAVE ();
 383 	return FALSE;
 384 }
 385 
 386 static void
 387 queue_update_visibility (RBSource *source)
 388 {
 389 	if (source->priv->update_visibility_id != 0) {
 390 		g_source_remove (source->priv->update_visibility_id);
 391 	}
 392 	source->priv->update_visibility_id = g_idle_add ((GSourceFunc) update_visibility_idle, source);
 393 }
 394 
 395 /**
 396  * rb_source_set_hidden_when_empty:
 397  * @source: a #RBSource
 398  * @hidden: if TRUE, automatically hide the source
 399  *
 400  * Enables or disables automatic hiding of the source when
 401  * there are no entries in it.
 402  */
 403 void
 404 rb_source_set_hidden_when_empty (RBSource *source,
 405 				 gboolean  hidden)
 406 {
 407 	g_return_if_fail (RB_IS_SOURCE (source));
 408 
 409 	if (source->priv->hidden_when_empty != hidden) {
 410 		source->priv->hidden_when_empty = hidden;
 411 		queue_update_visibility (source);
 412 	}
 413 }
 414 
 415 static void
 416 rb_source_set_query_model_internal (RBSource *source,
 417 				    RhythmDBQueryModel *model)
 418 {
 419 	if (source->priv->query_model == model) {
 420 		return;
 421 	}
 422 
 423 	if (source->priv->query_model != NULL) {
 424 		g_signal_handlers_disconnect_by_func (source->priv->query_model,
 425 						      G_CALLBACK (rb_source_post_entry_deleted_cb),
 426 						      source);
 427 		g_signal_handlers_disconnect_by_func (source->priv->query_model,
 428 						      G_CALLBACK (rb_source_row_inserted_cb),
 429 						      source);
 430 		g_object_unref (source->priv->query_model);
 431 	}
 432 
 433 	source->priv->query_model = model;
 434 	if (source->priv->query_model != NULL) {
 435 		g_object_ref (source->priv->query_model);
 436 		g_signal_connect_object (model, "post-entry-delete",
 437 					 G_CALLBACK (rb_source_post_entry_deleted_cb),
 438 					 source, 0);
 439 		g_signal_connect_object (model, "row_inserted",
 440 					 G_CALLBACK (rb_source_row_inserted_cb),
 441 					 source, 0);
 442 	}
 443 
 444 	rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 445 }
 446 
 447 static void
 448 rb_source_set_property (GObject *object,
 449 			guint prop_id,
 450 			const GValue *value,
 451 			GParamSpec *pspec)
 452 {
 453 	RBSource *source = RB_SOURCE (object);
 454 
 455 	switch (prop_id) {
 456 	case PROP_HIDDEN_WHEN_EMPTY:
 457 		rb_source_set_hidden_when_empty (source, g_value_get_boolean (value));
 458 		break;
 459 	case PROP_QUERY_MODEL:
 460 		rb_source_set_query_model_internal (source, g_value_get_object (value));
 461 		break;
 462 	case PROP_ENTRY_TYPE:
 463 		source->priv->entry_type = g_value_get_object (value);
 464 		break;
 465 	case PROP_SETTINGS:
 466 		source->priv->settings = g_value_dup_object (value);
 467 		break;
 468 	case PROP_SHOW_BROWSER:
 469 		/* not connected to anything here */
 470 		break;
 471 	case PROP_LOAD_STATUS:
 472 		source->priv->load_status = g_value_get_enum (value);
 473 		break;
 474 	case PROP_TOOLBAR_PATH:
 475 		source->priv->toolbar_path = g_value_dup_string (value);
 476 		break;
 477 	default:
 478 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 479 		break;
 480 	}
 481 }
 482 
 483 static void
 484 rb_source_get_property (GObject *object,
 485 			guint prop_id,
 486 			GValue *value,
 487 			GParamSpec *pspec)
 488 {
 489 	RBSource *source = RB_SOURCE (object);
 490 
 491 	switch (prop_id) {
 492 	case PROP_QUERY_MODEL:
 493 		g_value_set_object (value, source->priv->query_model);
 494 		break;
 495 	case PROP_ENTRY_TYPE:
 496 		g_value_set_object (value, source->priv->entry_type);
 497 		break;
 498 	case PROP_BASE_QUERY_MODEL:
 499 		/* unless the subclass overrides it, just assume the
 500 		 * current query model is the base model.
 501 		 */
 502 		g_value_set_object (value, source->priv->query_model);
 503 		break;
 504 	case PROP_PLAY_ORDER:
 505 		g_value_set_object (value, NULL);		/* ? */
 506 		break;
 507 	case PROP_SETTINGS:
 508 		g_value_set_object (value, source->priv->settings);
 509 		break;
 510 	case PROP_SHOW_BROWSER:
 511 		g_value_set_boolean (value, FALSE);
 512 		break;
 513 	case PROP_LOAD_STATUS:
 514 		g_value_set_enum (value, source->priv->load_status);
 515 		break;
 516 	case PROP_TOOLBAR_PATH:
 517 		g_value_set_string (value, source->priv->toolbar_path);
 518 		break;
 519 	default:
 520 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 521 		break;
 522 	}
 523 }
 524 
 525 static void
 526 default_activate (RBDisplayPage *page)
 527 {
 528 	RBShell *shell;
 529 
 530 	g_object_get (page, "shell", &shell, NULL);
 531 	rb_shell_activate_source (shell,
 532 				  RB_SOURCE (page),
 533 				  RB_SHELL_ACTIVATION_ALWAYS_PLAY,
 534 				  NULL);
 535 }
 536 
 537 static void
 538 default_get_status (RBDisplayPage *page,
 539 		    char **text,
 540 		    char **progress_text,
 541 		    float *progress)
 542 {
 543 	RBSource *source = RB_SOURCE (page);
 544 	/* hack to get these strings marked for translation */
 545 	if (0) {
 546 		ngettext ("%d song", "%d songs", 0);
 547 	}
 548 
 549 	if (source->priv->query_model) {
 550 		*text = rhythmdb_query_model_compute_status_normal (source->priv->query_model,
 551 								    "%d song",
 552 								    "%d songs");
 553 		if (rhythmdb_query_model_has_pending_changes (source->priv->query_model)) {
 554 			*progress = -1.0f;
 555 		}
 556 	} else {
 557 		*text = g_strdup ("");
 558 	}
 559 }
 560 
 561 /**
 562  * rb_source_notify_filter_changed:
 563  * @source: a #RBSource
 564  *
 565  * Source implementations call this when their filter state changes
 566  */
 567 void
 568 rb_source_notify_filter_changed (RBSource *source)
 569 {
 570 	g_signal_emit (G_OBJECT (source), rb_source_signals[FILTER_CHANGED], 0);
 571 }
 572 
 573 /**
 574  * rb_source_update_play_statistics:
 575  * @source: a #RBSource
 576  * @db: the #RhythmDB instance
 577  * @entry: the #RhythmDBEntry to update
 578  *
 579  * Updates play count and play time statistics for a database entry.
 580  * Sources containing entries that do not normally reach EOS should
 581  * call this for an entry when it is no longer being played.
 582  */
 583 void
 584 rb_source_update_play_statistics (RBSource *source,
 585 				  RhythmDB *db,
 586 				  RhythmDBEntry *entry)
 587 {
 588 	time_t now;
 589 	gulong current_count;
 590 	GValue value = { 0, };
 591 
 592 	g_value_init (&value, G_TYPE_ULONG);
 593 
 594 	current_count = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_PLAY_COUNT);
 595 
 596 	g_value_set_ulong (&value, current_count + 1);
 597 
 598 	/* Increment current play count */
 599 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAY_COUNT, &value);
 600 	g_value_unset (&value);
 601 
 602 	/* Reset the last played time */
 603 	time (&now);
 604 
 605 	g_value_init (&value, G_TYPE_ULONG);
 606 	g_value_set_ulong (&value, now);
 607 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_PLAYED, &value);
 608 	g_value_unset (&value);
 609 
 610 	rhythmdb_commit (db);
 611 }
 612 
 613 /**
 614  * rb_source_get_entry_view:
 615  * @source: a #RBSource
 616  *
 617  * Returns the entry view widget for the source.
 618  *
 619  * Return value: (transfer none): the #RBEntryView instance for the source
 620  */
 621 RBEntryView *
 622 rb_source_get_entry_view (RBSource *source)
 623 {
 624 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 625 
 626 	return klass->impl_get_entry_view (source);
 627 }
 628 
 629 static GList *
 630 default_get_property_views (RBSource *source)
 631 {
 632 	return NULL;
 633 }
 634 
 635 /**
 636  * rb_source_get_property_views:
 637  * @source: a #RBSource
 638  *
 639  * Returns a list containing the #RBPropertyView instances for the
 640  * source, if any.
 641  *
 642  * Return value: (element-type RB.PropertyView) (transfer container): list of property views
 643  */
 644 GList *
 645 rb_source_get_property_views (RBSource *source)
 646 {
 647 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 648 
 649 	return klass->impl_get_property_views (source);
 650 }
 651 
 652 static gboolean
 653 default_can_rename (RBSource *source)
 654 {
 655 	return FALSE;
 656 }
 657 
 658 static gboolean
 659 is_party_mode (RBSource *source)
 660 {
 661 	gboolean result = FALSE;
 662 	RBShell *shell;
 663 
 664 	g_object_get (source, "shell", &shell, NULL);
 665 	result = rb_shell_get_party_mode (shell);
 666 	g_object_unref (shell);
 667 
 668 	return result;
 669 }
 670 
 671 /**
 672  * rb_source_can_rename:
 673  * @source: a #RBSource.
 674  *
 675  * Determines whether the source can be renamed.
 676  *
 677  * Return value: TRUE if this source can be renamed
 678  */
 679 gboolean
 680 rb_source_can_rename (RBSource *source)
 681 {
 682 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 683 
 684 	if (is_party_mode (source)) {
 685 		return FALSE;
 686 	} else {
 687 		return klass->impl_can_rename (source);
 688 	}
 689 }
 690 
 691 /**
 692  * rb_source_search:
 693  * @source: a #RBSource
 694  * @search: (allow-none): the active #RBSourceSearch instance
 695  * @cur_text: (allow-none): the current search text
 696  * @new_text: the new search text
 697  *
 698  * Updates the source with new search text.  The source
 699  * should recreate the database query that feeds into the
 700  * browser (if any).
 701  */
 702 void
 703 rb_source_search (RBSource *source,
 704 		  RBSourceSearch *search,
 705 		  const char *cur_text,
 706 		  const char *new_text)
 707 {
 708 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 709 	g_assert (new_text != NULL);
 710 
 711 	if (klass->impl_search != NULL)
 712 		klass->impl_search (source, search, cur_text, new_text);
 713 }
 714 
 715 
 716 /**
 717  * rb_source_can_cut:
 718  * @source: a #RBSource
 719  *
 720  * Determines whether the source supporst the typical cut
 721  * (as in cut-and-paste) operation.
 722  *
 723  * Return value: TRUE if cutting is supported
 724  */
 725 gboolean
 726 rb_source_can_cut (RBSource *source)
 727 {
 728 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 729 
 730 	return klass->impl_can_cut (source);
 731 }
 732 
 733 /**
 734  * rb_source_can_paste:
 735  * @source: a #RBSource
 736  *
 737  * Determines whether the source supports paste operations.
 738  *
 739  * Return value: TRUE if the pasting is supported
 740  */
 741 gboolean
 742 rb_source_can_paste (RBSource *source)
 743 {
 744 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 745 
 746 	return klass->impl_can_paste (source);
 747 }
 748 
 749 /**
 750  * rb_source_can_delete:
 751  * @source: a #RBSource
 752  *
 753  * Determines whether the source allows the user to delete
 754  * a selected set of entries.
 755  *
 756  * Return value: TRUE if deletion is supported
 757  */
 758 gboolean
 759 rb_source_can_delete (RBSource *source)
 760 {
 761 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 762 	if (is_party_mode (source)) {
 763 		return FALSE;
 764 	} else {
 765 		return klass->impl_can_delete (source);
 766 	}
 767 }
 768 
 769 /**
 770  * rb_source_can_move_to_trash:
 771  * @source: a #RBSource
 772  *
 773  * Determines whether the source allows the user to trash
 774  * the files backing a selected set of entries.
 775  *
 776  * Return value: TRUE if trashing is supported
 777  */
 778 gboolean
 779 rb_source_can_move_to_trash (RBSource *source)
 780 {
 781 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 782 	if (is_party_mode (source)) {
 783 		return FALSE;
 784 	} else {
 785 		return klass->impl_can_move_to_trash (source);
 786 	}
 787 }
 788 
 789 /**
 790  * rb_source_can_copy:
 791  * @source: a #RBSource
 792  *
 793  * Determines whether the source supports the copy part
 794  * of a copy-and-paste operation.
 795  *
 796  * Return value: TRUE if copying is supported
 797  */
 798 gboolean
 799 rb_source_can_copy (RBSource *source)
 800 {
 801 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 802 
 803 	return klass->impl_can_copy (source);
 804 }
 805 
 806 /**
 807  * rb_source_cut:
 808  * @source: a #RBSource
 809  *
 810  * Removes the currently selected entries from the source and
 811  * returns them so they can be pasted into another source.
 812  *
 813  * Return value: (element-type RB.RhythmDBEntry) (transfer full): entries cut
 814  * from the source.
 815  */
 816 GList *
 817 rb_source_cut (RBSource *source)
 818 {
 819 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 820 
 821 	return klass->impl_cut (source);
 822 }
 823 
 824 static GList *
 825 default_copy (RBSource *source)
 826 {
 827 	RBEntryView *entry_view;
 828 	entry_view = rb_source_get_entry_view (source);
 829 	if (entry_view == NULL)
 830 		return NULL;
 831 
 832 	return rb_entry_view_get_selected_entries (entry_view);
 833 }
 834 
 835 /**
 836  * rb_source_copy:
 837  * @source: a #RBSource
 838  *
 839  * Copies the selected entries to the clipboard.
 840  *
 841  * Return value: (element-type RB.RhythmDBEntry) (transfer full): a list containing
 842  * the currently selected entries from the source.
 843  */
 844 GList *
 845 rb_source_copy (RBSource *source)
 846 {
 847 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 848 
 849 	return klass->impl_copy (source);
 850 }
 851 
 852 /**
 853  * rb_source_paste:
 854  * @source: a #RBSource
 855  * @entries: (element-type RB.RhythmDBEntry): a list of #RhythmDBEntry objects to paste in
 856  *
 857  * Adds a list of entries previously cut or copied from another
 858  * source.  If the entries are not of the type used by the source,
 859  * the entries will be copied and possibly converted into an acceptable format.
 860  * This can be used for transfers to and from devices and network shares.
 861  *
 862  * If the transfer is performed using an #RBTrackTransferBatch, the batch object
 863  * is returned so the caller can monitor the transfer progress.  The caller does not
 864  * own a reference on the batch object.
 865  *
 866  * Return value: (transfer none): the #RBTrackTransferBatch used to perform the transfer (if any)
 867  */
 868 RBTrackTransferBatch *
 869 rb_source_paste (RBSource *source, GList *entries)
 870 {
 871 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 872 
 873 	return klass->impl_paste (source, entries);
 874 }
 875 
 876 /**
 877  * rb_source_can_add_to_queue:
 878  * @source: a #RBSource
 879  *
 880  * Determines whether the source can add the selected entries to
 881  * the play queue.
 882  *
 883  * Return value: TRUE if adding to the play queue is supported
 884  */
 885 gboolean
 886 rb_source_can_add_to_queue (RBSource *source)
 887 {
 888 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 889 	return klass->impl_can_add_to_queue (source);
 890 }
 891 
 892 static void
 893 default_add_to_queue (RBSource *source,
 894 		      RBSource *queue)
 895 {
 896 	RBEntryView *songs;
 897 	GList *selection;
 898 	GList *iter;
 899 
 900 	songs = rb_source_get_entry_view (source);
 901 	if (songs == NULL)
 902 		return;
 903 
 904 	selection = rb_entry_view_get_selected_entries (songs);
 905 	if (selection == NULL)
 906 		return;
 907 
 908 	for (iter = selection; iter; iter = iter->next) {
 909 		rb_static_playlist_source_add_entry (RB_STATIC_PLAYLIST_SOURCE (queue),
 910 						     (RhythmDBEntry *)iter->data, -1);
 911 	}
 912 
 913 	g_list_foreach (selection, (GFunc)rhythmdb_entry_unref, NULL);
 914 	g_list_free (selection);
 915 }
 916 
 917 /**
 918  * rb_source_add_to_queue:
 919  * @source: a #RBSource
 920  * @queue: the #RBSource for the play queue
 921  *
 922  * Adds the currently selected entries to the end of the
 923  * play queue.
 924  */
 925 void
 926 rb_source_add_to_queue (RBSource *source,
 927 			RBSource *queue)
 928 {
 929 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 930 	klass->impl_add_to_queue (source, queue);
 931 }
 932 
 933 /**
 934  * rb_source_delete:
 935  * @source: a #RBSource
 936  *
 937  * Deletes the currently selected entries from the source.
 938  */
 939 void
 940 rb_source_delete (RBSource *source)
 941 {
 942 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 943 
 944 	klass->impl_delete (source);
 945 }
 946 
 947 static void
 948 default_move_to_trash (RBSource *source)
 949 {
 950 	GList *sel, *tem;
 951 	RBEntryView *entry_view;
 952 	RhythmDB *db;
 953 
 954 	g_object_get (source->priv->query_model, "db", &db, NULL);
 955 
 956 	sel = NULL;
 957 	entry_view = rb_source_get_entry_view (source);
 958 	if (entry_view != NULL) {
 959 		sel = rb_entry_view_get_selected_entries (entry_view);
 960 	}
 961 
 962 	for (tem = sel; tem != NULL; tem = tem->next) {
 963 		rhythmdb_entry_move_to_trash (db, (RhythmDBEntry *)tem->data);
 964 		rhythmdb_commit (db);
 965 	}
 966 
 967 	g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
 968 	g_list_free (sel);
 969 	g_object_unref (db);
 970 }
 971 
 972 /**
 973  * rb_source_move_to_trash:
 974  * @source: a #RBSource
 975  *
 976  * Trashes the files backing the currently selected set of entries.
 977  * In general, this should use #rhythmdb_entry_move_to_trash to
 978  * perform the actual trash operation.
 979  */
 980 void
 981 rb_source_move_to_trash (RBSource *source)
 982 {
 983 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 984 
 985 	klass->impl_move_to_trash (source);
 986 }
 987 
 988 static void
 989 default_reset_filters (RBSource *source)
 990 {
 991 	rb_debug ("no implementation of reset_filters for this source");
 992 }
 993 
 994 /**
 995  * rb_source_reset_filters:
 996  * @source: a #RBSource
 997  *
 998  * Clears all filters (browser selections, etc.) in this source.
 999  */
1000 void
1001 rb_source_reset_filters (RBSource *source)
1002 {
1003 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1004 
1005 	klass->impl_reset_filters (source);
1006 }
1007 
1008 /**
1009  * rb_source_can_show_properties:
1010  * @source: a #RBSource
1011  *
1012  * Determines whether the source can display a properties
1013  * window for the currently selected entry (or set of entries)
1014  *
1015  * Return value: TRUE if showing properties is supported
1016  */
1017 gboolean
1018 rb_source_can_show_properties (RBSource *source)
1019 {
1020 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1021 
1022 	return (klass->impl_song_properties != NULL);
1023 }
1024 
1025 /**
1026  * rb_source_song_properties:
1027  * @source: a #RBSource
1028  *
1029  * Displays a properties window for the currently selected entries.
1030  */
1031 void
1032 rb_source_song_properties (RBSource *source)
1033 {
1034 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1035 
1036 	g_assert (klass->impl_song_properties);
1037 	klass->impl_song_properties (source);
1038 }
1039 
1040 /**
1041  * rb_source_try_playlist:
1042  * @source: a #RBSource
1043  *
1044  * Determines whether playback URIs for entries in the source should
1045  * be parsed as playlists rather than just played.
1046  *
1047  * Return value: TRUE to attempt playlist parsing
1048  */
1049 gboolean
1050 rb_source_try_playlist (RBSource *source)
1051 {
1052 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1053 
1054 	return klass->impl_try_playlist (source);
1055 }
1056 
1057 /**
1058  * rb_source_want_uri:
1059  * @source: a #RBSource
1060  * @uri: a URI for the source to consider
1061  *
1062  * Returns an indication of how much the source wants to handle
1063  * the specified URI.  100 is the highest usual value, and should
1064  * only be used when the URI can only be associated with this source.
1065  * 0 should be used when the URI does not match the source at all.
1066  *
1067  * Return value: value from 0 to 100 indicating how much the
1068  *  source wants this URI.
1069  */
1070 guint
1071 rb_source_want_uri (RBSource *source, const char *uri)
1072 {
1073 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1074 	if (klass->impl_want_uri)
1075 		return klass->impl_want_uri (source, uri);
1076 	return 0;
1077 }
1078 
1079 /**
1080  * rb_source_uri_is_source:
1081  * @source: a #RBSource
1082  * @uri: a URI for the source to consider
1083  *
1084  * Checks if the URI matches the source itself.  A source
1085  * should return TRUE here if the URI points to the device that
1086  * the source represents, for example.
1087  *
1088  * Return value: TRUE if the URI identifies the source itself.
1089  */
1090 gboolean
1091 rb_source_uri_is_source (RBSource *source, const char *uri)
1092 {
1093 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1094 	if (klass->impl_uri_is_source)
1095 		return klass->impl_uri_is_source (source, uri);
1096 	return FALSE;
1097 }
1098 
1099 /**
1100  * rb_source_add_uri:
1101  * @source: a #RBSource
1102  * @uri: a URI to add
1103  * @title: theoretically, the title of the entity the URI points to
1104  * @genre: theoretically, the genre of the entity the URI points to
1105  * @callback: a callback function to call when complete
1106  * @data: data to pass to the callback
1107  * @destroy_data: function to call to destroy the callback data
1108  *
1109  * Adds an entry corresponding to the URI to the source.  The
1110  * @title and @genre parameters are not really used.
1111  */
1112 void
1113 rb_source_add_uri (RBSource *source,
1114 		   const char *uri,
1115 		   const char *title,
1116 		   const char *genre,
1117 		   RBSourceAddCallback callback,
1118 		   gpointer data,
1119 		   GDestroyNotify destroy_data)
1120 {
1121 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1122 	if (klass->impl_add_uri)
1123 		klass->impl_add_uri (source, uri, title, genre, callback, data, destroy_data);
1124 }
1125 
1126 /**
1127  * rb_source_can_pause:
1128  * @source: a #RBSource
1129  *
1130  * Determines whether playback of entries from the source can
1131  * be paused.
1132  *
1133  * Return value: TRUE if pausing is supported
1134  */
1135 gboolean
1136 rb_source_can_pause (RBSource *source)
1137 {
1138 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1139 
1140 	return klass->impl_can_pause (source);
1141 }
1142 
1143 static gboolean
1144 default_try_playlist (RBSource *source)
1145 {
1146 	return FALSE;
1147 }
1148 
1149 static RBSourceEOFType
1150 default_handle_eos (RBSource *source)
1151 {
1152 	return RB_SOURCE_EOF_NEXT;
1153 }
1154 
1155 /**
1156  * rb_source_handle_eos:
1157  * @source: a #RBSource
1158  *
1159  * Determines how EOS events should be handled when playing entries
1160  * from the source.
1161  *
1162  * Return value: EOS event handling type
1163  */
1164 RBSourceEOFType
1165 rb_source_handle_eos (RBSource *source)
1166 {
1167 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1168 
1169 	return klass->impl_handle_eos (source);
1170 }
1171 
1172 static RBEntryView*
1173 default_get_entry_view (RBSource *source)
1174 {
1175 	return NULL;
1176 }
1177 
1178 static char *
1179 default_get_delete_action (RBSource *source)
1180 {
1181 	return g_strdup ("EditRemove");
1182 }
1183 
1184 /**
1185  * rb_source_get_delete_action:
1186  * @source: a #RBSource
1187  *
1188  * Returns the name of the UI action to use for 'delete'.
1189  * This allows the source to customise the visible action name
1190  * and description to better describe what deletion actually does.
1191  *
1192  * Return value: allocated string holding UI action name
1193  */
1194 char *
1195 rb_source_get_delete_action (RBSource *source)
1196 {
1197 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
1198 	return klass->impl_get_delete_action (source);
1199 }
1200 
1201 static gboolean
1202 _update_status_idle (RBSource *source)
1203 {
1204 	rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
1205 
1206 	if (source->priv->hidden_when_empty)
1207 		update_visibility_idle (source);
1208 
1209 	source->priv->update_status_id = 0;
1210 	return FALSE;
1211 }
1212 
1213 static void
1214 rb_source_row_inserted_cb (GtkTreeModel *model,
1215 			   GtkTreePath *path,
1216 			   GtkTreeIter *iter,
1217 			   RBSource *source)
1218 {
1219 	if (source->priv->update_status_id == 0)
1220 		source->priv->update_status_id = g_idle_add ((GSourceFunc)_update_status_idle, source);
1221 }
1222 
1223 static void
1224 rb_source_post_entry_deleted_cb (GtkTreeModel *model,
1225 				 RhythmDBEntry *entry,
1226 				 RBSource *source)
1227 {
1228 	if (source->priv->update_status_id == 0)
1229 		source->priv->update_status_id = g_idle_add ((GSourceFunc)_update_status_idle, source);
1230 }
1231 
1232 static void
1233 rb_source_gather_hash_keys (char *key,
1234 			    gpointer unused,
1235 			    GList **data)
1236 {
1237 	*data = g_list_prepend (*data, key);
1238 }
1239 
1240 /**
1241  * rb_source_gather_selected_properties:
1242  * @source: a #RBSource
1243  * @prop: property for which to gather selection
1244  *
1245  * Returns a list containing the values of the specified
1246  * property from the selected entries in the source.
1247  * This is used to implement the 'browse this artist' (etc.)
1248  * actions.
1249  *
1250  * Return value: (element-type utf8) (transfer full): list of property values
1251  */
1252 GList *
1253 rb_source_gather_selected_properties (RBSource *source,
1254 				      RhythmDBPropType prop)
1255 {
1256 	RBEntryView *entryview;
1257 	GList *selected, *tem;
1258 	GHashTable *selected_set;
1259 
1260 	entryview = rb_source_get_entry_view (source);
1261 	if (entryview == NULL)
1262 		return NULL;
1263 
1264 	selected_set = g_hash_table_new (g_str_hash, g_str_equal);
1265 	selected = rb_entry_view_get_selected_entries (entryview);
1266 
1267 	for (tem = selected; tem; tem = tem->next) {
1268 		RhythmDBEntry *entry = tem->data;
1269 		char *val = g_strdup (rhythmdb_entry_get_string (entry, prop));
1270 		g_hash_table_insert (selected_set, val, NULL);
1271 	}
1272 
1273 	g_list_foreach (selected, (GFunc)rhythmdb_entry_unref, NULL);
1274 	g_list_free (selected);
1275 
1276 	tem = NULL;
1277 	g_hash_table_foreach (selected_set, (GHFunc) rb_source_gather_hash_keys,
1278 			      &tem);
1279 	g_hash_table_destroy (selected_set);
1280 	return tem;
1281 }
1282 
1283 /**
1284  * _rb_source_check_entry_type:
1285  * @source: a #RBSource
1286  * @entry: a #RhythmDBEntry
1287  *
1288  * Checks if a database entry matches the entry type for the source.
1289  *
1290  * Return value: %TRUE if the entry matches the source's entry type.
1291  */
1292 gboolean
1293 _rb_source_check_entry_type (RBSource *source, RhythmDBEntry *entry)
1294 {
1295 	RhythmDBEntryType *entry_type;
1296 	gboolean ret = TRUE;
1297 
1298 	g_object_get (source, "entry-type", &entry_type, NULL);
1299 	if (entry_type != NULL) {
1300 		if (rhythmdb_entry_get_entry_type (entry) != entry_type) {
1301 			ret = FALSE;
1302 		}
1303 		g_object_unref (entry_type);
1304 	}
1305 	return ret;
1306 }
1307 
1308 /**
1309  * _rb_source_set_import_status:
1310  * @source: an #RBSource
1311  * @job: a #RhythmDBImportJob
1312  * @progress_text: used to return progress text
1313  * @progress: used to return progress fraction
1314  *
1315  * Used in implementations of the get_status method to provide source
1316  * status information based on a #RhythmDBImportJob.
1317  */
1318 void
1319 _rb_source_set_import_status (RBSource *source, RhythmDBImportJob *job, char **progress_text, float *progress)
1320 {
1321 	int total;
1322 	int imported;
1323 
1324 	total = rhythmdb_import_job_get_total (job);
1325 	imported = rhythmdb_import_job_get_imported (job);
1326 
1327 	g_free (*progress_text);
1328 	*progress_text = g_strdup_printf (_("Importing (%d/%d)"), imported, total);
1329 	*progress = ((float)imported / (float)total);
1330 }
1331 
1332 static gboolean
1333 sort_order_get_mapping (GValue *value, GVariant *variant, gpointer data)
1334 {
1335 	const char *column;
1336 	gboolean sort_type;
1337 	char *str;
1338 
1339 	g_variant_get (variant, "(&sb)", &column, &sort_type);
1340 	str = g_strdup_printf ("%s,%s", column, sort_type ? "ascending" : "descending");
1341 	g_value_take_string (value, str);
1342 	return TRUE;
1343 }
1344 
1345 static GVariant *
1346 sort_order_set_mapping (const GValue *value, const GVariantType *expected_type, gpointer data)
1347 {
1348 	gboolean sort_type;
1349 	GVariant *var;
1350 	char **strs;
1351 
1352 	strs = g_strsplit (g_value_get_string (value), ",", 0);
1353 	if (!strcmp ("ascending", strs[1])) {
1354 		sort_type = TRUE;
1355 	} else if (!strcmp ("descending", strs[1])) {
1356 		sort_type = FALSE;
1357 	} else {
1358 		g_warning ("atttempting to sort in unknown direction");
1359 		sort_type = TRUE;
1360 	}
1361 
1362 	var = g_variant_new ("(sb)", strs[0], sort_type);
1363 	g_strfreev (strs);
1364 	return var;
1365 }
1366 
1367 static void
1368 sync_paned_position (GSettings *settings, GObject *paned)
1369 {
1370 	int pos;
1371 	g_object_get (paned, "position", &pos, NULL);
1372 
1373 	if (pos != g_settings_get_int (settings, "paned-position")) {
1374 		g_settings_set_int (settings, "paned-position", pos);
1375 	}
1376 }
1377 
1378 static void
1379 paned_position_changed_cb (GObject *paned, GParamSpec *pspec, GSettings *settings)
1380 {
1381 	rb_settings_delayed_sync (settings,
1382 				  (RBDelayedSyncFunc) sync_paned_position,
1383 				  g_object_ref (paned),
1384 				  g_object_unref);
1385 }
1386 
1387 /**
1388  * rb_source_bind_settings:
1389  * @source: the #RBSource
1390  * @entry_view: (allow-none): the #RBEntryView for the source
1391  * @paned: (allow-none): the #GtkPaned containing the entry view and the browser
1392  * @browser: (allow-none):  the browser (typically a #RBLibraryBrowser) for the source
1393  *
1394  * Binds the source's #GSettings instance to the given widgets.  Should be called
1395  * from the source's constructed method.
1396  *
1397  * If the browser widget has a browser-views property, it will be bound to the
1398  * browser-views settings key.
1399  */
1400 void
1401 rb_source_bind_settings (RBSource *source, GtkWidget *entry_view, GtkWidget *paned, GtkWidget *browser)
1402 {
1403 	char *name;
1404 	GSettings *common_settings;
1405 
1406 	common_settings = g_settings_new ("org.gnome.rhythmbox.sources");
1407 	g_object_get (source, "name", &name, NULL);
1408 
1409 	if (entry_view != NULL) {
1410 		rb_debug ("binding entry view sort order for %s", name);
1411 		if (source->priv->settings) {
1412 			g_settings_bind_with_mapping (source->priv->settings, "sorting", entry_view, "sort-order",
1413 						      G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET | G_SETTINGS_BIND_NO_SENSITIVITY,
1414 						      (GSettingsBindGetMapping) sort_order_get_mapping,
1415 						      (GSettingsBindSetMapping) sort_order_set_mapping,
1416 						      NULL, NULL);
1417 		}
1418 
1419 		g_settings_bind (common_settings, "visible-columns",
1420 				 entry_view, "visible-columns",
1421 				 G_SETTINGS_BIND_DEFAULT);
1422 	}
1423 
1424 	if (paned != NULL && source->priv->settings != NULL) {
1425 		rb_debug ("binding paned position for %s", name);
1426 		/* can't use a normal binding here, as we want to delay writing to the
1427 		 * setting while the separator is being dragged.
1428 		 */
1429 		g_settings_bind (source->priv->settings, "paned-position", paned, "position", G_SETTINGS_BIND_GET);
1430 		g_signal_connect_object (paned, "notify::position", G_CALLBACK (paned_position_changed_cb), source->priv->settings, 0);
1431 	}
1432 
1433 	if (browser) {
1434 		rb_debug ("binding show-browser for %s", name);
1435 		if (source->priv->settings) {
1436 			g_settings_bind (source->priv->settings, "show-browser", source, "show-browser", G_SETTINGS_BIND_DEFAULT);
1437 		}
1438 
1439 		if (g_object_class_find_property (G_OBJECT_GET_CLASS (browser), "browser-views")) {
1440 			g_settings_bind (common_settings, "browser-views", browser, "browser-views", G_SETTINGS_BIND_DEFAULT);
1441 		}
1442 	}
1443 
1444 	g_free (name);
1445 }
1446 
1447 /* This should really be standard. */
1448 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
1449 
1450 GType
1451 rb_source_eof_type_get_type (void)
1452 {
1453 	static GType etype = 0;
1454 
1455 	if (etype == 0)	{
1456 		static const GEnumValue values[] = {
1457 			ENUM_ENTRY (RB_SOURCE_EOF_ERROR, "error"),
1458 			ENUM_ENTRY (RB_SOURCE_EOF_STOP, "stop"),
1459 			ENUM_ENTRY (RB_SOURCE_EOF_RETRY, "retry"),
1460 			ENUM_ENTRY (RB_SOURCE_EOF_NEXT, "next"),
1461 			{ 0, 0, 0 }
1462 		};
1463 
1464 		etype = g_enum_register_static ("RBSourceEOFType", values);
1465 	}
1466 
1467 	return etype;
1468 }
1469 
1470 GType
1471 rb_source_load_status_get_type (void)
1472 {
1473 	static GType etype = 0;
1474 
1475 	if (etype == 0) {
1476 		static const GEnumValue values[] = {
1477 			ENUM_ENTRY (RB_SOURCE_LOAD_STATUS_NOT_LOADED, "not-loaded"),
1478 			ENUM_ENTRY (RB_SOURCE_LOAD_STATUS_WAITING, "waiting"),
1479 			ENUM_ENTRY (RB_SOURCE_LOAD_STATUS_LOADING, "loading"),
1480 			ENUM_ENTRY (RB_SOURCE_LOAD_STATUS_LOADED, "loaded"),
1481 			{ 0, 0, 0 }
1482 		};
1483 
1484 		etype = g_enum_register_static ("RBSourceLoadStatus", values);
1485 	}
1486 
1487 	return etype;
1488 }
1489 
1490 /* introspection annotations for vmethods */
1491 
1492 /**
1493  * impl_get_entry_view:
1494  * @source: a #RBSource
1495  *
1496  * Return value: (transfer none): the RBEntryView for the source
1497  */
1498 
1499 /**
1500  * impl_get_property_views:
1501  * @source: a #RBSource
1502  *
1503  * Return value: (element-type RB.PropertyView) (transfer container): list of property views
1504  */
1505 
1506 /**
1507  * impl_cut:
1508  * @source: a #RBSource
1509  *
1510  * Return value: (element-type RB.RhythmDBEntry) (transfer full): list of entries
1511  */
1512 
1513 /**
1514  * impl_copy:
1515  * @source: a #RBSource
1516  *
1517  * Return value: (element-type RB.RhythmDBEntry) (transfer full): list of entries
1518  */
1519 
1520 /**
1521  * impl_paste:
1522  * @source: a #RBSource
1523  * @entries: (element-type RB.RhythmDBEntry) (transfer none): list of entries to paste
1524  */