banshee r4372 - in trunk/banshee: . libbanshee src/Backends/Banshee.GStreamer/Banshee.GStreamer src/Core/Banshee.Services/Banshee.Preferences src/Core/Banshee.ThickClient/Banshee.Preferences.Gui



Author: abock
Date: Thu Aug 14 23:23:47 2008
New Revision: 4372
URL: http://svn.gnome.org/viewvc/banshee?rev=4372&view=rev

Log:
2008-08-14  Aaron Bockover  <abock gnome org>

    * src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs:
    Implement ReplayGain preference and cope with the volume format change

    * libbanshee/banshee-player.c:
    * libbanshee/banshee-player-pipeline.c:
    * libbanshee/banshee-player-private.h: Integrate the ReplayGain support
    into the pipeline/player object; store user volume in the player object
    since the pipeline volume may have a ReplayGain scale factor; use a
    double [0..1] to store the volume to make life easier

    * libbanshee/banshee-player-replaygain.c:
    * libbanshee/banshee-player-replaygain.h: Implemented ReplayGain volume
    scaling, reading data from GST_TAG_[TRACK|ALBUM]_[GAIN|PEAK] tags;
    implemented based on the http://replaygain.hydrogenaudio.org/ spec,
    including the 10 track scale history to implement average scale on
    tracks without ReplayGain data

    * src/Core/Banshee.ThickClient/Banshee.Preferences.Gui/NotebookPage.cs:
    Do not show empty sections

    * src/Core/Banshee.Services/Banshee.Preferences/Page.cs: Add an empty
    playback section

    * src/Core/Banshee.Services/Banshee.Preferences/SchemaPreference.cs:
    Support a notification callback when the schema value is toggled



Added:
   trunk/banshee/libbanshee/banshee-player-replaygain.c
   trunk/banshee/libbanshee/banshee-player-replaygain.h
Modified:
   trunk/banshee/ChangeLog
   trunk/banshee/libbanshee/Makefile.am
   trunk/banshee/libbanshee/banshee-player-pipeline.c
   trunk/banshee/libbanshee/banshee-player-private.h
   trunk/banshee/libbanshee/banshee-player.c
   trunk/banshee/libbanshee/libbanshee.mdp
   trunk/banshee/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Preferences/Page.cs
   trunk/banshee/src/Core/Banshee.Services/Banshee.Preferences/SchemaPreference.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Preferences.Gui/NotebookPage.cs

Modified: trunk/banshee/libbanshee/Makefile.am
==============================================================================
--- trunk/banshee/libbanshee/Makefile.am	(original)
+++ trunk/banshee/libbanshee/Makefile.am	Thu Aug 14 23:23:47 2008
@@ -17,6 +17,7 @@
 	banshee-player-equalizer.c \
 	banshee-player-missing-elements.c \
 	banshee-player-pipeline.c \
+	banshee-player-replaygain.c \
 	banshee-player-video.c \
 	banshee-ripper.c \
 	banshee-tagger.c \
@@ -29,6 +30,7 @@
 	banshee-player-missing-elements.h \
 	banshee-player-pipeline.h \
 	banshee-player-private.h \
+	banshee-player-replaygain.h \
 	banshee-player-video.h \
 	banshee-tagger.h
 

Modified: trunk/banshee/libbanshee/banshee-player-pipeline.c
==============================================================================
--- trunk/banshee/libbanshee/banshee-player-pipeline.c	(original)
+++ trunk/banshee/libbanshee/banshee-player-pipeline.c	Thu Aug 14 23:23:47 2008
@@ -31,6 +31,7 @@
 #include "banshee-player-video.h"
 #include "banshee-player-equalizer.h"
 #include "banshee-player-missing-elements.h"
+#include "banshee-player-replaygain.h"
 
 // ---------------------------------------------------------------------------
 // Private Functions
@@ -51,8 +52,12 @@
     
     value = gst_tag_list_get_value_index (tag_list, tag_name, 0);
 
-    if (value != NULL && player->tag_found_cb != NULL) {
-        player->tag_found_cb (player, tag_name, value);
+    if (value != NULL) {
+        _bp_replaygain_process_tag (player, tag_name, value);
+    
+        if (player->tag_found_cb != NULL) {
+            player->tag_found_cb (player, tag_name, value);
+        }
     }
 }
 
@@ -77,6 +82,7 @@
             gst_message_parse_state_changed (message, &old, &new, &pending);
             
             _bp_missing_elements_handle_state_changed (player, old, new);
