hythmbox-2.98/plugins/grilo/rb-grilo-source.c

No issues found

   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
   2  *
   3  *  Copyright (C) 2011 Jonathan Matthew
   4  *
   5  *  This program is free software; you can redistribute it and/or modify
   6  *  it under the terms of the GNU General Public License as published by
   7  *  the Free Software Foundation; either version 2 of the License, or
   8  *  (at your option) any later version.
   9  *
  10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
  11  *  GStreamer plugins to be used and distributed together with GStreamer
  12  *  and Rhythmbox. This permission is above and beyond the permissions granted
  13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  14  *  you may extend this exception to your version of the code, but you are not
  15  *  obligated to do so. If you do not wish to do so, delete this exception
  16  *  statement from your version.
  17  *
  18  *  This program is distributed in the hope that it will be useful,
  19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21  *  GNU General Public License for more details.
  22  *
  23  *  You should have received a copy of the GNU General Public License
  24  *  along with this program; if not, write to the Free Software
  25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  26  *
  27  */
  28 
  29 #include "config.h"
  30 
  31 #include <string.h>
  32 #include <gtk/gtk.h>
  33 #include <glib/gi18n.h>
  34 #include <grilo.h>
  35 
  36 #include "rhythmdb.h"
  37 #include "rb-shell.h"
  38 #include "rb-shell-player.h"
  39 #include "rb-grilo-source.h"
  40 #include "rb-util.h"
  41 #include "rb-debug.h"
  42 #include "rb-file-helpers.h"
  43 #include "rb-gst-media-types.h"
  44 #include "rb-search-entry.h"
  45 
  46 /* number of items to check before giving up on finding any
  47  * of a particular type
  48  */
  49 #define CONTAINER_GIVE_UP_POINT		100
  50 
  51 /* maximum number of tracks to fetch before stopping and
  52  * requiring the user to ask for more.
  53  */
  54 #define CONTAINER_MAX_TRACKS		1000
  55 
  56 /* number of items to fetch at once */
  57 #define CONTAINER_FETCH_SIZE		50
  58 
  59 enum {
  60 	CONTAINER_UNKNOWN_MEDIA = 0,
  61 	CONTAINER_NO_MEDIA,
  62 	CONTAINER_HAS_MEDIA
  63 };
  64 
  65 enum
  66 {
  67 	PROP_0,
  68 	PROP_GRILO_SOURCE
  69 };
  70 
  71 static void rb_grilo_source_dispose (GObject *object);
  72 static void rb_grilo_source_finalize (GObject *object);
  73 static void rb_grilo_source_constructed (GObject *object);
  74 static void impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
  75 static void impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
  76 
  77 static void browser_selection_changed_cb (GtkTreeSelection *selection, RBGriloSource *source);
  78 static void browser_row_expanded_cb (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, RBGriloSource *source);
  79 static void scroll_adjust_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source);
  80 static void scroll_adjust_value_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source);
  81 static gboolean maybe_expand_container (RBGriloSource *source);
  82 static void fetch_more_cb (GtkInfoBar *bar, gint response, RBGriloSource *source);
  83 static void search_cb (RBSearchEntry *search, const char *text, RBGriloSource *source);
  84 static void notify_sort_order_cb (GObject *object, GParamSpec *pspec, RBGriloSource *source);
  85 
  86 static void impl_delete_thyself (RBDisplayPage *page);
  87 static void impl_selected (RBDisplayPage *page);
  88 static void impl_deselected (RBDisplayPage *page);
  89 
  90 static RBEntryView *impl_get_entry_view (RBSource *source);
  91 
  92 struct _RBGriloSourcePrivate
  93 {
  94 	GrlSource *grilo_source;
  95 	GList *grilo_keys;
  96 
  97 	RhythmDBEntryType *entry_type;
  98 
  99 	/* some widgets and things */
 100 	GtkWidget *paned;
 101 	RhythmDBQueryModel *query_model;
 102 	RBEntryView *entry_view;
 103 	GtkTreeStore *browser_model;
 104 	GtkWidget *browser_view;
 105 	gboolean done_initial_browse;
 106 	GtkWidget *info_bar;
 107 	GtkWidget *info_bar_label;
 108 	RBSearchEntry *search_entry;
 109 
 110 	/* current browsing operation (should allow multiple concurrent ops?) */
 111 	guint browse_op;
 112 	GrlMedia *browse_container;
 113 	GtkTreeIter browse_container_iter;
 114 	guint browse_position;
 115 	gboolean browse_got_results;
 116 	gboolean browse_got_media;
 117 	guint maybe_expand_idle;
 118 
 119 	/* current media browse operation */
 120 	guint media_browse_op;
 121 	char *search_text;
 122 	GrlMedia *media_browse_container;
 123 	GtkTreeIter media_browse_container_iter;
 124 	guint media_browse_position;
 125 	gboolean media_browse_got_results;
 126 	gboolean media_browse_got_containers;
 127 	guint media_browse_limit;
 128 
 129 	RhythmDB *db;
 130 };
 131 
 132 G_DEFINE_DYNAMIC_TYPE (RBGriloSource, rb_grilo_source, RB_TYPE_SOURCE)
 133 
 134 /* entry type */
 135 
 136 G_DEFINE_DYNAMIC_TYPE (RBGriloEntryType, rb_grilo_entry_type, RHYTHMDB_TYPE_ENTRY_TYPE);
 137 
 138 static void
 139 rb_grilo_entry_type_destroy_entry (RhythmDBEntryType *etype, RhythmDBEntry *entry)
 140 {
 141 	RBGriloEntryData *data;
 142 
 143 	data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBGriloEntryData);
 144 	g_object_unref (data->grilo_data);
 145 	if (data->grilo_container != NULL)
 146 		g_object_unref (data->grilo_container);
 147 }
 148 
 149 static void
 150 rb_grilo_entry_type_class_init (RBGriloEntryTypeClass *klass)
 151 {
 152 	RhythmDBEntryTypeClass *etype_class = RHYTHMDB_ENTRY_TYPE_CLASS (klass);
 153 	etype_class->can_sync_metadata = (RhythmDBEntryTypeBooleanFunc) rb_true_function;
 154 	etype_class->sync_metadata = (RhythmDBEntryTypeSyncFunc) rb_null_function;
 155 	etype_class->destroy_entry = rb_grilo_entry_type_destroy_entry;
 156 }
 157 
 158 static void
 159 rb_grilo_entry_type_class_finalize (RBGriloEntryTypeClass *klass)
 160 {
 161 }
 162 
 163 static void
 164 rb_grilo_entry_type_init (RBGriloEntryType *etype)
 165 {
 166 }
 167 
 168 static void
 169 rb_grilo_source_class_init (RBGriloSourceClass *klass)
 170 {
 171 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 172 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 173 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 174 
 175 	object_class->constructed = rb_grilo_source_constructed;
 176 	object_class->dispose = rb_grilo_source_dispose;
 177 	object_class->finalize = rb_grilo_source_finalize;
 178 	object_class->set_property = impl_set_property;
 179 	object_class->get_property = impl_get_property;
 180 
 181 	page_class->delete_thyself = impl_delete_thyself;
 182 	page_class->selected = impl_selected;
 183 	page_class->deselected = impl_deselected;
 184 
 185 	source_class->impl_get_entry_view = impl_get_entry_view;
 186 
 187 	g_object_class_install_property (object_class,
 188 					 PROP_GRILO_SOURCE,
 189 					 g_param_spec_object ("grilo-source",
 190 							      "grilo source",
 191 							      "grilo source object",
 192 							      GRL_TYPE_SOURCE,
 193 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 194 
 195 	g_type_class_add_private (klass, sizeof (RBGriloSourcePrivate));
 196 }
 197 
 198 static void
 199 rb_grilo_source_class_finalize (RBGriloSourceClass *klass)
 200 {
 201 }
 202 
 203 static void
 204 rb_grilo_source_init (RBGriloSource *self)
 205 {
 206 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, RB_TYPE_GRILO_SOURCE, RBGriloSourcePrivate);
 207 }
 208 
 209 static void
 210 rb_grilo_source_finalize (GObject *object)
 211 {
 212 	RBGriloSource *source = RB_GRILO_SOURCE (object);
 213 
 214 	g_free (source->priv->search_text);
 215 
 216 	g_list_free (source->priv->grilo_keys);
 217 
 218 	G_OBJECT_CLASS (rb_grilo_source_parent_class)->finalize (object);
 219 }
 220 
 221 static void
 222 rb_grilo_source_dispose (GObject *object)
 223 {
 224 	RBGriloSource *source = RB_GRILO_SOURCE (object);
 225 
 226 	if (source->priv->browse_op != 0) {
 227 		grl_operation_cancel (source->priv->browse_op);
 228 		source->priv->browse_op = 0;
 229 	}
 230 
 231 	if (source->priv->media_browse_op != 0) {
 232 		grl_operation_cancel (source->priv->media_browse_op);
 233 		source->priv->media_browse_op = 0;
 234 	}
 235 
 236 	if (source->priv->query_model != NULL) {
 237 		g_object_unref (source->priv->query_model);
 238 		source->priv->query_model = NULL;
 239 	}
 240 
 241 	if (source->priv->entry_type != NULL) {
 242 		g_object_unref (source->priv->entry_type);
 243 		source->priv->entry_type = NULL;
 244 	}
 245 
 246 	if (source->priv->maybe_expand_idle != 0) {
 247 		g_source_remove (source->priv->maybe_expand_idle);
 248 		source->priv->maybe_expand_idle = 0;
 249 	}
 250 
 251 	G_OBJECT_CLASS (rb_grilo_source_parent_class)->dispose (object);
 252 }
 253 
 254 static void
 255 rb_grilo_source_constructed (GObject *object)
 256 {
 257 	RBGriloSource *source;
 258 	RBShell *shell;
 259 	RBShellPlayer *shell_player;
 260 	const GList *source_keys;
 261 	GtkTreeViewColumn *column;
 262 	GtkCellRenderer *renderer;
 263 	GtkTreeSelection *selection;
 264 	GtkWidget *scrolled;
 265 	GtkWidget *browserbox;
 266 	GtkWidget *vbox;
 267 	GtkWidget *mainbox;
 268 	GtkAdjustment *adjustment;
 269 
 270 	RB_CHAIN_GOBJECT_METHOD (rb_grilo_source_parent_class, constructed, object);
 271 	source = RB_GRILO_SOURCE (object);
 272 
 273 	g_object_get (source, "shell", &shell, NULL);
 274 	g_object_get (shell,
 275 		      "db", &source->priv->db,
 276 		      "shell-player", &shell_player,
 277 		      NULL);
 278 	g_object_unref (shell);
 279 
 280 	g_object_get (source, "entry-type", &source->priv->entry_type, NULL);
 281 
 282 	source->priv->entry_view = rb_entry_view_new (source->priv->db, G_OBJECT (shell_player), TRUE, FALSE);
 283 	g_object_unref (shell_player);
 284 	g_signal_connect (source->priv->entry_view,
 285 			  "notify::sort-order",
 286 			  G_CALLBACK (notify_sort_order_cb),
 287 			  source);
 288 
 289 	source_keys = grl_source_supported_keys (source->priv->grilo_source);
 290 
 291 	if (g_list_find ((GList *)source_keys, GUINT_TO_POINTER(GRL_METADATA_KEY_TRACK_NUMBER))) {
 292 		rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_TRACK_NUMBER, FALSE);
 293 		source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
 294 							   GUINT_TO_POINTER(GRL_METADATA_KEY_TRACK_NUMBER));
 295 	}
 296 
 297 	if (g_list_find ((GList *)source_keys, GUINT_TO_POINTER(GRL_METADATA_KEY_TITLE))) {
 298 		rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_TITLE, TRUE);
 299 		source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
 300 							   GUINT_TO_POINTER(GRL_METADATA_KEY_TITLE));
 301 	}
 302 
 303 	if (g_list_find ((GList *)source_keys, GUINT_TO_POINTER(GRL_METADATA_KEY_GENRE))) {
 304 		rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_GENRE, FALSE);
 305 		source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
 306 							   GUINT_TO_POINTER(GRL_METADATA_KEY_GENRE));
 307 	}
 308 	if (g_list_find ((GList *)source_keys, GUINT_TO_POINTER(GRL_METADATA_KEY_ARTIST))) {
 309 		rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_ARTIST, FALSE);
 310 		source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
 311 							   GUINT_TO_POINTER(GRL_METADATA_KEY_ARTIST));
 312 	}
 313 	if (g_list_find ((GList *)source_keys, GUINT_TO_POINTER(GRL_METADATA_KEY_ALBUM))) {
 314 		rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_ALBUM, FALSE);
 315 		source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
 316 							   GUINT_TO_POINTER(GRL_METADATA_KEY_ALBUM));
 317 	}
 318 	/*
 319 	if (g_list_find ((GList *)source_keys, GUINT_TO_POINTER(GRL_METADATA_KEY_DATE))) {
 320 		rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_YEAR, FALSE);
 321 		source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
 322 							   GUINT_TO_POINTER(GRL_METADATA_KEY_DATE));
 323 	}
 324 	*/
 325 	if (g_list_find ((GList *)source_keys, GUINT_TO_POINTER(GRL_METADATA_KEY_DURATION))) {
 326 		rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_DURATION, FALSE);
 327 		source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
 328 							   GUINT_TO_POINTER(GRL_METADATA_KEY_DURATION));
 329 	}
 330 
 331 	source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys, GUINT_TO_POINTER(GRL_METADATA_KEY_CHILDCOUNT));
 332 	source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys, GUINT_TO_POINTER(GRL_METADATA_KEY_URL));
 333 	source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys, GUINT_TO_POINTER(GRL_METADATA_KEY_THUMBNAIL));
 334 
 335 	/* probably add an image column too? */
 336 	source->priv->browser_model = gtk_tree_store_new (4, GRL_TYPE_MEDIA, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
 337 	source->priv->browser_view = gtk_tree_view_new ();
 338 	gtk_tree_view_set_model (GTK_TREE_VIEW (source->priv->browser_view), GTK_TREE_MODEL (source->priv->browser_model));
 339 
 340 	column = gtk_tree_view_column_new ();
 341 	renderer = gtk_cell_renderer_text_new ();
 342 	gtk_tree_view_column_set_title (column, _("Browse"));
 343 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
 344 	gtk_tree_view_column_add_attribute (column, renderer, "text", 1);
 345 	gtk_tree_view_column_set_expand (column, TRUE);
 346 	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
 347 
 348 	gtk_tree_view_append_column (GTK_TREE_VIEW (source->priv->browser_view), column);
 349 	gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (source->priv->browser_view), TRUE);
 350 	gtk_tree_view_set_expander_column (GTK_TREE_VIEW (source->priv->browser_view), column);
 351 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (source->priv->browser_view), TRUE);
 352 	gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (source->priv->browser_view), TRUE);
 353 
 354 	g_signal_connect (source->priv->browser_view, "row-expanded", G_CALLBACK (browser_row_expanded_cb), source);
 355 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (source->priv->browser_view));
 356 	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);	/* should be multiple eventually */
 357 	g_signal_connect (selection, "changed", G_CALLBACK (browser_selection_changed_cb), source);
 358 
 359 	scrolled = gtk_scrolled_window_new (NULL, NULL);
 360 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN);
 361 	adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled));
 362 	g_signal_connect (adjustment, "changed", G_CALLBACK (scroll_adjust_changed_cb), source);
 363 	g_signal_connect (adjustment, "value-changed", G_CALLBACK (scroll_adjust_value_changed_cb), source);
 364 
 365 	browserbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
 366 
 367 	/* search bar (if the source supports searching) */
 368 	if (grl_source_supported_operations (source->priv->grilo_source) & GRL_OP_SEARCH) {
 369 		source->priv->search_entry = rb_search_entry_new (FALSE);
 370 		g_object_set (source->priv->search_entry, "explicit-mode", TRUE, NULL);
 371 		g_signal_connect (source->priv->search_entry, "search", G_CALLBACK (search_cb), source);
 372 		g_signal_connect (source->priv->search_entry, "activate", G_CALLBACK (search_cb), source);
 373 		gtk_box_pack_start (GTK_BOX (browserbox), GTK_WIDGET (source->priv->search_entry), FALSE, FALSE, 6);
 374 	}
 375 	gtk_container_add (GTK_CONTAINER (scrolled), source->priv->browser_view);
 376 	gtk_box_pack_start (GTK_BOX (browserbox), scrolled, TRUE, TRUE, 0);
 377 
 378 	mainbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
 379 	gtk_box_pack_start (GTK_BOX (source), mainbox, TRUE, TRUE, 0);
 380 
 381 	/* info bar */
 382 	source->priv->info_bar_label = gtk_label_new ("");
 383 	source->priv->info_bar = gtk_info_bar_new ();
 384 	gtk_info_bar_set_message_type (GTK_INFO_BAR (source->priv->info_bar), GTK_MESSAGE_INFO);
 385 	gtk_info_bar_add_button (GTK_INFO_BAR (source->priv->info_bar), _("Fetch more tracks"), GTK_RESPONSE_OK);
 386 	gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (source->priv->info_bar))),
 387 			   source->priv->info_bar_label);
 388 	gtk_widget_show (GTK_WIDGET (source->priv->info_bar_label));
 389 	gtk_widget_set_no_show_all (GTK_WIDGET (source->priv->info_bar), TRUE);
 390 	g_signal_connect (source->priv->info_bar, "response", G_CALLBACK (fetch_more_cb), source);
 391 
 392 	/* don't allow the browser to be hidden? */
 393 	source->priv->paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
 394 	rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (source->priv->entry_view), source->priv->paned, NULL);
 395 	gtk_paned_pack1 (GTK_PANED (source->priv->paned), browserbox, FALSE, FALSE);
 396 
 397 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
 398 	gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (source->priv->entry_view), TRUE, TRUE, 0);
 399 	gtk_box_pack_start (GTK_BOX (vbox), source->priv->info_bar, FALSE, FALSE, 0);
 400 	gtk_paned_pack2 (GTK_PANED (source->priv->paned), vbox, TRUE, FALSE);
 401 
 402 	gtk_box_pack_start (GTK_BOX (mainbox), source->priv->paned, TRUE, TRUE, 0);
 403 
 404 	gtk_widget_show_all (GTK_WIDGET (source));
 405 }
 406 
 407 RBSource *
 408 rb_grilo_source_new (GObject *plugin, GrlSource *grilo_source)
 409 {
 410 	GObject *source;
 411 	RBShell *shell;
 412 	GSettings *settings;
 413 	RhythmDBEntryType *entry_type;
 414 	RhythmDB *db;
 415 	char *name;
 416 
 417 	name = g_strdup_printf ("grilo:%s", grl_source_get_id (grilo_source));
 418 
 419 	g_object_get (plugin, "object", &shell, NULL);
 420 	g_object_get (shell, "db", &db, NULL);
 421 	entry_type = g_object_new (rb_grilo_entry_type_get_type (),
 422 				   "db", db,
 423 				   "name", name,
 424 				   "save-to-disk", FALSE,
 425 				   "category", RHYTHMDB_ENTRY_NORMAL,
 426 				   "type-data-size", sizeof(RBGriloEntryData),
 427 				   NULL);
 428 	rhythmdb_register_entry_type (db, entry_type);
 429 	g_object_unref (db);
 430 	g_free (name);
 431 
 432 	settings = g_settings_new ("org.gnome.rhythmbox.plugins.grilo");
 433 	source = g_object_new (RB_TYPE_GRILO_SOURCE,
 434 			       "name", grl_source_get_name (grilo_source),
 435 			       "entry-type", entry_type,
 436 			       "shell", shell,
 437 			       "plugin", plugin,
 438 			       "show-browser", FALSE,
 439 			       "settings", g_settings_get_child (settings, "source"),
 440 			       "grilo-source", grilo_source,
 441 			       NULL);
 442 	g_object_unref (settings);
 443 
 444 	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
 445 
 446 	g_object_unref (shell);
 447 	return RB_SOURCE (source);
 448 }
 449 
 450 static void
 451 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 452 {
 453 	RBGriloSource *source = RB_GRILO_SOURCE (object);
 454 	switch (prop_id) {
 455 	case PROP_GRILO_SOURCE:
 456 		source->priv->grilo_source = g_value_get_object (value);
 457 		break;
 458 	default:
 459 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 460 		break;
 461 	}
 462 }
 463 
 464 static void
 465 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 466 {
 467 	RBGriloSource *source = RB_GRILO_SOURCE (object);
 468 
 469 	switch (prop_id) {
 470 	case PROP_GRILO_SOURCE:
 471 		g_value_set_object (value, source->priv->grilo_source);
 472 		break;
 473 	default:
 474 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 475 		break;
 476 	}
 477 }
 478 
 479 static void
 480 impl_delete_thyself (RBDisplayPage *page)
 481 {
 482 	RBGriloSource *source = RB_GRILO_SOURCE (page);
 483 	RhythmDBEntryType *entry_type;
 484 
 485 	if (source->priv->browse_op != 0) {
 486 		grl_operation_cancel (source->priv->browse_op);
 487 		source->priv->browse_op = 0;
 488 	}
 489 
 490 	if (source->priv->media_browse_op != 0) {
 491 		grl_operation_cancel (source->priv->media_browse_op);
 492 		source->priv->media_browse_op = 0;
 493 	}
 494 
 495 	g_object_get (source, "entry-type", &entry_type, NULL);
 496 	rhythmdb_entry_delete_by_type (source->priv->db, entry_type);
 497 	g_object_unref (entry_type);
 498 
 499 	rhythmdb_commit (source->priv->db);
 500 }
 501 
 502 void
 503 _rb_grilo_source_register_type (GTypeModule *module)
 504 {
 505 	rb_grilo_source_register_type (module);
 506 	rb_grilo_entry_type_register_type (module);
 507 }
 508 
 509 static GrlOperationOptions *
 510 make_operation_options (RBGriloSource *source, GrlSupportedOps op, int position)
 511 {
 512 	GrlOperationOptions *options;
 513 	GrlCaps *caps;
 514 
 515 	caps = grl_source_get_caps (source->priv->grilo_source, op);
 516 
 517 	options = grl_operation_options_new (caps);
 518 	grl_operation_options_set_skip (options, position);
 519 	grl_operation_options_set_count (options,
 520 					 CONTAINER_FETCH_SIZE);
 521 	grl_operation_options_set_type_filter (options, GRL_TYPE_FILTER_AUDIO);
 522 	grl_operation_options_set_flags (options, GRL_RESOLVE_NORMAL);
 523 
 524 	return options;
 525 }
 526 
 527 /* grilo media -> rhythmdb entry */
 528 
 529 static void
 530 set_string_prop_from_key (RhythmDB *db, RhythmDBEntry *entry, RhythmDBPropType prop, GrlData *data, GrlKeyID key)
 531 {
 532 	GValue v = {0,};
 533 	if (grl_data_has_key (data, key) == FALSE)
 534 		return;
 535 
 536 	g_value_init (&v, G_TYPE_STRING);
 537 	g_value_set_string (&v, grl_data_get_string (data, key));
 538 	rhythmdb_entry_set (db, entry, prop, &v);
 539 	g_value_unset (&v);
 540 }
 541 
 542 static RhythmDBEntry *
 543 create_entry_for_media (RhythmDB *db, RhythmDBEntryType *entry_type, GrlData *data, GrlData *container)
 544 {
 545 	RhythmDBEntry *entry;
 546 	RBGriloEntryData *entry_data;
 547 
 548 	entry = rhythmdb_entry_lookup_by_location (db, grl_media_get_url (GRL_MEDIA (data)));
 549 	if (entry != NULL) {
 550 		return entry;
 551 	}
 552 
 553 	rb_debug ("creating entry for %s / %s", grl_media_get_url (GRL_MEDIA (data)), grl_media_get_id (GRL_MEDIA (data)));
 554 
 555 	entry = rhythmdb_entry_new (db, entry_type, grl_media_get_url (GRL_MEDIA (data)));	/* just use the url? */
 556 	if (entry == NULL) {
 557 		/* crap. */
 558 		return NULL;
 559 	}
 560 
 561 	set_string_prop_from_key (db, entry, RHYTHMDB_PROP_TITLE, data, GRL_METADATA_KEY_TITLE);
 562 	set_string_prop_from_key (db, entry, RHYTHMDB_PROP_ALBUM, data, GRL_METADATA_KEY_ALBUM);
 563 	set_string_prop_from_key (db, entry, RHYTHMDB_PROP_ARTIST, data, GRL_METADATA_KEY_ARTIST);
 564 	set_string_prop_from_key (db, entry, RHYTHMDB_PROP_GENRE, data, GRL_METADATA_KEY_GENRE);
 565 	set_string_prop_from_key (db, entry, RHYTHMDB_PROP_TITLE, data, GRL_METADATA_KEY_TITLE);
 566 
 567 	if (grl_data_has_key (data, GRL_METADATA_KEY_PUBLICATION_DATE)) {
 568 		/* something - grilo has this as a string? */
 569 	}
 570 
 571 	if (grl_data_has_key (data, GRL_METADATA_KEY_BITRATE)) {
 572 		GValue v = {0,};
 573 		g_value_init (&v, G_TYPE_ULONG);
 574 		g_value_set_ulong (&v, grl_data_get_int (data, GRL_METADATA_KEY_BITRATE));
 575 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_BITRATE, &v);
 576 		g_value_unset (&v);
 577 	}
 578 
 579 	if (grl_data_has_key (data, GRL_METADATA_KEY_DURATION)) {
 580 		/* this is probably in seconds */
 581 		GValue v = {0,};
 582 		g_value_init (&v, G_TYPE_ULONG);
 583 		g_value_set_ulong (&v, grl_data_get_int (data, GRL_METADATA_KEY_DURATION));
 584 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &v);
 585 		g_value_unset (&v);
 586 	}
 587 
 588 	if (grl_data_has_key (data, GRL_METADATA_KEY_MIME)) {
 589 		const char *media_type;
 590 		media_type = rb_gst_mime_type_to_media_type (grl_data_get_string (data, GRL_METADATA_KEY_MIME));
 591 		if (media_type) {
 592 			GValue v = {0,};
 593 			g_value_init (&v, G_TYPE_STRING);
 594 			g_value_set_string (&v, media_type);
 595 			rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MEDIA_TYPE, &v);
 596 			g_value_unset (&v);
 597 		}
 598 	}
 599 
 600 	if (grl_data_has_key (data, GRL_METADATA_KEY_TRACK_NUMBER)) {
 601 		GValue v = {0,};
 602 		g_value_init (&v, G_TYPE_ULONG);
 603 		g_value_set_ulong (&v, grl_data_get_int (data, GRL_METADATA_KEY_TRACK_NUMBER));
 604 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TRACK_NUMBER, &v);
 605 		g_value_unset (&v);
 606 	}
 607 
 608 	/* rating and play count? */
 609 
 610 	entry_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBGriloEntryData);
 611 	entry_data->grilo_data = g_object_ref (data);
 612 	if (container != NULL) {
 613 		entry_data->grilo_container = g_object_ref (container);
 614 	}
 615 
 616 	/* might want to consider batching this */
 617 	rhythmdb_commit (db);
 618 
 619 	return entry;
 620 }
 621 
 622 /* container browsing */
 623 
 624 static void browse_next (RBGriloSource *source);
 625 
 626 static void
 627 delete_marker_row (RBGriloSource *source, GtkTreeIter *iter)
 628 {
 629 	GtkTreeIter marker_iter;
 630 	if (gtk_tree_model_iter_children (GTK_TREE_MODEL (source->priv->browser_model), &marker_iter, iter)) {
 631 		do {
 632 			GrlMedia *container;
 633 			gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model), &marker_iter,
 634 					    0, &container,
 635 					    -1);
 636 			if (container == NULL) {
 637 				gtk_tree_store_remove (GTK_TREE_STORE (source->priv->browser_model), &marker_iter);
 638 				break;
 639 			}
 640 		} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->browser_model), &marker_iter));
 641 	}
 642 }
 643 
 644 static void
 645 set_container_type (RBGriloSource *source, GtkTreeIter *iter, gboolean has_media)
 646 {
 647 	int container_type;
 648 
 649 	gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model),
 650 			    iter,
 651 			    2, &container_type,
 652 			    -1);
 653 	if (container_type == CONTAINER_UNKNOWN_MEDIA) {
 654 		container_type = has_media ? CONTAINER_HAS_MEDIA : CONTAINER_NO_MEDIA;
 655 	}
 656 
 657 	gtk_tree_store_set (source->priv->browser_model,
 658 			    iter,
 659 			    2, container_type,
 660 			    -1);
 661 }
 662 
 663 static void
 664 grilo_browse_cb (GrlSource *grilo_source, guint operation_id, GrlMedia *media, guint remaining, RBGriloSource *source, const GError *error)
 665 {
 666 	if (operation_id != source->priv->browse_op) {
 667 		return;
 668 	}
 669 
 670 	if (error != NULL) {
 671 		/* do something? */
 672 		rb_debug ("got error for %s: %s", grl_source_get_name (grilo_source), error->message);
 673 		source->priv->browse_op = 0;
 674 		return;
 675 	}
 676 
 677 	if (media != NULL) {
 678 		source->priv->browse_got_results = TRUE;
 679 		source->priv->browse_position++;
 680 	}
 681 
 682 	if (media && GRL_IS_MEDIA_BOX (media)) {
 683 
 684 		GtkTreeIter new_row;
 685 		if (source->priv->browse_container == NULL) {
 686 			/* insert at the end */
 687 			gtk_tree_store_insert_with_values (source->priv->browser_model,
 688 							   &new_row,
 689 							   NULL,
 690 							   -1,
 691 							   0, g_object_ref (media),
 692 							   1, grl_media_get_title (media),
 693 							   2, CONTAINER_UNKNOWN_MEDIA,
 694 							   3, 0,
 695 							   -1);
 696 		} else {
 697 			int n;
 698 			/* insert before the expand marker row */
 699 			n = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->browser_model),
 700 							    &source->priv->browse_container_iter);
 701 			gtk_tree_store_insert_with_values (source->priv->browser_model,
 702 							   &new_row,
 703 							   &source->priv->browse_container_iter,
 704 							   n - 1,
 705 							   0, g_object_ref (media),
 706 							   1, grl_media_get_title (media),
 707 							   2, CONTAINER_UNKNOWN_MEDIA,
 708 							   3, 0,
 709 							   -1);
 710 		}
 711 
 712 		/* and insert an expand marker below it too */
 713 		gtk_tree_store_insert_with_values (source->priv->browser_model,
 714 						   NULL,
 715 						   &new_row,
 716 						   -1,
 717 						   0, NULL,
 718 						   1, "...",	/* needs to be translatable? */
 719 						   2, CONTAINER_NO_MEDIA,
 720 						   3, 0,
 721 						   -1);
 722 	} else if (media && GRL_IS_MEDIA_AUDIO (media)) {
 723 		source->priv->browse_got_media = TRUE;
 724 	}
 725 
 726 	if (remaining == 0) {
 727 		source->priv->browse_op = 0;
 728 		if (source->priv->browse_got_results == FALSE &&
 729 		    source->priv->browse_container != NULL) {
 730 			/* no more results for this container, so delete the marker row */
 731 			delete_marker_row (source, &source->priv->browse_container_iter);
 732 
 733 			set_container_type (source, &source->priv->browse_container_iter, source->priv->browse_got_media);
 734 			gtk_tree_store_set (source->priv->browser_model,
 735 					    &source->priv->browse_container_iter,
 736 					    3, -1,
 737 					    -1);
 738 		} else if (source->priv->browse_container != NULL) {
 739 			if (source->priv->browse_position >= CONTAINER_GIVE_UP_POINT &&
 740 			    gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->browser_model),
 741 							    &source->priv->browse_container_iter) == 1) {
 742 				/* no containers yet, so remove the marker row */
 743 				delete_marker_row (source, &source->priv->browse_container_iter);
 744 			} else {
 745 				/* store browse position for next time we want more */
 746 				gtk_tree_store_set (source->priv->browser_model,
 747 						    &source->priv->browse_container_iter,
 748 						    3, source->priv->browse_position,
 749 						    -1);
 750 				maybe_expand_container (source);
 751 			}
 752 
 753 		} else if (source->priv->browse_got_results && source->priv->browse_container == NULL) {
 754 			/* get all top-level containers */
 755 			browse_next (source);
 756 		}
 757 	}
 758 }
 759 
 760 static void
 761 browse_next (RBGriloSource *source)
 762 {
 763 	GrlOperationOptions *options;
 764 	rb_debug ("next browse op for %s (%d)",
 765 		  grl_source_get_name (source->priv->grilo_source),
 766 		  source->priv->browse_position);
 767 	source->priv->browse_got_results = FALSE;
 768 	options = make_operation_options (source, GRL_OP_BROWSE, source->priv->browse_position);
 769 	source->priv->browse_op = grl_source_browse (source->priv->grilo_source,
 770 						     source->priv->browse_container,
 771 						     source->priv->grilo_keys,
 772 						     options,
 773 						     (GrlSourceResultCb) grilo_browse_cb,
 774 						     source);
 775 }
 776 
 777 static void
 778 start_browse (RBGriloSource *source, GrlMedia *container, GtkTreeIter *container_iter, int position)
 779 {
 780 	rb_debug ("starting browse op for %s", grl_source_get_name (source->priv->grilo_source));
 781 
 782 	/* cancel existing operation? */
 783 	if (source->priv->browse_op != 0) {
 784 		grl_operation_cancel (source->priv->browse_op);
 785 	}
 786 
 787 	if (source->priv->browse_container != NULL) {
 788 		g_object_unref (source->priv->browse_container);
 789 	}
 790 	source->priv->browse_container = container;
 791 	if (container_iter != NULL) {
 792 		/* hrm, probably have to use row references here.. */
 793 		source->priv->browse_container_iter = *container_iter;
 794 	}
 795 	source->priv->browse_position = position;
 796 	source->priv->browse_got_media = FALSE;
 797 
 798 	browse_next (source);
 799 }
 800 
 801 /* media browsing */
 802 
 803 static void media_browse_next (RBGriloSource *source);
 804 
 805 static void
 806 grilo_media_browse_cb (GrlSource *grilo_source, guint operation_id, GrlMedia *media, guint remaining, RBGriloSource *source, const GError *error)
 807 {
 808 	if (operation_id != source->priv->media_browse_op) {
 809 		return;
 810 	}
 811 
 812 	if (error != NULL) {
 813 		/* do something? */
 814 		rb_debug ("got error for %s: %s",
 815 			  grl_source_get_name (grilo_source),
 816 			  error->message);
 817 		return;
 818 	}
 819 
 820 	GDK_THREADS_ENTER ();
 821 	if (media != NULL) {
 822 		source->priv->media_browse_got_results = TRUE;
 823 		source->priv->media_browse_position++;
 824 
 825 		if (GRL_IS_MEDIA_AUDIO (media)) {
 826 			RhythmDBEntry *entry;
 827 			entry = create_entry_for_media (source->priv->db,
 828 							source->priv->entry_type,
 829 							GRL_DATA (media),
 830 							GRL_DATA (source->priv->browse_container));
 831 			if (entry != NULL) {
 832 				rhythmdb_query_model_add_entry (source->priv->query_model, entry, -1);
 833 			}
 834 		} else if (GRL_IS_MEDIA_BOX (media)) {
 835 			source->priv->media_browse_got_containers = TRUE;
 836 		}
 837 	}
 838 
 839 	if (remaining == 0) {
 840 		source->priv->media_browse_op = 0;
 841 
 842 		if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->query_model), NULL) == 0 &&
 843 		    source->priv->media_browse_position >= CONTAINER_GIVE_UP_POINT) {
 844 			/* if we don't find any media within the first 100 or so results,
 845 			 * assume this container doesn't have any.  otherwise we'd load
 846 			 * the entire thing when it got selected, which would suck.
 847 			 */
 848 			rb_debug ("didn't find any media in %s, giving up", grl_media_get_title (source->priv->media_browse_container));
 849 			gtk_tree_store_set (source->priv->browser_model,
 850 					    &source->priv->media_browse_container_iter,
 851 					    2, CONTAINER_NO_MEDIA,
 852 					    -1);
 853 		} else if (source->priv->media_browse_got_results) {
 854 			if (source->priv->media_browse_position < source->priv->media_browse_limit) {
 855 				media_browse_next (source);
 856 			} else {
 857 				char *text;
 858 
 859 				text = g_strdup_printf (ngettext ("Only showing %d result",
 860 								  "Only showing %d results",
 861 								  source->priv->media_browse_position),
 862 							source->priv->media_browse_position);
 863 				gtk_label_set_text (GTK_LABEL (source->priv->info_bar_label), text);
 864 				g_free (text);
 865 
 866 				gtk_widget_show (source->priv->info_bar);
 867 			}
 868 		} else if (source->priv->media_browse_got_containers == FALSE &&
 869 			   source->priv->media_browse_container != NULL) {
 870 			/* make sure there's no marker row for this container */
 871 			delete_marker_row (source, &source->priv->media_browse_container_iter);
 872 		}
 873 	}
 874 	GDK_THREADS_LEAVE ();
 875 }
 876 
 877 static void
 878 media_browse_next (RBGriloSource *source)
 879 {
 880 	GrlOperationOptions *options;
 881 
 882 	rb_debug ("next media_browse op for %s (%d)",
 883 		  grl_source_get_name (source->priv->grilo_source),
 884 		  source->priv->media_browse_position);
 885 
 886 	source->priv->media_browse_got_results = FALSE;
 887 	if (source->priv->media_browse_container != NULL) {
 888 		options = make_operation_options (source,
 889 						  GRL_OP_BROWSE,
 890 						  source->priv->media_browse_position);
 891 		source->priv->media_browse_op =
 892 			grl_source_browse (source->priv->grilo_source,
 893 					   source->priv->media_browse_container,
 894 					   source->priv->grilo_keys,
 895 					   options,
 896 					   (GrlSourceResultCb) grilo_media_browse_cb,
 897 					   source);
 898 	} else {
 899 		options = make_operation_options (source,
 900 						  GRL_OP_SEARCH,
 901 						  source->priv->media_browse_position);
 902 		source->priv->media_browse_op =
 903 			grl_source_search (source->priv->grilo_source,
 904 					   source->priv->search_text,
 905 					   source->priv->grilo_keys,
 906 					   options,
 907 					   (GrlSourceResultCb) grilo_media_browse_cb,
 908 					   source);
 909 	}
 910 }
 911 
 912 static void
 913 start_media_browse (RBGriloSource *source, GrlMedia *container, GtkTreeIter *container_iter, guint limit)
 914 {
 915 	rb_debug ("starting media browse for %s",
 916 		  grl_source_get_name (source->priv->grilo_source));
 917 
 918 	/* cancel existing operation? */
 919 	if (source->priv->media_browse_op != 0) {
 920 		grl_operation_cancel (source->priv->media_browse_op);
 921 	}
 922 
 923 	if (source->priv->media_browse_container != NULL) {
 924 		g_object_unref (source->priv->media_browse_container);
 925 	}
 926 	source->priv->media_browse_container = container;
 927 	if (container_iter != NULL) {
 928 		/* hrm, probably have to use row references here.. */
 929 		source->priv->media_browse_container_iter = *container_iter;
 930 	}
 931 	source->priv->media_browse_position = 0;
 932 	source->priv->media_browse_limit = limit;
 933 	source->priv->media_browse_got_containers = FALSE;
 934 
 935 	if (source->priv->query_model != NULL) {
 936 		g_object_unref (source->priv->query_model);
 937 	}
 938 	source->priv->query_model = rhythmdb_query_model_new_empty (source->priv->db);
 939 	rb_entry_view_set_model (RB_ENTRY_VIEW (source->priv->entry_view), source->priv->query_model);
 940 	g_object_set (source, "query-model", source->priv->query_model, NULL);
 941 
 942 	media_browse_next (source);
 943 }
 944 
 945 static void
 946 fetch_more_cb (GtkInfoBar *bar, gint response, RBGriloSource *source)
 947 {
 948 	if (response != GTK_RESPONSE_OK) {
 949 		return;
 950 	}
 951 
 952 	gtk_widget_hide (GTK_WIDGET (bar));
 953 	source->priv->media_browse_limit += CONTAINER_MAX_TRACKS;
 954 	media_browse_next (source);
 955 }
 956 
 957 static gboolean
 958 expand_from_marker (RBGriloSource *source, GtkTreeIter *iter)
 959 {
 960 	/* this is a marker row, fetch more containers underneath the parent */
 961 	GrlMedia *container;
 962 	GtkTreeIter browse;
 963 	int position;
 964 	gtk_tree_model_iter_parent (GTK_TREE_MODEL (source->priv->browser_model), &browse, iter);
 965 	gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model),
 966 			    &browse,
 967 			    0, &container,
 968 			    3, &position,
 969 			    -1);
 970 	if (position >= 0) {
 971 		start_browse (source, container, &browse, position);
 972 		return TRUE;
 973 	}
 974 
 975 	return FALSE;
 976 }
 977 
 978 static void
 979 browser_selection_changed_cb (GtkTreeSelection *selection, RBGriloSource *source)
 980 {
 981 	GtkTreeIter iter;
 982 	GrlMedia *container;
 983 	int container_type;
 984 
 985 	gtk_widget_hide (GTK_WIDGET (source->priv->info_bar));
 986 	if (gtk_tree_selection_get_selected (selection, NULL, &iter) == FALSE) {
 987 		rb_debug ("nothing selected");
 988 		return;
 989 	}
 990 
 991 	if (source->priv->search_entry != NULL) {
 992 		rb_search_entry_clear (source->priv->search_entry);
 993 	}
 994 
 995 	gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model), &iter,
 996 			    0, &container,
 997 			    2, &container_type,
 998 			    -1);
 999 
