1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of library browser widget
5 * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
6 * Copyright (C) 2003,2004 Colin Walters <walters@verbum.org>
7 * Copyright (C) 2006 James Livingston <jrl@ids.org.au>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
29 #include <glib/gi18n.h>
32 #include "rb-library-browser.h"
33 #include "rb-preferences.h"
34 #include "eel-gconf-extensions.h"
35 #include "rhythmdb-property-model.h"
36 #include "rhythmdb-query-model.h"
37 #include "rb-property-view.h"
41 static void rb_library_browser_class_init (RBLibraryBrowserClass
*klass
);
42 static void rb_library_browser_init (RBLibraryBrowser
*entry
);
43 static void rb_library_browser_finalize (GObject
*object
);
44 static GObject
* rb_library_browser_constructor (GType type
, guint n_construct_properties
,
45 GObjectConstructParam
*construct_properties
);
46 static void rb_library_browser_set_property (GObject
*object
,
50 static void rb_library_browser_get_property (GObject
*object
,
55 static void view_property_selected_cb (RBPropertyView
*view
,
57 RBLibraryBrowser
*widget
);
58 static void view_selection_reset_cb (RBPropertyView
*view
,
59 RBLibraryBrowser
*widget
);
61 static void update_browser_views_visibility (RBLibraryBrowser
*widget
);
62 static void rb_library_browser_views_changed (GConfClient
*client
,
65 RBLibraryBrowser
*widget
);
67 G_DEFINE_TYPE (RBLibraryBrowser
, rb_library_browser
, GTK_TYPE_HBOX
)
68 #define RB_LIBRARY_BROWSER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_LIBRARY_BROWSER, RBLibraryBrowserPrivate))
73 RhythmDBEntryType entry_type
;
74 RhythmDBQueryModel
*input_model
;
75 RhythmDBQueryModel
*output_model
;
77 guint browser_view_notify_id
;
78 GSList
*browser_views_group
;
80 GHashTable
*property_views
;
81 GHashTable
*selections
;
82 } RBLibraryBrowserPrivate
;
94 RhythmDBPropType type
;
96 } BrowserPropertyInfo
;
98 static BrowserPropertyInfo browser_properties
[] = {
99 {RHYTHMDB_PROP_GENRE
, N_("Genre")},
100 {RHYTHMDB_PROP_ARTIST
, N_("Artist")},
101 {RHYTHMDB_PROP_ALBUM
, N_("Album")}
103 const int num_browser_properties
= G_N_ELEMENTS (browser_properties
);
106 rb_library_browser_class_init (RBLibraryBrowserClass
*klass
)
108 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
110 object_class
->finalize
= rb_library_browser_finalize
;
111 object_class
->constructor
= rb_library_browser_constructor
;
112 object_class
->set_property
= rb_library_browser_set_property
;
113 object_class
->get_property
= rb_library_browser_get_property
;
115 g_object_class_install_property (object_class
,
117 g_param_spec_object ("db",
121 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
122 g_object_class_install_property (object_class
,
124 g_param_spec_object ("input-model",
126 "input RhythmDBQueryModel instance",
127 RHYTHMDB_TYPE_QUERY_MODEL
,
129 g_object_class_install_property (object_class
,
131 g_param_spec_object ("output-model",
133 "output RhythmDBQueryModel instance",
134 RHYTHMDB_TYPE_QUERY_MODEL
,
136 g_object_class_install_property (object_class
,
138 g_param_spec_boxed ("entry-type",
140 "Type of entry to display in this browser",
141 RHYTHMDB_TYPE_ENTRY_TYPE
,
142 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
144 g_type_class_add_private (klass
, sizeof (RBLibraryBrowserPrivate
));
148 rb_library_browser_init (RBLibraryBrowser
*widget
)
150 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
152 gtk_box_set_spacing (GTK_BOX (widget
), 5);
154 priv
->property_views
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
155 priv
->selections
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
, NULL
, (GDestroyNotify
)rb_list_deep_free
);
159 rb_library_browser_constructor (GType type
,
160 guint n_construct_properties
,
161 GObjectConstructParam
*construct_properties
)
163 RBLibraryBrowserClass
*klass
;
164 RBLibraryBrowser
*browser
;
165 RBLibraryBrowserPrivate
*priv
;
168 klass
= RB_LIBRARY_BROWSER_CLASS (g_type_class_peek (RB_TYPE_LIBRARY_BROWSER
));
170 browser
= RB_LIBRARY_BROWSER (G_OBJECT_CLASS (rb_library_browser_parent_class
)->
171 constructor (type
, n_construct_properties
, construct_properties
));
172 priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (browser
);
174 for (i
= 0; i
< num_browser_properties
; i
++) {
175 RBPropertyView
*view
;
177 view
= rb_property_view_new (priv
->db
,
178 browser_properties
[i
].type
,
179 _(browser_properties
[i
].name
));
180 g_hash_table_insert (priv
->property_views
, (gpointer
)(browser_properties
[i
].type
), view
);
182 rb_property_view_set_selection_mode (view
, GTK_SELECTION_MULTIPLE
);
183 g_signal_connect_object (G_OBJECT (view
),
184 "properties-selected",
185 G_CALLBACK (view_property_selected_cb
),
187 g_signal_connect_object (G_OBJECT (view
),
188 "property-selection-reset",
189 G_CALLBACK (view_selection_reset_cb
),
191 gtk_widget_show_all (GTK_WIDGET (view
));
192 gtk_widget_set_no_show_all (GTK_WIDGET (view
), TRUE
);
193 gtk_box_pack_start_defaults (GTK_BOX (browser
), GTK_WIDGET (view
));
196 update_browser_views_visibility (browser
);
197 priv
->browser_view_notify_id
=
198 eel_gconf_notification_add (CONF_UI_BROWSER_VIEWS
,
199 (GConfClientNotifyFunc
) rb_library_browser_views_changed
, browser
);
201 return G_OBJECT (browser
);
204 rb_library_browser_finalize (GObject
*object
)
206 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (object
);
208 eel_gconf_notification_remove (priv
->browser_view_notify_id
);
210 if (priv
->db
!= NULL
) {
211 g_object_unref (priv
->db
);
213 if (priv
->input_model
!= NULL
) {
214 g_object_unref (priv
->input_model
);
216 if (priv
->output_model
!= NULL
) {
217 g_object_unref (priv
->output_model
);
220 g_hash_table_destroy (priv
->property_views
);
221 g_hash_table_destroy (priv
->selections
);
223 G_OBJECT_CLASS (rb_library_browser_parent_class
)->finalize (object
);
227 rb_library_browser_set_property (GObject
*object
,
232 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (object
);
236 if (priv
->db
!= NULL
) {
237 g_object_unref (priv
->db
);
239 priv
->db
= g_value_get_object (value
);
241 if (priv
->db
!= NULL
) {
242 g_object_ref (priv
->db
);
245 case PROP_ENTRY_TYPE
:
246 priv
->entry_type
= g_value_get_boxed (value
);
249 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
255 rb_library_browser_get_property (GObject
*object
,
260 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (object
);
264 g_value_set_object (value
, priv
->db
);
266 case PROP_INPUT_MODEL
:
267 g_value_set_object (value
, priv
->input_model
);
269 case PROP_OUTPUT_MODEL
:
270 g_value_set_object (value
, priv
->output_model
);
272 case PROP_ENTRY_TYPE
:
273 g_value_set_boxed (value
, priv
->entry_type
);
276 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
282 rb_library_browser_new (RhythmDB
*db
,
283 RhythmDBEntryType entry_type
)
285 RBLibraryBrowser
*widget
;
288 widget
= RB_LIBRARY_BROWSER (g_object_new (RB_TYPE_LIBRARY_BROWSER
,
290 "entry-type", entry_type
,
296 update_browser_property_visibilty (RhythmDBPropType prop
,
297 RBPropertyView
*view
,
300 gboolean old_vis
, new_vis
;
302 old_vis
= GTK_WIDGET_VISIBLE (view
);
303 new_vis
= (g_list_find (properties
, (gpointer
)prop
) != NULL
);
305 if (old_vis
!= new_vis
) {
307 gtk_widget_show (GTK_WIDGET (view
));
309 gtk_widget_hide (GTK_WIDGET (view
));
310 rb_property_view_reset (view
);
316 update_browser_views_visibility (RBLibraryBrowser
*widget
)
318 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
319 GList
*properties
= NULL
;
322 int views
= eel_gconf_get_integer (CONF_UI_BROWSER_VIEWS
);
324 if (views
== 0 || views
== 2)
325 properties
= g_list_prepend (properties
, (gpointer
)RHYTHMDB_PROP_ALBUM
);
326 properties
= g_list_prepend (properties
, (gpointer
)RHYTHMDB_PROP_ARTIST
);
327 if (views
== 1 || views
== 2)
328 properties
= g_list_prepend (properties
, (gpointer
)RHYTHMDB_PROP_GENRE
);
331 g_hash_table_foreach (priv
->property_views
, (GHFunc
)update_browser_property_visibilty
, properties
);
335 rb_library_browser_views_changed (GConfClient
*client
,
338 RBLibraryBrowser
*widget
)
340 update_browser_views_visibility (widget
);
344 view_property_selected_cb (RBPropertyView
*view
,
346 RBLibraryBrowser
*widget
)
348 RhythmDBPropType prop
;
350 g_object_get (G_OBJECT (view
), "prop", &prop
, NULL
);
351 rb_library_browser_set_selection (widget
, prop
, selection
);
354 view_selection_reset_cb (RBPropertyView
*view
,
355 RBLibraryBrowser
*widget
)
357 RhythmDBPropType prop
;
359 g_object_get (G_OBJECT (view
), "prop", &prop
, NULL
);
360 rb_library_browser_set_selection (widget
, prop
, NULL
);
364 reset_view_cb (RhythmDBPropType prop
,
365 RBPropertyView
*view
,
366 RBLibraryBrowser
*widget
)
368 rb_property_view_set_selection (view
, NULL
);
372 rb_library_browser_reset (RBLibraryBrowser
*widget
)
374 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
376 if (rb_library_browser_has_selection (widget
)) {
377 g_hash_table_foreach (priv
->property_views
, (GHFunc
)reset_view_cb
, widget
);
385 RBLibraryBrowser
*widget
;
387 RhythmDBQuery
*query
;
388 } ConstructQueryData
;
391 construct_query_cb (RhythmDBPropType type
,
393 ConstructQueryData
*data
)
395 rhythmdb_query_append_prop_multiple (data
->db
,
402 rb_library_browser_construct_query (RBLibraryBrowser
*widget
)
404 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
405 RhythmDBQuery
*query
;
406 ConstructQueryData
*data
;
408 query
= g_ptr_array_new ();
409 data
= g_new0 (ConstructQueryData
, 1);
410 data
->widget
= widget
;
414 g_hash_table_foreach (priv
->selections
, (GHFunc
)construct_query_cb
, data
);
421 rb_library_browser_has_selection (RBLibraryBrowser
*widget
)
423 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
425 return (g_hash_table_size (priv
->selections
) > 0);
429 prop_to_index (RhythmDBPropType type
)
433 for (i
= 0; i
< num_browser_properties
; i
++)
434 if (browser_properties
[i
].type
== type
)
441 ignore_selection_changes (RBLibraryBrowser
*widget
,
442 RBPropertyView
*view
,
446 g_signal_handlers_block_by_func (view
, view_selection_reset_cb
, widget
);
447 g_signal_handlers_block_by_func (view
, view_property_selected_cb
, widget
);
449 g_signal_handlers_unblock_by_func (view
, view_selection_reset_cb
, widget
);
450 g_signal_handlers_unblock_by_func (view
, view_property_selected_cb
, widget
);
455 RBLibraryBrowser
*widget
;
456 RBPropertyView
*view
;
458 RhythmDBQueryModel
*model
;
460 } SelectionRestoreData
;
463 selection_restore_data_destroy (SelectionRestoreData
*data
)
465 g_object_unref (G_OBJECT (data
->widget
));
470 query_complete_cb (RhythmDBQueryModel
*model
,
471 SelectionRestoreData
*data
)
473 ignore_selection_changes (data
->widget
, data
->view
, FALSE
);
474 rb_property_view_set_selection (data
->view
, data
->selections
);
476 g_signal_handler_disconnect (data
->model
, data
->handler_id
);
480 restore_selection (RBLibraryBrowser
*widget
,
482 gboolean query_pending
)
484 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
485 RBPropertyView
*view
;
487 SelectionRestoreData
*data
;
489 view
= g_hash_table_lookup (priv
->property_views
, (gpointer
)browser_properties
[property_index
].type
);
490 selections
= g_hash_table_lookup (priv
->selections
, (gpointer
)browser_properties
[property_index
].type
);
493 g_object_ref (widget
);
495 data
= g_new0 (SelectionRestoreData
, 1);
496 data
->widget
= widget
;
498 data
->selections
= selections
;
499 data
->model
= priv
->input_model
;
502 g_signal_connect_data (priv
->input_model
,
504 G_CALLBACK (query_complete_cb
),
506 (GClosureNotify
) selection_restore_data_destroy
,
509 ignore_selection_changes (widget
, view
, FALSE
);
510 rb_property_view_set_selection (view
, selections
);
515 rebuild_child_model (RBLibraryBrowser
*widget
,
517 gboolean query_pending
)
519 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
520 RhythmDBPropertyModel
*prop_model
;
521 RhythmDBQueryModel
*base_model
, *child_model
;
522 RBPropertyView
*view
;
523 RhythmDBQuery
*query
;
526 g_assert (property_index
>= 0);
527 g_assert (property_index
< num_browser_properties
);
529 /* get the query model for the previous property view */
530 view
= g_hash_table_lookup (priv
->property_views
, (gpointer
)browser_properties
[property_index
].type
);
531 prop_model
= rb_property_view_get_model (view
);
532 g_object_get (prop_model
, "query-model", &base_model
, NULL
);
534 selections
= g_hash_table_lookup (priv
->selections
, (gpointer
)browser_properties
[property_index
].type
);
535 if (selections
!= NULL
) {
537 /* create a new query model based on it, filtered by
538 * the selections of the previous property view.
539 * we need the entry type query criteria to allow the
540 * backend to optimise the query.
542 query
= rhythmdb_query_parse (priv
->db
,
543 RHYTHMDB_QUERY_PROP_EQUALS
, RHYTHMDB_PROP_TYPE
, priv
->entry_type
,
545 rhythmdb_query_append_prop_multiple (priv
->db
,
547 browser_properties
[property_index
].type
,
550 child_model
= rhythmdb_query_model_new_empty (priv
->db
);
552 rb_debug ("rebuilding child model for browser %d; query is pending", property_index
);
553 g_object_set (child_model
,
555 "base-model", base_model
,
558 rb_debug ("rebuilding child model for browser %d; running new query", property_index
);
559 rhythmdb_query_model_chain (child_model
, base_model
, FALSE
);
560 rhythmdb_do_full_query_parsed (priv
->db
,
561 RHYTHMDB_QUERY_RESULTS (child_model
),
564 rhythmdb_query_free (query
);
566 rb_debug ("no selection for browser %d - reusing parent model", property_index
);
567 child_model
= g_object_ref (base_model
);
570 /* If this is the last property, use the child model as the output model
571 * for the browser. Otherwise, use it as the input for the next property
574 if (property_index
== num_browser_properties
-1) {
575 if (priv
->output_model
!= NULL
) {
576 g_object_unref (priv
->output_model
);
579 priv
->output_model
= child_model
;
581 g_object_notify (G_OBJECT (widget
), "output-model");
584 view
= g_hash_table_lookup (priv
->property_views
, (gpointer
)browser_properties
[property_index
+1].type
);
585 ignore_selection_changes (widget
, view
, TRUE
);
587 prop_model
= rb_property_view_get_model (view
);
588 g_object_set (prop_model
, "query-model", child_model
, NULL
);
590 g_object_unref (child_model
);
592 rebuild_child_model (widget
, property_index
+ 1, query_pending
);
593 restore_selection (widget
, property_index
+ 1, query_pending
);
596 g_object_unref (base_model
);
600 rb_library_browser_set_selection (RBLibraryBrowser
*widget
,
601 RhythmDBPropType type
,
604 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
605 GList
*old_selection
;
606 RBPropertyView
*view
;
608 old_selection
= g_hash_table_lookup (priv
->selections
, (gpointer
)type
);
610 if (rb_string_list_equal (old_selection
, selection
))
614 g_hash_table_insert (priv
->selections
, (gpointer
)type
, rb_string_list_copy (selection
));
616 g_hash_table_remove (priv
->selections
, (gpointer
)type
);
618 view
= g_hash_table_lookup (priv
->property_views
, (gpointer
)type
);
620 ignore_selection_changes (widget
, view
, TRUE
);
622 rebuild_child_model (widget
, prop_to_index (type
), FALSE
);
624 ignore_selection_changes (widget
, view
, FALSE
);
628 rb_library_browser_get_property_views (RBLibraryBrowser
*widget
)
630 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
632 return rb_collate_hash_table_values (priv
->property_views
);
636 rb_library_browser_get_property_view (RBLibraryBrowser
*widget
,
637 RhythmDBPropType type
)
639 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
640 RBPropertyView
*view
;
642 view
= g_hash_table_lookup (priv
->property_views
, GINT_TO_POINTER (type
));
647 rb_library_browser_set_model (RBLibraryBrowser
*widget
,
648 RhythmDBQueryModel
*model
,
649 gboolean query_pending
)
651 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
652 RBPropertyView
*view
;
653 RhythmDBPropertyModel
*prop_model
;
655 if (priv
->input_model
!= NULL
) {
656 g_object_unref (priv
->input_model
);
659 priv
->input_model
= model
;
661 if (priv
->input_model
!= NULL
) {
662 g_object_ref (priv
->input_model
);
665 view
= g_hash_table_lookup (priv
->property_views
, (gpointer
)browser_properties
[0].type
);
666 ignore_selection_changes (widget
, view
, TRUE
);
668 prop_model
= rb_property_view_get_model (view
);
669 g_object_set (prop_model
, "query-model", priv
->input_model
, NULL
);
671 rebuild_child_model (widget
, 0, query_pending
);
672 restore_selection (widget
, 0, query_pending
);