+            _bp_replaygain_handle_state_changed (player, old, new, pending);
             
             if (player->state_changed_cb != NULL) {
                 player->state_changed_cb (player, old, new, pending);

Modified: trunk/banshee/libbanshee/banshee-player-private.h
==============================================================================
--- trunk/banshee/libbanshee/banshee-player-private.h	(original)
+++ trunk/banshee/libbanshee/banshee-player-private.h	Thu Aug 14 23:23:47 2008
@@ -82,6 +82,7 @@
     GstElement *equalizer;
     GstElement *preamp;
     gint equalizer_status;
+    gdouble current_volume;
     
     // Pipeline/Playback State
     GMutex *mutex;
@@ -104,6 +105,24 @@
     #ifdef HAVE_GST_PBUTILS
     GstInstallPluginsContext *install_plugins_context;
     #endif
+    
+    // ReplayGain State
+    gboolean replaygain_enabled;
+    
+    // ReplayGain history: stores the previous 10 scale factors
+    // and the current scale factor with the current at index 0
+    // and the oldest at index 10. History is used to compute 
+    // gain on a track where no adjustment information is present.
+    // http://replaygain.hydrogenaudio.org/player_scale.html
+    gdouble volume_scale_history[11];
+    gboolean volume_scale_history_shift;
+    gboolean current_scale_from_history;
+    
+    // ReplayGain cache
+    gdouble album_gain;
+    gdouble album_peak;
+    gdouble track_gain;
+    gdouble track_peak;
 };
 
 #endif /* _BANSHEE_PLAYER_PRIVATE_H */

Added: trunk/banshee/libbanshee/banshee-player-replaygain.c
==============================================================================
--- (empty file)
+++ trunk/banshee/libbanshee/banshee-player-replaygain.c	Thu Aug 14 23:23:47 2008
@@ -0,0 +1,173 @@
+//
+// banshee-player-replaygain.c
+//
+// Author:
+//   Aaron Bockover <abockover novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#include <math.h>
+#include "banshee-player-replaygain.h"
+
+// ---------------------------------------------------------------------------
+// Private Functions
+// ---------------------------------------------------------------------------
+
+static inline void
+bp_replaygain_debug (BansheePlayer *player)
+{
+    gint i;
+    for (i = 0; i < 11; i++) {
+       printf ("%g ", player->volume_scale_history[i]);
+    }
+    printf ("\n");
+}
+
+static void
+bp_replaygain_update_pipeline (BansheePlayer *player, 
+    gdouble album_gain, gdouble album_peak,
+    gdouble track_gain, gdouble track_peak)
+{
+    gdouble gain = album_gain == 0.0 ? album_gain : track_gain;
+    gdouble peak = album_peak == 0.0 ? album_peak : track_peak;
+    gdouble scale = 0.0;
+    
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
+    
+    if (gain == 0.0) {
+        gint i;
+        player->current_scale_from_history = TRUE;
+        // Compute the average scale from history
+        for (i = 1; i <= 10; i++) {
+            scale += player->volume_scale_history[i] / 10.0;
+        }
+    } else {
+        player->current_scale_from_history = FALSE;
+        scale = pow (10.0, gain / 20.0);
+        
+        if (peak != 0.0 && scale * peak > 1.0) {
+            scale = 1.0 / peak;
+        }
+        
+        if (scale > 15.0) {
+            scale = 15.0;
+        }
+    }
+    
+    player->volume_scale_history[0] = scale;
+    _bp_replaygain_update_volume (player);
+}
+
+// ---------------------------------------------------------------------------
+// Internal Functions
+// ---------------------------------------------------------------------------
+
+void
+_bp_replaygain_process_tag (BansheePlayer *player, const gchar *tag_name, const GValue *value)
+{
+    if (strcmp (tag_name, GST_TAG_ALBUM_GAIN) == 0) {
+        player->album_gain = g_value_get_double (value);
+    } else if (strcmp (tag_name, GST_TAG_ALBUM_PEAK) == 0) {
+        player->album_peak = g_value_get_double (value);
+    } else if (strcmp (tag_name, GST_TAG_TRACK_GAIN) == 0) {
+        player->track_gain = g_value_get_double (value);
+    } else if (strcmp (tag_name, GST_TAG_TRACK_PEAK) == 0) {
+        player->track_peak = g_value_get_double (value);
+    }
+}
+
+void 
+_bp_replaygain_handle_state_changed (BansheePlayer *player, GstState old, GstState new, GstState pending)
+{
+    if (old == GST_STATE_READY && new == GST_STATE_NULL && 
+        pending == GST_STATE_VOID_PENDING && player->volume_scale_history_shift) {
+        
+        memmove (player->volume_scale_history + 1, 
+            player->volume_scale_history, sizeof (gdouble) * 10);
+            
+        if (player->current_scale_from_history) {
+            player->volume_scale_history[1] = 1.0;
+        }
+        
+        player->volume_scale_history[0] = 1.0;
+        player->volume_scale_history_shift = FALSE;
+        
+        player->album_gain = player->album_peak = 0.0;
+        player->track_gain = player->track_peak = 0.0;
+    } else if (old == GST_STATE_READY && new == GST_STATE_PAUSED && 
+        pending == GST_STATE_PLAYING &&  !player->volume_scale_history_shift) {
+        
+        player->volume_scale_history_shift = TRUE;
+        
+        bp_replaygain_update_pipeline (player, 
+            player->album_gain, player->album_peak, 
+            player->track_gain, player->track_peak);
+    }
+}
+
+void
+_bp_replaygain_update_volume (BansheePlayer *player)
+{
+    GParamSpec *volume_spec;
+    GValue value = { 0, };
+    gdouble scale;
+    
+    if (player == NULL || player->playbin == NULL) {
+        return;
+    }
+    
+    scale = player->replaygain_enabled ? player->volume_scale_history[0] : 1.0;
+    
+    volume_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (player->playbin), "volume");
+    g_value_init (&value, G_TYPE_DOUBLE);
+    g_value_set_double (&value, player->current_volume * scale);
+    g_param_value_validate (volume_spec, &value);
+    
+    if (player->replaygain_enabled) {
+        bp_debug ("scaled volume: %f (ReplayGain) * %f (User) = %f", scale, player->current_volume, 
+            g_value_get_double (&value));
+    }
+    
+    g_object_set_property (G_OBJECT (player->playbin), "volume", &value);
+    g_value_unset (&value);
+}
+
+// ---------------------------------------------------------------------------
+// Public Functions
+// ---------------------------------------------------------------------------
+
+P_INVOKE void
+bp_replaygain_set_enabled (BansheePlayer *player, gboolean enabled)
+{
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
+    player->replaygain_enabled = enabled;
+    bp_debug ("%s ReplayGain", enabled ? "Enabled" : "Disabled");
+    _bp_replaygain_update_volume (player);
+}
+
+P_INVOKE gboolean
+bp_replaygain_get_enabled (BansheePlayer *player)
+{
+    g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
+    return player->replaygain_enabled;
+}