1000 	if (container == NULL) {
1001 		expand_from_marker (source, &iter);
1002 	} else if (container_type != CONTAINER_NO_MEDIA) {
1003 		/* fetch media directly under this container */
1004 		start_media_browse (source, container, &iter, CONTAINER_MAX_TRACKS);
1005 	} else {
1006 		/* clear the track list? */
1007 	}
1008 }
1009 
1010 static gboolean
1011 maybe_expand_container (RBGriloSource *source)
1012 {
1013 	GtkTreePath *path;
1014 	GtkTreePath *end;
1015 	GtkTreeIter iter;
1016 	GtkTreeIter end_iter;
1017 	GtkTreeIter next;
1018 	GrlMedia *container;
1019 	gboolean last;
1020 
1021 	source->priv->maybe_expand_idle = 0;
1022 
1023 	if (source->priv->browse_op != 0) {
1024 		rb_debug ("not expanding, already browsing");
1025 		return FALSE;
1026 	}
1027 
1028 	/* if we find a visible marker row, find more results */
1029 	if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (source->priv->browser_view), &path, &end) == FALSE) {
1030 		rb_debug ("not expanding, nothing to expand");
1031 		return FALSE;
1032 	}
1033 
1034 	gtk_tree_model_get_iter (GTK_TREE_MODEL (source->priv->browser_model), &iter, path);
1035 	gtk_tree_model_get_iter (GTK_TREE_MODEL (source->priv->browser_model), &end_iter, end);
1036 
1037 	do {
1038 		gtk_tree_path_free (path);
1039 		path = gtk_tree_model_get_path (GTK_TREE_MODEL (source->priv->browser_model), &iter);
1040 		last = (gtk_tree_path_compare (path, end) >= 0);
1041 		gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model), &iter,
1042 				    0, &container,
1043 				    -1);
1044 		if (container == NULL) {
1045 			if (expand_from_marker (source, &iter)) {
1046 				rb_debug ("expanding");
1047 				break;
1048 			}
1049 		}
1050 
1051 		next = iter;
1052 		if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (source->priv->browser_view), path) &&
1053 		    gtk_tree_model_iter_has_child (GTK_TREE_MODEL (source->priv->browser_model), &iter)) {
1054 			gtk_tree_model_iter_children (GTK_TREE_MODEL (source->priv->browser_model), &iter, &next);
1055 		} else if (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->browser_model), &next)) {
1056 			iter = next;
1057 		} else {
1058 			if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (source->priv->browser_model), &next, &iter) == FALSE) {
1059 				break;
1060 			}
1061 			iter = next;
1062 			if (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->browser_model), &iter) == FALSE) {
1063 				break;
1064 			}
1065 		}
1066 	} while (last == FALSE);
1067 
1068 	gtk_tree_path_free (path);
1069 	gtk_tree_path_free (end);
1070 	return FALSE;
1071 }
1072 
1073 static void
1074 maybe_expand_container_idle (RBGriloSource *source)
1075 {
1076 	if (source->priv->maybe_expand_idle == 0) {
1077 		source->priv->maybe_expand_idle = g_idle_add ((GSourceFunc)maybe_expand_container, source);
1078 	}
1079 }
1080 
1081 static void
1082 browser_row_expanded_cb (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, RBGriloSource *source)
1083 {
1084 	maybe_expand_container_idle (source);
1085 }
1086 
1087 static void
1088 scroll_adjust_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source)
1089 {
1090 	maybe_expand_container_idle (source);
1091 }
1092 
1093 static void
1094 scroll_adjust_value_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source)
1095 {
1096 	maybe_expand_container_idle (source);
1097 }
1098 
1099 static void
1100 impl_selected (RBDisplayPage *page)
1101 {
1102 	RBGriloSource *source = RB_GRILO_SOURCE (page);
1103 
1104 	RB_DISPLAY_PAGE_CLASS (rb_grilo_source_parent_class)->selected (page);
1105 
1106 	if (source->priv->done_initial_browse == FALSE) {
1107 		source->priv->done_initial_browse = TRUE;
1108 		start_browse (source, NULL, NULL, 0);
1109 	}
1110 
1111 	rb_search_entry_set_mnemonic (source->priv->search_entry, TRUE);
1112 }
1113 
1114 static void
1115 impl_deselected (RBDisplayPage *page)
1116 {
1117 	RBGriloSource *source = RB_GRILO_SOURCE (page);
1118 
1119 	RB_DISPLAY_PAGE_CLASS (rb_grilo_source_parent_class)->deselected (page);
1120 
1121 	rb_search_entry_set_mnemonic (source->priv->search_entry, FALSE);
1122 }
1123 
1124 static RBEntryView *
1125 impl_get_entry_view (RBSource *bsource)
1126 {
1127 	RBGriloSource *source = RB_GRILO_SOURCE (bsource);
1128 	return source->priv->entry_view;
1129 }
1130 
1131 static void
1132 search_cb (RBSearchEntry *search, const char *text, RBGriloSource *source)
1133 {
1134 	g_free (source->priv->search_text);
1135 	source->priv->search_text = g_strdup (text);
1136 
1137 	gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (source->priv->browser_view)));
1138 
1139 	start_media_browse (source, NULL, NULL, CONTAINER_MAX_TRACKS);
1140 }
1141 
1142 static void
1143 notify_sort_order_cb (GObject *object, GParamSpec *pspec, RBGriloSource *source)
1144 {
1145 	rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
1146 }