2006-12-14 Francisco Javier F. Serrador <serrador@openshine.com>
[rhythmbox.git] / shell / rb-source-header.c
blobc259d32ffb44592bf77a0d5972ff345af54370fd
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_col_spacings (GTK_TABLE (header), 5);
233 gtk_table_resize (GTK_TABLE (header), 1, 3);
235 ebox = GTK_EVENT_BOX (gtk_event_box_new ());
236 header->priv->search = GTK_WIDGET (rb_search_entry_new ());
237 gtk_tooltips_set_tip (GTK_TOOLTIPS (header->priv->tooltips),
238 GTK_WIDGET (ebox),
239 _("Filter music display by genre, artist, album, or title"),
240 NULL);
241 gtk_container_add (GTK_CONTAINER (ebox), GTK_WIDGET (header->priv->search));
243 g_signal_connect_object (G_OBJECT (header->priv->search), "search",
244 G_CALLBACK (rb_source_header_search_cb), header, 0);
245 g_signal_connect_object (G_OBJECT (header->priv->search), "activate",
246 G_CALLBACK (rb_source_header_search_activate_cb), header, 0);
248 align = gtk_alignment_new (1.0, 0.5, 1.0, 1.0);
249 gtk_container_add (GTK_CONTAINER (align), GTK_WIDGET (ebox));
250 gtk_table_attach (GTK_TABLE (header),
251 align,
252 0, 1, 0, 1,
253 GTK_EXPAND | GTK_FILL,
254 GTK_EXPAND | GTK_FILL,
255 5, 0);
257 header->priv->source_states = g_hash_table_new_full (g_direct_hash, g_direct_equal,
258 NULL, (GDestroyNotify)sourcestate_free);
261 static void
262 rb_source_header_source_weak_unref (RBSource *source, char *text, RBSourceHeader *header)
264 g_object_weak_unref (G_OBJECT (source),
265 (GWeakNotify)rb_source_header_source_weak_destroy_cb,
266 header);
269 static void
270 rb_source_header_finalize (GObject *object)
272 RBSourceHeader *header;
274 g_return_if_fail (object != NULL);
275 g_return_if_fail (RB_IS_SOURCE_HEADER (object));
277 header = RB_SOURCE_HEADER (object);
279 g_return_if_fail (header->priv != NULL);
281 g_hash_table_foreach (header->priv->source_states,
282 (GHFunc) rb_source_header_source_weak_unref,
283 header);
284 g_hash_table_destroy (header->priv->source_states);
286 g_free (header->priv->browser_key);
288 G_OBJECT_CLASS (rb_source_header_parent_class)->finalize (object);
291 static void
292 merge_source_ui_cb (const char *action,
293 RBSourceHeader *header)
295 gtk_ui_manager_add_ui (header->priv->ui_manager,
296 header->priv->source_ui_merge_id,
297 "/SearchBar",
298 action,
299 action,
300 GTK_UI_MANAGER_AUTO,
301 FALSE);
304 static void
305 rb_source_header_set_source_internal (RBSourceHeader *header,
306 RBSource *source)
308 GList *actions;
310 if (header->priv->selected_source != NULL) {
311 g_signal_handlers_disconnect_by_func (G_OBJECT (header->priv->selected_source),
312 G_CALLBACK (rb_source_header_filter_changed_cb),
313 header);
314 gtk_ui_manager_remove_ui (header->priv->ui_manager, header->priv->source_ui_merge_id);
317 header->priv->selected_source = source;
318 rb_debug ("selected source %p", source);
320 if (header->priv->selected_source != NULL) {
321 SourceState *source_state;
322 char *text;
323 gboolean disclosed;
325 source_state = g_hash_table_lookup (header->priv->source_states,
326 header->priv->selected_source);
328 if (source_state) {
329 text = g_strdup (source_state->search_text);
330 disclosed = source_state->disclosed;
331 } else {
332 text = NULL;
333 disclosed = FALSE;
336 g_free (header->priv->browser_key);
337 header->priv->browser_key = rb_source_get_browser_key (header->priv->selected_source);
339 rb_search_entry_set_text (RB_SEARCH_ENTRY (header->priv->search), text);
340 g_signal_connect_object (G_OBJECT (header->priv->selected_source),
341 "filter_changed",
342 G_CALLBACK (rb_source_header_filter_changed_cb),
343 header, 0);
345 gtk_widget_set_sensitive (GTK_WIDGET (header->priv->search),
346 rb_source_can_search (header->priv->selected_source));
347 header->priv->have_search = rb_source_can_search (header->priv->selected_source);
348 header->priv->have_browser = rb_source_can_browse (header->priv->selected_source);
349 if (!header->priv->have_browser)
350 header->priv->disclosed = FALSE;
351 else if (header->priv->browser_key)
352 header->priv->disclosed = eel_gconf_get_boolean (header->priv->browser_key);
353 else
354 /* restore the previous state of the source*/
355 header->priv->disclosed = disclosed;
357 if (!header->priv->have_browser && !header->priv->have_search)
358 gtk_widget_hide (GTK_WIDGET (header));
359 else
360 gtk_widget_show (GTK_WIDGET (header));
363 /* merge the source-specific UI */
364 actions = rb_source_get_search_actions (source);
365 g_list_foreach (actions, (GFunc)merge_source_ui_cb, header);
366 rb_list_deep_free (actions);
368 rb_source_header_sync_control_state (header);
371 static void
372 rb_source_header_set_property (GObject *object,
373 guint prop_id,
374 const GValue *value,
375 GParamSpec *pspec)
377 RBSourceHeader *header = RB_SOURCE_HEADER (object);
379 switch (prop_id) {
380 case PROP_SOURCE:
381 rb_source_header_set_source_internal (header, g_value_get_object (value));
383 break;
384 case PROP_ACTION_GROUP:
385 header->priv->actiongroup = g_value_get_object (value);
386 gtk_action_group_add_toggle_actions (header->priv->actiongroup,
387 rb_source_header_toggle_entries,
388 rb_source_header_n_toggle_entries,
389 header);
390 break;
391 case PROP_UI_MANAGER:
392 header->priv->ui_manager = g_value_get_object (value);
393 break;
394 default:
395 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
396 break;
400 static void
401 rb_source_header_get_property (GObject *object,
402 guint prop_id,
403 GValue *value,
404 GParamSpec *pspec)
406 RBSourceHeader *header = RB_SOURCE_HEADER (object);
408 switch (prop_id) {
409 case PROP_SOURCE:
410 g_value_set_object (value, header->priv->selected_source);
411 break;
412 case PROP_ACTION_GROUP:
413 g_value_set_object (value, header->priv->actiongroup);
414 break;
415 case PROP_UI_MANAGER:
416 g_value_set_object (value, header->priv->ui_manager);
417 break;
418 default:
419 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
420 break;
424 void
425 rb_source_header_set_source (RBSourceHeader *header,
426 RBSource *source)
428 g_return_if_fail (RB_IS_SOURCE_HEADER (header));
429 g_return_if_fail (RB_IS_SOURCE (source));
431 g_object_set (G_OBJECT (header),
432 "source", source,
433 NULL);
436 RBSourceHeader *
437 rb_source_header_new (GtkUIManager *mgr,
438 GtkActionGroup *actiongroup)
440 RBSourceHeader *header = g_object_new (RB_TYPE_SOURCE_HEADER,
441 "action-group", actiongroup,
442 "ui-manager", mgr,
443 NULL);
445 g_return_val_if_fail (header->priv != NULL, NULL);
447 return header;
450 static void
451 rb_source_header_filter_changed_cb (RBSource *source,
452 RBSourceHeader *header)
454 rb_debug ("filter changed for %p", source);
455 /* To add this line back in, you need to add a search_changed signal,
456 * make RBShufflePlayOrder at least listen to it, and change the
457 * filter_changed notifies in all of the search functions to
458 * search_change notifies.
460 /* rb_search_entry_clear (RB_SEARCH_ENTRY (header->priv->search)); */
463 static void
464 rb_source_header_source_weak_destroy_cb (RBSourceHeader *header, RBSource *source)
466 g_hash_table_remove (header->priv->source_states, source);
469 static void
470 rb_source_state_sync (RBSourceHeader *header,
471 gboolean set_text,
472 const char *text,
473 gboolean set_disclosure,
474 gboolean disclosed)
476 SourceState *old_state;
478 old_state = g_hash_table_lookup (header->priv->source_states,
479 header->priv->selected_source);
481 if (old_state) {
482 if (set_text)
483 old_state->search_text = text ? g_strdup (text) : NULL;
484 if (set_disclosure)
485 old_state->disclosed = disclosed;
486 } else {
487 SourceState *new_state;
489 /* if we haven't seen the source before, monitor it for deletion */
490 g_object_weak_ref (G_OBJECT (header->priv->selected_source),
491 (GWeakNotify)rb_source_header_source_weak_destroy_cb,
492 header);
494 new_state = g_new (SourceState, 1);
495 new_state->search_text = text ? g_strdup (text) : NULL;
496 new_state->disclosed = disclosed;
497 g_hash_table_insert (header->priv->source_states,
498 header->priv->selected_source,
499 new_state);
503 static void
504 rb_source_header_search_cb (RBSearchEntry *search,
505 const char *text,
506 RBSourceHeader *header)
509 rb_debug ("searching for \"%s\"", text);
511 rb_source_state_sync (header, TRUE, text, FALSE, FALSE);
513 rb_source_search (header->priv->selected_source, text);
514 rb_source_header_sync_control_state (header);
517 void
518 rb_source_header_clear_search (RBSourceHeader *header)
520 rb_debug ("clearing search");
522 if (!rb_search_entry_searching (RB_SEARCH_ENTRY (header->priv->search)))
523 return;
525 if (header->priv->selected_source) {
526 rb_source_search (header->priv->selected_source, NULL);
527 rb_source_state_sync (header, TRUE, NULL, FALSE, FALSE);
530 rb_search_entry_clear (RB_SEARCH_ENTRY (header->priv->search));
531 rb_source_header_sync_control_state (header);
534 static void
535 rb_source_header_view_browser_changed_cb (GtkAction *action,
536 RBSourceHeader *header)
538 rb_debug ("got view browser toggle");
539 header->priv->disclosed = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
541 if (header->priv->browser_key)
542 eel_gconf_set_boolean (header->priv->browser_key,
543 header->priv->disclosed);
544 else {
545 rb_source_state_sync (header, FALSE, NULL, TRUE, header->priv->disclosed);
548 rb_debug ("got view browser toggle");
550 rb_source_header_sync_control_state (header);
553 void
554 rb_source_header_sync_control_state (RBSourceHeader *header)
556 GtkAction *viewbrowser_action;
557 GtkAction *viewstatusbar_action;
558 GtkAction *viewall_action;
559 gboolean not_small = !eel_gconf_get_boolean (CONF_UI_SMALL_DISPLAY);
561 viewbrowser_action = gtk_action_group_get_action (header->priv->actiongroup,
562 "ViewBrowser");
563 g_object_set (G_OBJECT (viewbrowser_action), "sensitive",
564 header->priv->have_browser && not_small, NULL);
565 viewstatusbar_action = gtk_action_group_get_action (header->priv->actiongroup,
566 "ViewStatusbar");
567 g_object_set (G_OBJECT (viewstatusbar_action), "sensitive",
568 not_small, NULL);
569 viewall_action = gtk_action_group_get_action (header->priv->actiongroup,
570 "ViewAll");
571 g_object_set (G_OBJECT (viewall_action), "sensitive",
572 (header->priv->have_browser || header->priv->have_search) && not_small, NULL);
574 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (viewbrowser_action),
575 header->priv->disclosed);
577 if (header->priv->selected_source)
578 rb_source_browser_toggled (header->priv->selected_source, header->priv->disclosed);
581 static void
582 rb_source_header_search_activate_cb (RBSearchEntry *search,
583 RBSourceHeader *header)
585 gtk_widget_grab_focus (GTK_WIDGET (header->priv->selected_source));
588 void
589 rb_source_header_focus_search_box (RBSourceHeader *header)
591 rb_search_entry_grab_focus (RB_SEARCH_ENTRY (header->priv->search));