No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2002 Olivier Martin <olive.martin@gmail.com>
4 * Copyright (C) 2003 Colin Walters <walters@verbum.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * The Rhythmbox authors hereby grant permission for non-GPL compatible
12 * GStreamer plugins to be used and distributed together with GStreamer
13 * and Rhythmbox. This permission is above and beyond the permissions granted
14 * by the GPL license by which Rhythmbox is covered. If you modify this code
15 * you may extend this exception to your version of the code, but you are not
16 * obligated to do so. If you do not wish to do so, delete this exception
17 * statement from your version.
18 *
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
28 *
29 */
30
31 /*
32 * Yes, this code is ugly.
33 */
34
35 #include "config.h"
36
37 #include <string.h>
38 #include <time.h>
39 #include <math.h>
40
41 #define EPSILON 0.0001
42
43 #include <glib/gi18n.h>
44 #include <gtk/gtk.h>
45
46 #include "rhythmdb.h"
47 #include "rhythmdb-property-model.h"
48 #include "rb-song-info.h"
49 #include "rb-builder-helpers.h"
50 #include "rb-dialog.h"
51 #include "rb-rating.h"
52 #include "rb-source.h"
53 #include "rb-shell.h"
54 #include "rb-file-helpers.h"
55 #include "rb-util.h"
56
57 static void rb_song_info_class_init (RBSongInfoClass *klass);
58 static void rb_song_info_init (RBSongInfo *song_info);
59 static void rb_song_info_constructed (GObject *object);
60
61 static void rb_song_info_show (GtkWidget *widget);
62 static void rb_song_info_dispose (GObject *object);
63 static void rb_song_info_finalize (GObject *object);
64 static void rb_song_info_set_property (GObject *object,
65 guint prop_id,
66 const GValue *value,
67 GParamSpec *pspec);
68 static void rb_song_info_get_property (GObject *object,
69 guint prop_id,
70 GValue *value,
71 GParamSpec *pspec);
72 static void rb_song_info_response_cb (GtkDialog *dialog,
73 int response_id,
74 RBSongInfo *song_info);
75 static void rb_song_info_populate_dialog (RBSongInfo *song_info);
76 static void rb_song_info_populate_dialog_multiple (RBSongInfo *song_info);
77 static void rb_song_info_update_duration (RBSongInfo *song_info);
78 static void rb_song_info_update_location (RBSongInfo *song_info);
79 static void rb_song_info_update_filesize (RBSongInfo *song_info);
80 static void rb_song_info_update_play_count (RBSongInfo *song_info);
81 static void rb_song_info_update_last_played (RBSongInfo *song_info);
82 static void rb_song_info_update_bitrate (RBSongInfo *song_info);
83 static void rb_song_info_update_buttons (RBSongInfo *song_info);
84 static void rb_song_info_update_rating (RBSongInfo *song_info);
85 static void rb_song_info_update_year (RBSongInfo *song_info);
86 static void rb_song_info_update_date_added (RBSongInfo *song_info);
87 static void rb_song_info_update_playback_error (RBSongInfo *song_info);
88
89 static void rb_song_info_backward_clicked_cb (GtkWidget *button,
90 RBSongInfo *song_info);
91 static void rb_song_info_forward_clicked_cb (GtkWidget *button,
92 RBSongInfo *song_info);
93 static void rb_song_info_query_model_changed_cb (GObject *source,
94 GParamSpec *pspec,
95 RBSongInfo *song_info);
96 static void rb_song_info_base_query_model_changed_cb (GObject *source,
97 GParamSpec *pspec,
98 RBSongInfo *song_info);
99 static void rb_song_info_rated_cb (RBRating *rating,
100 double score,
101 RBSongInfo *song_info);
102 static void rb_song_info_mnemonic_cb (GtkWidget *target);
103 static void rb_song_info_sync_entries (RBSongInfo *dialog);
104
105 struct RBSongInfoPrivate
106 {
107 RhythmDB *db;
108 RBSource *source;
109 RBEntryView *entry_view;
110 RhythmDBQueryModel *query_model;
111 RhythmDBQueryModel *base_query_model;
112
113 /* information on the displayed song */
114 RhythmDBEntry *current_entry;
115 GList *selected_entries;
116
117 gboolean editable;
118
119 /* the dialog widgets */
120 GtkWidget *backward;
121 GtkWidget *forward;
122 GtkWidget *notebook;
123
124 GtkWidget *title;
125 GtkWidget *artist;
126 GtkWidget *album;
127 GtkWidget *album_artist;
128 GtkWidget *genre;
129 GtkWidget *track_cur;
130 GtkWidget *disc_cur;
131 GtkWidget *year;
132 GtkWidget *comment;
133 GtkTextBuffer *comment_buffer;
134 GtkWidget *playback_error_box;
135 GtkWidget *playback_error_label;
136 GtkWidget *bpm;
137
138 GtkWidget *artist_sortname;
139 GtkWidget *album_sortname;
140 GtkWidget *album_artist_sortname;
141
142 GtkWidget *bitrate;
143 GtkWidget *duration;
144 GtkWidget *name;
145 GtkWidget *location;
146 GtkWidget *filesize;
147 GtkWidget *date_added;
148 GtkWidget *play_count;
149 GtkWidget *last_played;
150 GtkWidget *rating;
151
152 RhythmDBPropertyModel* albums;
153 RhythmDBPropertyModel* artists;
154 RhythmDBPropertyModel* genres;
155 };
156
157 #define RB_SONG_INFO_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SONG_INFO, RBSongInfoPrivate))
158
159 /**
160 * SECTION:rb-song-info
161 * @short_description: song properties dialog
162 *
163 * Displays song properties and, if we know how to edit tags in the file,
164 * allows the user to edit them.
165 *
166 * This class has two modes. It can display and edit properties of a single
167 * entry, in which case it uses a #GtkNotebook to split the properties across
168 * 'basic' and 'details' pages, and it can display and edit properties of
169 * multiple entries at a time, in which case a smaller set of properties is
170 * displayed in a single set.
171 *
172 * In single-entry mode, it is possible to add extra pages to the #GtkNotebook
173 * widget in the dialog. The 'create-song-info' signal is emitted by the #RBShell
174 * object, allowing signal handlers to add pages by calling #rb_song_info_append_page.
175 * The lyrics plugin is currently the only place where this ability is used.
176 * In this mode, the dialog features 'back' and 'forward' buttons that move to the
177 * next or previous entries from the currently displayed track list.
178 *
179 * In multiple-entry mode, only the set of properties that can usefully be set
180 * across multiple entries at once are displayed.
181 *
182 * When the dialog is closed, any changes made will be applied to the entry (or entries)
183 * that were displayed in the dialog. For songs in the library, this will result
184 * in the song tags being updated on disk. For other entry types, this only updates
185 * the data store in the database.
186 */
187
188 enum
189 {
190 PRE_METADATA_CHANGE,
191 POST_METADATA_CHANGE,
192 LAST_SIGNAL
193 };
194
195 enum
196 {
197 PROP_0,
198 PROP_SOURCE,
199 PROP_ENTRY_VIEW,
200 PROP_CURRENT_ENTRY,
201 PROP_SELECTED_ENTRIES
202 };
203
204 static guint rb_song_info_signals[LAST_SIGNAL] = { 0 };
205
206 G_DEFINE_TYPE (RBSongInfo, rb_song_info, GTK_TYPE_DIALOG)
207
208 static void
209 rb_song_info_class_init (RBSongInfoClass *klass)
210 {
211 GObjectClass *object_class = G_OBJECT_CLASS (klass);
212 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
213
214 object_class->set_property = rb_song_info_set_property;
215 object_class->get_property = rb_song_info_get_property;
216 object_class->constructed = rb_song_info_constructed;
217
218 widget_class->show = rb_song_info_show;
219
220 /**
221 * RBSongInfo:source:
222 *
223 * The #RBSource that created the song properties window. Used to update
224 * for track list changes, and to find the sets of albums, artist, and genres
225 * to use for tag edit completion.
226 */
227 g_object_class_install_property (object_class,
228 PROP_SOURCE,
229 g_param_spec_object ("source",
230 "RBSource",
231 "RBSource object",
232 RB_TYPE_SOURCE,
233 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
234 /**
235 * RBSongInfo:entry-view:
236 *
237 * The #RBEntryView for the source that created the song properties window. Used
238 * find the set of selected entries, and to change the selection when the 'back' and
239 * 'forward' buttons are pressed.
240 */
241 g_object_class_install_property (object_class,
242 PROP_ENTRY_VIEW,
243 g_param_spec_object ("entry-view",
244 "RBEntryView",
245 "RBEntryView object",
246 RB_TYPE_ENTRY_VIEW,
247 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
248 /**
249 * RBSongInfo:current-entry:
250 *
251 * The #RhythmDBEntry that is currently being displayed. Will be NULL for
252 * multiple-entry song properties windows.
253 */
254 g_object_class_install_property (object_class,
255 PROP_CURRENT_ENTRY,
256 g_param_spec_boxed ("current-entry",
257 "RhythmDBEntry",
258 "RhythmDBEntry object",
259 RHYTHMDB_TYPE_ENTRY,
260 G_PARAM_READABLE));
261
262 /**
263 * RBSongInfo:selected-entries:
264 *
265 * The set of #RhythmDBEntry objects currently being displayed. Valid for both
266 * single-entry and multiple-entry song properties windows.
267 */
268 g_object_class_install_property (object_class,
269 PROP_SELECTED_ENTRIES,
270 g_param_spec_boxed ("selected-entries",
271 "selected entries",
272 "List of selected entries, if this is a multiple-entry dialog",
273 G_TYPE_ARRAY,
274 G_PARAM_READABLE));
275
276 object_class->dispose = rb_song_info_dispose;
277 object_class->finalize = rb_song_info_finalize;
278
279 /**
280 * RBSongInfo::pre-metadata-change:
281 * @song_info: the #RBSongInfo instance
282 * @entry: the #RhythmDBEntry being changed
283 *
284 * Emitted just before the changes made in the song properties window
285 * are applied to the database. This is only emitted in the single-entry
286 * case.
287 */
288 rb_song_info_signals[PRE_METADATA_CHANGE] =
289 g_signal_new ("pre-metadata-change",
290 G_OBJECT_CLASS_TYPE (object_class),
291 G_SIGNAL_RUN_LAST,
292 G_STRUCT_OFFSET (RBSongInfoClass, pre_metadata_change),
293 NULL, NULL,
294 g_cclosure_marshal_VOID__BOXED,
295 G_TYPE_NONE,
296 1,
297 RHYTHMDB_TYPE_ENTRY);
298
299 /**
300 * RBSongInfo::post-metadata-change:
301 * @song_info: the #RBSongInfo instance
302 * @entry: the #RhythmDBEntry that was changed
303 *
304 * Emitted just after changes have been applied to the database.
305 * Probably useless.
306 */
307 rb_song_info_signals[POST_METADATA_CHANGE] =
308 g_signal_new ("post-metadata-change",
309 G_OBJECT_CLASS_TYPE (object_class),
310 G_SIGNAL_RUN_LAST,
311 G_STRUCT_OFFSET (RBSongInfoClass, post_metadata_change),
312 NULL, NULL,
313 g_cclosure_marshal_VOID__BOXED,
314 G_TYPE_NONE,
315 1,
316 RHYTHMDB_TYPE_ENTRY);
317
318 g_type_class_add_private (klass, sizeof (RBSongInfoPrivate));
319 }
320
321 static void
322 rb_song_info_init (RBSongInfo *song_info)
323 {
324 /* create the dialog and some buttons backward - forward - close */
325 song_info->priv = RB_SONG_INFO_GET_PRIVATE (song_info);
326
327 g_signal_connect_object (G_OBJECT (song_info),
328 "response",
329 G_CALLBACK (rb_song_info_response_cb),
330 song_info, 0);
331
332 gtk_container_set_border_width (GTK_CONTAINER (song_info), 5);
333 gtk_window_set_resizable (GTK_WINDOW (song_info), TRUE);
334 gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (song_info))), 2);
335 }
336
337 static void
338 rb_song_info_show (GtkWidget *widget)
339 {
340 if (GTK_WIDGET_CLASS (rb_song_info_parent_class)->show)
341 GTK_WIDGET_CLASS (rb_song_info_parent_class)->show (widget);
342
343 rb_song_info_update_playback_error (RB_SONG_INFO (widget));
344 }
345
346 static void
347 rb_song_info_construct_single (RBSongInfo *song_info, GtkBuilder *builder, gboolean editable)
348 {
349 song_info->priv->backward = gtk_dialog_add_button (GTK_DIALOG (song_info),
350 GTK_STOCK_GO_BACK,
351 GTK_RESPONSE_NONE);
352
353 g_signal_connect_object (G_OBJECT (song_info->priv->backward),
354 "clicked",
355 G_CALLBACK (rb_song_info_backward_clicked_cb),
356 song_info, 0);
357
358 song_info->priv->forward = gtk_dialog_add_button (GTK_DIALOG (song_info),
359 GTK_STOCK_GO_FORWARD,
360 GTK_RESPONSE_NONE);
361
362 g_signal_connect_object (G_OBJECT (song_info->priv->forward),
363 "clicked",
364 G_CALLBACK (rb_song_info_forward_clicked_cb),
365 song_info, 0);
366
367 gtk_window_set_title (GTK_WINDOW (song_info), _("Song Properties"));
368
369 /* get the widgets from the XML */
370 song_info->priv->notebook = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_vbox"));
371 song_info->priv->title = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_title"));
372 song_info->priv->track_cur = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_track_cur"));
373 song_info->priv->bitrate = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_bitrate"));
374 song_info->priv->duration = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_duration"));
375 song_info->priv->bpm = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_bpm"));
376 song_info->priv->location = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_location"));
377 song_info->priv->filesize = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_filesize"));
378 song_info->priv->date_added = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_dateadded"));
379 song_info->priv->play_count = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_playcount"));
380 song_info->priv->last_played = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_lastplayed"));
381 song_info->priv->name = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_name"));
382 song_info->priv->comment = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_comment"));
383 song_info->priv->comment_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (song_info->priv->comment));
384
385 rb_builder_boldify_label (builder, "title_label");
386 rb_builder_boldify_label (builder, "trackn_label");
387 rb_builder_boldify_label (builder, "name_label");
388 rb_builder_boldify_label (builder, "location_label");
389 rb_builder_boldify_label (builder, "filesize_label");
390 rb_builder_boldify_label (builder, "date_added_label");
391 rb_builder_boldify_label (builder, "last_played_label");
392 rb_builder_boldify_label (builder, "play_count_label");
393 rb_builder_boldify_label (builder, "duration_label");
394 rb_builder_boldify_label (builder, "bitrate_label");
395 rb_builder_boldify_label (builder, "bpm_label");
396 rb_builder_boldify_label (builder, "comment_label");
397
398 /* whenever you press a mnemonic, the associated GtkEntry's text gets highlighted */
399 g_signal_connect_object (G_OBJECT (song_info->priv->title),
400 "mnemonic-activate",
401 G_CALLBACK (rb_song_info_mnemonic_cb),
402 NULL, 0);
403 g_signal_connect_object (G_OBJECT (song_info->priv->track_cur),
404 "mnemonic-activate",
405 G_CALLBACK (rb_song_info_mnemonic_cb),
406 NULL, 0);
407 g_signal_connect_object (G_OBJECT (song_info->priv->comment),
408 "mnemonic-activate",
409 G_CALLBACK (rb_song_info_mnemonic_cb),
410 NULL, 0);
411
412 gtk_editable_set_editable (GTK_EDITABLE (song_info->priv->title), editable);
413 gtk_editable_set_editable (GTK_EDITABLE (song_info->priv->track_cur), editable);
414 gtk_text_view_set_editable (GTK_TEXT_VIEW (song_info->priv->comment), editable);
415
416 /* default focus */
417 gtk_widget_grab_focus (song_info->priv->title);
418 }
419
420 static void
421 rb_song_info_construct_multiple (RBSongInfo *song_info, GtkBuilder *builder, gboolean editable)
422 {
423 gtk_window_set_title (GTK_WINDOW (song_info),
424 _("Multiple Song Properties"));
425 gtk_widget_grab_focus (song_info->priv->artist);
426
427 song_info->priv->notebook = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_notebook"));
428 }
429
430 static void
431 rb_song_info_add_completion (GtkEntry *entry, RhythmDBPropertyModel *propmodel)
432 {
433 GtkEntryCompletion* completion;
434
435 completion = gtk_entry_completion_new();
436 gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (propmodel));
437 gtk_entry_completion_set_text_column (completion, RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE);
438 gtk_entry_set_completion (entry, completion);
439 g_object_unref (completion);
440 }
441
442 static void
443 rb_song_info_constructed (GObject *object)
444 {
445 RBSongInfo *song_info;
446 GList *selected_entries;
447 GList *tem;
448 gboolean editable = TRUE;
449 RBShell *shell;
450 AtkObject *lobj, *robj;
451 GtkBuilder *builder;
452 GtkWidget *content_area;
453
454 RB_CHAIN_GOBJECT_METHOD (rb_song_info_parent_class, constructed, object);
455
456 song_info = RB_SONG_INFO (object);
457
458 selected_entries = rb_entry_view_get_selected_entries (song_info->priv->entry_view);
459
460 g_return_if_fail (selected_entries != NULL);
461
462 for (tem = selected_entries; tem; tem = tem->next) {
463 if (!rhythmdb_entry_can_sync_metadata (selected_entries->data)) {
464 editable = FALSE;
465 break;
466 }
467 }
468
469 song_info->priv->editable = editable;
470
471 if (selected_entries->next == NULL) {
472 song_info->priv->current_entry = selected_entries->data;
473 song_info->priv->selected_entries = NULL;
474
475 g_list_foreach (selected_entries, (GFunc)rhythmdb_entry_unref, NULL);
476 g_list_free (selected_entries);
477 } else {
478 song_info->priv->current_entry = NULL;
479 song_info->priv->selected_entries = selected_entries;
480 }
481
482 content_area = gtk_dialog_get_content_area (GTK_DIALOG (song_info));
483 if (song_info->priv->current_entry) {
484 builder = rb_builder_load ("song-info.ui", song_info);
485 gtk_container_add (GTK_CONTAINER (content_area),
486 GTK_WIDGET (gtk_builder_get_object (builder, "song_info_vbox")));
487 } else {
488 builder = rb_builder_load ("song-info-multiple.ui", song_info);
489 gtk_container_add (GTK_CONTAINER (content_area),
490 GTK_WIDGET (gtk_builder_get_object (builder, "song_info_notebook")));
491 }
492
493 song_info->priv->artist = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_artist"));
494 song_info->priv->album = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_album"));
495 song_info->priv->album_artist = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_album_artist"));
496 song_info->priv->genre = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_genre"));
497 song_info->priv->year = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_year"));
498 song_info->priv->playback_error_box = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_error_box"));
499 song_info->priv->playback_error_label = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_error_label"));
500 song_info->priv->disc_cur = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_disc_cur"));
501
502 song_info->priv->artist_sortname = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_artist_sortname"));
503 song_info->priv->album_sortname = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_album_sortname"));
504 song_info->priv->album_artist_sortname = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_album_artist_sortname"));
505
506 rb_song_info_add_completion (GTK_ENTRY (song_info->priv->genre), song_info->priv->genres);
507 rb_song_info_add_completion (GTK_ENTRY (song_info->priv->artist), song_info->priv->artists);
508 rb_song_info_add_completion (GTK_ENTRY (song_info->priv->album), song_info->priv->albums);
509
510 rb_builder_boldify_label (builder, "album_label");
511 rb_builder_boldify_label (builder, "artist_label");
512 rb_builder_boldify_label (builder, "album_artist_label");
513 rb_builder_boldify_label (builder, "genre_label");
514 rb_builder_boldify_label (builder, "year_label");
515 rb_builder_boldify_label (builder, "rating_label");
516 rb_builder_boldify_label (builder, "discn_label");
517 rb_builder_boldify_label (builder, "artist_sortname_label");
518 rb_builder_boldify_label (builder, "album_sortname_label");
519 rb_builder_boldify_label (builder, "album_artist_sortname_label");
520
521 g_signal_connect_object (G_OBJECT (song_info->priv->artist),
522 "mnemonic-activate",
523 G_CALLBACK (rb_song_info_mnemonic_cb),
524 NULL, 0);
525 g_signal_connect_object (G_OBJECT (song_info->priv->album),
526 "mnemonic-activate",
527 G_CALLBACK (rb_song_info_mnemonic_cb),
528 NULL, 0);
529 g_signal_connect_object (G_OBJECT (song_info->priv->album_artist),
530 "mnemonic-activate",
531 G_CALLBACK (rb_song_info_mnemonic_cb),
532 NULL, 0);
533 g_signal_connect_object (G_OBJECT (song_info->priv->genre),
534 "mnemonic-activate",
535 G_CALLBACK (rb_song_info_mnemonic_cb),
536 NULL, 0);
537 g_signal_connect_object (G_OBJECT (song_info->priv->year),
538 "mnemonic-activate",
539 G_CALLBACK (rb_song_info_mnemonic_cb),
540 NULL, 0);
541 g_signal_connect_object (G_OBJECT (song_info->priv->disc_cur),
542 "mnemonic-activate",
543 G_CALLBACK (rb_song_info_mnemonic_cb),
544 NULL, 0);
545 g_signal_connect_object (G_OBJECT (song_info->priv->artist_sortname),
546 "mnemonic-activate",
547 G_CALLBACK (rb_song_info_mnemonic_cb),
548 NULL, 0);
549 g_signal_connect_object (G_OBJECT (song_info->priv->album_sortname),
550 "mnemonic-activate",
551 G_CALLBACK (rb_song_info_mnemonic_cb),
552 NULL, 0);
553 g_signal_connect_object (G_OBJECT (song_info->priv->album_artist_sortname),
554 "mnemonic-activate",
555 G_CALLBACK (rb_song_info_mnemonic_cb),
556 NULL, 0);
557
558 /* this widget has to be customly created */
559 song_info->priv->rating = GTK_WIDGET (rb_rating_new ());
560 g_signal_connect_object (song_info->priv->rating, "rated",
561 G_CALLBACK (rb_song_info_rated_cb),
562 G_OBJECT (song_info), 0);
563 gtk_container_add (GTK_CONTAINER (gtk_builder_get_object (builder, "song_info_rating_container")),
564 song_info->priv->rating);
565 g_object_set (gtk_builder_get_object (builder, "rating_label"), "mnemonic-widget", song_info->priv->rating, NULL);
566
567 /* add relationship between the rating label and the rating widget */
568 lobj = gtk_widget_get_accessible (GTK_WIDGET (gtk_builder_get_object (builder, "rating_label")));
569 robj = gtk_widget_get_accessible (song_info->priv->rating);
570
571 atk_object_add_relationship (lobj, ATK_RELATION_LABEL_FOR, robj);
572 atk_object_add_relationship (robj, ATK_RELATION_LABELLED_BY, lobj);
573
574 gtk_editable_set_editable (GTK_EDITABLE (song_info->priv->artist), editable);
575 gtk_editable_set_editable (GTK_EDITABLE (song_info->priv->album), editable);
576 gtk_editable_set_editable (GTK_EDITABLE (song_info->priv->album_artist), editable);
577 gtk_editable_set_editable (GTK_EDITABLE (song_info->priv->genre), editable);
578 gtk_editable_set_editable (GTK_EDITABLE (song_info->priv->year), editable);
579 gtk_editable_set_editable (GTK_EDITABLE (song_info->priv->disc_cur), editable);
580
581 /* Finish construction */
582 if (song_info->priv->current_entry) {
583
584 rb_song_info_construct_single (song_info, builder, editable);
585 rb_song_info_populate_dialog (song_info);
586 } else {
587 rb_song_info_construct_multiple (song_info, builder, editable);
588 rb_song_info_populate_dialog_multiple (song_info);
589 }
590 g_object_get (G_OBJECT (song_info->priv->source), "shell", &shell, NULL);
591 g_signal_emit_by_name (G_OBJECT (shell), "create_song_info", song_info, (song_info->priv->current_entry == NULL));
592 g_object_unref (G_OBJECT (shell));
593
594 gtk_dialog_add_button (GTK_DIALOG (song_info),
595 GTK_STOCK_CLOSE,
596 GTK_RESPONSE_CLOSE);
597
598 gtk_dialog_set_default_response (GTK_DIALOG (song_info),
599 GTK_RESPONSE_CLOSE);
600
601 g_object_unref (builder);
602 }
603
604 static void
605 rb_song_info_dispose (GObject *object)
606 {
607 RBSongInfo *song_info;
608
609 g_return_if_fail (object != NULL);
610 g_return_if_fail (RB_IS_SONG_INFO (object));
611
612 song_info = RB_SONG_INFO (object);
613
614 g_return_if_fail (song_info->priv != NULL);
615
616 if (song_info->priv->albums != NULL) {
617 g_object_unref (song_info->priv->albums);
618 song_info->priv->albums = NULL;
619 }
620 if (song_info->priv->artists != NULL) {
621 g_object_unref (song_info->priv->artists);
622 song_info->priv->artists = NULL;
623 }
624 if (song_info->priv->genres != NULL) {
625 g_object_unref (song_info->priv->genres);
626 song_info->priv->genres = NULL;
627 }
628
629 if (song_info->priv->db != NULL) {
630 g_object_unref (song_info->priv->db);
631 song_info->priv->db = NULL;
632 }
633 if (song_info->priv->source != NULL) {
634 g_signal_handlers_disconnect_by_func (song_info->priv->source,
635 G_CALLBACK (rb_song_info_query_model_changed_cb),
636 song_info);
637 g_signal_handlers_disconnect_by_func (song_info->priv->source,
638 G_CALLBACK (rb_song_info_base_query_model_changed_cb),
639 song_info);
640 g_object_unref (song_info->priv->source);
641 song_info->priv->source = NULL;
642 }
643 if (song_info->priv->query_model != NULL) {
644 g_object_unref (song_info->priv->query_model);
645 song_info->priv->query_model = NULL;
646 }
647
648 G_OBJECT_CLASS (rb_song_info_parent_class)->dispose (object);
649 }
650
651 static void
652 rb_song_info_finalize (GObject *object)
653 {
654 RBSongInfo *song_info;
655
656 g_return_if_fail (object != NULL);
657 g_return_if_fail (RB_IS_SONG_INFO (object));
658
659 song_info = RB_SONG_INFO (object);
660
661 g_return_if_fail (song_info->priv != NULL);
662
663 if (song_info->priv->selected_entries != NULL) {
664 g_list_foreach (song_info->priv->selected_entries, (GFunc)rhythmdb_entry_unref, NULL);
665 g_list_free (song_info->priv->selected_entries);
666 }
667
668 G_OBJECT_CLASS (rb_song_info_parent_class)->finalize (object);
669 }
670
671 static void
672 rb_song_info_set_source_internal (RBSongInfo *song_info,
673 RBSource *source)
674 {
675 if (song_info->priv->source != NULL) {
676 g_signal_handlers_disconnect_by_func (song_info->priv->source,
677 rb_song_info_query_model_changed_cb,
678 song_info);
679 g_signal_handlers_disconnect_by_func (song_info->priv->source,
680 rb_song_info_base_query_model_changed_cb,
681 song_info);
682 g_object_unref (song_info->priv->source);
683 g_object_unref (song_info->priv->query_model);
684 g_object_unref (song_info->priv->db);
685 }
686
687 song_info->priv->source = source;
688
689 g_object_ref (song_info->priv->source);
690
691 g_object_get (G_OBJECT (song_info->priv->source), "query-model", &song_info->priv->query_model, NULL);
692
693 g_signal_connect_object (G_OBJECT (song_info->priv->source),
694 "notify::query-model",
695 G_CALLBACK (rb_song_info_query_model_changed_cb),
696 song_info, 0);
697 g_signal_connect_object (G_OBJECT (song_info->priv->source),
698 "notify::base-query-model",
699 G_CALLBACK (rb_song_info_base_query_model_changed_cb),
700 song_info, 0);
701
702 g_object_get (G_OBJECT (song_info->priv->query_model), "db", &song_info->priv->db, NULL);
703
704 rb_song_info_base_query_model_changed_cb (G_OBJECT (song_info->priv->source), NULL, song_info);
705 }
706
707 static void
708 rb_song_info_set_property (GObject *object,
709 guint prop_id,
710 const GValue *value,
711 GParamSpec *pspec)
712 {
713 RBSongInfo *song_info = RB_SONG_INFO (object);
714
715 switch (prop_id) {
716 case PROP_SOURCE:
717 rb_song_info_set_source_internal (song_info, g_value_get_object (value));
718 break;
719 case PROP_ENTRY_VIEW:
720 song_info->priv->entry_view = g_value_get_object (value);
721 break;
722 default:
723 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
724 break;
725 }
726 }
727
728 static void
729 rb_song_info_get_property (GObject *object,
730 guint prop_id,
731 GValue *value,
732 GParamSpec *pspec)
733 {
734 RBSongInfo *song_info = RB_SONG_INFO (object);
735
736 switch (prop_id) {
737 case PROP_SOURCE:
738 g_value_set_object (value, song_info->priv->source);
739 break;
740 case PROP_ENTRY_VIEW:
741 g_value_set_object (value, song_info->priv->entry_view);
742 break;
743 case PROP_CURRENT_ENTRY:
744 g_value_set_boxed (value, song_info->priv->current_entry);
745 break;
746 case PROP_SELECTED_ENTRIES:
747 if (song_info->priv->selected_entries) {
748 GArray *value_array;
749 GValue entry_value = { 0, };
750 GList *entry_list;
751
752 value_array = g_array_sized_new (FALSE, TRUE, sizeof (GValue), 1);
753 g_array_set_clear_func (value_array, (GDestroyNotify) g_value_unset);
754 g_value_init (&entry_value, RHYTHMDB_TYPE_ENTRY);
755 for (entry_list = song_info->priv->selected_entries; entry_list; entry_list = entry_list->next) {
756 g_value_set_boxed (&entry_value, entry_list->data);
757 g_array_append_val (value_array, entry_value);
758 }
759 g_value_unset (&entry_value);
760 g_value_take_boxed (value, value_array);
761 } else {
762 g_value_set_boxed (value, NULL);
763 }
764 break;
765 default:
766 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
767 break;
768 }
769 }
770
771 /**
772 * rb_song_info_new:
773 * @source: #RBSource creating the song properties window
774 * @entry_view: the #RBEntryView to get selection data from
775 *
776 * Creates a new #RBSongInfo for the selected entry or entries in
777 * the specified entry view.
778 *
779 * Return value: the new song properties window
780 */
781 GtkWidget *
782 rb_song_info_new (RBSource *source, RBEntryView *entry_view)
783 {
784 RBSongInfo *song_info;
785
786 g_return_val_if_fail (RB_IS_SOURCE (source), NULL);
787 if (entry_view == NULL) {
788 entry_view = rb_source_get_entry_view (source);
789 if (entry_view == NULL) {
790 return NULL;
791 }
792 }
793
794 if (rb_entry_view_have_selection (entry_view) == FALSE)
795 return NULL;
796
797 /* create the dialog */
798 song_info = g_object_new (RB_TYPE_SONG_INFO,
799 "source", source,
800 "entry-view", entry_view,
801 NULL);
802
803 g_return_val_if_fail (song_info->priv != NULL, NULL);
804
805 return GTK_WIDGET (song_info);
806 }
807
808 /**
809 * rb_song_info_append_page:
810 * @info: a #RBSongInfo
811 * @title: the title of the new page
812 * @page: the page #GtkWidget
813 *
814 * Adds a new page to the song properties window. Should be called
815 * in a handler connected to the #RBShell 'create-song-info' signal.
816 *
817 * Return value: the page number
818 */
819 guint
820 rb_song_info_append_page (RBSongInfo *info, const char *title, GtkWidget *page)
821 {
822 GtkWidget *label;
823 guint page_num;
824
825 label = gtk_label_new (title);
826 page_num = gtk_notebook_append_page (GTK_NOTEBOOK (info->priv->notebook),
827 page,
828 label);
829 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (info->priv->notebook), TRUE);
830
831 return page_num;
832 }
833
834 typedef void (*RBSongInfoSelectionFunc)(RBSongInfo *info,
835 RhythmDBEntry *entry,
836 void *data);
837
838 static void
839 rb_song_info_selection_for_each (RBSongInfo *info, RBSongInfoSelectionFunc func,
840 void *data)
841 {
842 if (info->priv->current_entry)
843 func (info, info->priv->current_entry, data);
844 else {
845 GList *tem;
846 for (tem = info->priv->selected_entries; tem ; tem = tem->next)
847 func (info, tem->data, data);
848 }
849 }
850
851 static void
852 rb_song_info_response_cb (GtkDialog *dialog,
853 int response_id,
854 RBSongInfo *song_info)
855 {
856 if (response_id == GTK_RESPONSE_CLOSE) {
857 rb_song_info_sync_entries (RB_SONG_INFO (dialog));
858 gtk_widget_destroy (GTK_WIDGET (dialog));
859 }
860 }
861
862 static void
863 rb_song_info_set_entry_rating (RBSongInfo *info,
864 RhythmDBEntry *entry,
865 void *data)
866 {
867 GValue value = {0, };
868 double trouble = *((double*) data);
869
870 /* set the new value for the song */
871 g_value_init (&value, G_TYPE_DOUBLE);
872 g_value_set_double (&value, trouble);
873 rhythmdb_entry_set (info->priv->db, entry, RHYTHMDB_PROP_RATING, &value);
874 g_value_unset (&value);
875 }
876
877 static void
878 rb_song_info_rated_cb (RBRating *rating,
879 double score,
880 RBSongInfo *song_info)
881 {
882 g_return_if_fail (RB_IS_RATING (rating));
883 g_return_if_fail (RB_IS_SONG_INFO (song_info));
884 g_return_if_fail (score >= 0 && score <= 5 );
885
886 rb_song_info_selection_for_each (song_info,
887 rb_song_info_set_entry_rating,
888 &score);
889 rhythmdb_commit (song_info->priv->db);
890
891 g_object_set (G_OBJECT (song_info->priv->rating),
892 "rating", score,
893 NULL);
894 }
895
896 static void
897 rb_song_info_mnemonic_cb (GtkWidget *target)
898 {
899 g_return_if_fail (GTK_IS_EDITABLE (target) || GTK_IS_TEXT_VIEW (target));
900
901 gtk_widget_grab_focus (target);
902
903 if (GTK_IS_EDITABLE (target)) {
904 gtk_editable_select_region (GTK_EDITABLE (target), 0, -1);
905 } else { /* GtkTextViews need special treatment */
906 g_signal_emit_by_name (G_OBJECT (target), "select-all");
907 }
908 }
909
910 static void
911 rb_song_info_populate_num_field (GtkEntry *field, gulong num)
912 {
913 char *tmp;
914 if (num > 0)
915 tmp = g_strdup_printf ("%.2ld", num);
916 else
917 tmp = g_strdup (_("Unknown"));
918 gtk_entry_set_text (field, tmp);
919 g_free (tmp);
920 }
921
922 static void
923 rb_song_info_populate_dnum_field (GtkEntry *field, gdouble num)
924 {
925 char *tmp;
926 if (num > 0)
927 tmp = g_strdup_printf ("%.2f", num);
928 else
929 tmp = g_strdup (_("Unknown"));
930 gtk_entry_set_text (field, tmp);
931 g_free (tmp);
932 }
933
934 static void
935 rb_song_info_populate_dialog_multiple (RBSongInfo *song_info)
936 {
937 gboolean mixed_artists = FALSE;
938 gboolean mixed_albums = FALSE;
939 gboolean mixed_album_artists = FALSE;
940 gboolean mixed_genres = FALSE;
941 gboolean mixed_years = FALSE;
942 gboolean mixed_disc_numbers = FALSE;
943 gboolean mixed_ratings = FALSE;
944 gboolean mixed_artist_sortnames = FALSE;
945 gboolean mixed_album_sortnames = FALSE;
946 gboolean mixed_album_artist_sortnames = FALSE;
947 const char *artist = NULL;
948 const char *album = NULL;
949 const char *album_artist = NULL;
950 const char *genre = NULL;
951 int year = 0;
952 int disc_number = 0;
953 double rating = 0.0; /* Zero is used for both "unrated" and "mixed ratings" too */
954 const char *artist_sortname = NULL;
955 const char *album_sortname = NULL;
956 const char *album_artist_sortname = NULL;
957 GList *l;
958
959 g_assert (song_info->priv->selected_entries);
960
961 for (l = song_info->priv->selected_entries; l != NULL; l = g_list_next (l)) {
962 RhythmDBEntry *entry;
963 const char *entry_artist;
964 const char *entry_album;
965 const char *entry_album_artist;
966 const char *entry_genre;
967 int entry_year;
968 int entry_disc_number;
969 double entry_rating;
970 const char *entry_artist_sortname;
971 const char *entry_album_sortname;
972 const char *entry_album_artist_sortname;
973
974 entry = (RhythmDBEntry*)l->data;
975 entry_artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
976 entry_album = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
977 entry_album_artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
978 entry_genre = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE);
979 entry_year = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_YEAR);
980 entry_disc_number = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER);
981 entry_rating = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING);
982 entry_artist_sortname = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST_SORTNAME);
983 entry_album_sortname = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_SORTNAME);
984 entry_album_artist_sortname = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME);
985
986 /* grab first valid values */
987 if (artist == NULL)
988 artist = entry_artist;
989 if (album == NULL)
990 album = entry_album;
991 if (album_artist == NULL)
992 album_artist = entry_album_artist;
993 if (genre == NULL)
994 genre = entry_genre;
995 if (year == 0)
996 year = entry_year;
997 if (disc_number == 0)
998 disc_number = entry_disc_number;
999 if (fabs(rating) < EPSILON)
1000 rating = entry_rating;
1001 if (artist_sortname == NULL)
1002 artist_sortname = entry_artist_sortname;
1003 if (album_sortname == NULL)
1004 album_sortname = entry_album_sortname;
1005 if (album_artist_sortname == NULL)
1006 album_artist_sortname = entry_album_artist_sortname;
1007
1008 /* locate mixed values */
1009 if (artist != entry_artist)
1010 mixed_artists = TRUE;
1011 if (album != entry_album)
1012 mixed_albums = TRUE;
1013 if (album_artist != entry_album_artist)
1014 mixed_album_artists = TRUE;
1015 if (genre != entry_genre)
1016 mixed_genres = TRUE;
1017 if (year != entry_year)
1018 mixed_years = TRUE;
1019 if (disc_number != entry_disc_number)
1020 mixed_disc_numbers = TRUE;
1021 if (fabs(rating - entry_rating) >= EPSILON)
1022 mixed_ratings = TRUE;
1023 if (artist_sortname != entry_artist_sortname)
1024 mixed_artist_sortnames = TRUE;
1025 if (album_sortname != entry_album_sortname)
1026 mixed_album_sortnames = TRUE;
1027 if (album_artist_sortname != entry_album_artist_sortname)
1028 mixed_album_artist_sortnames = TRUE;
1029
1030 /* don't continue search if everything is mixed */
1031 if (mixed_artists && mixed_albums && mixed_genres &&
1032 mixed_years && mixed_disc_numbers && mixed_ratings &&
1033 mixed_artist_sortnames && mixed_album_sortnames)
1034 break;
1035 }
1036
1037 if (!mixed_artists && artist != NULL)
1038 gtk_entry_set_text (GTK_ENTRY (song_info->priv->artist), artist);
1039 if (!mixed_albums && album != NULL)
1040 gtk_entry_set_text (GTK_ENTRY (song_info->priv->album), album);
1041 if (!mixed_album_artists && album_artist != NULL)
1042 gtk_entry_set_text (GTK_ENTRY (song_info->priv->album_artist), album_artist);
1043 if (!mixed_genres && genre != NULL)
1044 gtk_entry_set_text (GTK_ENTRY (song_info->priv->genre), genre);
1045 if (!mixed_years && year != 0)
1046 rb_song_info_populate_num_field (GTK_ENTRY (song_info->priv->year), year);
1047 if (!mixed_disc_numbers && disc_number != 0)
1048 rb_song_info_populate_num_field (GTK_ENTRY (song_info->priv->disc_cur), disc_number);
1049 if (!mixed_ratings && fabs(rating) >= EPSILON)
1050 g_object_set (G_OBJECT (song_info->priv->rating), "rating", rating, NULL);
1051 if (!mixed_artist_sortnames && artist_sortname != NULL)
1052 gtk_entry_set_text (GTK_ENTRY (song_info->priv->artist_sortname), artist_sortname);
1053 if (!mixed_album_sortnames && album_sortname != NULL)
1054 gtk_entry_set_text (GTK_ENTRY (song_info->priv->album_sortname), album_sortname);
1055 if (!mixed_album_artist_sortnames && album_artist_sortname != NULL)
1056 gtk_entry_set_text (GTK_ENTRY (song_info->priv->album_artist_sortname), album_artist_sortname);
1057 }
1058
1059 static void
1060 rb_song_info_populate_dialog (RBSongInfo *song_info)
1061 {
1062 const char *text;
1063 char *tmp;
1064 gulong num;
1065 gdouble dnum;
1066
1067 g_assert (song_info->priv->current_entry);
1068
1069 /* update the buttons sensitivity */
1070 rb_song_info_update_buttons (song_info);
1071
1072 text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_TITLE);
1073 gtk_entry_set_text (GTK_ENTRY (song_info->priv->title), text);
1074
1075 tmp = g_strdup_printf (_("%s Properties"), text);
1076 gtk_window_set_title (GTK_WINDOW (song_info), tmp);
1077 g_free (tmp);
1078
1079 text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_ARTIST);
1080 gtk_entry_set_text (GTK_ENTRY (song_info->priv->artist), text);
1081 text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_ALBUM);
1082 gtk_entry_set_text (GTK_ENTRY (song_info->priv->album), text);
1083 text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_ALBUM_ARTIST);
1084 gtk_entry_set_text (GTK_ENTRY (song_info->priv->album_artist), text);
1085 text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_GENRE);
1086 gtk_entry_set_text (GTK_ENTRY (song_info->priv->genre), text);
1087
1088 num = rhythmdb_entry_get_ulong (song_info->priv->current_entry, RHYTHMDB_PROP_TRACK_NUMBER);
1089 rb_song_info_populate_num_field (GTK_ENTRY (song_info->priv->track_cur), num);
1090 num = rhythmdb_entry_get_ulong (song_info->priv->current_entry, RHYTHMDB_PROP_DISC_NUMBER);
1091 rb_song_info_populate_num_field (GTK_ENTRY (song_info->priv->disc_cur), num);
1092 dnum = rhythmdb_entry_get_double (song_info->priv->current_entry, RHYTHMDB_PROP_BPM);
1093 rb_song_info_populate_dnum_field (GTK_ENTRY (song_info->priv->bpm), dnum);
1094 text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_COMMENT);
1095 gtk_text_buffer_set_text (song_info->priv->comment_buffer, text, -1);
1096
1097 rb_song_info_update_duration (song_info);
1098 rb_song_info_update_location (song_info);
1099 rb_song_info_update_filesize (song_info);
1100 rb_song_info_update_date_added (song_info);
1101 rb_song_info_update_play_count (song_info);
1102 rb_song_info_update_last_played (song_info);
1103 rb_song_info_update_bitrate (song_info);
1104 rb_song_info_update_rating (song_info);
1105 rb_song_info_update_year (song_info);
1106 rb_song_info_update_playback_error (song_info);
1107
1108 text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_ARTIST_SORTNAME);
1109 gtk_entry_set_text (GTK_ENTRY (song_info->priv->artist_sortname), text);
1110 text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_ALBUM_SORTNAME);
1111 gtk_entry_set_text (GTK_ENTRY (song_info->priv->album_sortname), text);
1112 text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME);
1113 gtk_entry_set_text (GTK_ENTRY (song_info->priv->album_artist_sortname), text);
1114 }
1115
1116 static void
1117 rb_song_info_update_playback_error (RBSongInfo *song_info)
1118 {
1119 char *message = NULL;
1120
1121 if (!song_info->priv->current_entry)
1122 return;
1123
1124 message = rhythmdb_entry_dup_string (song_info->priv->current_entry, RHYTHMDB_PROP_PLAYBACK_ERROR);
1125
1126 if (message) {
1127 gtk_label_set_text (GTK_LABEL (song_info->priv->playback_error_label),
1128 message);
1129 gtk_widget_show (song_info->priv->playback_error_box);
1130 } else {
1131 gtk_label_set_text (GTK_LABEL (song_info->priv->playback_error_label),
1132 "No errors");
1133 gtk_widget_hide (song_info->priv->playback_error_box);
1134 }
1135
1136 g_free (message);
1137 }
1138
1139 static void
1140 rb_song_info_update_bitrate (RBSongInfo *song_info)
1141 {
1142 char *tmp;
1143 gulong bitrate;
1144
1145 bitrate = rhythmdb_entry_get_ulong (song_info->priv->current_entry, RHYTHMDB_PROP_BITRATE);
1146
1147 if (rhythmdb_entry_is_lossless (song_info->priv->current_entry)) {
1148 tmp = g_strdup (_("Lossless"));
1149 } else if (bitrate == 0) {
1150 tmp = g_strdup (_("Unknown"));
1151 } else {
1152 tmp = g_strdup_printf (_("%lu kbps"), bitrate);
1153 }
1154
1155 gtk_label_set_text (GTK_LABEL (song_info->priv->bitrate),
1156 tmp);
1157 g_free (tmp);
1158 }
1159
1160 static void
1161 rb_song_info_update_duration (RBSongInfo *song_info)
1162 {
1163 char *text = NULL;
1164 long duration = 0;
1165 int minutes, seconds;
1166 duration = rhythmdb_entry_get_ulong (song_info->priv->current_entry, RHYTHMDB_PROP_DURATION);
1167 minutes = duration / 60;
1168 seconds = duration % 60;
1169 text = g_strdup_printf ("%d:%02d", minutes, seconds);
1170 gtk_label_set_text (GTK_LABEL (song_info->priv->duration), text);
1171 g_free (text);
1172 }
1173
1174 static void
1175 rb_song_info_update_filesize (RBSongInfo *song_info)
1176 {
1177 char *text = NULL;
1178 guint64 filesize = 0;
1179 filesize = rhythmdb_entry_get_uint64 (song_info->priv->current_entry, RHYTHMDB_PROP_FILE_SIZE);
1180 text = g_format_size (filesize);
1181 gtk_label_set_text (GTK_LABEL (song_info->priv->filesize), text);
1182 g_free (text);
1183 }
1184
1185 static void
1186 rb_song_info_update_location (RBSongInfo *song_info)
1187 {
1188 const char *text;
1189
1190 g_return_if_fail (song_info != NULL);
1191
1192 text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_LOCATION);
1193
1194 if (text != NULL) {
1195 char *tmp;
1196 char *tmp_utf8;
1197 char *basename;
1198
1199 basename = g_path_get_basename (text);
1200 tmp = g_uri_unescape_string (basename, NULL);
1201 g_free (basename);
1202 tmp_utf8 = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
1203 g_free (tmp);
1204 tmp = NULL;
1205
1206 if (tmp_utf8 != NULL) {
1207 gtk_entry_set_text (GTK_ENTRY (song_info->priv->name),
1208 tmp_utf8);
1209 } else {
1210 gtk_entry_set_text (GTK_ENTRY (song_info->priv->name),
1211 _("Unknown file name"));
1212 }
1213
1214 g_free (tmp_utf8);
1215 tmp_utf8 = NULL;
1216
1217 if (rb_uri_is_local (text)) {
1218 const char *desktopdir;
1219 char *dir;
1220
1221 /* for local files, convert to path, extract dirname, and convert to utf8 */
1222 tmp = g_filename_from_uri (text, NULL, NULL);
1223
1224 dir = g_path_get_dirname (tmp);
1225 g_free (tmp);
1226 tmp_utf8 = g_filename_to_utf8 (dir, -1, NULL, NULL, NULL);
1227 g_free (dir);
1228
1229 /* special case for files on the desktop */
1230 desktopdir = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
1231 if (g_strcmp0 (tmp_utf8, desktopdir) == 0) {
1232 g_free (tmp_utf8);
1233 tmp_utf8 = g_strdup (_("On the desktop"));
1234 }
1235 } else {
1236 GFile *file;
1237 GFile *parent;
1238 char *parent_uri;
1239
1240 /* get parent URI and unescape it */
1241 file = g_file_new_for_uri (text);
1242 parent = g_file_get_parent (file);
1243 parent_uri = g_file_get_uri (parent);
1244 g_object_unref (file);
1245 g_object_unref (parent);
1246
1247 tmp_utf8 = g_uri_unescape_string (parent_uri, NULL);
1248 g_free (parent_uri);
1249 }
1250
1251 if (tmp_utf8 != NULL) {
1252 gtk_entry_set_text (GTK_ENTRY (song_info->priv->location),
1253 tmp_utf8);
1254 } else {
1255 gtk_entry_set_text (GTK_ENTRY (song_info->priv->location),
1256 _("Unknown location"));
1257 }
1258 g_free (tmp_utf8);
1259 }
1260 }
1261
1262 static void
1263 rb_song_info_backward_clicked_cb (GtkWidget *button,
1264 RBSongInfo *song_info)
1265 {
1266 RhythmDBEntry *new_entry;
1267
1268 rb_song_info_sync_entries (RB_SONG_INFO (song_info));
1269 new_entry = rhythmdb_query_model_get_previous_from_entry (song_info->priv->query_model,
1270 song_info->priv->current_entry);
1271 g_return_if_fail (new_entry != NULL);
1272
1273 song_info->priv->current_entry = new_entry;
1274 rb_entry_view_select_entry (song_info->priv->entry_view, new_entry);
1275 rb_entry_view_scroll_to_entry (song_info->priv->entry_view, new_entry);
1276
1277 rb_song_info_populate_dialog (song_info);
1278 g_object_notify (G_OBJECT (song_info), "current-entry");
1279 rhythmdb_entry_unref (new_entry);
1280 }
1281
1282 static void
1283 rb_song_info_forward_clicked_cb (GtkWidget *button,
1284 RBSongInfo *song_info)
1285 {
1286 RhythmDBEntry *new_entry;
1287
1288 rb_song_info_sync_entries (RB_SONG_INFO (song_info));
1289 new_entry = rhythmdb_query_model_get_next_from_entry (song_info->priv->query_model,
1290 song_info->priv->current_entry);
1291 g_return_if_fail (new_entry != NULL);
1292
1293 song_info->priv->current_entry = new_entry;
1294 rb_entry_view_select_entry (song_info->priv->entry_view, new_entry);
1295 rb_entry_view_scroll_to_entry (song_info->priv->entry_view, new_entry);
1296
1297 rb_song_info_populate_dialog (song_info);
1298 g_object_notify (G_OBJECT (song_info), "current-entry");
1299
1300 rhythmdb_entry_unref (new_entry);
1301 }
1302
1303 /*
1304 * rb_song_info_update_buttons: update back/forward sensitivity
1305 */
1306 static void
1307 rb_song_info_update_buttons (RBSongInfo *song_info)
1308 {
1309 RhythmDBEntry *entry = NULL;
1310
1311 g_return_if_fail (song_info != NULL);
1312 g_return_if_fail (song_info->priv->query_model != NULL);
1313
1314 if (!song_info->priv->current_entry)
1315 return;
1316
1317 /* backward */
1318 entry = rhythmdb_query_model_get_previous_from_entry (song_info->priv->query_model,
1319 song_info->priv->current_entry);
1320
1321 gtk_widget_set_sensitive (song_info->priv->backward, entry != NULL);
1322 if (entry != NULL)
1323 rhythmdb_entry_unref (entry);
1324
1325 /* forward */
1326 entry = rhythmdb_query_model_get_next_from_entry (song_info->priv->query_model,
1327 song_info->priv->current_entry);
1328
1329 gtk_widget_set_sensitive (song_info->priv->forward, entry != NULL);
1330 if (entry != NULL)
1331 rhythmdb_entry_unref (entry);
1332 }
1333
1334 static void
1335 rb_song_info_query_model_inserted_cb (RhythmDBQueryModel *model,
1336 GtkTreePath *path,
1337 GtkTreeIter *iter,
1338 RBSongInfo *song_info)
1339 {
1340 rb_song_info_update_buttons (song_info);
1341 }
1342
1343 static void
1344 rb_song_info_query_model_deleted_cb (RhythmDBQueryModel *model,
1345 RhythmDBEntry*entry,
1346 RBSongInfo *song_info)
1347 {
1348 rb_song_info_update_buttons (song_info);
1349 }
1350
1351 static void
1352 rb_song_info_query_model_reordered_cb (RhythmDBQueryModel *model,
1353 GtkTreePath *path,
1354 GtkTreeIter *iter,
1355 gpointer *map,
1356 RBSongInfo *song_info)
1357 {
1358 rb_song_info_update_buttons (song_info);
1359 }
1360
1361 static void
1362 rb_song_info_base_query_model_changed_cb (GObject *source,
1363 GParamSpec *whatever,
1364 RBSongInfo *song_info)
1365 {
1366 RhythmDBQueryModel *base_query_model;
1367
1368 g_object_get (source, "base-query-model", &base_query_model, NULL);
1369
1370 if (song_info->priv->albums) {
1371 g_object_unref (song_info->priv->albums);
1372 }
1373 if (song_info->priv->artists) {
1374 g_object_unref (song_info->priv->artists);
1375 }
1376 if (song_info->priv->genres) {
1377 g_object_unref (song_info->priv->genres);
1378 }
1379
1380 song_info->priv->albums = rhythmdb_property_model_new (song_info->priv->db, RHYTHMDB_PROP_ALBUM);
1381 song_info->priv->artists = rhythmdb_property_model_new (song_info->priv->db, RHYTHMDB_PROP_ARTIST);
1382 song_info->priv->genres = rhythmdb_property_model_new (song_info->priv->db, RHYTHMDB_PROP_GENRE);
1383
1384 g_object_set (song_info->priv->albums, "query-model", base_query_model, NULL);
1385 g_object_set (song_info->priv->artists, "query-model", base_query_model, NULL);
1386 g_object_set (song_info->priv->genres, "query-model", base_query_model, NULL);
1387
1388 if (song_info->priv->album) {
1389 GtkEntryCompletion *comp = gtk_entry_get_completion (GTK_ENTRY (song_info->priv->album));
1390 gtk_entry_completion_set_model (comp, GTK_TREE_MODEL (song_info->priv->albums));
1391 }
1392
1393 if (song_info->priv->artist) {
1394 GtkEntryCompletion *comp = gtk_entry_get_completion (GTK_ENTRY (song_info->priv->artist));
1395 gtk_entry_completion_set_model (comp, GTK_TREE_MODEL (song_info->priv->artist));
1396 }
1397
1398 if (song_info->priv->genre) {
1399 GtkEntryCompletion *comp = gtk_entry_get_completion (GTK_ENTRY (song_info->priv->genre));
1400 gtk_entry_completion_set_model (comp, GTK_TREE_MODEL (song_info->priv->genre));
1401 }
1402
1403 g_object_unref (base_query_model);
1404 }
1405
1406 static void
1407 rb_song_info_query_model_changed_cb (GObject *source,
1408 GParamSpec *whatever,
1409 RBSongInfo *song_info)
1410 {
1411 if (song_info->priv->query_model) {
1412 g_signal_handlers_disconnect_by_func (G_OBJECT (song_info->priv->query_model),
1413 G_CALLBACK (rb_song_info_query_model_inserted_cb),
1414 song_info);
1415 g_signal_handlers_disconnect_by_func (G_OBJECT (song_info->priv->query_model),
1416 G_CALLBACK (rb_song_info_query_model_deleted_cb),
1417 song_info);
1418 g_signal_handlers_disconnect_by_func (G_OBJECT (song_info->priv->query_model),
1419 G_CALLBACK (rb_song_info_query_model_reordered_cb),
1420 song_info);
1421
1422 g_object_unref (G_OBJECT (song_info->priv->query_model));
1423 }
1424
1425 g_object_get (source, "query-model", &song_info->priv->query_model, NULL);
1426
1427 g_signal_connect_object (G_OBJECT (song_info->priv->query_model),
1428 "row-inserted", G_CALLBACK (rb_song_info_query_model_inserted_cb),
1429 song_info, 0);
1430 g_signal_connect_object (G_OBJECT (song_info->priv->query_model),
1431 "row-changed", G_CALLBACK (rb_song_info_query_model_inserted_cb),
1432 song_info, 0);
1433 g_signal_connect_object (G_OBJECT (song_info->priv->query_model),
1434 "entry-deleted", G_CALLBACK (rb_song_info_query_model_deleted_cb),
1435 song_info, 0);
1436 g_signal_connect_object (G_OBJECT (song_info->priv->query_model),
1437 "rows-reordered", G_CALLBACK (rb_song_info_query_model_reordered_cb),
1438 song_info, 0);
1439
1440 /* update next button sensitivity */
1441 rb_song_info_update_buttons (song_info);
1442 }
1443
1444 static void
1445 rb_song_info_update_play_count (RBSongInfo *song_info)
1446 {
1447 gulong num;
1448 char *text;
1449
1450 num = rhythmdb_entry_get_ulong (song_info->priv->current_entry, RHYTHMDB_PROP_PLAY_COUNT);
1451 text = g_strdup_printf ("%ld", num);
1452 gtk_label_set_text (GTK_LABEL (song_info->priv->play_count), text);
1453 g_free (text);
1454 }
1455
1456 static void
1457 rb_song_info_update_last_played (RBSongInfo *song_info)
1458 {
1459 const char *str;
1460 str = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_LAST_PLAYED_STR);
1461 if (!strcmp ("", str))
1462 str = _("Never");
1463 gtk_label_set_text (GTK_LABEL (song_info->priv->last_played), str);
1464 }
1465
1466 static void
1467 rb_song_info_update_rating (RBSongInfo *song_info)
1468 {
1469 double rating;
1470
1471 g_return_if_fail (RB_IS_SONG_INFO (song_info));
1472
1473 rating = rhythmdb_entry_get_double (song_info->priv->current_entry, RHYTHMDB_PROP_RATING);
1474 g_object_set (song_info->priv->rating,
1475 "rating", rating,
1476 NULL);
1477 }
1478
1479 static void
1480 rb_song_info_update_year (RBSongInfo *song_info)
1481 {
1482 gulong year;
1483 char *text;
1484
1485 year = rhythmdb_entry_get_ulong (song_info->priv->current_entry, RHYTHMDB_PROP_YEAR);
1486 if (year > 0) {
1487 text = g_strdup_printf ("%lu", year);
1488 } else {
1489 text = g_strdup (_("Unknown"));
1490 }
1491 gtk_entry_set_text (GTK_ENTRY (song_info->priv->year), text);
1492 g_free (text);
1493 }
1494
1495 static void
1496 rb_song_info_update_date_added (RBSongInfo *song_info)
1497 {
1498 const char *str;
1499 str = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_FIRST_SEEN_STR);
1500 gtk_label_set_text (GTK_LABEL (song_info->priv->date_added), str);
1501 }
1502
1503 static gboolean
1504 sync_string_property (RBSongInfo *dialog, RhythmDBPropType property, GtkWidget *entry)
1505 {
1506 const char *new_text;
1507 GValue val = {0,};
1508 GList *t;
1509 gboolean changed = FALSE;
1510
1511 new_text = gtk_entry_get_text (GTK_ENTRY (entry));
1512 if (strlen (new_text) == 0)
1513 return FALSE;
1514
1515 g_value_init (&val, G_TYPE_STRING);
1516 g_value_set_string (&val, new_text);
1517 for (t = dialog->priv->selected_entries; t != NULL; t = t->next) {
1518 const char *entry_value;
1519 RhythmDBEntry *dbentry;
1520
1521 dbentry = (RhythmDBEntry *)t->data;
1522 entry_value = rhythmdb_entry_get_string (dbentry, property);
1523
1524 if (g_strcmp0 (new_text, entry_value) == 0)
1525 continue;
1526 rhythmdb_entry_set (dialog->priv->db, dbentry, property, &val);
1527 changed = TRUE;
1528 }
1529 g_value_unset (&val);
1530 return changed;
1531 }
1532
1533 static void
1534 rb_song_info_sync_entries_multiple (RBSongInfo *dialog)
1535 {
1536 const char *year_str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->year));
1537 const char *discn_str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->disc_cur));
1538
1539 char *endptr;
1540 GValue val = {0,};
1541 GList *tem;
1542 gint year;
1543 gint discn;
1544 gboolean changed = FALSE;
1545 RhythmDBEntry *entry;
1546
1547 changed |= sync_string_property (dialog, RHYTHMDB_PROP_ALBUM, dialog->priv->album);
1548 changed |= sync_string_property (dialog, RHYTHMDB_PROP_ARTIST, dialog->priv->artist);
1549 changed |= sync_string_property (dialog, RHYTHMDB_PROP_ALBUM_ARTIST, dialog->priv->album_artist);
1550 changed |= sync_string_property (dialog, RHYTHMDB_PROP_GENRE, dialog->priv->genre);
1551 changed |= sync_string_property (dialog, RHYTHMDB_PROP_ARTIST_SORTNAME, dialog->priv->artist_sortname);
1552 changed |= sync_string_property (dialog, RHYTHMDB_PROP_ALBUM_SORTNAME, dialog->priv->album_sortname);
1553 changed |= sync_string_property (dialog, RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME, dialog->priv->album_artist_sortname);
1554
1555 if (strlen (year_str) > 0) {
1556 GDate *date = NULL;
1557 GType type;
1558
1559 /* note: this will reset the day-of-year to Jan 1 for all entries */
1560 year = g_ascii_strtoull (year_str, &endptr, 10);
1561 if (year > 0)
1562 date = g_date_new_dmy (1, G_DATE_JANUARY, year);
1563
1564 type = rhythmdb_get_property_type (dialog->priv->db,
1565 RHYTHMDB_PROP_DATE);
1566
1567 g_value_init (&val, type);
1568 g_value_set_ulong (&val, (date ? g_date_get_julian (date) : 0));
1569
1570 for (tem = dialog->priv->selected_entries; tem; tem = tem->next) {
1571 entry = (RhythmDBEntry *)tem->data;
1572 rhythmdb_entry_set (dialog->priv->db, entry,
1573 RHYTHMDB_PROP_DATE, &val);
1574 changed = TRUE;
1575 }
1576 g_value_unset (&val);
1577 if (date)
1578 g_date_free (date);
1579
1580 }
1581
1582 discn = g_ascii_strtoull (discn_str, &endptr, 10);
1583 if (endptr != discn_str) {
1584 GType type;
1585 type = rhythmdb_get_property_type (dialog->priv->db,
1586 RHYTHMDB_PROP_DISC_NUMBER);
1587 g_value_init (&val, type);
1588 g_value_set_ulong (&val, discn);
1589
1590 for (tem = dialog->priv->selected_entries; tem; tem = tem->next) {
1591 gulong entry_disc_num;
1592
1593 entry = (RhythmDBEntry *)tem->data;
1594 entry_disc_num = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER);
1595
1596 if (discn != entry_disc_num) {
1597 rhythmdb_entry_set (dialog->priv->db, entry,
1598 RHYTHMDB_PROP_DISC_NUMBER, &val);
1599 changed = TRUE;
1600 }
1601 }
1602 g_value_unset (&val);
1603 }
1604
1605 if (changed)
1606 rhythmdb_commit (dialog->priv->db);
1607 }
1608
1609 static void
1610 rb_song_info_sync_entry_single (RBSongInfo *dialog)
1611 {
1612 const char *title;
1613 const char *genre;
1614 const char *artist;
1615 const char *album;
1616 const char *album_artist;
1617 const char *tracknum_str;
1618 const char *discnum_str;
1619 const char *year_str;
1620 const char *artist_sortname;
1621 const char *album_sortname;
1622 const char *album_artist_sortname;
1623 const char *entry_string;
1624 const char *bpm_str;
1625 char *comment = NULL;
1626 char *endptr;
1627 GType type;
1628 gulong tracknum;
1629 gulong discnum;
1630 gulong year;
1631 gulong entry_val;
1632 gdouble bpm;
1633 gdouble dentry_val;
1634 GValue val = {0,};
1635 gboolean changed = FALSE;
1636 RhythmDBEntry *entry = dialog->priv->current_entry;
1637 GtkTextIter start, end;
1638
1639 title = gtk_entry_get_text (GTK_ENTRY (dialog->priv->title));
1640 genre = gtk_entry_get_text (GTK_ENTRY (dialog->priv->genre));
1641 artist = gtk_entry_get_text (GTK_ENTRY (dialog->priv->artist));
1642 album = gtk_entry_get_text (GTK_ENTRY (dialog->priv->album));
1643 album_artist = gtk_entry_get_text (GTK_ENTRY (dialog->priv->album_artist));
1644 tracknum_str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->track_cur));
1645 discnum_str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->disc_cur));
1646 year_str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->year));
1647 artist_sortname = gtk_entry_get_text (GTK_ENTRY (dialog->priv->artist_sortname));
1648 album_sortname = gtk_entry_get_text (GTK_ENTRY (dialog->priv->album_sortname));
1649 album_artist_sortname = gtk_entry_get_text (GTK_ENTRY (dialog->priv->album_artist_sortname));
1650
1651 /* Get comment text (string is allocated) */
1652 gtk_text_buffer_get_bounds (dialog->priv->comment_buffer, &start, &end);
1653 comment = gtk_text_buffer_get_text (dialog->priv->comment_buffer, &start, &end, FALSE);
1654
1655 g_signal_emit (dialog, rb_song_info_signals[PRE_METADATA_CHANGE], 0,
1656 entry);
1657
1658 tracknum = g_ascii_strtoull (tracknum_str, &endptr, 10);
1659 entry_val = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
1660 if ((endptr != tracknum_str) && (tracknum != entry_val)) {
1661 type = rhythmdb_get_property_type (dialog->priv->db,
1662 RHYTHMDB_PROP_TRACK_NUMBER);
1663 g_value_init (&val, type);
1664 g_value_set_ulong (&val, tracknum);
1665 rhythmdb_entry_set (dialog->priv->db, entry, RHYTHMDB_PROP_TRACK_NUMBER, &val);
1666 g_value_unset (&val);
1667 changed = TRUE;
1668 }
1669
1670 discnum = g_ascii_strtoull (discnum_str, &endptr, 10);
1671 entry_val = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER);
1672 if ((endptr != discnum_str) && (discnum != entry_val)) {
1673 type = rhythmdb_get_property_type (dialog->priv->db,
1674 RHYTHMDB_PROP_DISC_NUMBER);
1675 g_value_init (&val, type);
1676 g_value_set_ulong (&val, discnum);
1677 rhythmdb_entry_set (dialog->priv->db, entry, RHYTHMDB_PROP_DISC_NUMBER, &val);
1678 g_value_unset (&val);
1679 changed = TRUE;
1680 }
1681
1682 year = g_ascii_strtoull (year_str, &endptr, 10);
1683 entry_val = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_YEAR);
1684 if ((endptr != year_str) &&
1685 (year != entry_val ||
1686 (entry_val == 0 && year > 0))) {
1687 GDate *date = NULL;
1688
1689 if (year > 0) {
1690 if (entry_val > 0) {
1691 gulong julian;
1692
1693 julian = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE);
1694 date = g_date_new_julian (julian);
1695 g_date_set_year (date, year);
1696 } else {
1697 date = g_date_new_dmy (1, G_DATE_JANUARY, year);
1698 }
1699 }
1700
1701 type = rhythmdb_get_property_type (dialog->priv->db,
1702 RHYTHMDB_PROP_DATE);
1703 g_value_init (&val, type);
1704 g_value_set_ulong (&val, (date ? g_date_get_julian (date) : 0));
1705 rhythmdb_entry_set (dialog->priv->db, entry, RHYTHMDB_PROP_DATE, &val);
1706 changed = TRUE;
1707
1708 g_value_unset (&val);
1709 if (date)
1710 g_date_free (date);
1711 }
1712 bpm_str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->bpm));
1713 bpm = g_strtod (bpm_str, &endptr);
1714 dentry_val = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_BPM);
1715 if ((endptr != bpm_str) && (bpm != dentry_val)) {
1716 type = rhythmdb_get_property_type (dialog->priv->db,
1717 RHYTHMDB_PROP_BPM);
1718 g_value_init (&val, type);
1719 g_value_set_double (&val, bpm);
1720 rhythmdb_entry_set (dialog->priv->db, entry, RHYTHMDB_PROP_BPM, &val);
1721 g_value_unset (&val);
1722 changed = TRUE;
1723 }
1724 entry_string = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
1725 if (g_strcmp0 (title, entry_string)) {
1726 type = rhythmdb_get_property_type (dialog->priv->db,
1727 RHYTHMDB_PROP_TITLE);
1728 g_value_init (&val, type);
1729 g_value_set_string (&val, title);
1730 rhythmdb_entry_set (dialog->priv->db, entry,
1731 RHYTHMDB_PROP_TITLE, &val);
1732 g_value_unset (&val);
1733 changed = TRUE;
1734 }
1735
1736 entry_string = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
1737 if (g_strcmp0 (album, entry_string)) {
1738 type = rhythmdb_get_property_type (dialog->priv->db,
1739 RHYTHMDB_PROP_ALBUM);
1740 g_value_init (&val, type);
1741 g_value_set_string (&val, album);
1742 rhythmdb_entry_set (dialog->priv->db, entry,
1743 RHYTHMDB_PROP_ALBUM, &val);
1744 g_value_unset (&val);
1745 changed = TRUE;
1746 }
1747
1748 entry_string = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
1749 if (g_strcmp0 (artist, entry_string)) {
1750 type = rhythmdb_get_property_type (dialog->priv->db,
1751 RHYTHMDB_PROP_ARTIST);
1752 g_value_init (&val, type);
1753 g_value_set_string (&val, artist);
1754 rhythmdb_entry_set (dialog->priv->db, entry,
1755 RHYTHMDB_PROP_ARTIST, &val);
1756 g_value_unset (&val);
1757 changed = TRUE;
1758 }
1759
1760 entry_string = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
1761 if (g_strcmp0 (album_artist, entry_string)) {
1762 type = rhythmdb_get_property_type (dialog->priv->db,
1763 RHYTHMDB_PROP_ALBUM_ARTIST);
1764 g_value_init (&val, type);
1765 g_value_set_string (&val, album_artist);
1766 rhythmdb_entry_set (dialog->priv->db, entry,
1767 RHYTHMDB_PROP_ALBUM_ARTIST, &val);
1768 g_value_unset (&val);
1769 changed = TRUE;
1770 }
1771
1772 entry_string = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE);
1773 if (g_strcmp0 (genre, entry_string)) {
1774 type = rhythmdb_get_property_type (dialog->priv->db,
1775 RHYTHMDB_PROP_GENRE);
1776 g_value_init (&val, type);
1777 g_value_set_string (&val, genre);
1778 rhythmdb_entry_set (dialog->priv->db, entry,
1779 RHYTHMDB_PROP_GENRE, &val);
1780 g_value_unset (&val);
1781 changed = TRUE;
1782 }
1783
1784 entry_string = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST_SORTNAME);
1785 if (g_strcmp0 (artist_sortname, entry_string)) {
1786 type = rhythmdb_get_property_type (dialog->priv->db,
1787 RHYTHMDB_PROP_ARTIST_SORTNAME);
1788 g_value_init (&val, type);
1789 g_value_set_string (&val, artist_sortname);
1790 rhythmdb_entry_set (dialog->priv->db, entry,
1791 RHYTHMDB_PROP_ARTIST_SORTNAME, &val);
1792 g_value_unset (&val);
1793 changed = TRUE;
1794 }
1795
1796 entry_string = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_SORTNAME);
1797 if (g_strcmp0 (album_sortname, entry_string)) {
1798 type = rhythmdb_get_property_type (dialog->priv->db,
1799 RHYTHMDB_PROP_ALBUM_SORTNAME);
1800 g_value_init (&val, type);
1801 g_value_set_string (&val, album_sortname);
1802 rhythmdb_entry_set (dialog->priv->db, entry,
1803 RHYTHMDB_PROP_ALBUM_SORTNAME, &val);
1804 g_value_unset (&val);
1805 changed = TRUE;
1806 }
1807
1808 entry_string = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_COMMENT);
1809 if (g_strcmp0 (comment, entry_string)) {
1810 type = rhythmdb_get_property_type (dialog->priv->db,
1811 RHYTHMDB_PROP_COMMENT);
1812 g_value_init (&val, type);
1813 g_value_set_string (&val, comment);
1814 rhythmdb_entry_set (dialog->priv->db, entry,
1815 RHYTHMDB_PROP_COMMENT, &val);
1816 g_value_unset (&val);
1817 changed = TRUE;
1818 }
1819
1820 entry_string = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME);
1821 if (g_strcmp0 (album_artist_sortname, entry_string)) {
1822 type = rhythmdb_get_property_type (dialog->priv->db,
1823 RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME);
1824 g_value_init (&val, type);
1825 g_value_set_string (&val, album_artist_sortname);
1826 rhythmdb_entry_set (dialog->priv->db, entry,
1827 RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME, &val);
1828 g_value_unset (&val);
1829 changed = TRUE;
1830 }
1831
1832 /* FIXME: when an entry is SYNCed, a changed signal is emitted, and
1833 * this signal is also emitted, aren't they redundant?
1834 */
1835 g_signal_emit (G_OBJECT (dialog), rb_song_info_signals[POST_METADATA_CHANGE], 0,
1836 entry);
1837
1838 if (changed)
1839 rhythmdb_commit (dialog->priv->db);
1840
1841 g_free (comment);
1842 }
1843
1844 static void
1845 rb_song_info_sync_entries (RBSongInfo *dialog)
1846 {
1847 if (!dialog->priv->editable)
1848 return;
1849
1850 if (dialog->priv->current_entry)
1851 rb_song_info_sync_entry_single (dialog);
1852 else
1853 rb_song_info_sync_entries_multiple (dialog);
1854 }