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_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
),
240 _("Filter music display by genre, artist, album, or title"),
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
),
254 GTK_EXPAND
| GTK_FILL
,
255 GTK_EXPAND
| GTK_FILL
,
258 header
->priv
->source_states
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
,
259 NULL
, (GDestroyNotify
)sourcestate_free
);
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
,
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
,
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
);
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
,
306 rb_source_header_set_source_internal (RBSourceHeader
*header
,
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
),
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
;
326 source_state
= g_hash_table_lookup (header
->priv
->source_states
,
327 header
->priv
->selected_source
);
330 text
= g_strdup (source_state
->search_text
);
331 disclosed
= source_state
->disclosed
;
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
),
343 G_CALLBACK (rb_source_header_filter_changed_cb
),
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
);
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
));
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
);
373 rb_source_header_set_property (GObject
*object
,
378 RBSourceHeader
*header
= RB_SOURCE_HEADER (object
);
382 rb_source_header_set_source_internal (header
, g_value_get_object (value
));
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
,
392 case PROP_UI_MANAGER
:
393 header
->priv
->ui_manager
= g_value_get_object (value
);
396 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
402 rb_source_header_get_property (GObject
*object
,
407 RBSourceHeader
*header
= RB_SOURCE_HEADER (object
);
411 g_value_set_object (value
, header
->priv
->selected_source
);
413 case PROP_ACTION_GROUP
:
414 g_value_set_object (value
, header
->priv
->actiongroup
);
416 case PROP_UI_MANAGER
:
417 g_value_set_object (value
, header
->priv
->ui_manager
);
420 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
426 rb_source_header_set_source (RBSourceHeader
*header
,
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
),
438 rb_source_header_new (GtkUIManager
*mgr
,
439 GtkActionGroup
*actiongroup
)
441 RBSourceHeader
*header
= g_object_new (RB_TYPE_SOURCE_HEADER
,
442 "action-group", actiongroup
,
446 g_return_val_if_fail (header
->priv
!= NULL
, NULL
);
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)); */
465 rb_source_header_source_weak_destroy_cb (RBSourceHeader
*header
, RBSource
*source
)
467 g_hash_table_remove (header
->priv
->source_states
, source
);
471 rb_source_state_sync (RBSourceHeader
*header
,
474 gboolean set_disclosure
,
477 SourceState
*old_state
;
479 old_state
= g_hash_table_lookup (header
->priv
->source_states
,
480 header
->priv
->selected_source
);
484 old_state
->search_text
= text
? g_strdup (text
) : NULL
;
486 old_state
->disclosed
= disclosed
;
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
,
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
,
505 rb_source_header_search_cb (RBSearchEntry
*search
,
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
);
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
)))
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
);
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
);
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
);
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
,
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
,
568 g_object_set (G_OBJECT (viewstatusbar_action
), "sensitive",
570 viewall_action
= gtk_action_group_get_action (header
->priv
->actiongroup
,
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
);
583 rb_source_header_search_activate_cb (RBSearchEntry
*search
,
584 RBSourceHeader
*header
)
586 gtk_widget_grab_focus (GTK_WIDGET (header
->priv
->selected_source
));
590 rb_source_header_focus_search_box (RBSourceHeader
*header
)
592 rb_search_entry_grab_focus (RB_SEARCH_ENTRY (header
->priv
->search
));