[rhythmbox] add support for beats per minute tag (bug #454889)



commit 512f875ebc36a72bc6e2f9ad5fcba5ddc821b6f8
Author: Mattias Eriksson <snaggen acc umu se>
Date:   Sat Jun 12 22:45:22 2010 +1000

    add support for beats per minute tag (bug #454889)

 backends/gstreamer/rb-encoder-gst.c   |    7 ++++
 bindings/python/rb.defs               |    1 +
 data/ui/general-prefs.ui              |   30 +++++++++++++----
 data/ui/song-info.ui                  |   58 ++++++++++++++++++++++++++++++--
 metadata/rb-metadata-common.c         |    3 ++
 metadata/rb-metadata-gst-common.c     |    4 ++
 metadata/rb-metadata.h                |    1 +
 rhythmdb/rhythmdb-private.h           |    1 +
 rhythmdb/rhythmdb-tree.c              |    3 ++
 rhythmdb/rhythmdb.c                   |   49 +++++++++++++++++++--------
 rhythmdb/rhythmdb.h                   |    1 +
 shell/rb-shell-preferences.c          |   10 +++++-
 sources/rb-browser-source.c           |    1 +
 sources/rb-playlist-source.c          |    1 +
 widgets/rb-entry-view.c               |   36 ++++++++++++++++++++
 widgets/rb-entry-view.h               |    1 +
 widgets/rb-query-creator-properties.c |   49 ++++++++++++++++++++++++++-
 widgets/rb-song-info.c                |   35 ++++++++++++++++++-
 18 files changed, 260 insertions(+), 31 deletions(-)
---
diff --git a/backends/gstreamer/rb-encoder-gst.c b/backends/gstreamer/rb-encoder-gst.c
index 87eefa4..bea8915 100644
--- a/backends/gstreamer/rb-encoder-gst.c
+++ b/backends/gstreamer/rb-encoder-gst.c
@@ -492,6 +492,7 @@ add_tags_from_entry (RBEncoderGst *encoder,
 	GstTagList *tags;
 	gboolean result = TRUE;
 	gulong day;
+	gdouble bpm;
 
 	tags = gst_tag_list_new ();
 
@@ -526,6 +527,12 @@ add_tags_from_entry (RBEncoderGst *encoder,
 	add_string_tag (tags, GST_TAG_MERGE_APPEND, GST_TAG_ARTIST_SORTNAME, entry, RHYTHMDB_PROP_ARTIST_SORTNAME);
 	add_string_tag (tags, GST_TAG_MERGE_APPEND, GST_TAG_ALBUM_SORTNAME, entry, RHYTHMDB_PROP_ALBUM_SORTNAME);
 
+	/* is zero a valid BPM? */
+	bpm = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_BPM);
+	if (bpm > 0.001) {
+		gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_BEATS_PER_MINUTE, bpm, NULL);
+	}
+
 	{
 		GstIterator *iter;
 		gboolean done;
diff --git a/bindings/python/rb.defs b/bindings/python/rb.defs
index 44d3012..de4c489 100644
--- a/bindings/python/rb.defs
+++ b/bindings/python/rb.defs
@@ -278,6 +278,7 @@
     '("first-seen" "RB_ENTRY_VIEW_COL_FIRST_SEEN")
     '("last-seen" "RB_ENTRY_VIEW_COL_LAST_SEEN")
     '("location" "RB_ENTRY_VIEW_COL_LOCATION")
+    '("bpm" "RB_ENTRY_VIEW_COL_BPM")
     '("error" "RB_ENTRY_VIEW_COL_ERROR")
   )
 )
diff --git a/data/ui/general-prefs.ui b/data/ui/general-prefs.ui
index 8528236..f317d0f 100644
--- a/data/ui/general-prefs.ui
+++ b/data/ui/general-prefs.ui
@@ -406,8 +406,8 @@
                               </packing>
                             </child>
                             <child>
-                              <object class="GtkCheckButton" id="location_check">
-                                <property name="label" translatable="yes">Lo_cation</property>
+                              <object class="GtkCheckButton" id="bpm_check">
+                                <property name="label" translatable="yes">BPM</property>
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
@@ -418,14 +418,28 @@
                               <packing>
                                 <property name="left_attach">1</property>
                                 <property name="right_attach">2</property>
-                                <property name="top_attach">5</property>
-                                <property name="bottom_attach">6</property>
+                                <property name="top_attach">6</property>
+                                <property name="bottom_attach">7</property>
                                 <property name="x_options">GTK_FILL</property>
                                 <property name="y_options"></property>
                               </packing>
                             </child>
                             <child>
-                              <placeholder/>
+                              <object class="GtkCheckButton" id="location_check">
+                                <property name="label" translatable="yes">Lo_cation</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="use_underline">True</property>
+                                <property name="draw_indicator">True</property>
+                                <signal name="toggled" handler="rb_shell_preferences_column_check_changed_cb"/>
+                              </object>
+                              <packing>
+                                <property name="top_attach">6</property>
+                                <property name="bottom_attach">7</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"></property>
+                              </packing>
                             </child>
                             <child>
                               <object class="GtkCheckButton" id="comment_check">
@@ -438,8 +452,10 @@
                                 <signal name="toggled" handler="rb_shell_preferences_column_check_changed_cb"/>
                               </object>
                               <packing>
-                                <property name="top_attach">6</property>
-                                <property name="bottom_attach">7</property>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">5</property>
+                                <property name="bottom_attach">6</property>
                                 <property name="x_options">GTK_FILL</property>
                                 <property name="y_options"></property>
                               </packing>
diff --git a/data/ui/song-info.ui b/data/ui/song-info.ui
index e6d7810..9fcc6c4 100644
--- a/data/ui/song-info.ui
+++ b/data/ui/song-info.ui
@@ -248,6 +248,56 @@
               </packing>
             </child>
             <child>
+              <object class="GtkEntry" id="song_info_bpm">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="editable">True</property>
+                <property name="visibility">True</property>
+                <property name="max_length">0</property>
+                <property name="text" translatable="yes"></property>
+                <property name="has_frame">True</property>
+                <property name="invisible_char">*</property>
+                <property name="activates_default">True</property>
+                <accessibility>
+                  <relation type="labelled-by" target="bpm_label"/>
+                </accessibility>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">8</property>
+                <property name="bottom_attach">9</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+
+            <child>
+              <object class="GtkLabel" id="bpm_label">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">BPM:</property>
+                <property name="use_underline">True</property>
+                <property name="use_markup">False</property>
+                <property name="justify">GTK_JUSTIFY_LEFT</property>
+                <property name="wrap">False</property>
+                <property name="selectable">False</property>
+                <property name="xalign">0</property>
+                <property name="yalign">0.5</property>
+                <property name="xpad">0</property>
+                <property name="ypad">0</property>
+                <property name="mnemonic_widget">song_info_bpm</property>
+                <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                <property name="width_chars">-1</property>
+                <property name="single_line_mode">False</property>
+                <property name="angle">0</property>
+              </object>
+              <packing>
+                <property name="top_attach">8</property>
+                <property name="bottom_attach">9</property>
+                <property name="x_options">fill</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
               <object class="GtkLabel" id="comment_label">
                 <property name="visible">True</property>
                 <property name="xalign">0</property>
@@ -256,8 +306,8 @@
                 <property name="mnemonic_widget">song_info_comment</property>
               </object>
               <packing>
-                <property name="top_attach">8</property>
-                <property name="bottom_attach">9</property>
+                <property name="top_attach">9</property>
+                <property name="bottom_attach">10</property>
                 <property name="x_options">GTK_FILL</property>
                 <property name="y_options"></property>
               </packing>
@@ -279,8 +329,8 @@
               <packing>
                 <property name="left_attach">1</property>
                 <property name="right_attach">2</property>
-                <property name="top_attach">8</property>
-                <property name="bottom_attach">9</property>
+                <property name="top_attach">9</property>
+                <property name="bottom_attach">10</property>
               </packing>
             </child>
             <child>
diff --git a/metadata/rb-metadata-common.c b/metadata/rb-metadata-common.c
index d0a4655..192502f 100644
--- a/metadata/rb-metadata-common.c
+++ b/metadata/rb-metadata-common.c
@@ -57,6 +57,7 @@
  * @RB_METADATA_FIELD_TRACK_PEAK: Track peak volume level
  * @RB_METADATA_FIELD_ALBUM_GAIN: Album gain in dB for replaygain
  * @RB_METADATA_FIELD_ALBUM_PEAK: Album peak volume level
+ * @RB_METADATA_FIELD_BPM: Beats Per Minute
  * @RB_METADATA_FIELD_LANGUAGE_CODE: Language code (ISO-639-1)
  * @RB_METADATA_FIELD_MUSICBRAINZ_TRACKID: MusicBrainz track ID
  * @RB_METADATA_FIELD_MUSICBRAINZ_ARTISTID: MusicBrainz artist ID
@@ -121,6 +122,7 @@ rb_metadata_get_field_type (RBMetaDataField field)
 	case RB_METADATA_FIELD_TRACK_PEAK:
 	case RB_METADATA_FIELD_ALBUM_GAIN:
 	case RB_METADATA_FIELD_ALBUM_PEAK:
+	case RB_METADATA_FIELD_BPM:
 		return G_TYPE_DOUBLE;
 
 	default:
@@ -199,6 +201,7 @@ rb_metadata_field_get_type (void)
 			ENUM_ENTRY (RB_METADATA_FIELD_TRACK_PEAK, "replaygain-track-peak"),
 			ENUM_ENTRY (RB_METADATA_FIELD_ALBUM_GAIN, "replaygain-album-gain"),
 			ENUM_ENTRY (RB_METADATA_FIELD_ALBUM_PEAK, "replaygain-album-peak"),
+			ENUM_ENTRY (RB_METADATA_FIELD_BPM, "beats-per-minute"),
 			ENUM_ENTRY (RB_METADATA_FIELD_LANGUAGE_CODE, "language-code"),
 			ENUM_ENTRY (RB_METADATA_FIELD_MUSICBRAINZ_TRACKID, "musicbrainz-trackid"),
 			ENUM_ENTRY (RB_METADATA_FIELD_MUSICBRAINZ_ARTISTID, "musicbrainz-artistid"),
diff --git a/metadata/rb-metadata-gst-common.c b/metadata/rb-metadata-gst-common.c
index db20aa8..63386cc 100644
--- a/metadata/rb-metadata-gst-common.c
+++ b/metadata/rb-metadata-gst-common.c
@@ -115,6 +115,8 @@ rb_metadata_gst_tag_to_field (const char *tag)
 		return RB_METADATA_FIELD_ALBUM_GAIN;
 	else if (!strcmp (tag, GST_TAG_ALBUM_PEAK))
 		return RB_METADATA_FIELD_ALBUM_PEAK;
+	else if (!strcmp (tag, GST_TAG_BEATS_PER_MINUTE))
+		return RB_METADATA_FIELD_BPM;
 	else if (!strcmp (tag, GST_TAG_MUSICBRAINZ_TRACKID))
 		return RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
 	else if (!strcmp (tag, GST_TAG_MUSICBRAINZ_ARTISTID))
@@ -192,6 +194,8 @@ rb_metadata_gst_field_to_gst_tag (RBMetaDataField field)
 		return GST_TAG_ALBUM_GAIN;
 	case RB_METADATA_FIELD_ALBUM_PEAK:
 		return GST_TAG_ALBUM_PEAK;
+	case RB_METADATA_FIELD_BPM:
+		return GST_TAG_BEATS_PER_MINUTE;
 	case RB_METADATA_FIELD_MUSICBRAINZ_TRACKID:
 		return GST_TAG_MUSICBRAINZ_TRACKID;
 	case RB_METADATA_FIELD_MUSICBRAINZ_ARTISTID:
diff --git a/metadata/rb-metadata.h b/metadata/rb-metadata.h
index 57a89ce..3b3c84d 100644
--- a/metadata/rb-metadata.h
+++ b/metadata/rb-metadata.h
@@ -60,6 +60,7 @@ typedef enum
 	RB_METADATA_FIELD_ALBUM_GAIN,		   /* double */
 	RB_METADATA_FIELD_ALBUM_PEAK,		   /* double */
 	RB_METADATA_FIELD_LANGUAGE_CODE,	   /* string */
+	RB_METADATA_FIELD_BPM,			   /* double */
 	RB_METADATA_FIELD_MUSICBRAINZ_TRACKID,     /* string */
 	RB_METADATA_FIELD_MUSICBRAINZ_ARTISTID,    /* string */
 	RB_METADATA_FIELD_MUSICBRAINZ_ALBUMID,     /* string */
diff --git a/rhythmdb/rhythmdb-private.h b/rhythmdb/rhythmdb-private.h
index 103dee3..e87c0e0 100644
--- a/rhythmdb/rhythmdb-private.h
+++ b/rhythmdb/rhythmdb-private.h
@@ -90,6 +90,7 @@ struct RhythmDBEntry_ {
 	gulong discnum;
 	gulong duration;
 	gulong bitrate;
+	double bpm;
 	GDate date;
 
 	/* filesystem */
diff --git a/rhythmdb/rhythmdb-tree.c b/rhythmdb/rhythmdb-tree.c
index 963e764..becd748 100644
--- a/rhythmdb/rhythmdb-tree.c
+++ b/rhythmdb/rhythmdb-tree.c
@@ -1024,6 +1024,9 @@ save_entry (RhythmDBTree *db,
 		case RHYTHMDB_PROP_LOCATION:
 			save_entry_string(ctx, elt_name, rb_refstring_get (entry->location));
 			break;
+		case RHYTHMDB_PROP_BPM:
+			save_entry_double(ctx, elt_name, entry->bpm);
+			break;
 		case RHYTHMDB_PROP_MOUNTPOINT:
 			save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->mountpoint));
 			break;
diff --git a/rhythmdb/rhythmdb.c b/rhythmdb/rhythmdb.c
index cc329dc..833c0cb 100644
--- a/rhythmdb/rhythmdb.c
+++ b/rhythmdb/rhythmdb.c
@@ -588,6 +588,9 @@ metadata_field_from_prop (RhythmDBPropType prop,
 	case RHYTHMDB_PROP_DATE:
 		*field = RB_METADATA_FIELD_DATE;
 		return TRUE;
+	case RHYTHMDB_PROP_BPM:
+		*field = RB_METADATA_FIELD_BPM;
+		return TRUE;
 	case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
 		*field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
 		return TRUE;
@@ -930,7 +933,7 @@ stat_thread_main (RhythmDBStatThreadData *data)
 	g_list_free (data->stat_list);
 
 	data->db->priv->stat_thread_running = FALSE;
-	
+
 	rb_debug ("exiting stat thread");
 	result = g_slice_new0 (RhythmDBEvent);
 	result->db = data->db;			/* need to unref? */
@@ -1548,7 +1551,7 @@ rhythmdb_commit_internal (RhythmDB *db,
 			  GThread *thread)
 {
 	g_mutex_lock (db->priv->change_mutex);
-	
+
 	if (sync_changes) {
 		g_hash_table_foreach (db->priv->changed_entries, (GHFunc) sync_entry_changed, db);
 	}
@@ -2068,6 +2071,16 @@ set_props_from_metadata (RhythmDB *db,
 					  RB_METADATA_FIELD_ARTIST,
 					  RHYTHMDB_PROP_ARTIST,
 					  _("Unknown"));
+
+	/* beats per minute */
+	if (rb_metadata_get (metadata,
+			     RB_METADATA_FIELD_BPM,
+			     &val)) {
+		rhythmdb_entry_set_internal (db, entry, TRUE,
+					     RHYTHMDB_PROP_BPM, &val);
+		g_value_unset (&val);
+	}
+
 	/* album */
 	set_metadata_string_with_default (db, metadata, entry,
 					  RB_METADATA_FIELD_ALBUM,
@@ -2147,7 +2160,7 @@ rhythmdb_process_stat_event (RhythmDB *db,
 	RhythmDBEntry *entry;
 	RhythmDBAction *action;
 	GFileType file_type;
-	
+
 	if (event->entry != NULL) {
 		entry = event->entry;
 	} else {
@@ -2578,7 +2591,7 @@ rhythmdb_process_metadata_load (RhythmDB *db,
 		gboolean processing;
 
 		rb_metadata_get_missing_plugins (event->metadata, &missing_plugins, &plugin_descriptions);
-		
+
 		rb_debug ("missing plugins during metadata load for %s", rb_refstring_get (event->real_uri));
 
 		g_mutex_lock (event->db->priv->metadata_lock);
@@ -2720,7 +2733,7 @@ static void
 rhythmdb_execute_stat_mount_ready_cb (GObject *source, GAsyncResult *result, RhythmDBEvent *event)
 {
 	GError *error = NULL;
-	
+
 	g_file_mount_enclosing_volume_finish (G_FILE (source), result, &error);
 	if (error != NULL) {
 		event->error = make_access_failed_error (rb_refstring_get (event->real_uri), error);
@@ -2752,7 +2765,7 @@ rhythmdb_execute_stat (RhythmDB *db,
 
 	event->real_uri = rb_refstring_new (uri);
 	file = g_file_new_for_uri (uri);
-	
+
 	g_mutex_lock (db->priv->stat_mutex);
 	db->priv->outstanding_stats = g_list_prepend (db->priv->outstanding_stats, event);
 	g_mutex_unlock (db->priv->stat_mutex);
@@ -2794,7 +2807,7 @@ rhythmdb_execute_stat (RhythmDB *db,
 	g_mutex_lock (event->db->priv->stat_mutex);
 	event->db->priv->outstanding_stats = g_list_remove (event->db->priv->outstanding_stats, event);
 	g_mutex_unlock (event->db->priv->stat_mutex);
-	
+
 	rhythmdb_push_event (event->db, event);
 	g_object_unref (file);
 }
@@ -2810,7 +2823,7 @@ rhythmdb_execute_load (RhythmDB *db,
 	resolved = rb_uri_resolve_symlink (uri, &error);
 	if (resolved != NULL) {
 		GFile *file;
-	
+
 		file = g_file_new_for_uri (uri);
 		event->file_info = g_file_query_info (file,
 						      RHYTHMDB_FILE_INFO_ATTRIBUTES,
@@ -3135,7 +3148,7 @@ rhythmdb_add_to_stat_list (RhythmDB *db,
 	result->entry_type = type;
 	result->ignore_type = ignore_type;
 	result->error_type = error_type;
-		
+
 	if (entry != NULL) {
 		result->entry = rhythmdb_entry_ref (entry);
 	}
@@ -3346,19 +3359,19 @@ void
 rhythmdb_save (RhythmDB *db)
 {
 	int new_save_count;
-	
+
 	rb_debug("saving the rhythmdb and blocking");
 
 	g_mutex_lock (db->priv->saving_mutex);
 	new_save_count = db->priv->save_count + 1;
-	
+
 	rhythmdb_save_async (db);
-	
+
 	/* wait until this save request is being processed */
 	while (db->priv->save_count < new_save_count) {
 		g_cond_wait (db->priv->saving_condition, db->priv->saving_mutex);
 	}
-	
+
 	/* wait until it's done */
 	while (db->priv->saving) {
 		g_cond_wait (db->priv->saving_condition, db->priv->saving_mutex);
@@ -3497,7 +3510,7 @@ rhythmdb_entry_set_internal (RhythmDB *db,
 		g_assert_not_reached ();
 		break;
 	}
-	
+
 	if (nop == FALSE && (entry->flags & RHYTHMDB_ENTRY_INSERTED) && notify_if_inserted) {
 		record_entry_change (db, entry, propid, &old_value, value);
 	}
@@ -3629,6 +3642,9 @@ rhythmdb_entry_set_internal (RhythmDB *db,
 			entry->last_played = g_value_get_ulong (value);
 			entry->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY;
 			break;
+		case RHYTHMDB_PROP_BPM:
+			entry->bpm = g_value_get_double (value);
+			break;
 		case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
 			rb_refstring_unref (entry->musicbrainz_trackid);
 			entry->musicbrainz_trackid = rb_refstring_new (g_value_get_string (value));
@@ -3896,7 +3912,7 @@ rhythmdb_entry_move_to_trash (RhythmDB *db,
 			  uri,
 			  error->message);
 		g_error_free (error);
-				
+
 	} else {
 		rhythmdb_entry_set_visibility (db, entry, FALSE);
 	}
@@ -4479,6 +4495,7 @@ rhythmdb_prop_type_get_type (void)
 			ENUM_ENTRY (RHYTHMDB_PROP_POST_TIME, "Podcast time of post (gulong) [post-time]"),
 
 			ENUM_ENTRY (RHYTHMDB_PROP_KEYWORD, "Keywords applied to track (gchararray) [keyword]"),
+			ENUM_ENTRY (RHYTHMDB_PROP_BPM, "Beats per minute (gdouble) [beats-per-minute]"),
 			{ 0, 0, 0 }
 		};
 		g_assert ((sizeof (values) / sizeof (values[0]) - 1) == RHYTHMDB_NUM_PROPERTIES);
@@ -5616,6 +5633,8 @@ rhythmdb_entry_get_double (RhythmDBEntry *entry,
 		return 1.0;
 	case RHYTHMDB_PROP_RATING:
 		return entry->rating;
+	case RHYTHMDB_PROP_BPM:
+		return entry->bpm;
 	default:
 		g_assert_not_reached ();
 		return 0.0;
diff --git a/rhythmdb/rhythmdb.h b/rhythmdb/rhythmdb.h
index 970945e..0dcb309 100644
--- a/rhythmdb/rhythmdb.h
+++ b/rhythmdb/rhythmdb.h
@@ -189,6 +189,7 @@ typedef enum
 	RHYTHMDB_PROP_PLAYBACK_ERROR,
 	RHYTHMDB_PROP_FIRST_SEEN_STR,
 	RHYTHMDB_PROP_LAST_SEEN_STR,
+	RHYTHMDB_PROP_BPM,
 
 	/* synthetic properties */
 	RHYTHMDB_PROP_SEARCH_MATCH,
diff --git a/shell/rb-shell-preferences.c b/shell/rb-shell-preferences.c
index f261c91..b057312 100644
--- a/shell/rb-shell-preferences.c
+++ b/shell/rb-shell-preferences.c
@@ -113,6 +113,7 @@ struct RBShellPreferencesPrivate
 	GtkWidget *play_count_check;
 	GtkWidget *last_played_check;
 	GtkWidget *first_seen_check;
+	GtkWidget *bpm_check;
 	GtkWidget *quality_check;
 	GtkWidget *year_check;
 	GtkWidget *location_check;
@@ -231,6 +232,8 @@ rb_shell_preferences_init (RBShellPreferences *shell_preferences)
 		GTK_WIDGET (gtk_builder_get_object (builder, "last_played_check"));
 	shell_preferences->priv->quality_check =
 		GTK_WIDGET (gtk_builder_get_object (builder, "quality_check"));
+	shell_preferences->priv->bpm_check =
+		GTK_WIDGET (gtk_builder_get_object (builder, "bpm_check"));
 	shell_preferences->priv->year_check =
 		GTK_WIDGET (gtk_builder_get_object (builder, "year_check"));
 	shell_preferences->priv->first_seen_check =
@@ -467,6 +470,8 @@ rb_shell_preferences_column_check_changed_cb (GtkCheckButton *butt,
 		colname = "RHYTHMDB_PROP_LAST_PLAYED";
 	else if (butt == GTK_CHECK_BUTTON (shell_preferences->priv->year_check))
 		colname = "RHYTHMDB_PROP_DATE";
+	else if (butt == GTK_CHECK_BUTTON (shell_preferences->priv->bpm_check))
+		colname = "RHYTHMDB_PROP_BPM";
 	else if (butt == GTK_CHECK_BUTTON (shell_preferences->priv->quality_check))
 		colname = "RHYTHMDB_PROP_BITRATE";
 	else if (butt == GTK_CHECK_BUTTON (shell_preferences->priv->first_seen_check))
@@ -566,9 +571,12 @@ rb_shell_preferences_sync (RBShellPreferences *shell_preferences)
 			       				 shell_preferences->priv->first_seen_check,
 							 columns, "RHYTHMDB_PROP_FIRST_SEEN");
 		rb_shell_preferences_sync_column_button (shell_preferences,
-			       				 shell_preferences->priv->quality_check,
+							 shell_preferences->priv->quality_check,
 							 columns, "RHYTHMDB_PROP_BITRATE");
 		rb_shell_preferences_sync_column_button (shell_preferences,
+							 shell_preferences->priv->bpm_check,
+							 columns, "RHYTHMDB_PROP_BPM");
+		rb_shell_preferences_sync_column_button (shell_preferences,
 			       				 shell_preferences->priv->location_check,
 							 columns, "RHYTHMDB_PROP_LOCATION");
 	}
diff --git a/sources/rb-browser-source.c b/sources/rb-browser-source.c
index 9d837af..9007612 100644
--- a/sources/rb-browser-source.c
+++ b/sources/rb-browser-source.c
@@ -407,6 +407,7 @@ rb_browser_source_constructed (GObject *object)
 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_DURATION, FALSE);
  	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_QUALITY, FALSE);
 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_PLAY_COUNT, FALSE);
+	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_BPM, FALSE);
 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_COMMENT, FALSE);
 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_LOCATION, FALSE);
 
diff --git a/sources/rb-playlist-source.c b/sources/rb-playlist-source.c
index c37bc5a..f7cb118 100644
--- a/sources/rb-playlist-source.c
+++ b/sources/rb-playlist-source.c
@@ -373,6 +373,7 @@ rb_playlist_source_constructed (GObject *object)
 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_LOCATION, FALSE);
 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
 	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_FIRST_SEEN, FALSE);
+	rb_entry_view_append_column (source->priv->songs, RB_ENTRY_VIEW_COL_BPM, FALSE);
 	rb_entry_view_set_columns_clickable (source->priv->songs, FALSE);
 
 	rb_playlist_source_setup_entry_view (source, source->priv->songs);
diff --git a/widgets/rb-entry-view.c b/widgets/rb-entry-view.c
index dabbe59..69832af 100644
--- a/widgets/rb-entry-view.c
+++ b/widgets/rb-entry-view.c
@@ -963,6 +963,31 @@ rb_entry_view_rating_cell_data_func (GtkTreeViewColumn *column,
 }
 
 static void
+rb_entry_view_bpm_cell_data_func (GtkTreeViewColumn *column,
+				   GtkCellRenderer *renderer,
+				   GtkTreeModel *tree_model,
+				   GtkTreeIter *iter,
+				   struct RBEntryViewCellDataFuncData *data)
+{
+	RhythmDBEntry *entry;
+	char *str;
+	gdouble val;
+
+	entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
+
+	val = rhythmdb_entry_get_double (entry, data->propid);
+
+	if (val > 0.001)
+		str = g_strdup_printf ("%.2f", val);
+	else
+		str = g_strdup ("");
+
+	g_object_set (renderer, "text", str, NULL);
+	g_free (str);
+	rhythmdb_entry_unref (entry);
+}
+
+static void
 rb_entry_view_long_cell_data_func (GtkTreeViewColumn *column,
 				   GtkCellRenderer *renderer,
 				   GtkTreeModel *tree_model,
@@ -1650,6 +1675,16 @@ rb_entry_view_append_column (RBEntryView *view,
 		key = "Location";
 		ellipsize = TRUE;
 		break;
+	case RB_ENTRY_VIEW_COL_BPM:
+		propid = RHYTHMDB_PROP_BPM;
+		cell_data->propid = propid;
+		cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_bpm_cell_data_func;
+		sort_func = (GCompareDataFunc) rhythmdb_query_model_double_ceiling_sort_func;
+		title = _("BPM");
+		key = "BPM";
+		strings[0] = title;
+		strings[1] = "999.99";
+		break;
 	case RB_ENTRY_VIEW_COL_ERROR:
 		propid = RHYTHMDB_PROP_PLAYBACK_ERROR;
 		cell_data->propid = RHYTHMDB_PROP_PLAYBACK_ERROR;
@@ -2692,6 +2727,7 @@ rb_entry_view_column_get_type (void)
 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_FIRST_SEEN, "First Seen"),
 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_SEEN, "Last Seen"),
 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_LOCATION, "Location"),
+			ENUM_ENTRY (RB_ENTRY_VIEW_COL_BPM, "BPM"),
 			ENUM_ENTRY (RB_ENTRY_VIEW_COL_ERROR, "Error"),
 			{ 0, 0, 0 }
 		};
diff --git a/widgets/rb-entry-view.h b/widgets/rb-entry-view.h
index 03f5fa6..ff2612b 100644
--- a/widgets/rb-entry-view.h
+++ b/widgets/rb-entry-view.h
@@ -59,6 +59,7 @@ typedef enum {
 	RB_ENTRY_VIEW_COL_FIRST_SEEN,
 	RB_ENTRY_VIEW_COL_LAST_SEEN,
 	RB_ENTRY_VIEW_COL_LOCATION,
+	RB_ENTRY_VIEW_COL_BPM,
 	RB_ENTRY_VIEW_COL_ERROR
 } RBEntryViewColumn;
 
diff --git a/widgets/rb-query-creator-properties.c b/widgets/rb-query-creator-properties.c
index 5175bc1..f6dfbbb 100644
--- a/widgets/rb-query-creator-properties.c
+++ b/widgets/rb-query-creator-properties.c
@@ -39,6 +39,7 @@
 const RBQueryCreatorPropertyType string_property_type;
 const RBQueryCreatorPropertyType escaped_string_property_type;
 const RBQueryCreatorPropertyType rating_property_type;
+const RBQueryCreatorPropertyType double_property_type;
 const RBQueryCreatorPropertyType integer_property_type;
 const RBQueryCreatorPropertyType year_property_type;
 const RBQueryCreatorPropertyType duration_property_type;
@@ -52,6 +53,9 @@ static void escapedStringCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
 static GtkWidget * ratingCriteriaCreateWidget (gboolean *constrain);
 static void ratingCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
 static void ratingCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
+static GtkWidget * doubleCriteriaCreateWidget (gboolean *constrain);
+static void doubleCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
+static void doubleCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
 static GtkWidget * integerCriteriaCreateWidget (gboolean *constrain);
 static void integerCriteriaSetWidgetData (GtkWidget *widget, GValue *val);
 static void integerCriteriaGetWidgetData (GtkWidget *widget, GValue *val);
@@ -86,7 +90,7 @@ const RBQueryCreatorPropertyOption property_options[] =
 	{ NC_("query-criteria", "Bitrate"), RHYTHMDB_PROP_BITRATE, RHYTHMDB_PROP_BITRATE, &integer_property_type },
 
 	{ NC_("query-criteria", "Duration"), RHYTHMDB_PROP_DURATION, RHYTHMDB_PROP_DURATION, &duration_property_type },
-
+	{ NC_("query-criteria", "Beats Per Minute"), RHYTHMDB_PROP_BPM, RHYTHMDB_PROP_BPM, &double_property_type },
 	{ NC_("query-criteria", "Time of Last Play"), RHYTHMDB_PROP_LAST_PLAYED, RHYTHMDB_PROP_LAST_PLAYED, &relative_time_property_type },
 	{ NC_("query-criteria", "Time Added to Library"), RHYTHMDB_PROP_FIRST_SEEN, RHYTHMDB_PROP_FIRST_SEEN, &relative_time_property_type },
 };
@@ -112,6 +116,7 @@ const RBQueryCreatorSortOption sort_options[] =
 	{ NC_("query-sort", "Last Played"), "LastPlayed", N_("W_ith more recently played tracks first") },
 	{ NC_("query-sort", "Date Added"), "FirstSeen", N_("W_ith more recently added tracks first") },
 	{ NC_("query-sort", "Comment"), "Comment", N_("_In reverse alphabetical order") },
+	{ NC_("query-sort", "Beats Per Minute"), "BPM", N_("W_ith faster tempo tracks first") },
 };
 
 const int num_sort_options = G_N_ELEMENTS (sort_options);
@@ -183,6 +188,15 @@ const RBQueryCreatorPropertyType rating_property_type =
 	ratingCriteriaGetWidgetData
 };
 
+const RBQueryCreatorPropertyType double_property_type =
+{
+	G_N_ELEMENTS (numeric_criteria_options),
+	numeric_criteria_options,
+	doubleCriteriaCreateWidget,
+	doubleCriteriaSetWidgetData,
+	doubleCriteriaGetWidgetData
+};
+
 const RBQueryCreatorPropertyType integer_property_type =
 {
 	G_N_ELEMENTS (numeric_criteria_options),
@@ -336,6 +350,37 @@ ratingCriteriaGetWidgetData (GtkWidget *widget, GValue *val)
 }
 
 /*
+ * Implementation for the double properties, using a single GtkSpinButton.
+ */
+
+static GtkWidget *
+doubleCriteriaCreateWidget (gboolean *constrain)
+{
+	GtkWidget *spin;
+	spin = gtk_spin_button_new_with_range (0.0, G_MAXDOUBLE, 1.0);
+	gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), 2);
+	return spin;
+}
+
+static void
+doubleCriteriaSetWidgetData (GtkWidget *widget, GValue *val)
+{
+	gdouble num = g_value_get_double (val);
+	g_assert (num <= G_MAXDOUBLE);
+
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), num );
+}
+
+static void
+doubleCriteriaGetWidgetData (GtkWidget *widget, GValue *val)
+{
+	gdouble num = gtk_spin_button_get_value (GTK_SPIN_BUTTON (widget));
+	g_assert (num >= 0);
+
+	g_value_init (val, G_TYPE_DOUBLE);
+	g_value_set_double (val, num);
+}
+/*
  * Implementation for the integer properties, using a single GtkSpinButton.
  */
 
