hythmbox-2.98/widgets/rb-library-browser.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,2004 Colin Walters <walters@verbum.org>
  5  *  Copyright (C) 2006 James Livingston <doclivingston@gmail.com>
  6  *
  7  *  This program is free software; you can redistribute it and/or modify
  8  *  it under the terms of the GNU General Public License as published by
  9  *  the Free Software Foundation; either version 2 of the License, or
 10  *  (at your option) any later version.
 11  *
 12  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 13  *  GStreamer plugins to be used and distributed together with GStreamer
 14  *  and Rhythmbox. This permission is above and beyond the permissions granted
 15  *  by the GPL license by which Rhythmbox is covered. If you modify this code
 16  *  you may extend this exception to your version of the code, but you are not
 17  *  obligated to do so. If you do not wish to do so, delete this exception
 18  *  statement from your version.
 19  *
 20  *  This program is distributed in the hope that it will be useful,
 21  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 22  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 23  *  GNU General Public License for more details.
 24  *
 25  *  You should have received a copy of the GNU General Public License
 26  *  along with this program; if not, write to the Free Software
 27  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 28  *
 29  */
 30 
 31 #include <config.h>
 32 
 33 #include <string.h>
 34 
 35 #include <glib/gi18n.h>
 36 #include <gtk/gtk.h>
 37 
 38 #include "rb-library-browser.h"
 39 #include "rhythmdb-property-model.h"
 40 #include "rhythmdb-query-model.h"
 41 #include "rb-property-view.h"
 42 #include "rb-debug.h"
 43 #include "rb-util.h"
 44 
 45 static void rb_library_browser_class_init (RBLibraryBrowserClass *klass);
 46 static void rb_library_browser_init (RBLibraryBrowser *entry);
 47 static void rb_library_browser_finalize (GObject *object);
 48 static void rb_library_browser_dispose (GObject *object);
 49 static void rb_library_browser_constructed (GObject *object);
 50 static void rb_library_browser_set_property (GObject *object,
 51 					     guint prop_id,
 52 					     const GValue *value,
 53 					     GParamSpec *pspec);
 54 static void rb_library_browser_get_property (GObject *object,
 55 					     guint prop_id,
 56 					     GValue *value,
 57 					     GParamSpec *pspec);
 58 
 59 static void view_property_selected_cb (RBPropertyView *view,
 60 				       GList *selection,
 61 				       RBLibraryBrowser *widget);
 62 static void view_selection_reset_cb (RBPropertyView *view,
 63 				     RBLibraryBrowser *widget);
 64 
 65 static void update_browser_views_visibility (RBLibraryBrowser *widget);
 66 
 67 typedef struct _RBLibraryBrowserRebuildData RBLibraryBrowserRebuildData;
 68 
 69 static void destroy_idle_rebuild_model (RBLibraryBrowserRebuildData *data);
 70 
 71 G_DEFINE_TYPE (RBLibraryBrowser, rb_library_browser, GTK_TYPE_HBOX)
 72 #define RB_LIBRARY_BROWSER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_LIBRARY_BROWSER, RBLibraryBrowserPrivate))
 73 
 74 /**
 75  * SECTION:rb-library-browser
 76  * @short_description: album/artist/genre browser widget
 77  * @include: rb-library-browser.h
 78  *
 79  * This widget contains a set of #RBPropertyView<!-- -->s backed by
 80  * #RhythmDBPropertyModel<!-- -->s and constructs a chain of
 81  * #RhythmDBQueryModel<!-- -->s to perform filtering of the entries
 82  * in a source.
 83  *
 84  * It operates on an input query model, containing the full set of
 85  * entries that may be displayed in the source, and produces an
 86  * output query model containing those entries that match the current
 87  * selection.
 88  *
 89  * When the selection in any of the property views changes, or when
 90  * #rb_library_browser_reset or #rb_library_browser_set_selection are
 91  * called to manipulate the selection, the query chain is rebuilt
 92  * asynchronously to update the property views.
 93  */
 94 
 95 struct _RBLibraryBrowserRebuildData
 96 {
 97 	RBLibraryBrowser *widget;
 98 	int rebuild_prop_index;
 99 	int rebuild_idle_id;
100 };
101 
102 typedef struct
103 {
104 	RhythmDB *db;
105 	RhythmDBEntryType *entry_type;
106 	RhythmDBQueryModel *input_model;
107 	RhythmDBQueryModel *output_model;
108 
109 	GSList *browser_views_group;
110 	char *browser_views;
111 
112 	GHashTable *property_views;
113 	GHashTable *selections;
114 
115 	RBLibraryBrowserRebuildData *rebuild_data;
116 } RBLibraryBrowserPrivate;
117 
118 enum
119 {
120 	PROP_0,
121 	PROP_DB,
122 	PROP_INPUT_MODEL,
123 	PROP_OUTPUT_MODEL,
124 	PROP_ENTRY_TYPE,
125 	PROP_BROWSER_VIEWS
126 };
127 
128 typedef struct {
129 	RhythmDBPropType type;
130 	const char *name;
131 } BrowserPropertyInfo;
132 
133 static BrowserPropertyInfo browser_properties[] = {
134 	{RHYTHMDB_PROP_GENRE, N_("Genre")},
135 	{RHYTHMDB_PROP_ARTIST, N_("Artist")},
136 	{RHYTHMDB_PROP_ALBUM, N_("Album")}
137 };
138 const int num_browser_properties = G_N_ELEMENTS (browser_properties);
139 
140 static void
141 rb_library_browser_class_init (RBLibraryBrowserClass *klass)
142 {
143 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
144 
145 	object_class->finalize = rb_library_browser_finalize;
146 	object_class->dispose = rb_library_browser_dispose;
147 	object_class->constructed = rb_library_browser_constructed;
148 	object_class->set_property = rb_library_browser_set_property;
149 	object_class->get_property = rb_library_browser_get_property;
150 
151 	/**
152 	 * RBLibraryBrowser:db:
153 	 *
154 	 * #RhythmDB instance
155 	 */
156 	g_object_class_install_property (object_class,
157 					 PROP_DB,
158 					 g_param_spec_object ("db",
159 							      "db",
160 							      "RhythmDB instance",
161 							      RHYTHMDB_TYPE,
162 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
163 	/**
164 	 * RBLibraryBrowser:input-model:
165 	 *
166 	 * This #RhythmDBQueryModel defines the set of entries that
167 	 * the browser filters.  This property is not writeable.
168 	 * To set a new input query model, use 
169 	 * #rb_library_browser_set_model.
170 	 */
171 	g_object_class_install_property (object_class,
172 					 PROP_INPUT_MODEL,
173 					 g_param_spec_object ("input-model",
174 							      "input-model",
175 							      "input RhythmDBQueryModel instance",
176 							      RHYTHMDB_TYPE_QUERY_MODEL,
177 							      G_PARAM_READABLE));
178 	/**
179 	 * RBLibraryBrowser:output-model:
180 	 *
181 	 * This #RhythmDBQueryModel contains the filtered set of
182 	 * entries.  It is a subset of the entries contained in the
183 	 * input model.  This should be used as the model backing
184 	 * the source's entry view.
185 	 *
186 	 * Sources using this widget should connect to the notify
187 	 * signal for this property, updating their entry view when
188 	 * it changes.
189 	 */
190 	g_object_class_install_property (object_class,
191 					 PROP_OUTPUT_MODEL,
192 					 g_param_spec_object ("output-model",
193 							      "output-model",
194 							      "output RhythmDBQueryModel instance",
195 							      RHYTHMDB_TYPE_QUERY_MODEL,
196 							      G_PARAM_READABLE));
197 	/**
198 	 * RBLibraryBrowser:entry-type:
199 	 *
200 	 * The type of entries to use in the browser.
201 	 */
202 	g_object_class_install_property (object_class,
203 					 PROP_ENTRY_TYPE,
204 					 g_param_spec_object ("entry-type",
205 							      "Entry type",
206 							      "Type of entry to display in this browser",
207 							      RHYTHMDB_TYPE_ENTRY_TYPE,
208 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
209 	/**
210 	 * RBLibraryBrowser:browser-views:
211 	 *
212 	 * The set of browsers to display.
213 	 */
214 	g_object_class_install_property (object_class,
215 					 PROP_BROWSER_VIEWS,
216 					 g_param_spec_string ("browser-views",
217 							      "browser views",
218 							      "browser view selection",
219 							      "artists-albums",
220 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
221 
222 	g_type_class_add_private (klass, sizeof (RBLibraryBrowserPrivate));
223 }
224 
225 static void
226 rb_library_browser_init (RBLibraryBrowser *widget)
227 {
228 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
229 
230 	gtk_box_set_spacing (GTK_BOX (widget), 5);
231 
232 	priv->property_views = g_hash_table_new (g_direct_hash, g_direct_equal);
233 	priv->selections = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)rb_list_deep_free);
234 }
235 
236 static void
237 rb_library_browser_constructed (GObject *object)
238 {
239 	RBLibraryBrowser *browser;
240 	RBLibraryBrowserPrivate *priv;
241 	int i;
242 
243 	RB_CHAIN_GOBJECT_METHOD (rb_library_browser_parent_class, constructed, object);
244 
245 	browser = RB_LIBRARY_BROWSER (object);
246 	priv = RB_LIBRARY_BROWSER_GET_PRIVATE (browser);
247 
248 	for (i = 0; i < num_browser_properties; i++) {
249 		RBPropertyView *view;
250 
251 		view = rb_property_view_new (priv->db,
252 					     browser_properties[i].type,
253 					     _(browser_properties[i].name));
254 		g_hash_table_insert (priv->property_views, (gpointer)(browser_properties[i].type), view);
255 
256 		rb_property_view_set_selection_mode (view, GTK_SELECTION_MULTIPLE);
257 		g_signal_connect_object (G_OBJECT (view),
258 					 "properties-selected",
259 					 G_CALLBACK (view_property_selected_cb),
260 					 browser, 0);
261 		g_signal_connect_object (G_OBJECT (view),
262 					 "property-selection-reset",
263 					 G_CALLBACK (view_selection_reset_cb),
264 					 browser, 0);
265 		gtk_widget_show_all (GTK_WIDGET (view));
266 		gtk_widget_set_no_show_all (GTK_WIDGET (view), TRUE);
267 		gtk_box_pack_start (GTK_BOX (browser), GTK_WIDGET (view), TRUE, TRUE, 0);
268 	}
269 
270 	update_browser_views_visibility (browser);
271 }
272 
273 static void
274 rb_library_browser_dispose (GObject *object)
275 {
276 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (object);
277 	
278 	if (priv->rebuild_data != NULL) {
279 		/* this looks a bit odd, but removing the idle handler cleans up the
280 		 * data too.
281 		 */
282 		guint id = priv->rebuild_data->rebuild_idle_id;
283 		priv->rebuild_data = NULL;
284 		g_source_remove (id);
285 	}
286 	
287 	if (priv->db != NULL) {
288 		g_object_unref (priv->db);
289 		priv->db = NULL;
290 	}
291 
292 	if (priv->input_model != NULL) {
293 		g_object_unref (priv->input_model);
294 		priv->input_model = NULL;
295 	}
296 
297 	if (priv->output_model != NULL) {
298 		g_object_unref (priv->output_model);
299 		priv->output_model = NULL;
300 	}
301 
302 	G_OBJECT_CLASS (rb_library_browser_parent_class)->dispose (object);
303 }
304 
305 static void
306 rb_library_browser_finalize (GObject *object)
307 {
308 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (object);
309 
310 	g_hash_table_destroy (priv->property_views);
311 	g_hash_table_destroy (priv->selections);
312 	g_free (priv->browser_views);
313 
314 	G_OBJECT_CLASS (rb_library_browser_parent_class)->finalize (object);
315 }
316 
317 static void
318 rb_library_browser_set_property (GObject *object,
319 				 guint prop_id,
320 				 const GValue *value,
321 				 GParamSpec *pspec)
322 {
323 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (object);
324 
325 	switch (prop_id) {
326 	case PROP_DB:
327 		if (priv->db != NULL) {
328 			g_object_unref (priv->db);
329 		}
330 		priv->db = g_value_get_object (value);
331 
332 		if (priv->db != NULL) {
333 			g_object_ref (priv->db);
334 		}
335 		break;
336 	case PROP_ENTRY_TYPE:
337 		priv->entry_type = g_value_get_object (value);
338 		break;
339 	case PROP_BROWSER_VIEWS:
340 		g_free (priv->browser_views);
341 		priv->browser_views = g_value_dup_string (value);
342 		update_browser_views_visibility (RB_LIBRARY_BROWSER (object));
343 		break;
344 	default:
345 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
346 		break;
347 	}
348 }
349 
350 static void
351 rb_library_browser_get_property (GObject *object,
352 				 guint prop_id,
353 				 GValue *value,
354 				 GParamSpec *pspec)
355 {
356 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (object);
357 
358 	switch (prop_id) {
359 	case PROP_DB:
360 		g_value_set_object (value, priv->db);
361 		break;
362 	case PROP_INPUT_MODEL:
363 		g_value_set_object (value, priv->input_model);
364 		break;
365 	case PROP_OUTPUT_MODEL:
366 		g_value_set_object (value, priv->output_model);
367 		break;
368 	case PROP_ENTRY_TYPE:
369 		g_value_set_object (value, priv->entry_type);
370 		break;
371 	case PROP_BROWSER_VIEWS:
372 		g_value_set_string (value, priv->browser_views);
373 		break;
374 	default:
375 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
376 		break;
377 	}
378 }
379 
380 /**
381  * rb_library_browser_new:
382  * @db: the #RhythmDB instance
383  * @entry_type: the entry type to use in the browser
384  *
385  * Creates a new library browser.
386  *
387  * Return value: a new RBLibraryBrowser
388  */
389 RBLibraryBrowser *
390 rb_library_browser_new (RhythmDB *db,
391 			RhythmDBEntryType *entry_type)
392 {
393 	RBLibraryBrowser *widget;
394 
395 	g_assert (db);
396 	widget = RB_LIBRARY_BROWSER (g_object_new (RB_TYPE_LIBRARY_BROWSER,
397 						   "db", db,
398 						   "entry-type", entry_type,
399 						   NULL));
400 	return widget;
401 }
402 
403 static void
404 update_browser_property_visibilty (RhythmDBPropType prop,
405 				   RBPropertyView *view,
406 				   GList *properties)
407 {
408 	gboolean old_vis, new_vis;
409 
410 	old_vis = gtk_widget_get_visible (GTK_WIDGET (view));
411 	new_vis = (g_list_find (properties, (gpointer)prop) != NULL);
412 
413 	if (old_vis != new_vis) {
414 		if (new_vis) {
415 			gtk_widget_show (GTK_WIDGET (view));
416 		} else {
417 			gtk_widget_hide (GTK_WIDGET (view));
418 			rb_property_view_set_selection (view, NULL);
419 		}
420 	}
421 }
422 
423 static void
424 update_browser_views_visibility (RBLibraryBrowser *widget)
425 {
426 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
427 	GList *properties = NULL;
428 
429 	if (strstr (priv->browser_views, "albums") != NULL)
430 		properties = g_list_prepend (properties, (gpointer)RHYTHMDB_PROP_ALBUM);
431 	properties = g_list_prepend (properties, (gpointer)RHYTHMDB_PROP_ARTIST);
432 	if (strstr (priv->browser_views, "genres") != NULL)
433 		properties = g_list_prepend (properties, (gpointer)RHYTHMDB_PROP_GENRE);
434 
435 	g_hash_table_foreach (priv->property_views, (GHFunc)update_browser_property_visibilty, properties);
436 	g_list_free (properties);
437 }
438 
439 static void
440 view_property_selected_cb (RBPropertyView *view,
441 			   GList *selection,
442 			   RBLibraryBrowser *widget)
443 {
444 	RhythmDBPropType prop;
445 
446 	g_object_get (G_OBJECT (view), "prop", &prop, NULL);
447 	rb_library_browser_set_selection (widget, prop, selection);
448 }
449 static void
450 view_selection_reset_cb (RBPropertyView *view,
451 			 RBLibraryBrowser *widget)
452 {
453 	RhythmDBPropType prop;
454 
455 	g_object_get (G_OBJECT (view), "prop", &prop, NULL);
456 	rb_library_browser_set_selection (widget, prop, NULL);
457 }
458 
459 static void
460 reset_view_cb (RhythmDBPropType prop,
461 	       RBPropertyView *view,
462 	       RBLibraryBrowser *widget)
463 {
464 	rb_property_view_set_selection (view, NULL);
465 }
466 
467 /**
468  * rb_library_browser_reset:
469  * @widget: a #RBLibraryBrowser
470  *
471  * Clears all selections in the browser.
472  *
473  * Return value: TRUE if anything was changed
474  */
475 gboolean
476 rb_library_browser_reset (RBLibraryBrowser *widget)
477 {
478 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
479 
480 	if (rb_library_browser_has_selection (widget)) {
481 		g_hash_table_foreach (priv->property_views, (GHFunc)reset_view_cb, widget);
482 		return TRUE;
483 	} else {
484 		return FALSE;
485 	}
486 }
487 
488 typedef struct {
489 	RBLibraryBrowser *widget;
490 	RhythmDB *db;
491 	RhythmDBQuery *query;
492 } ConstructQueryData;
493 
494 static void
495 construct_query_cb (RhythmDBPropType type,
496 		    GList *selections,
497 		    ConstructQueryData *data)
498 {
499 	rhythmdb_query_append_prop_multiple (data->db,
500 					     data->query,
501 					     type,
502 					     selections);
503 }
504 
505 /**
506  * rb_library_browser_construct_query:
507  * @widget: a #RBLibraryBrowser
508  *
509  * Constructs a #RhythmDBQuery from the current selections in the browser.
510  *
511  * Return value: (transfer full): a #RhythmDBQuery constructed from the current selection.
512  */
513 RhythmDBQuery *
514 rb_library_browser_construct_query (RBLibraryBrowser *widget)
515 {
516 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
517 	RhythmDBQuery *query;
518 	ConstructQueryData *data;
519 
520 	query = g_ptr_array_new ();
521 	data = g_new0 (ConstructQueryData, 1);
522 	data->widget = widget;
523 	data->db = priv->db;
524 	data->query = query;
525 
526 	g_hash_table_foreach (priv->selections, (GHFunc)construct_query_cb, data);
527 	g_free (data);
528 
529 	return query;
530 }
531 
532 /**
533  * rb_library_browser_has_selection:
534  * @widget: a #RBLibraryBrowser
535  *
536  * Determines whether the browser has an active selection.
537  *
538  * Return value: TRUE if any items in the browser are selected.
539  */
540 gboolean
541 rb_library_browser_has_selection (RBLibraryBrowser *widget)
542 {
543 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
544 
545 	return (g_hash_table_size (priv->selections) > 0);
546 }
547 
548 static gint
549 prop_to_index (RhythmDBPropType type)
550 {
551 	int i;
552 
553 	for (i = 0; i < num_browser_properties; i++)
554 		if (browser_properties[i].type == type)
555 			return i;
556 
557 	return -1;
558 }
559 
560 static void
561 ignore_selection_changes (RBLibraryBrowser *widget,
562 			  RBPropertyView *view,
563 			  gboolean block)
564 {
565 	if (block) {
566 		g_signal_handlers_block_by_func (view, view_selection_reset_cb, widget);
567 		g_signal_handlers_block_by_func (view, view_property_selected_cb, widget);
568 	} else {
569 		g_signal_handlers_unblock_by_func (view, view_selection_reset_cb, widget);
570 		g_signal_handlers_unblock_by_func (view, view_property_selected_cb, widget);
571 	}
572 }
573 
574 typedef struct {
575 	RBLibraryBrowser *widget;
576 	RBPropertyView *view;
577 	GList *selections;
578 	RhythmDBQueryModel *model;
579 	guint handler_id;
580 } SelectionRestoreData;
581 
582 static void
583 selection_restore_data_destroy (SelectionRestoreData *data)
584 {
585 	g_object_unref (G_OBJECT (data->widget));
586 	g_free (data);
587 }
588 
589 static void
590 query_complete_cb (RhythmDBQueryModel *model,
591 		   SelectionRestoreData *data)
592 {
593 	ignore_selection_changes (data->widget, data->view, FALSE);
594 	rb_property_view_set_selection (data->view, data->selections);
595 
596 	g_signal_handler_disconnect (data->model, data->handler_id);
597 }
598 
599 static void
600 restore_selection (RBLibraryBrowser *widget,
601 		   gint property_index,
602 		   gboolean query_pending)
603 {
604 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
605 	RBPropertyView *view;
606 	GList *selections;
607 	SelectionRestoreData *data;
608 
609 	view = g_hash_table_lookup (priv->property_views, (gpointer)browser_properties[property_index].type);
610 	selections = g_hash_table_lookup (priv->selections, (gpointer)browser_properties[property_index].type);
611 
612 	if (query_pending) {
613 		g_object_ref (widget);
614 
615 		data = g_new0 (SelectionRestoreData, 1);
616 		data->widget = widget;
617 		data->view = view;
618 		data->selections = selections;
619 		data->model = priv->input_model;
620 
621 		data->handler_id =
622 			g_signal_connect_data (priv->input_model,
623 					       "complete",
624 					       G_CALLBACK (query_complete_cb),
625 					       data,
626 					       (GClosureNotify) selection_restore_data_destroy,
627 					       0);
628 	} else {
629 		ignore_selection_changes (widget, view, FALSE);
630 		rb_property_view_set_selection (view, selections);
631 	}
632 }
633 
634 static void
635 rebuild_child_model (RBLibraryBrowser *widget,
636 		     gint property_index,
637 		     gboolean query_pending)
638 {
639 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
640 	RhythmDBPropertyModel *prop_model;
641 	RhythmDBQueryModel *base_model, *child_model;
642 	RBPropertyView *view;
643 	RhythmDBQuery *query;
644 	GList *selections;
645 
646 	g_assert (property_index >= 0);
647 	g_assert (property_index < num_browser_properties);
648 
649 	/* get the query model for the previous property view */
650 	view = g_hash_table_lookup (priv->property_views, (gpointer)browser_properties[property_index].type);
651 	prop_model = rb_property_view_get_model (view);
652 	g_object_get (prop_model, "query-model", &base_model, NULL);
653 
654 	selections = g_hash_table_lookup (priv->selections, (gpointer)browser_properties[property_index].type);
655 	if (selections != NULL) {
656 
657 		/* create a new query model based on it, filtered by
658 		 * the selections of the previous property view.
659 		 * we need the entry type query criteria to allow the
660 		 * backend to optimise the query.
661 		 */
662 		query = rhythmdb_query_parse (priv->db,
663 				              RHYTHMDB_QUERY_PROP_EQUALS, RHYTHMDB_PROP_TYPE, priv->entry_type,
664 					      RHYTHMDB_QUERY_END);
665 		rhythmdb_query_append_prop_multiple (priv->db,
666 						     query,
667 						     browser_properties[property_index].type,
668 						     selections);
669 
670 		child_model = rhythmdb_query_model_new_empty (priv->db);
671 		if (query_pending) {
672 			rb_debug ("rebuilding child model for browser %d; query is pending", property_index);
673 			g_object_set (child_model,
674 				      "query", query,
675 				      "base-model", base_model,
676 				      NULL);
677 		} else {
678 			rb_debug ("rebuilding child model for browser %d; running new query", property_index);
679 			rhythmdb_query_model_chain (child_model, base_model, FALSE);
680 			rhythmdb_do_full_query_parsed (priv->db,
681 						       RHYTHMDB_QUERY_RESULTS (child_model),
682 						       query);
683 		}
684 		rhythmdb_query_free (query);
685 	} else {
686 		rb_debug ("no selection for browser %d - reusing parent model", property_index);
687 		child_model = g_object_ref (base_model);
688 	}
689 
690 	/* If this is the last property, use the child model as the output model
691 	 * for the browser.  Otherwise, use it as the input for the next property
692 	 * view.
693 	 */
694 	if (property_index == num_browser_properties-1) {
695 		if (priv->output_model != NULL) {
696 			g_object_unref (priv->output_model);
697 		}
698 
699 		priv->output_model = child_model;
700 
701 		g_object_notify (G_OBJECT (widget), "output-model");
702 
703 	} else {
704 		view = g_hash_table_lookup (priv->property_views, (gpointer)browser_properties[property_index+1].type);
705 		ignore_selection_changes (widget, view, TRUE);
706 
707 		prop_model = rb_property_view_get_model (view);
708 		g_object_set (prop_model, "query-model", child_model, NULL);
709 
710 		g_object_unref (child_model);
711 
712 		rebuild_child_model (widget, property_index + 1, query_pending);
713 		restore_selection (widget, property_index + 1, query_pending);
714 	}
715 
716 	g_object_unref (base_model);
717 }
718 
719 static gboolean
720 idle_rebuild_model (RBLibraryBrowserRebuildData *data)
721 {
722 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (data->widget);
723 
724 	priv->rebuild_data = NULL;
725 	rebuild_child_model (data->widget, data->rebuild_prop_index, FALSE);
726 	return FALSE;
727 }
728 
729 static void
730 destroy_idle_rebuild_model (RBLibraryBrowserRebuildData *data)
731 {
732 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (data->widget);
733 	RBPropertyView *view;
734 	RhythmDBPropType prop_type;
735 	
736 	prop_type = browser_properties[data->rebuild_prop_index].type;
737 	view = g_hash_table_lookup (priv->property_views, (gpointer)prop_type);
738 	if (view != NULL) {
739 		ignore_selection_changes (data->widget, view, FALSE);
740 	}
741 
742 	priv->rebuild_data = NULL;
743 	g_object_unref (data->widget);
744 	g_free (data);
745 }
746 
747 /**
748  * rb_library_browser_set_selection:
749  * @widget: a #RBLibraryBrowser
750  * @type: the property for which to set the selection
751  * @selection: (element-type utf8) (transfer none): a list of strings to select
752  *
753  * Replaces any current selection for the specified property.
754  */
755 void
756 rb_library_browser_set_selection (RBLibraryBrowser *widget,
757 				  RhythmDBPropType type,
758 				  GList *selection)
759 {
760 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
761 	GList *old_selection;
762 	RBPropertyView *view;
763 	int rebuild_index;
764 	RBLibraryBrowserRebuildData *rebuild_data;
765 
766 	old_selection = g_hash_table_lookup (priv->selections, (gpointer)type);
767 
768 	if (rb_string_list_equal (old_selection, selection))
769 		return;
770 
771 	if (selection)
772 		g_hash_table_insert (priv->selections, (gpointer)type, rb_string_list_copy (selection));
773 	else
774 		g_hash_table_remove (priv->selections, (gpointer)type);
775 
776 	rebuild_index = prop_to_index (type);
777 	if (priv->rebuild_data != NULL) {
778 		rebuild_data = priv->rebuild_data;
779 		if (rebuild_data->rebuild_prop_index <= rebuild_index) {
780 			/* already rebuilding a model further up the chain,
781 			 * so we don't need to do anything for this one.
782 			 */
783 			return;
784 		}
785 		g_source_remove (rebuild_data->rebuild_idle_id);
786 		rebuild_data = NULL;
787 	}
788 
789 	view = g_hash_table_lookup (priv->property_views, (gpointer)type);
790 	if (view) {
791 		ignore_selection_changes (widget, view, TRUE);
792 	}
793 
794 	rebuild_data = g_new0 (RBLibraryBrowserRebuildData, 1);
795 	rebuild_data->widget = g_object_ref (widget);
796 	rebuild_data->rebuild_prop_index = rebuild_index;
797 	rebuild_data->rebuild_idle_id =
798 		g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
799 				 (GSourceFunc) idle_rebuild_model,
800 				 rebuild_data,
801 				 (GDestroyNotify) destroy_idle_rebuild_model);
802 	priv->rebuild_data = rebuild_data;
803 }
804 
805 /**
806  * rb_library_browser_get_property_views:
807  * @widget: a #RBLibraryBrowser
808  *
809  * Retrieves the property view widgets from the browser.
810  *
811  * Return value: (element-type RBPropertyView) (transfer container): a #GList
812  * containing the #RBPropertyView widgets in the browser.
813  */
814 GList*
815 rb_library_browser_get_property_views (RBLibraryBrowser *widget)
816 {
817 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
818 
819 	return rb_collate_hash_table_values (priv->property_views);
820 }
821 
822 /**
823  * rb_library_browser_get_property_view:
824  * @widget: a #RBLibraryBrowser
825  * @type: the property
826  *
827  * Retrieves the property view widget for the specified property,
828  * if there is one.
829  *
830  * Return value: (transfer none): #RBPropertyView widget, or NULL
831  */
832 RBPropertyView *
833 rb_library_browser_get_property_view (RBLibraryBrowser *widget,
834 				      RhythmDBPropType type)
835 {
836 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
837 	RBPropertyView *view;
838 
839 	view = g_hash_table_lookup (priv->property_views, GINT_TO_POINTER (type));
840 	return view;
841 }
842 
843 /**
844  * rb_library_browser_set_model:
845  * @widget: a #RBLibraryBrowser
846  * @model: (transfer none): the new input #RhythmDBQueryModel
847  * @query_pending: if TRUE, the caller promises to run a
848  *  query to populate the input query model.
849  *
850  * Specifies a new input query model for the browser.
851  * This should be the query model constructed from the 
852  * current search text, or the basic query model for the 
853  * source if there is no search text.
854  */
855 void
856 rb_library_browser_set_model (RBLibraryBrowser *widget,
857 			      RhythmDBQueryModel *model,
858 			      gboolean query_pending)
859 {
860 	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
861 	RBPropertyView *view;
862 	RhythmDBPropertyModel *prop_model;
863 
864 	if (priv->input_model != NULL) {
865 		g_object_unref (priv->input_model);
866 	}
867 
868 	priv->input_model = model;
869 
870 	if (priv->input_model != NULL) {
871 		g_object_ref (priv->input_model);
872 	}
873 
874 	view = g_hash_table_lookup (priv->property_views, (gpointer)browser_properties[0].type);
875 	ignore_selection_changes (widget, view, TRUE);
876 
877 	prop_model = rb_property_view_get_model (view);
878 	g_object_set (prop_model, "query-model", priv->input_model, NULL);
879 
880 	rebuild_child_model (widget, 0, query_pending);
881 	restore_selection (widget, 0, query_pending);
882 }