Updated Finnish translation
[rhythmbox.git] / shell / rb-source-header.c
blob507c023469654e1cace2b3e6977d16a39b08d991
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of search entry/browse toggle container
5 * Copyright (C) 2003 Jorn Baayen <jorn@nl.linux.org>
6 * Copyright (C) 2003,2004 Colin Walters <walters@redhat.com>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include <config.h>
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <string.h>
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.h>
32 #include "rb-source-header.h"
33 #include "rb-stock-icons.h"
34 #include "rb-preferences.h"
35 #include "rb-search-entry.h"
36 #include "rb-debug.h"
37 #include "rb-entry-view.h"
38 #include "eel-gconf-extensions.h"
39 #include "rb-util.h"
41 static void rb_source_header_class_init (RBSourceHeaderClass *klass);
42 static void rb_source_header_init (RBSourceHeader *shell_player);
43 static void rb_source_header_finalize (GObject *object);
44 static void rb_source_header_set_property (GObject *object,
45 guint prop_id,
46 const GValue *value,
47 GParamSpec *pspec);
48 static void rb_source_header_get_property (GObject *object,
49 guint prop_id,
50 GValue *value,
51 GParamSpec *pspec);
52 static void rb_source_header_filter_changed_cb (RBSource *source,
53 RBSourceHeader *header);
54 static void rb_source_header_search_cb (RBSearchEntry *search,
55 const char *text,
56 RBSourceHeader *header);
57 static void rb_source_header_search_activate_cb (RBSearchEntry *search,
58 RBSourceHeader *header);
59 static void rb_source_header_view_browser_changed_cb (GtkAction *action,
60 RBSourceHeader *header);
61 static void rb_source_header_source_weak_destroy_cb (RBSourceHeader *header, RBSource *source);
63 typedef struct {
64 gboolean disclosed;
65 char *search_text;
66 } SourceState;
68 static void
69 sourcestate_free (SourceState *state)
71 g_free (state->search_text);
72 g_free (state);
75 struct RBSourceHeaderPrivate
77 RBSource *selected_source;
79 GtkUIManager *ui_manager;
80 GtkActionGroup *actiongroup;
81 guint source_ui_merge_id;
83 GtkTooltips *tooltips;
85 GtkWidget *search;
86 GtkWidget *search_bar;
88 guint browser_notify_id;
89 guint search_notify_id;
90 gboolean have_search;
91 gboolean have_browser;
92 gboolean disclosed;
93 char *browser_key;
95 GHashTable *source_states;
99 #define RB_SOURCE_HEADER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SOURCE_HEADER, RBSourceHeaderPrivate))
101 enum
103 PROP_0,
104 PROP_ACTION_GROUP,
105 PROP_UI_MANAGER,
106 PROP_SOURCE,
109 static GtkToggleActionEntry rb_source_header_toggle_entries [] =
111 { "ViewBrowser", RB_STOCK_BROWSER, N_("_Browse"), "<control>B",
112 N_("Change the visibility of the browser"),
113 G_CALLBACK (rb_source_header_view_browser_changed_cb), FALSE }
115 static guint rb_source_header_n_toggle_entries = G_N_ELEMENTS (rb_source_header_toggle_entries);
117 G_DEFINE_TYPE (RBSourceHeader, rb_source_header, GTK_TYPE_TABLE)
119 static inline void
120 force_no_shadow (GtkWidget *widget)
122 gboolean first_time = TRUE;
124 if (first_time) {
125 gtk_rc_parse_string ("\n"
126 " style \"search-toolbar-style\"\n"
127 " {\n"
128 " GtkToolbar::shadow-type=GTK_SHADOW_NONE\n"
129 " }\n"
130 "\n"
131 " widget \"*.search-toolbar\" style \"search-toolbar-style\"\n"
132 "\n");
133 first_time = FALSE;
136 gtk_widget_set_name (widget, "search-toolbar");
139 static void
140 ui_manager_add_widget_cb (GtkUIManager *ui_manager,
141 GtkWidget *widget,
142 RBSourceHeader *header)
144 if (header->priv->search_bar != NULL) {
145 return;
148 if (GTK_IS_TOOLBAR (widget)) {
149 header->priv->search_bar = gtk_ui_manager_get_widget (header->priv->ui_manager, "/SearchBar");
150 if (header->priv->search_bar != NULL) {
151 gtk_toolbar_set_style (GTK_TOOLBAR (header->priv->search_bar), GTK_TOOLBAR_TEXT);
152 force_no_shadow (header->priv->search_bar);
153 gtk_widget_show (header->priv->search_bar);
154 gtk_table_attach (GTK_TABLE (header),
155 header->priv->search_bar,
156 1, 3, 0, 1,
157 GTK_EXPAND | GTK_FILL,
158 GTK_EXPAND | GTK_FILL,
159 5, 0);
164 static GObject *
165 rb_source_header_constructor (GType type,
166 guint n_construct_properties,
167 GObjectConstructParam *construct_properties)
169 RBSourceHeader *header;
170 RBSourceHeaderClass *klass;
172 klass = RB_SOURCE_HEADER_CLASS (g_type_class_peek (RB_TYPE_SOURCE_HEADER));
174 header = RB_SOURCE_HEADER (G_OBJECT_CLASS (rb_source_header_parent_class)->
175 constructor (type, n_construct_properties, construct_properties));
177 g_signal_connect (G_OBJECT (header->priv->ui_manager), "add_widget",
178 G_CALLBACK (ui_manager_add_widget_cb), header);
180 header->priv->source_ui_merge_id = gtk_ui_manager_new_merge_id (header->priv->ui_manager);
182 return G_OBJECT (header);
185 static void
186 rb_source_header_class_init (RBSourceHeaderClass *klass)
188 GObjectClass *object_class = G_OBJECT_CLASS (klass);
190 object_class->finalize = rb_source_header_finalize;
191 object_class->constructor = rb_source_header_constructor;
193 object_class->set_property = rb_source_header_set_property;
194 object_class->get_property = rb_source_header_get_property;
196 g_object_class_install_property (object_class,
197 PROP_SOURCE,
198 g_param_spec_object ("source",
199 "RBSource",
200 "RBSource object",
201 RB_TYPE_SOURCE,
202 G_PARAM_READWRITE));
203 g_object_class_install_property (object_class,
204 PROP_ACTION_GROUP,
205 g_param_spec_object ("action-group",
206 "GtkActionGroup",
207 "GtkActionGroup object",
208 GTK_TYPE_ACTION_GROUP,
209 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
210 g_object_class_install_property (object_class,
211 PROP_UI_MANAGER,
212 g_param_spec_object ("ui-manager",
213 "GtkUIManager",
214 "GtkUIManager object",
215 GTK_TYPE_UI_MANAGER,
216 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
218 g_type_class_add_private (klass, sizeof (RBSourceHeaderPrivate));
221 static void
222 rb_source_header_init (RBSourceHeader *header)
224 GtkWidget *align;
225 GtkEventBox *ebox;
227 header->priv = RB_SOURCE_HEADER_GET_PRIVATE (header);
229 header->priv->tooltips = gtk_tooltips_new ();
230 gtk_tooltips_enable (header->priv->tooltips);
232 gtk_table_set_homogeneous (GTK_TABLE (header), TRUE);
233 gtk_table_set_col_spacings (GTK_TABLE (header), 5);
234 gtk_table_resize (GTK_TABLE (header), 1, 3);
236 ebox = GTK_EVENT_BOX (gtk_event_box_new ());
237 header->priv->search = GTK_WIDGET (rb_search_entry_new ());
238 gtk_tooltips_set_tip (GTK_TOOLTIPS (header->priv->tooltips),
239 GTK_WIDGET (ebox),
240 _("Filter music display by genre, artist, album, or title"),
241 NULL);
242 gtk_container_add (GTK_CONTAINER (ebox), GTK_WIDGET (header->priv->search));
244 g_signal_connect_object (G_OBJECT (header->priv->search), "search",
245 G_CALLBACK (rb_source_header_search_cb), header, 0);
246 g_signal_connect_object (G_OBJECT (header->priv->search), "activate",
247 G_CALLBACK (rb_source_header_search_activate_cb), header, 0);
249 align = gtk_alignment_new (1.0, 0.5, 1.0, 1.0);
250 gtk_container_add (GTK_CONTAINER (align), GTK_WIDGET (ebox));
251 gtk_table_attach (GTK_TABLE (header),
252 align,
253 0, 1, 0, 1,
254 GTK_EXPAND | GTK_FILL,
255 GTK_EXPAND | GTK_FILL,
256 5, 0);
258 header->priv->source_states = g_hash_table_new_full (g_direct_hash, g_direct_equal,
259 NULL, (GDestroyNotify)sourcestate_free);
262 static void
263 rb_source_header_source_weak_unref (RBSource *source, char *text, RBSourceHeader *header)
265 g_object_weak_unref (G_OBJECT (source),
266 (GWeakNotify)rb_source_header_source_weak_destroy_cb,
267 header);
270 static void
271 rb_source_header_finalize (GObject *object)
273 RBSourceHeader *header;
275 g_return_if_fail (object != NULL);
276 g_return_if_fail (RB_IS_SOURCE_HEADER (object));
278 header = RB_SOURCE_HEADER (object);
280 g_return_if_fail (header->priv != NULL);
282 g_hash_table_foreach (header->priv->source_states,
283 (GHFunc) rb_source_header_source_weak_unref,
284 header);
285 g_hash_table_destroy (header->priv->source_states);
287 g_free (header->priv->browser_key);
289 G_OBJECT_CLASS (rb_source_header_parent_class)->finalize (object);
292 static void
293 merge_source_ui_cb (const char *action,
294 RBSourceHeader *header)
296 gtk_ui_manager_add_ui (header->priv->ui_manager,
297 header->priv->source_ui_merge_id,
298 "/SearchBar",
299 action,
300 action,
301 GTK_UI_MANAGER_AUTO,
302 FALSE);
305 static void
306 rb_source_header_set_source_internal (RBSourceHeader *header,
307 RBSource *source)
309 GList *actions;
311 if (header->priv->selected_source != NULL) {
312 g_signal_handlers_disconnect_by_func (G_OBJECT (header->priv->selected_source),
313 G_CALLBACK (rb_source_header_filter_changed_cb),
314 header);
315 gtk_ui_manager_remove_ui (header->priv->ui_manager, header->priv->source_ui_merge_id);
318 header->priv->selected_source = source;
319 rb_debug ("selected source %p", source);
321 if (header->priv->selected_source != NULL) {
322 SourceState *source_state;
323 char *text;
324 gboolean disclosed;
326 source_state = g_hash_table_lookup (header->priv->source_states,
327 header->priv->selected_source);
329 if (source_state) {
330 text = g_strdup (source_state->search_text);
331 disclosed = source_state->disclosed;
332 } else {
333 text = NULL;
334 disclosed = FALSE;
337 g_free (header->priv->browser_key);
338 header->priv->browser_key = rb_source_get_browser_key (header->priv->selected_source);
340 rb_search_entry_set_text (RB_SEARCH_ENTRY (header->priv->search), text);
341 g_signal_connect_object (G_OBJECT (header->priv->selected_source),
342 "filter_changed",
343 G_CALLBACK (rb_source_header_filter_changed_cb),
344 header, 0);
346 gtk_widget_set_sensitive (GTK_WIDGET (header->priv->search),
347 rb_source_can_search (header->priv->selected_source));
348 header->priv->have_search = rb_source_can_search (header->priv->selected_source);
349 header->priv->have_browser = rb_source_can_browse (header->priv->selected_source);
350 if (!header->priv->have_browser)
351 header->priv->disclosed = FALSE;
352 else if (header->priv->browser_key)
353 header->priv->disclosed = eel_gconf_get_boolean (header->priv->browser_key);
354 else
355 /* restore the previous state of the source*/
356 header->priv->disclosed = disclosed;
358 if (!header->priv->have_browser && !header->priv->have_search)
359 gtk_widget_hide (GTK_WIDGET (header));
360 else
361 gtk_widget_show (GTK_WIDGET (header));
364 /* merge the source-specific UI */
365 actions = rb_source_get_search_actions (source);
366 g_list_foreach (actions, (GFunc)merge_source_ui_cb, header);
367 rb_list_deep_free (actions);
369 rb_source_header_sync_control_state (header);
372 static void
373 rb_source_header_set_property (GObject *object,
374 guint prop_id,
375 const GValue *value,
376 GParamSpec *pspec)
378 RBSourceHeader *header = RB_SOURCE_HEADER (object);
380 switch (prop_id) {
381 case PROP_SOURCE:
382 rb_source_header_set_source_internal (header, g_value_get_object (value));
384 break;
385 case PROP_ACTION_GROUP:
386 header->priv->actiongroup = g_value_get_object (value);
387 gtk_action_group_add_toggle_actions (header->priv->actiongroup,
388 rb_source_header_toggle_entries,
389 rb_source_header_n_toggle_entries,
390 header);
391 break;
392 case PROP_UI_MANAGER:
393 header->priv->ui_manager = g_value_get_object (value);
394 break;
395 default:
396 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
397 break;
401 static void
402 rb_source_header_get_property (GObject *object,
403 guint prop_id,
404 GValue *value,
405 GParamSpec *pspec)
407 RBSourceHeader *header = RB_SOURCE_HEADER (object);
409 switch (prop_id) {
410 case PROP_SOURCE:
411 g_value_set_object (value, header->priv->selected_source);
412 break;
413 case PROP_ACTION_GROUP:
414 g_value_set_object (value, header->priv->actiongroup);
415 break;
416 case PROP_UI_MANAGER:
417 g_value_set_object (value, header->priv->ui_manager);
418 break;
419 default:
420 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
421 break;
425 void
426 rb_source_header_set_source (RBSourceHeader *header,
427 RBSource *source)
429 g_return_if_fail (RB_IS_SOURCE_HEADER (header));
430 g_return_if_fail (RB_IS_SOURCE (source));
432 g_object_set (G_OBJECT (header),
433 "source", source,
434 NULL);
437 RBSourceHeader *
438 rb_source_header_new (GtkUIManager *mgr,
439 GtkActionGroup *actiongroup)
441 RBSourceHeader *header = g_object_new (RB_TYPE_SOURCE_HEADER,
442 "action-group", actiongroup,
443 "ui-manager", mgr,
444 NULL);
446 g_return_val_if_fail (header->priv != NULL, NULL);
448 return header;
451 static void
452 rb_source_header_filter_changed_cb (RBSource *source,
453 RBSourceHeader *header)
455 rb_debug ("filter changed for %p", source);
456 /* To add this line back in, you need to add a search_changed signal,
457 * make RBShufflePlayOrder at least listen to it, and change the
458 * filter_changed notifies in all of the search functions to
459 * search_change notifies.
461 /* rb_search_entry_clear (RB_SEARCH_ENTRY (header->priv->search)); */
464 static void
465 rb_source_header_source_weak_destroy_cb (RBSourceHeader *header, RBSource *source)
467 g_hash_table_remove (header->priv->source_states, source);
470 static void
471 rb_source_state_sync (RBSourceHeader *header,
472 gboolean set_text,
473 const char *text,
474 gboolean set_disclosure,
475 gboolean disclosed)
477 SourceState *old_state;
479 old_state = g_hash_table_lookup (header->priv->source_states,
480 header->priv->selected_source);
482 if (old_state) {
483 if (set_text)
484 old_state->search_text = text ? g_strdup (text) : NULL;
485 if (set_disclosure)
486 old_state->disclosed = disclosed;
487 } else {
488 SourceState *new_state;
490 /* if we haven't seen the source before, monitor it for deletion */
491 g_object_weak_ref (G_OBJECT (header->priv->selected_source),
492 (GWeakNotify)rb_source_header_source_weak_destroy_cb,
493 header);
495 new_state = g_new (SourceState, 1);
496 new_state->search_text = text ? g_strdup (text) : NULL;
497 new_state->disclosed = disclosed;
498 g_hash_table_insert (header->priv->source_states,
499 header->priv->selected_source,
500 new_state);
504 static void
505 rb_source_header_search_cb (RBSearchEntry *search,
506 const char *text,
507 RBSourceHeader *header)
510 rb_debug ("searching for \"%s\"", text);
512 rb_source_state_sync (header, TRUE, text, FALSE, FALSE);
514 rb_source_search (header->priv->selected_source, text);
515 rb_source_header_sync_control_state (header);
518 void
519 rb_source_header_clear_search (RBSourceHeader *header)
521 rb_debug ("clearing search");
523 if (!rb_search_entry_searching (RB_SEARCH_ENTRY (header->priv->search)))
524 return;
526 if (header->priv->selected_source) {
527 rb_source_search (header->priv->selected_source, NULL);
528 rb_source_state_sync (header, TRUE, NULL, FALSE, FALSE);
531 rb_search_entry_clear (RB_SEARCH_ENTRY (header->priv->search));
532 rb_source_header_sync_control_state (header);
535 static void
536 rb_source_header_view_browser_changed_cb (GtkAction *action,
537 RBSourceHeader *header)
539 rb_debug ("got view browser toggle");
540 header->priv->disclosed = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
542 if (header->priv->browser_key)
543 eel_gconf_set_boolean (header->priv->browser_key,
544 header->priv->disclosed);
545 else {
546 rb_source_state_sync (header, FALSE, NULL, TRUE, header->priv->disclosed);
549 rb_debug ("got view browser toggle");
551 rb_source_header_sync_control_state (header);
554 void
555 rb_source_header_sync_control_state (RBSourceHeader *header)
557 GtkAction *viewbrowser_action;
558 GtkAction *viewstatusbar_action;
559 GtkAction *viewall_action;
560 gboolean not_small = !eel_gconf_get_boolean (CONF_UI_SMALL_DISPLAY);
562 viewbrowser_action = gtk_action_group_get_action (header->priv->actiongroup,
563 "ViewBrowser");
564 g_object_set (G_OBJECT (viewbrowser_action), "sensitive",
565 header->priv->have_browser && not_small, NULL);
566 viewstatusbar_action = gtk_action_group_get_action (header->priv->actiongroup,
567 "ViewStatusbar");
568 g_object_set (G_OBJECT (viewstatusbar_action), "sensitive",
569 not_small, NULL);
570 viewall_action = gtk_action_group_get_action (header->priv->actiongroup,
571 "ViewAll");
572 g_object_set (G_OBJECT (viewall_action), "sensitive",
573 (header->priv->have_browser || header->priv->have_search) && not_small, NULL);
575 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (viewbrowser_action),
576 header->priv->disclosed);
578 if (header->priv->selected_source)
579 rb_source_browser_toggled (header->priv->selected_source, header->priv->disclosed);
582 static void
583 rb_source_header_search_activate_cb (RBSearchEntry *search,
584 RBSourceHeader *header)
586 gtk_widget_grab_focus (GTK_WIDGET (header->priv->selected_source));
589 void
590 rb_source_header_focus_search_box (RBSourceHeader *header)
592 rb_search_entry_grab_focus (RB_SEARCH_ENTRY (header->priv->search));