Added: trunk/banshee/libbanshee/banshee-player-replaygain.h
==============================================================================
--- (empty file)
+++ trunk/banshee/libbanshee/banshee-player-replaygain.h	Thu Aug 14 23:23:47 2008
@@ -0,0 +1,48 @@
+//
+// banshee-player-replaygain.h
+//
+// Author:
+//   Aaron Bockover <abockover novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#ifndef _BANSHEE_PLAYER_REPLAYGAIN_H
+#define _BANSHEE_PLAYER_REPLAYGAIN_H
+
+#include "banshee-player-private.h"
+
+void _bp_replaygain_process_tag          (BansheePlayer *player, const gchar *tag_name, const GValue *value);
+void _bp_replaygain_handle_state_changed (BansheePlayer *player, GstState old, GstState new, GstState pending);
+void _bp_replaygain_update_volume        (BansheePlayer *player);
+
+static inline void
+_bp_replaygain_init (BansheePlayer *player)
+{
+    gint i;
+    for (i = 0; i < 11; i++) {
+        player->volume_scale_history[i] = 1.0;
+    }
+    
+}
+
+#endif /* _BANSHEE_PLAYER_REPLAYGAIN_H */

Modified: trunk/banshee/libbanshee/banshee-player.c
==============================================================================
--- trunk/banshee/libbanshee/banshee-player.c	(original)
+++ trunk/banshee/libbanshee/banshee-player.c	Thu Aug 14 23:23:47 2008
@@ -30,6 +30,7 @@
 #include "banshee-player-pipeline.h"
 #include "banshee-player-cdda.h"
 #include "banshee-player-missing-elements.h"
+#include "banshee-player-replaygain.h"
 
 // ---------------------------------------------------------------------------
 // Private Functions
@@ -123,6 +124,8 @@
     
     player->mutex = g_mutex_new ();
     