@@ -519,7 +564,7 @@ relativeTimeCriteriaCreateWidget (gboolean *constrain)
 	timeOption = create_time_unit_option_menu (time_unit_options, G_N_ELEMENTS (time_unit_options));
 	gtk_combo_box_set_active (GTK_COMBO_BOX (timeOption), time_unit_options_default);
 	gtk_box_pack_start (box, timeOption, TRUE, TRUE, 0);
-	
+
 	g_signal_connect_object (timeOption, "changed",
 				 G_CALLBACK (update_time_unit_limits),
 				 timeSpin, 0);
diff --git a/widgets/rb-song-info.c b/widgets/rb-song-info.c
index 59f1ef1..c0773d2 100644
--- a/widgets/rb-song-info.c
+++ b/widgets/rb-song-info.c
@@ -135,6 +135,7 @@ struct RBSongInfoPrivate
 	GtkTextBuffer *comment_buffer;
 	GtkWidget   *playback_error_box;
 	GtkWidget   *playback_error_label;
+	GtkWidget   *bpm;
 
 	GtkWidget   *artist_sortname;
 	GtkWidget   *album_sortname;
@@ -379,6 +380,7 @@ rb_song_info_construct_single (RBSongInfo *song_info, GtkBuilder *builder, gbool
 	song_info->priv->track_cur     = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_track_cur"));
 	song_info->priv->bitrate       = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_bitrate"));
 	song_info->priv->duration      = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_duration"));
+	song_info->priv->bpm           = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_bpm"));
 	song_info->priv->location = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_location"));
 	song_info->priv->filesize = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_filesize"));
 	song_info->priv->date_added    = GTK_WIDGET (gtk_builder_get_object (builder, "song_info_dateadded"));
@@ -396,6 +398,7 @@ rb_song_info_construct_single (RBSongInfo *song_info, GtkBuilder *builder, gbool
 	rb_builder_boldify_label (builder, "play_count_label");
 	rb_builder_boldify_label (builder, "duration_label");
 	rb_builder_boldify_label (builder, "bitrate_label");
+	rb_builder_boldify_label (builder, "bpm_label");
 
 	/* whenever you press a mnemonic, the associated GtkEntry's text gets highlighted */
 	g_signal_connect_object (G_OBJECT (song_info->priv->title),
@@ -925,6 +928,18 @@ rb_song_info_populate_num_field (GtkEntry *field, gulong num)
 }
 
 static void
+rb_song_info_populate_dnum_field (GtkEntry *field, gdouble num)
+{
+	char *tmp;
+	if (num > 0)
+		tmp = g_strdup_printf ("%.2f", num);
+	else
+		tmp = g_strdup (_("Unknown"));
+	gtk_entry_set_text (field, tmp);
+	g_free (tmp);
+}
+
+static void
 rb_song_info_populate_dialog_multiple (RBSongInfo *song_info)
 {
 	gboolean mixed_artists = FALSE;
@@ -1055,6 +1070,7 @@ rb_song_info_populate_dialog (RBSongInfo *song_info)
 	const char *text;
 	char *tmp;
 	gulong num;
+	gdouble dnum;
 
 	g_assert (song_info->priv->current_entry);
 
@@ -1081,7 +1097,8 @@ rb_song_info_populate_dialog (RBSongInfo *song_info)
 	rb_song_info_populate_num_field (GTK_ENTRY (song_info->priv->track_cur), num);
 	num = rhythmdb_entry_get_ulong (song_info->priv->current_entry, RHYTHMDB_PROP_DISC_NUMBER);
 	rb_song_info_populate_num_field (GTK_ENTRY (song_info->priv->disc_cur), num);
-
+	dnum = rhythmdb_entry_get_double (song_info->priv->current_entry, RHYTHMDB_PROP_BPM);
+	rb_song_info_populate_dnum_field (GTK_ENTRY (song_info->priv->bpm), dnum);
 	text = rhythmdb_entry_get_string (song_info->priv->current_entry, RHYTHMDB_PROP_COMMENT);
 	gtk_text_buffer_set_text (song_info->priv->comment_buffer, text, -1);
 
@@ -1612,6 +1629,7 @@ rb_song_info_sync_entry_single (RBSongInfo *dialog)
 	const char *album_sortname;
 	const char *album_artist_sortname;
 	const char *entry_string;
+	const char *bpm_str;
 	char *comment = NULL;
 	char *endptr;
 	GType type;
@@ -1619,6 +1637,8 @@ rb_song_info_sync_entry_single (RBSongInfo *dialog)
 	gulong discnum;
 	gulong year;
 	gulong entry_val;
+	gdouble bpm;
+	gdouble dentry_val;
 	GValue val = {0,};
 	gboolean changed = FALSE;
 	RhythmDBEntry *entry = dialog->priv->current_entry;
@@ -1697,7 +1717,18 @@ rb_song_info_sync_entry_single (RBSongInfo *dialog)
 		if (date)
 			g_date_free (date);
 	}
-
+	bpm_str = gtk_entry_get_text (GTK_ENTRY (dialog->priv->bpm));
+	bpm = g_strtod (bpm_str, &endptr);
+	dentry_val = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_BPM);
+	if ((endptr != bpm_str) && (bpm != dentry_val)) {
+		type = rhythmdb_get_property_type (dialog->priv->db,
+						   RHYTHMDB_PROP_BPM);
+		g_value_init (&val, type);
+		g_value_set_double (&val, bpm);
+		rhythmdb_entry_set (dialog->priv->db, entry, RHYTHMDB_PROP_BPM, &val);
+		g_value_unset (&val);
+		changed = TRUE;
+	}
 	entry_string = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
 	if (g_strcmp0 (title, entry_string)) {
 		type = rhythmdb_get_property_type (dialog->priv->db,



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]