No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2011 Jonathan Matthew <jonathan@d14n.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include <config.h>
30
31 #include <widgets/rb-source-toolbar.h>
32 #include <lib/rb-util.h>
33
34 static void rb_source_toolbar_class_init (RBSourceToolbarClass *klass);
35 static void rb_source_toolbar_init (RBSourceToolbar *toolbar);
36
37 static void toolbar_add_widget_cb (GtkUIManager *ui_manager, GtkWidget *widget, RBSourceToolbar *toolbar);
38 static void popup_add_widget_cb (GtkUIManager *ui_manager, GtkWidget *widget, RBSourceToolbar *toolbar);
39
40 struct _RBSourceToolbarPrivate
41 {
42 GtkUIManager *ui_manager;
43 RBSource *source;
44 RBSearchEntry *search_entry;
45 GtkWidget *search_popup;
46 GtkWidget *toolbar;
47 GBinding *browse_binding;
48 GtkAction *browse_action;
49 char *popup_path;
50
51 /* search state */
52 int search_value;
53 gulong search_change_cb_id;
54 RBSourceSearch *active_search;
55 char *search_text;
56 GtkRadioAction *search_group;
57 };
58
59 G_DEFINE_TYPE (RBSourceToolbar, rb_source_toolbar, GTK_TYPE_GRID)
60
61 /**
62 * SECTION:rb-source-toolbar
63 * @short_description: toolbar+search entry for sources
64 *
65 * This class combines a toolbar for custom source actions with a
66 * search entry. The toolbar content is specified using a UI path.
67 * The #RBSourceToolbar takes care of preserving search state when
68 * the selected page changes, and performs searches when the user
69 * selects a new search type or changes the search text.
70 */
71
72 enum
73 {
74 PROP_0,
75 PROP_SOURCE,
76 PROP_UI_MANAGER,
77 };
78
79 static void
80 prepare_toolbar (GtkWidget *toolbar)
81 {
82 static GtkCssProvider *provider = NULL;
83
84 if (provider == NULL) {
85 const char *style =
86 "GtkToolbar {\n"
87 " -GtkToolbar-shadow-type: none;\n"
88 " border-style: none;\n"
89 "}";
90
91 provider = gtk_css_provider_new ();
92 gtk_css_provider_load_from_data (provider, style, -1, NULL);
93 }
94
95 gtk_style_context_add_provider (gtk_widget_get_style_context (toolbar),
96 GTK_STYLE_PROVIDER (provider),
97 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
98
99 gtk_widget_set_hexpand (toolbar, TRUE);
100
101 gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_TEXT);
102 }
103
104 static void
105 search_change_cb (GtkRadioAction *group, GtkRadioAction *current, RBSourceToolbar *toolbar)
106 {
107 toolbar->priv->active_search = rb_source_search_get_from_action (G_OBJECT (current));
108
109 if (toolbar->priv->search_text != NULL) {
110 rb_source_search (toolbar->priv->source, toolbar->priv->active_search, NULL, toolbar->priv->search_text);
111 }
112
113 rb_search_entry_set_placeholder (toolbar->priv->search_entry, gtk_action_get_label (GTK_ACTION (current)));
114 }
115
116 static void
117 source_selected_cb (GObject *object, GParamSpec *pspec, RBSourceToolbar *toolbar)
118 {
119 gboolean selected;
120
121 g_object_get (object, "selected", &selected, NULL);
122
123 if (selected) {
124 char *toolbar_path;
125 char *browse_path;
126
127 if (toolbar->priv->toolbar != NULL) {
128 gtk_grid_attach (GTK_GRID (toolbar), toolbar->priv->toolbar, 0, 0, 2, 1);
129 gtk_widget_show_all (GTK_WIDGET (toolbar->priv->toolbar));
130 }
131
132 if (toolbar->priv->search_entry != NULL) {
133 rb_search_entry_set_mnemonic (toolbar->priv->search_entry, TRUE);
134
135 gtk_widget_add_accelerator (GTK_WIDGET (toolbar->priv->search_entry),
136 "grab-focus",
137 gtk_ui_manager_get_accel_group (toolbar->priv->ui_manager),
138 gdk_unicode_to_keyval ('f'),
139 GDK_CONTROL_MASK,
140 0);
141 }
142
143 if (toolbar->priv->search_group != NULL) {
144 if (toolbar->priv->search_value != -1) {
145 gtk_radio_action_set_current_value (toolbar->priv->search_group,
146 toolbar->priv->search_value);
147 }
148
149 toolbar->priv->search_change_cb_id = g_signal_connect (toolbar->priv->search_group,
150 "changed",
151 G_CALLBACK (search_change_cb),
152 toolbar);
153 }
154
155 g_object_get (toolbar->priv->source, "toolbar-path", &toolbar_path, NULL);
156 if (toolbar_path != NULL) {
157
158 browse_path = g_strdup_printf ("%s/Browse", toolbar_path);
159 toolbar->priv->browse_action = gtk_ui_manager_get_action (toolbar->priv->ui_manager,
160 browse_path);
161 g_free (browse_path);
162
163 if (toolbar->priv->browse_action != NULL) {
164 toolbar->priv->browse_binding =
165 g_object_bind_property (toolbar->priv->source, "show-browser",
166 toolbar->priv->browse_action, "active",
167 G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
168 gtk_action_connect_accelerator (toolbar->priv->browse_action);
169 }
170 g_free (toolbar_path);
171 } else {
172 toolbar->priv->browse_action = NULL;
173 }
174 } else {
175 if (toolbar->priv->toolbar != NULL) {
176 gtk_container_remove (GTK_CONTAINER (toolbar), toolbar->priv->toolbar);
177 }
178
179 if (toolbar->priv->search_entry != NULL) {
180 rb_search_entry_set_mnemonic (toolbar->priv->search_entry, FALSE);
181
182 gtk_widget_remove_accelerator (GTK_WIDGET (toolbar->priv->search_entry),
183 gtk_ui_manager_get_accel_group (toolbar->priv->ui_manager),
184 gdk_unicode_to_keyval ('f'),
185 GDK_CONTROL_MASK);
186 }
187
188 if (toolbar->priv->search_group != NULL) {
189 if (toolbar->priv->search_change_cb_id != 0) {
190 g_signal_handler_disconnect (toolbar->priv->search_group,
191 toolbar->priv->search_change_cb_id);
192 }
193
194 toolbar->priv->search_value = gtk_radio_action_get_current_value (toolbar->priv->search_group);
195 }
196
197 if (toolbar->priv->browse_binding != NULL) {
198 g_object_unref (toolbar->priv->browse_binding);
199 toolbar->priv->browse_binding = NULL;
200 }
201
202 if (toolbar->priv->browse_action != NULL) {
203 gtk_action_disconnect_accelerator (toolbar->priv->browse_action);
204 toolbar->priv->browse_action = NULL;
205 }
206 }
207 }
208
209 static void
210 search_cb (RBSearchEntry *search_entry, const char *text, RBSourceToolbar *toolbar)
211 {
212 rb_source_search (toolbar->priv->source, toolbar->priv->active_search, toolbar->priv->search_text, text);
213
214 g_free (toolbar->priv->search_text);
215 toolbar->priv->search_text = NULL;
216 if (text != NULL) {
217 toolbar->priv->search_text = g_strdup (text);
218 }
219 }
220
221 static void
222 show_popup_cb (RBSearchEntry *search_entry, RBSourceToolbar *toolbar)
223 {
224 gtk_menu_popup (GTK_MENU (toolbar->priv->search_popup),
225 NULL, NULL, NULL, NULL, 3,
226 gtk_get_current_event_time ());
227 }
228
229
230 static void
231 impl_finalize (GObject *object)
232 {
233 RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
234
235 g_free (toolbar->priv->search_text);
236 g_free (toolbar->priv->popup_path);
237
238 G_OBJECT_CLASS (rb_source_toolbar_parent_class)->finalize (object);
239 }
240
241 static void
242 impl_dispose (GObject *object)
243 {
244 RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
245
246 if (toolbar->priv->ui_manager != NULL) {
247 g_signal_handlers_disconnect_by_func (toolbar->priv->ui_manager, G_CALLBACK (popup_add_widget_cb), toolbar);
248 g_signal_handlers_disconnect_by_func (toolbar->priv->ui_manager, G_CALLBACK (toolbar_add_widget_cb), toolbar);
249
250 g_object_unref (toolbar->priv->ui_manager);
251 toolbar->priv->ui_manager = NULL;
252 }
253 if (toolbar->priv->search_popup != NULL) {
254 g_object_unref (toolbar->priv->search_popup);
255 toolbar->priv->search_popup = NULL;
256 }
257 if (toolbar->priv->toolbar != NULL) {
258 g_object_unref (toolbar->priv->toolbar);
259 toolbar->priv->toolbar = NULL;
260 }
261 if (toolbar->priv->browse_binding != NULL) {
262 g_object_unref (toolbar->priv->browse_binding);
263 toolbar->priv->browse_binding = NULL;
264 }
265
266 G_OBJECT_CLASS (rb_source_toolbar_parent_class)->dispose (object);
267 }
268
269 static void
270 toolbar_add_widget_cb (GtkUIManager *ui_manager, GtkWidget *widget, RBSourceToolbar *toolbar)
271 {
272 char *toolbar_path;
273 gboolean selected;
274
275 g_object_get (toolbar->priv->source, "toolbar-path", &toolbar_path, "selected", &selected, NULL);
276 toolbar->priv->toolbar = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, toolbar_path);
277 g_free (toolbar_path);
278
279 if (toolbar->priv->toolbar) {
280 g_object_ref (toolbar->priv->toolbar);
281 g_signal_handlers_disconnect_by_func (ui_manager, G_CALLBACK (toolbar_add_widget_cb), toolbar);
282
283 prepare_toolbar (toolbar->priv->toolbar);
284
285 if (selected) {
286 gtk_grid_attach (GTK_GRID (toolbar), toolbar->priv->toolbar, 0, 0, 2, 1);
287 gtk_widget_show_all (GTK_WIDGET (toolbar->priv->toolbar));
288 }
289 }
290 }
291
292 static void
293 impl_constructed (GObject *object)
294 {
295 RBSourceToolbar *toolbar;
296 char *toolbar_path;
297 GtkWidget *blank;
298
299 RB_CHAIN_GOBJECT_METHOD (rb_source_toolbar_parent_class, constructed, object);
300
301 toolbar = RB_SOURCE_TOOLBAR (object);
302
303 g_object_get (toolbar->priv->source, "toolbar-path", &toolbar_path, NULL);
304 if (toolbar_path) {
305 toolbar->priv->toolbar = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, toolbar_path);
306 if (toolbar->priv->toolbar == NULL) {
307 g_signal_connect (toolbar->priv->ui_manager, "add-widget", G_CALLBACK (toolbar_add_widget_cb), toolbar);
308 } else {
309 g_object_ref (toolbar->priv->toolbar);
310 prepare_toolbar (toolbar->priv->toolbar);
311 }
312 } else {
313 blank = gtk_toolbar_new ();
314 prepare_toolbar (blank);
315 gtk_grid_attach (GTK_GRID (toolbar), blank, 0, 0, 2 ,1);
316 }
317 g_free (toolbar_path);
318
319 /* search entry gets created later if required */
320
321 g_signal_connect (toolbar->priv->source, "notify::selected", G_CALLBACK (source_selected_cb), toolbar);
322 }
323
324 static void
325 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
326 {
327 RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
328
329 switch (prop_id) {
330 case PROP_SOURCE:
331 g_value_set_object (value, toolbar->priv->source);
332 break;
333 case PROP_UI_MANAGER:
334 g_value_set_object (value, toolbar->priv->ui_manager);
335 break;
336 default:
337 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
338 break;
339 }
340 }
341
342 static void
343 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
344 {
345 RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
346
347 switch (prop_id) {
348 case PROP_SOURCE:
349 toolbar->priv->source = g_value_get_object (value); /* don't take a ref */
350 break;
351 case PROP_UI_MANAGER:
352 toolbar->priv->ui_manager = g_value_dup_object (value);
353 break;
354 default:
355 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
356 break;
357 }
358 }
359
360 static void
361 rb_source_toolbar_init (RBSourceToolbar *toolbar)
362 {
363 toolbar->priv = G_TYPE_INSTANCE_GET_PRIVATE (toolbar, RB_TYPE_SOURCE_TOOLBAR, RBSourceToolbarPrivate);
364
365 toolbar->priv->search_value = -1;
366 }
367
368 static void
369 rb_source_toolbar_class_init (RBSourceToolbarClass *klass)
370 {
371 GObjectClass *object_class = G_OBJECT_CLASS (klass);
372
373 object_class->constructed = impl_constructed;
374 object_class->dispose = impl_dispose;
375 object_class->finalize = impl_finalize;
376 object_class->set_property = impl_set_property;
377 object_class->get_property = impl_get_property;
378
379 /**
380 * RBSourceToolbar:source:
381 *
382 * The #RBSource the toolbar is associated with
383 */
384 g_object_class_install_property (object_class,
385 PROP_SOURCE,
386 g_param_spec_object ("source",
387 "source",
388 "RBSource instance",
389 RB_TYPE_DISPLAY_PAGE,
390 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
391 /**
392 * RBSourceToolbar:ui-manager:
393 *
394 * The #GtkUIManager instance
395 */
396 g_object_class_install_property (object_class,
397 PROP_UI_MANAGER,
398 g_param_spec_object ("ui-manager",
399 "ui manager",
400 "GtkUIManager instance",
401 GTK_TYPE_UI_MANAGER,
402 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
403 g_type_class_add_private (klass, sizeof (RBSourceToolbarPrivate));
404 }
405
406 /**
407 * rb_source_toolbar_new:
408 * @page: a #RBDisplayPage
409 * @ui_manager: the #GtkUIManager
410 *
411 * Creates a new source toolbar for @page. The toolbar does not
412 * initially include a search entry. Call #rb_source_toolbar_add_search_entry
413 * to add one. The toolbar content comes from the @RBSource:toolbar-path property.
414 *
415 * Return value: the #RBSourceToolbar
416 */
417 RBSourceToolbar *
418 rb_source_toolbar_new (RBDisplayPage *page, GtkUIManager *ui_manager)
419 {
420 GObject *object;
421 object = g_object_new (RB_TYPE_SOURCE_TOOLBAR,
422 "source", page,
423 "ui-manager", ui_manager,
424 "column-spacing", 6,
425 "column-homogeneous", TRUE,
426 "row-spacing", 6,
427 "row-homogeneous", TRUE,
428 NULL);
429 return RB_SOURCE_TOOLBAR (object);
430 }
431
432 static void
433 setup_search_popup (RBSourceToolbar *toolbar, GtkWidget *popup)
434 {
435 GList *items;
436 GSList *l;
437 int active_value;
438
439 toolbar->priv->search_popup = g_object_ref (popup);
440
441 items = gtk_container_get_children (GTK_CONTAINER (toolbar->priv->search_popup));
442 toolbar->priv->search_group = GTK_RADIO_ACTION (gtk_activatable_get_related_action (GTK_ACTIVATABLE (items->data)));
443 g_list_free (items);
444
445 active_value = gtk_radio_action_get_current_value (toolbar->priv->search_group);
446 for (l = gtk_radio_action_get_group (toolbar->priv->search_group); l != NULL; l = l->next) {
447 int value;
448 g_object_get (G_OBJECT (l->data), "value", &value, NULL);
449 if (value == active_value) {
450 rb_search_entry_set_placeholder (toolbar->priv->search_entry,
451 gtk_action_get_label (GTK_ACTION (l->data)));
452 }
453 }
454
455 g_signal_connect (toolbar->priv->search_entry, "show-popup", G_CALLBACK (show_popup_cb), toolbar);
456 }
457
458 static void
459 popup_add_widget_cb (GtkUIManager *ui_manager, GtkWidget *widget, RBSourceToolbar *toolbar)
460 {
461 GtkWidget *popup;
462 popup = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, toolbar->priv->popup_path);
463
464 if (popup) {
465 setup_search_popup (toolbar, popup);
466 g_signal_handlers_disconnect_by_func (ui_manager, G_CALLBACK (popup_add_widget_cb), toolbar);
467 }
468 }
469
470
471 /**
472 * rb_source_toolbar_add_search_entry:
473 * @toolbar: a #RBSourceToolbar
474 * @popup_path: the UI path for the search popup (or NULL)
475 * @placeholder: the placeholder text for the search entry (or NULL)
476 *
477 * Adds a search entry to the toolbar. If a popup path is specified,
478 * clicking on the primary icon will show a menu allowing the user to
479 * select a search type, and the placeholder text for the entry will
480 * be the selected search description. Otherwise, the specified placeholder
481 * text will be displayed.
482 */
483 void
484 rb_source_toolbar_add_search_entry (RBSourceToolbar *toolbar, const char *popup_path, const char *placeholder)
485 {
486 g_assert (toolbar->priv->search_entry == NULL);
487
488 toolbar->priv->search_entry = rb_search_entry_new (popup_path != NULL);
489 gtk_widget_set_margin_right (GTK_WIDGET (toolbar->priv->search_entry), 6);
490 gtk_grid_attach (GTK_GRID (toolbar), GTK_WIDGET (toolbar->priv->search_entry), 2, 0, 1, 1);
491
492 if (placeholder) {
493 rb_search_entry_set_placeholder (toolbar->priv->search_entry, placeholder);
494 }
495
496 g_signal_connect (toolbar->priv->search_entry, "search", G_CALLBACK (search_cb), toolbar);
497 /* activate? */
498
499 if (popup_path != NULL) {
500 GtkWidget *popup;
501 toolbar->priv->popup_path = g_strdup (popup_path);
502
503 popup = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, popup_path);
504 if (popup != NULL) {
505 setup_search_popup (toolbar, popup);
506 } else {
507 g_signal_connect (toolbar->priv->ui_manager, "add-widget", G_CALLBACK (popup_add_widget_cb), toolbar);
508 }
509 }
510 }
511
512 /**
513 * rb_source_toolbar_clear_search_entry:
514 * @toolbar: a #RBSourceToolbar
515 *
516 * Clears the search entry text. Call this from RBSource:impl_reset_filters.
517 */
518 void
519 rb_source_toolbar_clear_search_entry (RBSourceToolbar *toolbar)
520 {
521 g_assert (toolbar->priv->search_entry != NULL);
522 rb_search_entry_clear (toolbar->priv->search_entry);
523 }