No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2007 James Henstridge <james@jamesh.id.au>
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 <string.h>
32
33 #include <glib/gi18n-lib.h>
34 #include <gtk/gtk.h>
35
36 #include <lib/rb-debug.h>
37 #include <lib/rb-util.h>
38 #include <rhythmdb/rhythmdb-query-model.h>
39 #include <shell/rb-shell-player.h>
40 #include <widgets/rb-entry-view.h>
41 #include <widgets/rb-uri-dialog.h>
42 #include <widgets/rb-source-toolbar.h>
43
44 #include "rb-fm-radio-source.h"
45 #include "rb-radio-tuner.h"
46
47 typedef struct _RhythmDBEntryType RBFMRadioEntryType;
48 typedef struct _RhythmDBEntryTypeClass RBFMRadioEntryTypeClass;
49
50 static void rb_fm_radio_source_class_init (RBFMRadioSourceClass *class);
51 static void rb_fm_radio_source_init (RBFMRadioSource *self);
52 static void rb_fm_radio_source_constructed (GObject *object);
53 static void rb_fm_radio_source_dispose (GObject *object);
54 static void rb_fm_radio_source_do_query (RBFMRadioSource *self);
55 static void rb_fm_radio_source_songs_view_sort_order_changed (GObject *obj,
56 GParamSpec *pspec,
57 RBFMRadioSource *self);
58 static void rb_fm_radio_source_songs_view_show_popup (
59 RBEntryView *view,
60 gboolean over_entry,
61 RBFMRadioSource *self);
62
63 static void rb_fm_radio_source_cmd_new_station (GtkAction *action,
64 RBFMRadioSource *self);
65
66 static void playing_entry_changed (RBShellPlayer *player, RhythmDBEntry *entry,
67 RBFMRadioSource *self);
68
69 static void impl_delete (RBSource *source);
70 static gboolean impl_show_popup (RBDisplayPage *page);
71 static RBEntryView *impl_get_entry_view (RBSource *source);
72
73 static void rb_fm_radio_entry_type_class_init (RBFMRadioEntryTypeClass *klass);
74 static void rb_fm_radio_entry_type_init (RBFMRadioEntryType *etype);
75 GType rb_fm_radio_entry_type_get_type (void);
76
77 struct _RBFMRadioSourcePrivate {
78 RhythmDB *db;
79 RBShellPlayer *player;
80 RhythmDBEntryType *entry_type;
81 RhythmDBEntry *playing_entry;
82
83 RBEntryView *stations;
84
85 RBRadioTuner *tuner;
86
87 GtkActionGroup *action_group;
88 };
89
90 static GtkActionEntry rb_fm_radio_source_actions[] = {
91 { "MusicNewFMRadioStation", GTK_STOCK_NEW, N_("New FM R_adio Station"),
92 NULL, N_("Create a new FM Radio station"),
93 G_CALLBACK (rb_fm_radio_source_cmd_new_station) },
94 };
95
96 G_DEFINE_DYNAMIC_TYPE (RBFMRadioSource, rb_fm_radio_source, RB_TYPE_SOURCE);
97
98 G_DEFINE_DYNAMIC_TYPE (RBFMRadioEntryType, rb_fm_radio_entry_type, RHYTHMDB_TYPE_ENTRY_TYPE);
99
100 static char *
101 rb_fm_radio_source_get_playback_uri (RhythmDBEntryType *etype, RhythmDBEntry *entry)
102 {
103 return g_strdup("xrbsilence:///");
104 }
105
106 static void
107 rb_fm_radio_entry_type_class_init (RBFMRadioEntryTypeClass *klass)
108 {
109 RhythmDBEntryTypeClass *etype_class = RHYTHMDB_ENTRY_TYPE_CLASS (klass);
110 etype_class->can_sync_metadata = (RhythmDBEntryTypeBooleanFunc) rb_true_function;
111 etype_class->sync_metadata = (RhythmDBEntryTypeSyncFunc) rb_null_function;
112 etype_class->get_playback_uri = rb_fm_radio_source_get_playback_uri;
113 }
114
115 static void
116 rb_fm_radio_entry_type_class_finalize (RBFMRadioEntryTypeClass *klass)
117 {
118 }
119
120 static void
121 rb_fm_radio_entry_type_init (RBFMRadioEntryType *etype)
122 {
123 }
124
125 static void
126 rb_fm_radio_source_class_init (RBFMRadioSourceClass *class)
127 {
128 GObjectClass *object_class = G_OBJECT_CLASS (class);
129 RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (class);
130 RBSourceClass *source_class = RB_SOURCE_CLASS (class);
131
132 object_class->constructed = rb_fm_radio_source_constructed;
133 object_class->dispose = rb_fm_radio_source_dispose;
134
135 page_class->show_popup = impl_show_popup;
136
137 source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
138 source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
139 source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
140 source_class->impl_delete = impl_delete;
141 source_class->impl_get_entry_view = impl_get_entry_view;
142
143 g_type_class_add_private (class, sizeof (RBFMRadioSourcePrivate));
144 }
145
146 static void
147 rb_fm_radio_source_class_finalize (RBFMRadioSourceClass *class)
148 {
149 }
150
151 static void
152 rb_fm_radio_source_init (RBFMRadioSource *self)
153 {
154 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (
155 self, RB_TYPE_FM_RADIO_SOURCE, RBFMRadioSourcePrivate);
156 }
157
158 static void
159 rb_fm_radio_source_constructed (GObject *object)
160 {
161 RBFMRadioSource *self;
162 RBShell *shell;
163 RBSourceToolbar *toolbar;
164 GtkUIManager *ui_manager;
165 GtkWidget *grid;
166
167 RB_CHAIN_GOBJECT_METHOD (rb_fm_radio_source_parent_class, constructed, object);
168 self = RB_FM_RADIO_SOURCE (object);
169
170 g_object_get (self,
171 "shell", &shell,
172 "entry-type", &self->priv->entry_type,
173 NULL);
174 g_object_get (shell,
175 "db", &self->priv->db,
176 "shell-player", &self->priv->player,
177 "ui-manager", &ui_manager,
178 NULL);
179 g_object_unref (shell);
180
181 self->priv->action_group = _rb_display_page_register_action_group (
182 RB_DISPLAY_PAGE (self),
183 "FMRadioActions",
184 rb_fm_radio_source_actions,
185 G_N_ELEMENTS (rb_fm_radio_source_actions),
186 self);
187
188 toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (self), ui_manager);
189 g_object_unref (toolbar);
190
191 self->priv->stations = rb_entry_view_new (self->priv->db,
192 G_OBJECT (self->priv->player),
193 FALSE, FALSE);
194 rb_entry_view_append_column (self->priv->stations,
195 RB_ENTRY_VIEW_COL_TITLE, TRUE);
196 rb_entry_view_append_column (self->priv->stations,
197 RB_ENTRY_VIEW_COL_RATING, TRUE);
198 rb_entry_view_append_column (self->priv->stations,
199 RB_ENTRY_VIEW_COL_LAST_PLAYED, TRUE);
200
201 g_signal_connect_object (self->priv->stations,
202 "notify::sort-order",
203 G_CALLBACK (rb_fm_radio_source_songs_view_sort_order_changed),
204 self, 0);
205 /* sort order */
206
207 g_signal_connect_object (self->priv->stations, "show_popup",
208 G_CALLBACK (rb_fm_radio_source_songs_view_show_popup),
209 self, 0);
210
211 grid = gtk_grid_new ();
212 gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (toolbar), 0, 0, 1, 1);
213 gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (self->priv->stations), 0, 1, 1, 1);
214 gtk_container_add (GTK_CONTAINER (self), grid);
215 gtk_widget_show_all (GTK_WIDGET (self));
216
217 rb_fm_radio_source_do_query (self);
218
219 g_signal_connect_object (G_OBJECT (self->priv->player),
220 "playing-song-changed",
221 G_CALLBACK (playing_entry_changed),
222 self, 0);
223 }
224
225 RBSource *
226 rb_fm_radio_source_new (RBShell *shell, RBRadioTuner *tuner)
227 {
228 RBFMRadioSource *self;
229 RhythmDBEntryType *entry_type;
230 RhythmDB *db;
231
232 g_object_get (shell, "db", &db, NULL);
233
234 entry_type = rhythmdb_entry_type_get_by_name (db, "fmradio-station");
235 if (entry_type == NULL) {
236 entry_type = g_object_new (rb_fm_radio_entry_type_get_type (),
237 "db", db,
238 "name", "fmradio-station",
239 "save-to-disk", TRUE,
240 NULL);
241 rhythmdb_register_entry_type (db, entry_type);
242 }
243
244 self = g_object_new (RB_TYPE_FM_RADIO_SOURCE,
245 "name", _("FM Radio"),
246 "shell", shell,
247 "entry-type", entry_type,
248 "toolbar-path", "/FMRadioSourceToolBar",
249 NULL);
250 self->priv->tuner = g_object_ref (tuner);
251 rb_shell_register_entry_type_for_source (shell, RB_SOURCE (self),
252 entry_type);
253 g_object_unref (db);
254 return RB_SOURCE (self);
255 }
256
257 static void
258 rb_fm_radio_source_dispose (GObject *object)
259 {
260 RBFMRadioSource *self = (RBFMRadioSource *)object;
261
262 if (self->priv->playing_entry) {
263 rhythmdb_entry_unref (self->priv->playing_entry);
264 self->priv->playing_entry = NULL;
265 }
266
267 if (self->priv->db) {
268 g_object_unref (self->priv->db);
269 self->priv->db = NULL;
270 }
271
272 if (self->priv->tuner) {
273 g_object_unref (self->priv->tuner);
274 self->priv->tuner = NULL;
275 }
276
277 if (self->priv->action_group) {
278 g_object_unref (self->priv->action_group);
279 self->priv->action_group = NULL;
280 }
281
282 G_OBJECT_CLASS (rb_fm_radio_source_parent_class)->dispose (object);
283 }
284
285 static void
286 rb_fm_radio_source_do_query (RBFMRadioSource *self)
287 {
288 RhythmDBQueryModel *station_query_model;
289 GPtrArray *query;
290
291 query = rhythmdb_query_parse (self->priv->db,
292 RHYTHMDB_QUERY_PROP_EQUALS,
293 RHYTHMDB_PROP_TYPE,
294 self->priv->entry_type,
295 RHYTHMDB_QUERY_END);
296 station_query_model = rhythmdb_query_model_new_empty (self->priv->db);
297 rhythmdb_do_full_query_parsed (
298 self->priv->db, RHYTHMDB_QUERY_RESULTS (station_query_model),
299 query);
300 rhythmdb_query_free (query);
301 rb_entry_view_set_model (self->priv->stations, station_query_model);
302 g_object_set (self, "query-model", station_query_model, NULL);
303 g_object_unref (station_query_model);
304 }
305
306 static void
307 rb_fm_radio_source_songs_view_sort_order_changed (GObject *object, GParamSpec *pspec, RBFMRadioSource *self)
308 {
309 rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
310 }
311
312 static void
313 rb_fm_radio_source_songs_view_show_popup (RBEntryView *view,
314 gboolean over_entry,
315 RBFMRadioSource *self)
316 {
317 if (self == NULL)
318 return;
319
320 if (over_entry)
321 _rb_display_page_show_popup (RB_DISPLAY_PAGE (self), "/FMRadioViewPopup");
322 else
323 _rb_display_page_show_popup (RB_DISPLAY_PAGE (self), "/FMRadioSourcePopup");
324 }
325
326 void
327 rb_fm_radio_source_add_station (RBFMRadioSource *self, const char *frequency,
328 const char *title)
329 {
330 RhythmDBEntry *entry;
331 gchar *uri, *end = NULL;
332 GValue val = { 0, };
333
334 /* Check that the location is a double */
335 g_ascii_strtod (frequency, &end);
336 if (end == NULL || end[0] != '\0') {
337 rb_debug ("%s is not a frequency", frequency);
338 return;
339 }
340 uri = g_strconcat ("fmradio:", frequency, NULL);
341
342 entry = rhythmdb_entry_lookup_by_location (self->priv->db, uri);
343 if (entry) {
344 rb_debug ("uri %s already in db", uri);
345 g_free (uri);
346 return;
347 }
348
349 entry = rhythmdb_entry_new (self->priv->db, self->priv->entry_type,
350 uri);
351 g_free (uri);
352 if (!entry)
353 return;
354
355 g_value_init (&val, G_TYPE_STRING);
356 if (title)
357 g_value_set_static_string (&val, title);
358 else
359 g_value_set_static_string (&val, frequency);
360 rhythmdb_entry_set (self->priv->db, entry, RHYTHMDB_PROP_TITLE, &val);
361 g_value_unset (&val);
362
363 g_value_init (&val, G_TYPE_DOUBLE);
364 g_value_set_double (&val, 0.0);
365 rhythmdb_entry_set (self->priv->db, entry, RHYTHMDB_PROP_RATING, &val);
366 g_value_unset (&val);
367
368 rhythmdb_commit (self->priv->db);
369 }
370
371 static void
372 new_station_location_added (RBURIDialog *dialog, const char *frequency,
373 RBFMRadioSource *self)
374 {
375 rb_fm_radio_source_add_station (self, frequency, NULL);
376 }
377
378 static void
379 new_station_response_cb (GtkDialog *dialog, int response, gpointer meh)
380 {
381 gtk_widget_destroy (GTK_WIDGET (dialog));
382 }
383
384 static void
385 rb_fm_radio_source_cmd_new_station (GtkAction *action, RBFMRadioSource *self)
386 {
387 GtkWidget *dialog;
388
389 dialog = rb_uri_dialog_new (_("New FM Radio Station"),
390 _("Frequency of radio station"));
391 g_signal_connect_object (dialog, "location-added",
392 G_CALLBACK (new_station_location_added),
393 self, 0);
394 g_signal_connect (dialog, "response", G_CALLBACK (new_station_response_cb), NULL);
395 gtk_widget_show_all (dialog);
396 }
397
398 static void
399 playing_entry_changed (RBShellPlayer *player, RhythmDBEntry *entry,
400 RBFMRadioSource *self)
401 {
402 const gchar *location;
403 gdouble frequency;
404 gboolean was_playing = FALSE;
405
406 if (entry == self->priv->playing_entry)
407 return;
408
409 if (self->priv->playing_entry != NULL) {
410 rb_source_update_play_statistics (RB_SOURCE (self),
411 self->priv->db,
412 self->priv->playing_entry);
413 rhythmdb_entry_unref (self->priv->playing_entry);
414 self->priv->playing_entry = NULL;
415 was_playing = TRUE;
416 }
417
418 if (entry != NULL &&
419 rhythmdb_entry_get_entry_type (entry) == self->priv->entry_type) {
420 self->priv->playing_entry = rhythmdb_entry_ref (entry);
421 location = rhythmdb_entry_get_string (entry,
422 RHYTHMDB_PROP_LOCATION);
423 if (g_str_has_prefix (location, "fmradio:")) {
424 frequency = g_ascii_strtod(
425 &location[strlen("fmradio:")], NULL);
426 if (!was_playing)
427 rb_radio_tuner_set_mute (self->priv->tuner,
428 FALSE);
429 rb_radio_tuner_set_frequency (self->priv->tuner,
430 frequency);
431 }
432 } else {
433 if (was_playing)
434 rb_radio_tuner_set_mute (self->priv->tuner, TRUE);
435 }
436
437 }
438
439 static void
440 impl_delete (RBSource *source)
441 {
442 RBFMRadioSource *self = RB_FM_RADIO_SOURCE (source);
443 GList *selection, *tmp;
444
445 selection = rb_entry_view_get_selected_entries (self->priv->stations);
446 for (tmp = selection; tmp != NULL; tmp = tmp->next) {
447 RhythmDBEntry *entry = tmp->data;
448
449 rhythmdb_entry_delete (self->priv->db, entry);
450 rhythmdb_commit (self->priv->db);
451 rhythmdb_entry_unref (entry);
452 }
453 g_list_free (selection);
454 }
455
456 static gboolean
457 impl_show_popup (RBDisplayPage *page)
458 {
459 _rb_display_page_show_popup (page, "/FMRadioSourcePopup");
460 return TRUE;
461 }
462
463 static RBEntryView *
464 impl_get_entry_view (RBSource *source)
465 {
466 RBFMRadioSource *self = (RBFMRadioSource *)source;
467
468 return self->priv->stations;
469 }
470
471 void
472 _rb_fm_radio_source_register_type (GTypeModule *module)
473 {
474 rb_fm_radio_source_register_type (module);
475 rb_fm_radio_entry_type_register_type (module);
476 }