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 }