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.
29 #include <glib/gi18n.h>
32 #include "rb-source-header.h"
33 #include "rb-stock-icons.h"
34 #include "rb-preferences.h"
35 #include "rb-search-entry.h"
37 #include "rb-entry-view.h"
38 #include "eel-gconf-extensions.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
,
48 static void rb_source_header_get_property (GObject
*object
,
52 static void rb_source_header_filter_changed_cb (RBSource
*source
,
53 RBSourceHeader
*header
);
54 static void rb_source_header_search_cb (RBSearchEntry
*search
,
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
);
69 sourcestate_free (SourceState
*state
)
71 g_free (state
->search_text
);
75 struct RBSourceHeaderPrivate
77 RBSource
*selected_source
;
79 GtkUIManager
*ui_manager
;
80 GtkActionGroup
*actiongroup
;
81 guint source_ui_merge_id
;
83 GtkTooltips
*tooltips
;
86 GtkWidget
*search_bar
;
88 guint browser_notify_id
;
89 guint search_notify_id
;
91 gboolean have_browser
;
95 GHashTable
*source_states
;
99 #define RB_SOURCE_HEADER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SOURCE_HEADER, RBSourceHeaderPrivate))
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
)
120 force_no_shadow (GtkWidget
*widget
)
122 gboolean first_time
= TRUE
;
125 gtk_rc_parse_string ("\n"
126 " style \"search-toolbar-style\"\n"
128 " GtkToolbar::shadow-type=GTK_SHADOW_NONE\n"
131 " widget \"*.search-toolbar\" style \"search-toolbar-style\"\n"
136 gtk_widget_set_name (widget
, "search-toolbar");
140 ui_manager_add_widget_cb (GtkUIManager
*ui_manager
,
142 RBSourceHeader
*header
)
144 if (header
->priv
->search_bar
!= NULL
) {
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
,
157 GTK_EXPAND
| GTK_FILL
,
158 GTK_EXPAND
| GTK_FILL
,
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
);
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
,
198 g_param_spec_object ("source",
203 g_object_class_install_property (object_class
,
205 g_param_spec_object ("action-group",
207 "GtkActionGroup object",
208 GTK_TYPE_ACTION_GROUP
,
209 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
210 g_object_class_install_property (object_class
,
212 g_param_spec_object ("ui-manager",
214 "GtkUIManager object",
216 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
218 g_type_class_add_private (klass
, sizeof (RBSourceHeaderPrivate
));
222 rb_source_header_init (RBSourceHeader
*header
)
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
),
239 _("Filter music display by genre, artist, album, or title"),
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
),
253 GTK_EXPAND
| GTK_FILL
,
254 GTK_EXPAND
| GTK_FILL
,
257 header
->priv
->source_states
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
,
258 NULL
, (GDestroyNotify
)sourcestate_free
);
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
,
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
,
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
);
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
,
305 rb_source_header_set_source_internal (RBSourceHeader
*header
,
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
),
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
;
325 source_state
= g_hash_table_lookup (header
->priv
->source_states
,
326 header
->priv
->selected_source
);
329 text
= g_strdup (source_state
->search_text
);
330 disclosed
= source_state
->disclosed
;
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
),
342 G_CALLBACK (rb_source_header_filter_changed_cb
),
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
);
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
));
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
);
372 rb_source_header_set_property (GObject
*object
,
377 RBSourceHeader
*header
= RB_SOURCE_HEADER (object
);
381 rb_source_header_set_source_internal (header
, g_value_get_object (value
));
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
,
391 case PROP_UI_MANAGER
:
392 header
->priv
->ui_manager
= g_value_get_object (value
);
395 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
401 rb_source_header_get_property (GObject
*object
,
406 RBSourceHeader
*header
= RB_SOURCE_HEADER (object
);
410 g_value_set_object (value
, header
->priv
->selected_source
);
412 case PROP_ACTION_GROUP
:
413 g_value_set_object (value
, header
->priv
->actiongroup
);
415 case PROP_UI_MANAGER
:
416 g_value_set_object (value
, header
->priv
->ui_manager
);
419 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
425 rb_source_header_set_source (RBSourceHeader
*header
,
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
),
437 rb_source_header_new (GtkUIManager
*mgr
,
438 GtkActionGroup
*actiongroup
)
440 RBSourceHeader
*header
= g_object_new (RB_TYPE_SOURCE_HEADER
,
441 "action-group", actiongroup
,
445 g_return_val_if_fail (header
->priv
!= NULL
, NULL
);
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)); */
464 rb_source_header_source_weak_destroy_cb (RBSourceHeader
*header
, RBSource
*source
)
466 g_hash_table_remove (header
->priv
->source_states
, source
);
470 rb_source_state_sync (RBSourceHeader
*header
,
473 gboolean set_disclosure
,
476 SourceState
*old_state
;
478 old_state
= g_hash_table_lookup (header
->priv
->source_states
,
479 header
->priv
->selected_source
);
483 old_state
->search_text
= text
? g_strdup (text
) : NULL
;
485 old_state
->disclosed
= disclosed
;
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
,
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
,
504 rb_source_header_search_cb (RBSearchEntry
*search
,
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
);
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
)))
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
);
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
);
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
);
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
,
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
,
567 g_object_set (G_OBJECT (viewstatusbar_action
), "sensitive",
569 viewall_action
= gtk_action_group_get_action (header
->priv
->actiongroup
,
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
);
582 rb_source_header_search_activate_cb (RBSearchEntry
*search
,
583 RBSourceHeader
*header
)
585 gtk_widget_grab_focus (GTK_WIDGET (header
->priv
->selected_source
));
589 rb_source_header_focus_search_box (RBSourceHeader
*header
)
591 rb_search_entry_grab_focus (RB_SEARCH_ENTRY (header
->priv
->search
));