udapted vi.po
[rhythmbox.git] / widgets / rb-library-browser.c
blobaf269e10549666b0b24dae4afba43326a8027af4
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);
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))
70 typedef struct
72 RhythmDB *db;
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;
84 enum
86 PROP_0,
87 PROP_DB,
88 PROP_INPUT_MODEL,
89 PROP_OUTPUT_MODEL,
90 PROP_ENTRY_TYPE
93 typedef struct {
94 RhythmDBPropType type;
95 const char *name;
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);
105 static void
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,
116 PROP_DB,
117 g_param_spec_object ("db",
118 "db",
119 "RhythmDB instance",
120 RHYTHMDB_TYPE,
121 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
122 g_object_class_install_property (object_class,
123 PROP_INPUT_MODEL,
124 g_param_spec_object ("input-model",
125 "input-model",
126 "input RhythmDBQueryModel instance",
127 RHYTHMDB_TYPE_QUERY_MODEL,
128 G_PARAM_READABLE));
129 g_object_class_install_property (object_class,
130 PROP_OUTPUT_MODEL,
131 g_param_spec_object ("output-model",
132 "output-model",
133 "output RhythmDBQueryModel instance",
134 RHYTHMDB_TYPE_QUERY_MODEL,
135 G_PARAM_READABLE));
136 g_object_class_install_property (object_class,
137 PROP_ENTRY_TYPE,
138 g_param_spec_boxed ("entry-type",
139 "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));
147 static void
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);
158 static GObject *
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;
166 int i;
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),
186 browser, 0);
187 g_signal_connect_object (G_OBJECT (view),
188 "property-selection-reset",
189 G_CALLBACK (view_selection_reset_cb),
190 browser, 0);
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);
203 static void
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);
226 static void
227 rb_library_browser_set_property (GObject *object,
228 guint prop_id,
229 const GValue *value,
230 GParamSpec *pspec)
232 RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (object);
234 switch (prop_id) {
235 case PROP_DB:
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);
244 break;
245 case PROP_ENTRY_TYPE:
246 priv->entry_type = g_value_get_boxed (value);
247 break;
248 default:
249 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
250 break;
254 static void
255 rb_library_browser_get_property (GObject *object,
256 guint prop_id,
257 GValue *value,
258 GParamSpec *pspec)
260 RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (object);
262 switch (prop_id) {
263 case PROP_DB:
264 g_value_set_object (value, priv->db);
265 break;
266 case PROP_INPUT_MODEL:
267 g_value_set_object (value, priv->input_model);
268 break;
269 case PROP_OUTPUT_MODEL:
270 g_value_set_object (value, priv->output_model);
271 break;
272 case PROP_ENTRY_TYPE:
273 g_value_set_boxed (value, priv->entry_type);
274 break;
275 default:
276 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
277 break;
281 RBLibraryBrowser *
282 rb_library_browser_new (RhythmDB *db,
283 RhythmDBEntryType entry_type)
285 RBLibraryBrowser *widget;
287 g_assert (db);
288 widget = RB_LIBRARY_BROWSER (g_object_new (RB_TYPE_LIBRARY_BROWSER,
289 "db", db,
290 "entry-type", entry_type,
291 NULL));
292 return widget;
295 static void
296 update_browser_property_visibilty (RhythmDBPropType prop,
297 RBPropertyView *view,
298 GList *properties)
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) {
306 if (new_vis) {
307 gtk_widget_show (GTK_WIDGET (view));
308 } else {
309 gtk_widget_hide (GTK_WIDGET (view));
310 rb_property_view_reset (view);
315 static void
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);
334 static void
335 rb_library_browser_views_changed (GConfClient *client,
336 guint cnxn_id,
337 GConfEntry *entry,
338 RBLibraryBrowser *widget)
340 update_browser_views_visibility (widget);
343 static void
344 view_property_selected_cb (RBPropertyView *view,
345 GList *selection,
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);
353 static void
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);
363 static void
364 reset_view_cb (RhythmDBPropType prop,
365 RBPropertyView *view,
366 RBLibraryBrowser *widget)
368 rb_property_view_set_selection (view, NULL);
371 gboolean
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);
378 return TRUE;
379 } else {
380 return FALSE;
384 typedef struct {
385 RBLibraryBrowser *widget;
386 RhythmDB *db;
387 RhythmDBQuery *query;
388 } ConstructQueryData;
390 static void
391 construct_query_cb (RhythmDBPropType type,
392 GList *selections,
393 ConstructQueryData *data)
395 rhythmdb_query_append_prop_multiple (data->db,
396 data->query,
397 type,
398 selections);
401 RhythmDBQuery *
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;
411 data->db = priv->db;
412 data->query = query;
414 g_hash_table_foreach (priv->selections, (GHFunc)construct_query_cb, data);
415 g_free (data);
417 return query;
420 gboolean
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);
428 static gint
429 prop_to_index (RhythmDBPropType type)
431 int i;
433 for (i = 0; i < num_browser_properties; i++)
434 if (browser_properties[i].type == type)
435 return i;
437 return -1;
440 static void
441 ignore_selection_changes (RBLibraryBrowser *widget,
442 RBPropertyView *view,
443 gboolean block)
445 if (block) {
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);
448 } else {
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);
454 typedef struct {
455 RBLibraryBrowser *widget;
456 RBPropertyView *view;
457 GList *selections;
458 RhythmDBQueryModel *model;
459 guint handler_id;
460 } SelectionRestoreData;
462 static void
463 selection_restore_data_destroy (SelectionRestoreData *data)
465 g_object_unref (G_OBJECT (data->widget));
466 g_free (data);
469 static void
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);
479 static void
480 restore_selection (RBLibraryBrowser *widget,
481 gint property_index,
482 gboolean query_pending)
484 RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
485 RBPropertyView *view;
486 GList *selections;
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);
492 if (query_pending) {
493 g_object_ref (widget);
495 data = g_new0 (SelectionRestoreData, 1);
496 data->widget = widget;
497 data->view = view;
498 data->selections = selections;
499 data->model = priv->input_model;
501 data->handler_id =
502 g_signal_connect_data (priv->input_model,
503 "complete",
504 G_CALLBACK (query_complete_cb),
505 data,
506 (GClosureNotify) selection_restore_data_destroy,
508 } else {
509 ignore_selection_changes (widget, view, FALSE);
510 rb_property_view_set_selection (view, selections);
514 static void
515 rebuild_child_model (RBLibraryBrowser *widget,
516 gint property_index,
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;
524 GList *selections;
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,
544 RHYTHMDB_QUERY_END);
545 rhythmdb_query_append_prop_multiple (priv->db,
546 query,
547 browser_properties[property_index].type,
548 selections);
550 child_model = rhythmdb_query_model_new_empty (priv->db);
551 if (query_pending) {
552 rb_debug ("rebuilding child model for browser %d; query is pending", property_index);
553 g_object_set (child_model,
554 "query", query,
555 "base-model", base_model,
556 NULL);
557 } else {
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),
562 query);
564 rhythmdb_query_free (query);
565 } else {
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
572 * view.
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");
583 } else {
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);
599 void
600 rb_library_browser_set_selection (RBLibraryBrowser *widget,
601 RhythmDBPropType type,
602 GList *selection)
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))
611 return;
613 if (selection)
614 g_hash_table_insert (priv->selections, (gpointer)type, rb_string_list_copy (selection));
615 else
616 g_hash_table_remove (priv->selections, (gpointer)type);
618 view = g_hash_table_lookup (priv->property_views, (gpointer)type);
619 if (view)
620 ignore_selection_changes (widget, view, TRUE);
622 rebuild_child_model (widget, prop_to_index (type), FALSE);
623 if (view)
624 ignore_selection_changes (widget, view, FALSE);
627 GList*
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);
635 RBPropertyView *
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));
643 return view;
646 void
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);