Updated Macedonian Translation <arangela@cvs.gnome.org>
[rhythmbox.git] / widgets / rb-library-browser.c
blob1f328d898c9dd5c15db27ba98361475439c61d11
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.
25 #include <config.h>
27 #include <string.h>
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.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"
38 #include "rb-debug.h"
39 #include "rb-util.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,
47 guint prop_id,
48 const GValue *value,
49 GParamSpec *pspec);
50 static void rb_library_browser_get_property (GObject *object,
51 guint prop_id,
52 GValue *value,
53 GParamSpec *pspec);
55 static void view_property_selected_cb (RBPropertyView *view,
56 GList *selection,
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,
63 guint cnxn_id,
64 GConfEntry *entry,
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))
72 typedef struct
74 RhythmDB *db;
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;
87 enum
89 PROP_0,
90 PROP_DB,
91 PROP_INPUT_MODEL,
92 PROP_OUTPUT_MODEL,
93 PROP_ENTRY_TYPE
96 typedef struct {
97 RhythmDBPropType type;
98 const char *name;
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);
108 static void
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,
119 PROP_DB,
120 g_param_spec_object ("db",
121 "db",
122 "RhythmDB instance",
123 RHYTHMDB_TYPE,
124 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
125 g_object_class_install_property (object_class,
126 PROP_INPUT_MODEL,
127 g_param_spec_object ("input-model",
128 "input-model",
129 "input RhythmDBQueryModel instance",
130 RHYTHMDB_TYPE_QUERY_MODEL,
131 G_PARAM_READABLE));
132 g_object_class_install_property (object_class,
133 PROP_OUTPUT_MODEL,
134 g_param_spec_object ("output-model",
135 "output-model",
136 "output RhythmDBQueryModel instance",
137 RHYTHMDB_TYPE_QUERY_MODEL,
138 G_PARAM_READABLE));
139 g_object_class_install_property (object_class,
140 PROP_ENTRY_TYPE,
141 g_param_spec_pointer ("entry-type",
142 "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));
149 static void
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);
160 static GObject *
161 rb_library_browser_constructor (GType type, guint n_construct_properties,
162 GObjectConstructParam *construct_properties)
164 RBLibraryBrowserClass *klass;
165 RBLibraryBrowser *browser;
166 RBLibraryBrowserPrivate *priv;
167 int i;
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),
187 browser, 0);
188 g_signal_connect_object (G_OBJECT (view),
189 "property-selection-reset",
190 G_CALLBACK (view_selection_reset_cb),
191 browser, 0);
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);
204 static void
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);
219 static void
220 rb_library_browser_set_property (GObject *object,
221 guint prop_id,
222 const GValue *value,
223 GParamSpec *pspec)
225 RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (object);
227 switch (prop_id)
229 case PROP_DB:
230 if (priv->db) {
231 g_object_unref (G_OBJECT (priv->db));
233 priv->db = g_value_get_object (value);
235 if (priv->db)
236 g_object_ref (priv->db);
237 break;
238 case PROP_ENTRY_TYPE:
239 priv->entry_type = g_value_get_pointer (value);
240 break;
241 default:
242 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
243 break;
247 static void
248 rb_library_browser_get_property (GObject *object,
249 guint prop_id,
250 GValue *value,
251 GParamSpec *pspec)
253 RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (object);
255 switch (prop_id)
257 case PROP_DB:
258 g_value_set_object (value, priv->db);
259 break;
260 case PROP_INPUT_MODEL:
261 g_value_set_object (value, priv->input_model);
262 break;
263 case PROP_OUTPUT_MODEL:
264 g_value_set_object (value, priv->output_model);
265 break;
266 case PROP_ENTRY_TYPE:
267 g_value_set_pointer (value, priv->entry_type);
268 break;
269 default:
270 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
271 break;
275 RBLibraryBrowser *
276 rb_library_browser_new (RhythmDB *db, RhythmDBEntryType entry_type)
278 RBLibraryBrowser *widget;
280 g_assert (db);
281 widget = RB_LIBRARY_BROWSER (g_object_new (RB_TYPE_LIBRARY_BROWSER,
282 "db", db,
283 "entry-type", entry_type,
284 NULL));
285 return widget;
288 static void
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) {
297 if (new_vis) {
298 gtk_widget_show (GTK_WIDGET (view));
299 } else {
300 gtk_widget_hide (GTK_WIDGET (view));
301 rb_property_view_reset (view);
306 static void
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);
325 static void
326 rb_library_browser_views_changed (GConfClient *client,
327 guint cnxn_id,
328 GConfEntry *entry,
329 RBLibraryBrowser *widget)
331 update_browser_views_visibility (widget);
334 static void
335 view_property_selected_cb (RBPropertyView *view,
336 GList *selection,
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);
344 static void
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);
353 static void
354 reset_view_cb (RhythmDBPropType prop, RBPropertyView *view, RBLibraryBrowser *widget)
356 rb_property_view_set_selection (view, NULL);
359 gboolean
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);
366 return TRUE;
367 } else {
368 return FALSE;
372 typedef struct {
373 RBLibraryBrowser *widget;
374 RhythmDB *db;
375 RhythmDBQuery *query;
376 } ConstructQueryData;
378 static void
379 construct_query_cb (RhythmDBPropType type, GList *selections, ConstructQueryData *data)
381 rhythmdb_query_append_prop_multiple (data->db,
382 data->query,
383 type,
384 selections);
387 RhythmDBQuery*
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;
397 data->db = priv->db;
398 data->query = query;
400 g_hash_table_foreach (priv->selections, (GHFunc)construct_query_cb, data);
401 g_free (data);
403 return query;
406 gboolean
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);
414 static gint
415 prop_to_index (RhythmDBPropType type)
417 int i;
419 for (i = 0; i < num_browser_properties; i++)
420 if (browser_properties[i].type == type)
421 return i;
423 return -1;
426 static void
427 ignore_selection_changes (RBLibraryBrowser *widget, RBPropertyView *view, gboolean block)
429 if (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);
432 } else {
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);
438 typedef struct {
439 RBLibraryBrowser *widget;
440 RBPropertyView *view;
441 GList *selections;
442 RhythmDBQueryModel *model;
443 guint handler_id;
444 } SelectionRestoreData;
446 static void
447 selection_restore_data_destroy (SelectionRestoreData *data)
449 g_object_unref (G_OBJECT (data->widget));
450 g_free (data);
453 static void
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);
463 static void
464 restore_selection (RBLibraryBrowser *widget,
465 gint property_index,
466 gboolean query_pending)
468 RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
469 RBPropertyView *view;
470 GList *selections;
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);
476 if (query_pending) {
477 g_object_ref (G_OBJECT (widget));
479 data = g_new0 (SelectionRestoreData, 1);
480 data->widget = widget;
481 data->view = view;
482 data->selections = selections;
483 data->model = priv->input_model;
485 data->handler_id =
486 g_signal_connect_data (priv->input_model,
487 "complete",
488 G_CALLBACK (query_complete_cb),
489 data,
490 (GClosureNotify) selection_restore_data_destroy,
492 } else {
493 ignore_selection_changes (widget, view, FALSE);
494 rb_property_view_set_selection (view, selections);
498 static void
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;
506 GList *selections;
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,
527 RHYTHMDB_QUERY_END);
528 rhythmdb_query_append_prop_multiple (priv->db,
529 query,
530 browser_properties[property_index].type,
531 selections);
533 child_model = rhythmdb_query_model_new_empty (priv->db);
534 if (query_pending) {
535 rb_debug ("rebuilding child model for browser %d; query is pending", property_index);
536 g_object_set (G_OBJECT (child_model),
537 "query", query,
538 "base-model", base_model,
539 NULL);
540 } else {
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),
545 query);
547 rhythmdb_query_free (query);
548 } else {
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
555 * view.
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");
564 } else {
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));
582 void
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))
592 return;
594 if (selection)
595 g_hash_table_insert (priv->selections, (gpointer)type, rb_string_list_copy (selection));
596 else
597 g_hash_table_remove (priv->selections, (gpointer)type);
599 view = g_hash_table_lookup (priv->property_views, (gpointer)type);
600 if (view)
601 ignore_selection_changes (widget, view, TRUE);
603 rebuild_child_model (widget, prop_to_index (type), FALSE);
604 if (view)
605 ignore_selection_changes (widget, view, FALSE);
608 GList*
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);
616 RBPropertyView*
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));
624 return view;
627 void
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);