hythmbox-2.98/widgets/rb-song-info.c

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 }