+    _bp_replaygain_init (player);
+    
     if (!_bp_pipeline_construct (player)) {
         bp_destroy (player);
         return NULL;
@@ -268,19 +271,18 @@
 }
 
 P_INVOKE void
-bp_set_volume (BansheePlayer *player, gint volume)
+bp_set_volume (BansheePlayer *player, gdouble volume)
 {
     g_return_if_fail (IS_BANSHEE_PLAYER (player));
-    g_object_set (G_OBJECT (player->playbin), "volume", CLAMP (volume, 0, 100) / 100.0, NULL);
+    player->current_volume = CLAMP (volume, 0.0, 1.0);
+    _bp_replaygain_update_volume (player);
 }
 
-P_INVOKE gint
+P_INVOKE gdouble
 bp_get_volume (BansheePlayer *player)
 {
-    gdouble volume = 0.0;
-    g_return_val_if_fail (IS_BANSHEE_PLAYER (player), 0);
-    g_object_get (player->playbin, "volume", &volume, NULL);
-    return (gint)(volume * 100.0);
+    g_return_val_if_fail (IS_BANSHEE_PLAYER (player), 0.0);
+    return player->current_volume;
 }
 
 P_INVOKE gboolean

Modified: trunk/banshee/libbanshee/libbanshee.mdp
==============================================================================
--- trunk/banshee/libbanshee/libbanshee.mdp	(original)
+++ trunk/banshee/libbanshee/libbanshee.mdp	Thu Aug 14 23:23:47 2008
@@ -26,6 +26,8 @@
     <File name="banshee-tagger.h" subtype="Code" buildaction="Nothing" />
     <File name="banshee-gst.h" subtype="Code" buildaction="Nothing" />
     <File name="banshee-player-equalizer.h" subtype="Code" buildaction="Nothing" />
+    <File name="banshee-player-replaygain.c" subtype="Code" buildaction="Compile" />
+    <File name="banshee-player-replaygain.h" subtype="Code" buildaction="Nothing" />
   </Contents>
   <compiler ctype="GccCompiler" />
   <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="Makefile.am">

Modified: trunk/banshee/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
==============================================================================
--- trunk/banshee/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs	(original)
+++ trunk/banshee/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs	Thu Aug 14 23:23:47 2008
@@ -37,6 +37,8 @@
 using Banshee.Streaming;
 using Banshee.MediaEngine;
 using Banshee.ServiceStack;
+using Banshee.Configuration;
+using Banshee.Preferences;
 
 namespace Banshee.GStreamer
 {
@@ -74,7 +76,7 @@
         private GstTaggerTagFoundCallback tag_found_callback;
         
         private bool buffering_finished;
-        private short pending_volume = -1;
+        private int pending_volume = -1;
         private bool xid_is_set = false;
         
         public PlayerEngine ()
@@ -126,10 +128,14 @@
             if (pending_volume >= 0) {
                 Volume = (ushort)pending_volume;
             }
+            
+            InstallPreferences ();
+            ReplayGainEnabled = ReplayGainEnabledSchema.Get ();
         }
         
         public override void Dispose ()
         {
+            UninstallPreferences ();
             base.Dispose ();
             bp_destroy (handle);
         }
@@ -314,14 +320,14 @@
         }
         
         public override ushort Volume {
-            get { return (ushort)bp_get_volume (handle); }
+            get { return (ushort)Math.Round (bp_get_volume (handle) * 100.0); }
             set { 
                 if ((IntPtr)handle == IntPtr.Zero) {
-                    pending_volume = (short)value;
+                    pending_volume = value;
                     return;
                 }
                 
-                bp_set_volume (handle, (int)value);
+                bp_set_volume (handle, value / 100.0);
                 OnEventChanged (PlayerEvent.Volume);
             }
         }
@@ -329,7 +335,7 @@
         public override uint Position {
             get { return (uint)bp_get_position(handle); }
             set { 
-                bp_set_position(handle, (ulong)value);
+                bp_set_position (handle, (ulong)value);
                 OnEventChanged (PlayerEvent.Seek);
             }
         }
@@ -425,6 +431,49 @@
             get { return decoder_capabilities; }
         }
         
