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
);
69 G_DEFINE_TYPE (RBLibraryBrowser
, rb_library_browser
, GTK_TYPE_HBOX
)
70 #define RB_LIBRARY_BROWSER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_LIBRARY_BROWSER, RBLibraryBrowserPrivate))
75 RhythmDBEntryType entry_type
;
76 RhythmDBQueryModel
*input_model
;
77 RhythmDBQueryModel
*output_model
;
79 guint browser_view_notify_id
;
80 GSList
*browser_views_group
;
82 GHashTable
*property_views
;
83 GHashTable
*selections
;
84 } RBLibraryBrowserPrivate
;
97 RhythmDBPropType type
;
99 } BrowserPropertyInfo
;
101 static BrowserPropertyInfo browser_properties
[] = {
102 {RHYTHMDB_PROP_GENRE
, N_("Genre")},
103 {RHYTHMDB_PROP_ARTIST
, N_("Artist")},
104 {RHYTHMDB_PROP_ALBUM
, N_("Album")}
106 const int num_browser_properties
= G_N_ELEMENTS (browser_properties
);
109 rb_library_browser_class_init (RBLibraryBrowserClass
*klass
)
111 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
113 object_class
->finalize
= rb_library_browser_finalize
;
114 object_class
->constructor
= rb_library_browser_constructor
;
115 object_class
->set_property
= rb_library_browser_set_property
;
116 object_class
->get_property
= rb_library_browser_get_property
;
118 g_object_class_install_property (object_class
,
120 g_param_spec_object ("db",
124 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
125 g_object_class_install_property (object_class
,
127 g_param_spec_object ("input-model",
129 "input RhythmDBQueryModel instance",
130 RHYTHMDB_TYPE_QUERY_MODEL
,
132 g_object_class_install_property (object_class
,
134 g_param_spec_object ("output-model",
136 "output RhythmDBQueryModel instance",
137 RHYTHMDB_TYPE_QUERY_MODEL
,
139 g_object_class_install_property (object_class
,
141 g_param_spec_pointer ("entry-type",
143 "Type of entry to display in this browser",
144 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
146 g_type_class_add_private (klass
, sizeof (RBLibraryBrowserPrivate
));
150 rb_library_browser_init (RBLibraryBrowser
*widget
)
152 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
154 gtk_box_set_spacing (GTK_BOX (widget
), 5);
156 priv
->property_views
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
157 priv
->selections
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
, NULL
, (GDestroyNotify
)rb_list_deep_free
);
161 rb_library_browser_constructor (GType type
, guint n_construct_properties
,
162 GObjectConstructParam
*construct_properties
)
164 RBLibraryBrowserClass
*klass
;
165 RBLibraryBrowser
*browser
;
166 RBLibraryBrowserPrivate
*priv
;
169 klass
= RB_LIBRARY_BROWSER_CLASS (g_type_class_peek (RB_TYPE_LIBRARY_BROWSER
));
171 browser
= RB_LIBRARY_BROWSER (G_OBJECT_CLASS (rb_library_browser_parent_class
)->
172 constructor (type
, n_construct_properties
, construct_properties
));
173 priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (browser
);
175 for (i
= 0; i
< num_browser_properties
; i
++) {
176 RBPropertyView
*view
;
178 view
= rb_property_view_new (priv
->db
,
179 browser_properties
[i
].type
,
180 _(browser_properties
[i
].name
));
181 g_hash_table_insert (priv
->property_views
, (gpointer
)(browser_properties
[i
].type
), view
);
183 rb_property_view_set_selection_mode (view
, GTK_SELECTION_MULTIPLE
);
184 g_signal_connect_object (G_OBJECT (view
),
185 "properties-selected",
186 G_CALLBACK (view_property_selected_cb
),
188 g_signal_connect_object (G_OBJECT (view
),
189 "property-selection-reset",
190 G_CALLBACK (view_selection_reset_cb
),
192 gtk_widget_show_all (GTK_WIDGET (view
));
193 gtk_widget_set_no_show_all (GTK_WIDGET (view
), TRUE
);
194 gtk_box_pack_start_defaults (GTK_BOX (browser
), GTK_WIDGET (view
));
197 update_browser_views_visibility (browser
);
198 priv
->browser_view_notify_id
=
199 eel_gconf_notification_add (CONF_UI_BROWSER_VIEWS
,
200 (GConfClientNotifyFunc
) rb_library_browser_views_changed
, browser
);
202 return G_OBJECT (browser
);
205 rb_library_browser_finalize (GObject
*object
)
207 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (object
);
209 eel_gconf_notification_remove (priv
->browser_view_notify_id
);
211 g_object_unref (G_OBJECT (priv
->db
));
213 g_hash_table_destroy (priv
->property_views
);
214 g_hash_table_destroy (priv
->selections
);
216 G_OBJECT_CLASS (rb_library_browser_parent_class
)->finalize (object
);
220 rb_library_browser_set_property (GObject
*object
,
225 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (object
);
231 g_object_unref (G_OBJECT (priv
->db
));
233 priv
->db
= g_value_get_object (value
);
236 g_object_ref (priv
->db
);
238 case PROP_ENTRY_TYPE
:
239 priv
->entry_type
= g_value_get_pointer (value
);
242 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
248 rb_library_browser_get_property (GObject
*object
,
253 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (object
);
258 g_value_set_object (value
, priv
->db
);
260 case PROP_INPUT_MODEL
:
261 g_value_set_object (value
, priv
->input_model
);
263 case PROP_OUTPUT_MODEL
:
264 g_value_set_object (value
, priv
->output_model
);
266 case PROP_ENTRY_TYPE
:
267 g_value_set_pointer (value
, priv
->entry_type
);
270 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
276 rb_library_browser_new (RhythmDB
*db
, RhythmDBEntryType entry_type
)
278 RBLibraryBrowser
*widget
;
281 widget
= RB_LIBRARY_BROWSER (g_object_new (RB_TYPE_LIBRARY_BROWSER
,
283 "entry-type", entry_type
,
289 update_browser_property_visibilty (RhythmDBPropType prop
, RBPropertyView
*view
, GList
*properties
)
291 gboolean old_vis
, new_vis
;
293 old_vis
= GTK_WIDGET_VISIBLE (view
);
294 new_vis
= (g_list_find (properties
, (gpointer
)prop
) != NULL
);
296 if (old_vis
!= new_vis
) {
298 gtk_widget_show (GTK_WIDGET (view
));
300 gtk_widget_hide (GTK_WIDGET (view
));
301 rb_property_view_reset (view
);
307 update_browser_views_visibility (RBLibraryBrowser
*widget
)
309 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
310 GList
*properties
= NULL
;
313 int views
= eel_gconf_get_integer (CONF_UI_BROWSER_VIEWS
);
315 if (views
== 0 || views
== 2)
316 properties
= g_list_prepend (properties
, (gpointer
)RHYTHMDB_PROP_ALBUM
);
317 properties
= g_list_prepend (properties
, (gpointer
)RHYTHMDB_PROP_ARTIST
);
318 if (views
== 1 || views
== 2)
319 properties
= g_list_prepend (properties
, (gpointer
)RHYTHMDB_PROP_GENRE
);
322 g_hash_table_foreach (priv
->property_views
, (GHFunc
)update_browser_property_visibilty
, properties
);
326 rb_library_browser_views_changed (GConfClient
*client
,
329 RBLibraryBrowser
*widget
)
331 update_browser_views_visibility (widget
);
335 view_property_selected_cb (RBPropertyView
*view
,
337 RBLibraryBrowser
*widget
)
339 RhythmDBPropType prop
;
341 g_object_get (G_OBJECT (view
), "prop", &prop
, NULL
);
342 rb_library_browser_set_selection (widget
, prop
, selection
);
345 view_selection_reset_cb (RBPropertyView
*view
, RBLibraryBrowser
*widget
)
347 RhythmDBPropType prop
;
349 g_object_get (G_OBJECT (view
), "prop", &prop
, NULL
);
350 rb_library_browser_set_selection (widget
, prop
, NULL
);
354 reset_view_cb (RhythmDBPropType prop
, RBPropertyView
*view
, RBLibraryBrowser
*widget
)
356 rb_property_view_set_selection (view
, NULL
);
360 rb_library_browser_reset (RBLibraryBrowser
*widget
)
362 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
364 if (rb_library_browser_has_selection (widget
)) {
365 g_hash_table_foreach (priv
->property_views
, (GHFunc
)reset_view_cb
, widget
);
373 RBLibraryBrowser
*widget
;
375 RhythmDBQuery
*query
;
376 } ConstructQueryData
;
379 construct_query_cb (RhythmDBPropType type
, GList
*selections
, ConstructQueryData
*data
)
381 rhythmdb_query_append_prop_multiple (data
->db
,
388 rb_library_browser_construct_query (RBLibraryBrowser
*widget
)
390 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
391 RhythmDBQuery
*query
;
392 ConstructQueryData
*data
;
394 query
= g_ptr_array_new ();
395 data
= g_new0 (ConstructQueryData
, 1);
396 data
->widget
= widget
;
400 g_hash_table_foreach (priv
->selections
, (GHFunc
)construct_query_cb
, data
);
407 rb_library_browser_has_selection (RBLibraryBrowser
*widget
)
409 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
411 return (g_hash_table_size (priv
->selections
) > 0);
415 prop_to_index (RhythmDBPropType type
)
419 for (i
= 0; i
< num_browser_properties
; i
++)
420 if (browser_properties
[i
].type
== type
)
427 ignore_selection_changes (RBLibraryBrowser
*widget
, RBPropertyView
*view
, gboolean block
)
430 g_signal_handlers_block_by_func (view
, view_selection_reset_cb
, widget
);
431 g_signal_handlers_block_by_func (view
, view_property_selected_cb
, widget
);
433 g_signal_handlers_unblock_by_func (view
, view_selection_reset_cb
, widget
);
434 g_signal_handlers_unblock_by_func (view
, view_property_selected_cb
, widget
);
439 RBLibraryBrowser
*widget
;
440 RBPropertyView
*view
;
442 RhythmDBQueryModel
*model
;
444 } SelectionRestoreData
;
447 selection_restore_data_destroy (SelectionRestoreData
*data
)
449 g_object_unref (G_OBJECT (data
->widget
));
454 query_complete_cb (RhythmDBQueryModel
*model
,
455 SelectionRestoreData
*data
)
457 ignore_selection_changes (data
->widget
, data
->view
, FALSE
);
458 rb_property_view_set_selection (data
->view
, data
->selections
);
460 g_signal_handler_disconnect (data
->model
, data
->handler_id
);
464 restore_selection (RBLibraryBrowser
*widget
,
466 gboolean query_pending
)
468 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
469 RBPropertyView
*view
;
471 SelectionRestoreData
*data
;
473 view
= g_hash_table_lookup (priv
->property_views
, (gpointer
)browser_properties
[property_index
].type
);
474 selections
= g_hash_table_lookup (priv
->selections
, (gpointer
)browser_properties
[property_index
].type
);
477 g_object_ref (G_OBJECT (widget
));
479 data
= g_new0 (SelectionRestoreData
, 1);
480 data
->widget
= widget
;
482 data
->selections
= selections
;
483 data
->model
= priv
->input_model
;
486 g_signal_connect_data (priv
->input_model
,
488 G_CALLBACK (query_complete_cb
),
490 (GClosureNotify
) selection_restore_data_destroy
,
493 ignore_selection_changes (widget
, view
, FALSE
);
494 rb_property_view_set_selection (view
, selections
);
499 rebuild_child_model (RBLibraryBrowser
*widget
, gint property_index
, gboolean query_pending
)
501 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
502 RhythmDBPropertyModel
*prop_model
;
503 RhythmDBQueryModel
*base_model
, *child_model
;
504 RBPropertyView
*view
;
505 RhythmDBQuery
*query
;
508 g_assert (property_index
>= 0);
509 g_assert (property_index
< num_browser_properties
);
511 /* get the query model for the previous property view */
512 view
= g_hash_table_lookup (priv
->property_views
, (gpointer
)browser_properties
[property_index
].type
);
513 prop_model
= rb_property_view_get_model (view
);
514 g_object_get (G_OBJECT (prop_model
), "query-model", &base_model
, NULL
);
515 g_object_unref (G_OBJECT (prop_model
));
517 selections
= g_hash_table_lookup (priv
->selections
, (gpointer
)browser_properties
[property_index
].type
);
518 if (selections
!= NULL
) {
520 /* create a new query model based on it, filtered by
521 * the selections of the previous property view.
522 * we need the entry type query criteria to allow the
523 * backend to optimise the query.
525 query
= rhythmdb_query_parse (priv
->db
,
526 RHYTHMDB_QUERY_PROP_EQUALS
, RHYTHMDB_PROP_TYPE
, priv
->entry_type
,
528 rhythmdb_query_append_prop_multiple (priv
->db
,
530 browser_properties
[property_index
].type
,
533 child_model
= rhythmdb_query_model_new_empty (priv
->db
);
535 rb_debug ("rebuilding child model for browser %d; query is pending", property_index
);
536 g_object_set (G_OBJECT (child_model
),
538 "base-model", base_model
,
541 rb_debug ("rebuilding child model for browser %d; running new query", property_index
);
542 rhythmdb_query_model_chain (child_model
, base_model
, FALSE
);
543 rhythmdb_do_full_query_parsed (priv
->db
,
544 RHYTHMDB_QUERY_RESULTS (child_model
),
547 rhythmdb_query_free (query
);
549 rb_debug ("no selection for browser %d - reusing parent model", property_index
);
550 child_model
= base_model
;
553 /* If this is the last property, use the child model as the output model
554 * for the browser. Otherwise, use it as the input for the next property
557 if (property_index
== num_browser_properties
-1) {
558 if (priv
->output_model
)
559 g_object_unref (G_OBJECT (priv
->output_model
));
561 priv
->output_model
= child_model
;
562 g_object_notify (G_OBJECT (widget
), "output-model");
565 view
= g_hash_table_lookup (priv
->property_views
, (gpointer
)browser_properties
[property_index
+1].type
);
566 ignore_selection_changes (widget
, view
, TRUE
);
568 prop_model
= rb_property_view_get_model (view
);
569 g_object_set (G_OBJECT (prop_model
), "query-model", child_model
, NULL
);
570 g_object_unref (G_OBJECT (prop_model
));
572 g_object_unref (G_OBJECT (base_model
));
574 rebuild_child_model (widget
, property_index
+ 1, query_pending
);
575 restore_selection (widget
, property_index
+ 1, query_pending
);
578 if (child_model
!= base_model
)
579 g_object_unref (G_OBJECT (child_model
));
583 rb_library_browser_set_selection (RBLibraryBrowser
*widget
, RhythmDBPropType type
, GList
*selection
)
585 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
586 GList
*old_selection
;
587 RBPropertyView
*view
;
589 old_selection
= g_hash_table_lookup (priv
->selections
, (gpointer
)type
);
591 if (rb_string_list_equal (old_selection
, selection
))
595 g_hash_table_insert (priv
->selections
, (gpointer
)type
, rb_string_list_copy (selection
));
597 g_hash_table_remove (priv
->selections
, (gpointer
)type
);
599 view
= g_hash_table_lookup (priv
->property_views
, (gpointer
)type
);
601 ignore_selection_changes (widget
, view
, TRUE
);
603 rebuild_child_model (widget
, prop_to_index (type
), FALSE
);
605 ignore_selection_changes (widget
, view
, FALSE
);
609 rb_library_browser_get_property_views (RBLibraryBrowser
*widget
)
611 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
613 return rb_collate_hash_table_values (priv
->property_views
);
617 rb_library_browser_get_property_view (RBLibraryBrowser
*widget
,
618 RhythmDBPropType type
)
620 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
621 RBPropertyView
*view
;
623 view
= g_hash_table_lookup (priv
->property_views
, GINT_TO_POINTER (type
));
628 rb_library_browser_set_model (RBLibraryBrowser
*widget
,
629 RhythmDBQueryModel
*model
,
630 gboolean query_pending
)
632 RBLibraryBrowserPrivate
*priv
= RB_LIBRARY_BROWSER_GET_PRIVATE (widget
);
633 RBPropertyView
*view
;
634 RhythmDBPropertyModel
*prop_model
;
636 if (priv
->input_model
)
637 g_object_unref (G_OBJECT (priv
->input_model
));
639 priv
->input_model
= model
;
640 g_object_ref (G_OBJECT (model
));
642 view
= g_hash_table_lookup (priv
->property_views
, (gpointer
)browser_properties
[0].type
);
643 ignore_selection_changes (widget
, view
, TRUE
);
645 prop_model
= rb_property_view_get_model (view
);
646 g_object_set (G_OBJECT (prop_model
), "query-model", priv
->input_model
, NULL
);
647 g_object_unref (G_OBJECT (prop_model
));
649 rebuild_child_model (widget
, 0, query_pending
);
650 restore_selection (widget
, 0, query_pending
);