+        private bool ReplayGainEnabled {
+            get { return bp_replaygain_get_enabled (handle); }
+            set { bp_replaygain_set_enabled (handle, value); }
+        }
+        
+#region Preferences
+
+        private PreferenceBase replaygain_preference;
+
+        private void InstallPreferences ()
+        {
+            PreferenceService service = ServiceManager.Get<PreferenceService> ();
+            if (service == null) {
+                return;
+            }
+            
+            replaygain_preference = service["general"]["playback"].Add (new SchemaPreference<bool> (ReplayGainEnabledSchema, 
+                Catalog.GetString ("_Enable ReplayGain Correction"),
+                Catalog.GetString ("For tracks that have ReplayGain data, automatically scale (normalize) playback volume."),
+                delegate { ReplayGainEnabled = ReplayGainEnabledSchema.Get (); }
+            ));
+        }
+        
+        private void UninstallPreferences ()
+        {
+            PreferenceService service = ServiceManager.Get<PreferenceService> ();
+            if (service == null) {
+                return;
+            }
+            
+            service["general"]["playback"].Remove (replaygain_preference);
+            replaygain_preference = null;
+        }
+        
+        public static readonly SchemaEntry<bool> ReplayGainEnabledSchema = new SchemaEntry<bool> (
+            "player_engine", "replay_gain_enabled", 
+            false,
+            "Enable ReplayGain",
+            "If ReplayGain data is present on tracks when playing, allow volume scaling"
+        );
+
+#endregion
+        
         [DllImport ("libbanshee")]
         private static extern IntPtr bp_new ();
         
@@ -466,10 +515,10 @@
         private static extern void bp_play (HandleRef player);
         
         [DllImport ("libbanshee")]
-        private static extern void bp_set_volume (HandleRef player, int volume);
+        private static extern void bp_set_volume (HandleRef player, double volume);
         
         [DllImport("libbanshee")]
-        private static extern int bp_get_volume (HandleRef player);
+        private static extern double bp_get_volume (HandleRef player);
         
         [DllImport ("libbanshee")]
         private static extern bool bp_can_seek (HandleRef player);
@@ -521,5 +570,11 @@
         [DllImport ("libbanshee")]
         private static extern void bp_equalizer_get_frequencies (HandleRef player,
             [MarshalAs (UnmanagedType.LPArray)] out double [] freq);
+            
+        [DllImport ("libbanshee")]
+        private static extern void bp_replaygain_set_enabled (HandleRef player, bool enabled);
+        
+        [DllImport ("libbanshee")]
+        private static extern bool bp_replaygain_get_enabled (HandleRef player);
     }
 }

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Preferences/Page.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Preferences/Page.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Preferences/Page.cs	Thu Aug 14 23:23:47 2008
@@ -73,6 +73,8 @@
             
             file_system.Add (new SchemaPreference<string> (LibrarySchema.FilePattern,     
                 Catalog.GetString ("File _name")));
+                
+            general.Add (new Section ("playback", Catalog.GetString ("Playback"), 20));
             
             service.Add (new Page ("extensions", Catalog.GetString ("Extensions"), 10));
         }

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Preferences/SchemaPreference.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Preferences/SchemaPreference.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Preferences/SchemaPreference.cs	Thu Aug 14 23:23:47 2008
@@ -32,22 +32,37 @@
 
 namespace Banshee.Preferences
 {
+    public delegate void SchemaPreferenceUpdatedHandler ();
+
     public class SchemaPreference<T> : Preference<T>
     {
         private SchemaEntry<T> schema;
+        private SchemaPreferenceUpdatedHandler handler;
         
         public SchemaPreference (SchemaEntry<T> schema, string name) : this (schema, name, null)
         {
         }
         
-        public SchemaPreference (SchemaEntry<T> schema, string name, string description) : base (schema.Key, name, description)
+        public SchemaPreference (SchemaEntry<T> schema, string name, string description) 
+            : this (schema, name, description, null)
+        {
+        }
+        
+        public SchemaPreference (SchemaEntry<T> schema, string name, string description, SchemaPreferenceUpdatedHandler handler) 
+            : base (schema.Key, name, description)
         {
             this.schema = schema;
+            this.handler = handler;
         }
         
         public override T Value {
             get { return schema.Get (); }
-            set { schema.Set (value); }
+            set { 
+                schema.Set (value); 
+                if (handler != null) {
+                    handler ();
+                }
+            }
         }
     }
 }

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Preferences.Gui/NotebookPage.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Preferences.Gui/NotebookPage.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Preferences.Gui/NotebookPage.cs	Thu Aug 14 23:23:47 2008
@@ -70,6 +70,10 @@
         {
             Frame frame = null;
             
+            if (section.Count == 0) {
+                return;
+            }
+            
             if (section.ShowLabel) {
                 frame = new Frame ();
                 Label label = new Label ();



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