//  BMP
//  Copyright (C) 2003-2007 BMP Development
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  --
//
//  The BMPx project hereby grants permission for non GPL-compatible GStreamer
//  plugins to be used and distributed together with GStreamer and BMPx. This
//  permission is above and beyond the permissions granted by the GPL license
//  BMPx is covered by.

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif //HAVE_CONFIG_H

#include <revision.h>
#include <build.h>

#include <utility>
#include <iostream>

#include <glibmm.h>
#include <glib/gi18n.h>
#include <gtkmm.h>
#include <libglademm.h>

#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>

#ifdef HAVE_ALSA
#  define ALSA_PCM_NEW_HW_PARAMS_API
#  define ALSA_PCM_NEW_SW_PARAMS_API

#  include <alsa/global.h>
#  include <alsa/asoundlib.h>
#  include <alsa/pcm_plugin.h>
#  include <alsa/control.h>
#endif //HAVE_ALSA

#include <mcs/mcs.h>

#ifdef HAVE_HAL
#  include <libhal.h>
#  include <libhal-storage.h>
#  include "x_hal.hh"
#  include "dialog-manage-volume.hh"
#endif

//BMP Widgets
#include "widgets/taskdialog.hh"

//BMP Audio
#include "audio/audio-typefind.hh"

#include "x_library.hh"
#include "x_mcsbind.hh"

#include "amazon.hh"
#include "audio/play.hh"
#include "core.hh"
#include "lastfm.hh"
#include "main.hh"
#include "paths.hh"
#include "network.hh"
#include "stock.hh"
#include "ui-tools.hh"
#include "util.hh"
#include "util-file.hh"

#include "dialog-progress.hh"
#include "dialog-simple-progress.hh"

#include "preferences.hh"

using namespace Glib;
using namespace Gtk;
using namespace Bmp::DB;
using namespace Bmp::Audio;

namespace Bmp
{
  namespace
  {
    static boost::format gbyte_f  (_("%.2Lf GiB"));
    static boost::format mbyte_f  (_("%.2f MiB"));
    static boost::format uint64_f ("%llu");

    const double MBYTE = 1048576;

    std::string
    get_size_string (guint64 size_)
    {
      double size = size_/MBYTE;
      return (mbyte_f % size).str();

      /* FIXME
      if (size > 1024)
        return (gbyte_f % (size / 1024.)).str();
      else
        return (mbyte_f % size).str();
      */ 
    }

    struct AudioSystem
    {
        char const* description;
        char const* name;
        gint        tab;
        Sink        sink;
    };

    AudioSystem audiosystems[] =
    {
        {"Automatic Choice",
         "autoaudiosink",
         0,
         SINK_AUTO},

        {"GNOME Configured Audio Output",
         "gconfaudiosink",
         1,
         SINK_GCONF},

#ifdef HAVE_ALSA
        {"ALSA Audio Output",
         "alsasink",
         2,
         SINK_ALSA},
#endif //HAVE_ALSA

        {"OSS",
         "osssink",
         3,
         SINK_OSS},

#ifdef HAVE_SUN
        {"Sun/Solaris Audio",
         "sunaudiosink",
         4,
         SINK_SUNAUDIO},
#endif // HAVE_SUN

        {"ESD",
         "esdsink",
         5,
         SINK_ESD},

#ifdef HAVE_HAL
        {"HAL",
         "halaudiosink",
         6,
         SINK_HAL},
#endif // HAVE_HAL

        {"PulseAudio",
         "pulsesink",
         7,
         SINK_PULSEAUDIO},

        {"JACK",
         "jackaudiosink",
         8,
         SINK_JACKSINK},

    };
  } // <anonymous> namespace

  //-- Preferences::HALView implementation -------------------------------------

#ifdef HAVE_HAL

  int
  Preferences::HALView::store_sort_func ( TreeModel::iterator const& iter_a,
                                          TreeModel::iterator const& iter_b)
  {
    HAL::Volume const& vol_a ((*iter_a)[volume_cr.volume]);
    HAL::Volume const& vol_b ((*iter_a)[volume_cr.volume]);

    return vol_a.volume_udi.compare (vol_b.volume_udi);
  }

  void
  Preferences::HALView::hal_volume_add (HAL::Volume const& volume)
  {
    MVolumes::iterator v_iter = m_volumes.find (VolumeKey (volume.volume_udi, volume.device_udi));

    if (v_iter != m_volumes.end())
    {
      TreeModel::iterator iter = m_list_store->get_iter (v_iter->second.get_path());
      try{ 
        (*iter)[volume_cr.mounted] = hal->volume_is_mounted  (volume);
        (*iter)[volume_cr.voltype] = hal->get_volume_type    (volume);
        (*iter)[volume_cr.volume]  = volume;
        (*iter)[volume_cr.volname] = volume.label;
        m_list_store->row_changed (v_iter->second.get_path(), iter);
      }
      catch (HAL::InvalidVolumeSpecifiedError & cxe)
      {
        m_list_store->erase (iter);
      }
    }
    else
    if (Library::Obj()->volume_exists (volume.volume_udi, volume.device_udi))
    {
      TreeModel::iterator iter (m_list_store->append());
      try{ 
        (*iter)[volume_cr.mounted] = hal->volume_is_mounted  (volume);
        (*iter)[volume_cr.voltype] = hal->get_volume_type    (volume);
        (*iter)[volume_cr.volume]  = volume;
        (*iter)[volume_cr.volname] = volume.label;
        m_volumes[VolumeKey (volume.volume_udi, volume.device_udi)] = TreeModel::RowReference (m_list_store, m_list_store->get_path (iter));
      }
      catch (HAL::InvalidVolumeSpecifiedError & cxe)
      {
        m_list_store->erase (iter);
      }
    }

    update_volume_details ();
  }

  void
  Preferences::HALView::hal_volume_del (HAL::Volume const& volume)
  {
    MVolumes::iterator v_iter (m_volumes.find ( VolumeKey (volume.volume_udi, volume.device_udi)));

    if (v_iter != m_volumes.end())
    {
      TreeModel::iterator iter (m_list_store->get_iter (v_iter->second.get_path()));
      (*iter)[volume_cr.mounted] = false;
      m_list_store->row_changed (v_iter->second.get_path(), iter);
    }
    else
    {
      //FIXME: Error...
    }

    update_volume_details ();
  }

  // Model Celldata Functions

  void
  Preferences::HALView::cell_data_func_mounted (CellRenderer*              cell_,
                                                TreeModel::iterator const& iter)
  {
    CellRendererPixbuf* cell = dynamic_cast<CellRendererPixbuf*> (cell_);
    bool mounted = (*iter)[volume_cr.mounted];
    cell->property_pixbuf() = render_icon (StockID (mounted ? BMP_STOCK_YES : BMP_STOCK_NO), ICON_SIZE_MENU);
  }

  void
  Preferences::HALView::cell_data_func_mountpath (CellRenderer*              cell_,
                                                  TreeModel::iterator const& iter)
  {
    CellRendererText* cell = dynamic_cast<CellRendererText*> (cell_);
    HAL::Volume const& volume = (*iter)[volume_cr.volume];
    bool mounted = (*iter)[volume_cr.mounted];
    cell->property_text () = mounted ? volume.mount_point : std::string();
  }

  void
  Preferences::HALView::cell_data_func_device_udi (CellRenderer*   cell_,
                                                   TreeModel::iterator const& iter)
  {
    CellRendererText* cell = dynamic_cast<CellRendererText*> (cell_);
    HAL::Volume const& volume = (*iter)[volume_cr.volume];
    cell->property_text () = volume.device_udi;
  }

  void
  Preferences::HALView::cell_data_func_volume_udi
                            (CellRenderer *  cell_, TreeModel::iterator const& iter)
  {
    CellRendererText* cell = dynamic_cast<CellRendererText*> (cell_);
    HAL::Volume const& volume = (*iter)[volume_cr.volume];
    cell->property_text () = volume.volume_udi;
  }

  void
  Preferences::HALView::update_volume_details ()
  {
    bool selected (get_selection()->count_selected_rows ());

    if (!selected)
    {
      dynamic_cast<Button*> (m_ref_xml->get_widget ("preferences-button-manage-volume"))->set_sensitive (false);

      for (unsigned int n = 0; n < N_LABELS; ++n)
      {
        m_label[n]->set_text (_("(nothing selected)"));
      }

      m_ref_xml->get_widget ("preferences-library-vbox-hal-details")->set_sensitive (false);
    }
    else
    {
      TreeModel::iterator iter (get_selection()->get_selected());
      HAL::Volume const& volume = (*iter)[volume_cr.volume];

      bool mounted = (*iter)[volume_cr.mounted];

      dynamic_cast<Button*> (m_ref_xml->get_widget ("preferences-button-manage-volume"))->set_sensitive (mounted);

      m_label[L_MOUNT_PATH]->set_text (mounted ? volume.mount_point : _("(Not Mounted)"));
      m_label[L_DEVICE_FILE]->set_text (mounted ? volume.device_file : std::string());
      m_label[L_SIZE]->set_text (get_size_string (volume.size));
      m_label[L_MOUNT_TIME]->set_text (Util::get_timestr (volume.mount_time, 0));
      m_ref_xml->get_widget ("preferences-library-vbox-hal-details")->set_sensitive (true);
    }
  }

  Preferences::HALView::HALView (BaseObjectType                       * obj,
                                 RefPtr<Gnome::Glade::Xml> const& xml)
      : TreeView    (obj),
        m_ref_xml   (xml)
  {
    if (hal->is_initialized())
    {
      get_selection()->set_mode (SELECTION_SINGLE);

      const char * label_names[] =
      {
        "hal-mount-path",
        "hal-device-file",
        "hal-size",
        "hal-mount-time"
      };

      for (unsigned int n = 0; n < N_LABELS; ++n)
      {
        m_ref_xml->get_widget (label_names[n], m_label[n]);
        m_label[n]->set_text (_("(nothing selected)"));
      }

      set_headers_clickable (false);
      set_headers_visible   (true);

      CellRendererText   *cell_text    = 0;
      CellRendererPixbuf *cell_pixbuf  = 0;

      cell_pixbuf = manage (new CellRendererPixbuf ());
      append_column (_("Mounted"), *cell_pixbuf);
      get_column (0)->set_resizable (false);
      get_column (0)->set_cell_data_func (*cell_pixbuf,
        sigc::mem_fun(this, &Preferences::HALView::cell_data_func_mounted));

      cell_text = manage (new CellRendererText ());
      append_column (_("Type"), *cell_text);
      get_column (1)->set_resizable (false);
      get_column (1)->add_attribute (*cell_text, "text", 0);

      cell_text = manage (new CellRendererText ());
      append_column (_("Name"), *cell_text);
      get_column (2)->set_resizable (false);
      get_column (2)->add_attribute (*cell_text, "text", 1);

      cell_text = manage (new CellRendererText ());
      append_column (_("Mount Path"), *cell_text);
      get_column (3)->set_resizable (false);
      get_column (3)->set_cell_data_func (*cell_text,
        sigc::mem_fun(this, &Preferences::HALView::cell_data_func_mountpath));

      cell_text = manage (new CellRendererText ());
      append_column (_("Volume UDI"), *cell_text);
      get_column (4)->set_resizable (false);
      get_column (4)->set_cell_data_func (*cell_text,
        sigc::mem_fun(this, &Preferences::HALView::cell_data_func_volume_udi));

      cell_text = manage (new CellRendererText ());
      append_column (_("Device UDI"), *cell_text);
      get_column (5)->set_resizable (false);
      get_column (5)->set_cell_data_func (*cell_text,
        sigc::mem_fun (*this, &Preferences::HALView::cell_data_func_device_udi));

      m_list_store = ListStore::create (volume_cr);
      m_list_store->set_default_sort_func
        (sigc::mem_fun (*this, &Preferences::HALView::store_sort_func));
      m_list_store->set_sort_column
        (TreeSortable::DEFAULT_SORT_COLUMN_ID, SORT_ASCENDING);

      rescan_devices ();
      set_model (m_list_store);

      m_ref_xml->get_widget ("preferences-library-vbox-hal-details")->set_sensitive (false);

      get_selection()->signal_changed().connect
        (sigc::mem_fun (*this, &Preferences::HALView::update_volume_details));
      hal->signal_volume_added().connect
        (sigc::mem_fun (*this, &Preferences::HALView::hal_volume_add));
      hal->signal_volume_removed().connect
        (sigc::mem_fun (*this, &Preferences::HALView::hal_volume_del));

      dynamic_cast <Button *> (m_ref_xml->get_widget ("preferences-button-manage-volume"))->signal_clicked().connect
        (sigc::mem_fun (*this, &Preferences::HALView::on_manage_volume));

      m_volume_manage_dialog = VolumeManageDialog::create (m_ref_xml);
    }
    else
    {
      set_headers_visible (false);
      set_sensitive (false);
    }
  }

  void
  Preferences::HALView::rescan_devices ()
  {
    set_sensitive (false);
    m_list_store->clear ();

    HAL::VVolumes vvolumes;
    hal->volumes (vvolumes);

    for (HAL::VVolumes::const_iterator v = vvolumes.begin (); v != vvolumes.end (); ++v)
    {
      if (!Library::Obj()->volume_exists ( v->volume_udi, v->device_udi )) continue;
      TreeModel::iterator iter (m_list_store->append());

      try{
            (*iter)[volume_cr.volume]  = *v;
            (*iter)[volume_cr.mounted] = hal->volume_is_mounted (*v);
            (*iter)[volume_cr.voltype] = hal->get_volume_type (*v);
            (*iter)[volume_cr.volname] = v->label;
            m_volumes[VolumeKey (v->volume_udi, v->device_udi)] = TreeModel::RowReference (m_list_store, m_list_store->get_path (iter));
        }
     catch (HAL::InvalidVolumeSpecifiedError & cxe)
        {
            m_list_store->erase (iter);
        }
    }
    set_sensitive (true);
  }

  void
  Preferences::HALView::on_manage_volume ()
  {
    TreeModel::iterator iter (get_selection()->get_selected());
    HAL::Volume const& volume = (*iter)[volume_cr.volume];

    m_volume_manage_dialog->go (volume);
    m_volume_manage_dialog->hide ();
    rescan_devices ();
  }
#endif //HAVE_HAL

  //-- Preferences implementation ----------------------------------------------
  Preferences *
  Preferences::create ()
  {
      const std::string path = BMP_GLADE_DIR G_DIR_SEPARATOR_S "preferences.glade";
      RefPtr<Gnome::Glade::Xml> glade_xml = Gnome::Glade::Xml::create (path);

      Preferences *preferences = 0;
      glade_xml->get_widget_derived ("preferences", preferences);
      return preferences;
  }

#ifdef HAVE_HAL
  void
  Preferences::rescan_devices ()
  {
    m_volumes_view->rescan_devices ();
  }
#endif //HAVE_HAL

  void
  Preferences::setup_lastfm ()
  {
    if (NM::Obj()->Check_Status())
    {
      mcs_bind->bind_entry (*dynamic_cast <Entry*>
          (m_ref_xml->get_widget ("last-fm-username")) , "lastfm", "username");
      mcs_bind->bind_entry (*dynamic_cast <Entry*>
          (m_ref_xml->get_widget ("last-fm-password")) , "lastfm", "password");

      mcs->subscribe ("Last.fm-Preferences", "lastfm", "username",
          sigc::mem_fun (*this, &Preferences::mcs_lastfm_credentials_changed));
      mcs->subscribe ("Last.fm-Preferences", "lastfm", "password",
          sigc::mem_fun (*this, &Preferences::mcs_lastfm_credentials_changed));

      dynamic_cast<Gtk::Image*>(m_ref_xml->get_widget ("preferences-lastfm-hs-state"))->set
        (m_state_err);
      dynamic_cast<Gtk::Image*>(m_ref_xml->get_widget ("preferences-scrobbler-hs-state"))->set
        (m_state_err);

      if( LastFM::Radio::Obj() )
      {
        LastFM::Radio::Obj()->signal_disconnected().connect
          (sigc::mem_fun (*this, &Bmp::Preferences::on_last_fm_radio_disconnected));
        LastFM::Radio::Obj()->signal_connected().connect
          (sigc::mem_fun (*this, &Bmp::Preferences::on_last_fm_radio_connected));
        dynamic_cast<Gtk::Button *>(m_ref_xml->get_widget ("preferences-lastfm-hs"))->signal_clicked().connect (
          sigc::mem_fun (*this, &Bmp::Preferences::on_last_fm_radio_handshake));
        on_last_fm_radio_handshake ();
      }
      else
        m_ref_xml->get_widget ("preferences-lastfm-hs")->set_sensitive (false);

      if( LastFM::Scrobbler::Obj() )
      {
        LastFM::Scrobbler::Obj()->signal_disconnected().connect
          (sigc::mem_fun (*this, &Bmp::Preferences::on_scrobbler_disconnected));
        LastFM::Scrobbler::Obj()->signal_connected().connect
          (sigc::mem_fun (*this, &Bmp::Preferences::on_scrobbler_connected));
        dynamic_cast<Gtk::Button *>(m_ref_xml->get_widget ("preferences-scrobbler-hs"))->signal_clicked().connect (
          sigc::mem_fun (*this, &Bmp::Preferences::on_scrobbler_handshake));
        m_ref_xml->get_widget ("preferences-scrobbler-hs")->set_sensitive (mcs->key_get <bool> ("lastfm", "queue-enable"));  
      }
      else
        m_ref_xml->get_widget ("preferences-scrobbler-hs")->set_sensitive (false);
    }
    else
    {
      m_ref_xml->get_widget ("preferences-lastfm-hs")->set_sensitive (false);
      m_ref_xml->get_widget ("preferences-scrobbler-hs")->set_sensitive (false);
      m_ref_xml->get_widget ("last-fm-username")->set_sensitive (false);
      m_ref_xml->get_widget ("last-fm-password")->set_sensitive (false);
    }

    m_ref_xml->get_widget ("last-fm-queue-enable", m_lastfm_queue_enable);
    mcs->subscribe ("Preferences", "lastfm", "queue-enable", sigc::mem_fun (*this, &Preferences::mcs_scrobbler_queue_enable_changed));
    mcs_bind->bind_toggle_button (*m_lastfm_queue_enable, "lastfm", "queue-enable");
  }

  void
  Preferences::mcs_lastfm_credentials_changed (MCS_CB_DEFAULT_SIGNATURE)
  {
    if( LastFM::Scrobbler::Obj() )
      LastFM::Scrobbler::Obj()->disconnect ();

    if( LastFM::Radio::Obj() )
      LastFM::Radio::Obj()->disconnect ();
  }

  void
  Preferences::mcs_scrobbler_queue_enable_changed (MCS_CB_DEFAULT_SIGNATURE)
  {
    m_ref_xml->get_widget ("preferences-scrobbler-hs")->set_sensitive (boost::get<bool>(value) && NM::Obj()->Check_Status());  
    while (gtk_events_pending()) gtk_main_iteration();
  }

  void
  Preferences::on_scrobbler_disconnected ()
  {
    dynamic_cast<Gtk::Image*>(m_ref_xml->get_widget ("preferences-scrobbler-hs-state"))->set (m_state_err);
    m_ref_xml->get_widget ("preferences-scrobbler-hs")->set_sensitive (1);
  }

  void
  Preferences::on_scrobbler_connected ()
  {
    dynamic_cast<Gtk::Image*>(m_ref_xml->get_widget ("preferences-scrobbler-hs-state"))->set (m_state_ok);
    m_ref_xml->get_widget ("preferences-scrobbler-hs")->set_sensitive (1);
  }

  void
  Preferences::on_scrobbler_handshake ()
  {
    LastFM::Scrobbler::Obj()->disconnect ();
    dynamic_cast<Gtk::Image*>(m_ref_xml->get_widget ("preferences-scrobbler-hs-state"))->set (m_throbber);
    m_ref_xml->get_widget ("preferences-scrobbler-hs")->set_sensitive (0);
    LastFM::Scrobbler::Obj()->handshake ();
  }

  void
  Preferences::on_last_fm_radio_disconnected ()
  {
    dynamic_cast<Gtk::Image*>(m_ref_xml->get_widget ("preferences-lastfm-hs-state"))->set (m_state_err);
    m_ref_xml->get_widget ("preferences-lastfm-hs")->set_sensitive (1);
  }

  void
  Preferences::on_last_fm_radio_connected ()
  {
    dynamic_cast<Gtk::Image*>(m_ref_xml->get_widget ("preferences-lastfm-hs-state"))->set (m_state_ok);
    m_ref_xml->get_widget ("preferences-lastfm-hs")->set_sensitive (1);
  }

  void
  Preferences::on_last_fm_radio_handshake ()
  {
    LastFM::Radio::Obj()->disconnect ();
    dynamic_cast<Gtk::Image*>(m_ref_xml->get_widget ("preferences-lastfm-hs-state"))->set (m_throbber);
    m_ref_xml->get_widget ("preferences-lastfm-hs")->set_sensitive (0);
    LastFM::Radio::Obj()->handshake ();
  }

  void
  Preferences::fetch_covers ()
  {
    SimpleProgress * p = SimpleProgress::create ();
    p->set_title (_("Fetching Album Covers"));
    p->percentage (0.);
    p->enable_cancel ();
    p->show_all ();
    double n = 0.;
    guint64 ctr = 0;
    StrV v = Library::Obj()->get_asins();
    for(StrV::iterator i = v.begin(); i != v.end(); ++i)
    {
        n = (1.*ctr) / (1.*v.size());
        ++ctr;
        p->percentage (n);
        try{
            Amazon::Covers::Obj()->cache( *i );
        } catch (...) {} 

        if(p->canceled())
            break;
    }
    delete p;
    Signals.LibraryUpdateRequest.emit();
  }

  void
  Preferences::import_tracks_cancel ()
  {
    g_atomic_int_set( &m_import_canceled, 1 );
  }

  void
  Preferences::import_tracks ()
  {
    Gtk::FileChooserDialog dialog (_("Select Path To Add - BMP"),
                                   Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);

    dialog.add_button (Gtk::Stock::CANCEL, RESPONSE_CANCEL);
    dialog.add_button (Gtk::Stock::ADD, RESPONSE_OK);
    dialog.set_current_folder (mcs->key_get<string>("bmp", "file-chooser-path"));
    dialog.set_default_response (Gtk::RESPONSE_CANCEL);
    dialog.set_default_size (750, 570);
    dialog.set_position (Gtk::WIN_POS_CENTER);

    if (dialog.run() == Gtk::RESPONSE_OK)
    {
      mcs->key_set<string>("bmp", "file-chooser-path", filename_from_uri (dialog.get_current_folder_uri()));
      string folder (filename_from_uri (dialog.get_uri ()));
      dialog.hide ();

#ifndef HAVE_HAL
      Library::Obj()->vacuum_nonexistent();
      Library::Obj()->vacuum_tables();
#else //HAVE_HAL

      // Check if this is a sub-path of some already present path
      std::string path_to_be_added = filename_from_uri (dialog.get_uri()) + G_DIR_SEPARATOR_S;
      HAL::Volume volume;

      try{
        volume = hal->get_volume_for_uri (dialog.get_uri());
        }
      catch (HAL::NoVolumeForUriError)
        {
          /* we're in SERIOUS trouble now */
          const std::string 
            message
            (_("<big>No volume for this path was found</big>\n\n"
                "BMPx' HAL backend was unable to identify on which volume this path is located.\n"
                "The adding operation can not proceed."));
          MessageDialog dialog (message, true, MESSAGE_ERROR, BUTTONS_OK, true);
          dialog.run ();
          return;
        }

      StrV insert_paths;
      Library::Obj()->volume_insert_paths (volume.volume_udi, volume.device_udi, insert_paths);

      for (StrV::const_iterator i = insert_paths.begin(); i != insert_paths.end(); ++i)
      {
        using boost::algorithm::split;
        using boost::algorithm::find_nth;
        using boost::iterator_range;

        string const& full_path = build_filename (volume.mount_point, (*i)) + G_DIR_SEPARATOR_S;

        if (path_to_be_added.substr (0, full_path.size()) == full_path) 
        {
          static boost::format message 
            (_("<b><big>Rescan Path</big></b>\n\n"
               "<b>%s</b>\n\n"
               "This path is already being managed by the Library. "
               "BMP will now open the rescan dialog to rescan this path instead of re-adding it. "
               "You will be given the option to abort rescanning at this time if you choose to."));

          MessageDialog * d = new MessageDialog ((message % full_path).str(),
            true, MESSAGE_INFO, BUTTONS_OK, true);
          d->run ();
          delete d;

          TaskDialog dialog ( this,
                              _("Rescan Path - BMP"),
                              _("Rescan the given Path (remove missing files, update existing and add new files)"),
                              MESSAGE_QUESTION);
          dialog.add_button ( _("Rescan"),
                              _("Rescan the Path right now"),
                              Gtk::Stock::APPLY,
                              Gtk::RESPONSE_OK);
          dialog.add_button ( _("Do not rescan"),
                              _("Do not rescan this Path at this time"),
                              Gtk::Stock::CANCEL,
                              Gtk::RESPONSE_CANCEL);
          dialog.set_default_response (Gtk::RESPONSE_OK);

          if (dialog.run() == Gtk::RESPONSE_OK)
          {
            Library::Obj()->vacuum_nonexistent ( volume.volume_udi,
                                          volume.device_udi,
                                          *i);
            scan_path (full_path);
            Library::Obj()->vacuum_tables ();
          }
          return;
        }
      }
#endif //HAVE_HAL
      scan_path (folder);
    }
  }

  void
  Preferences::scan_path (string const &folder)
  {
    Util::FileList list;
    Util::collect_audio_paths (folder, list);

    guint64 n_items   = list.size ();
    guint64 new_items = 0;
    guint64 upd_items = 0;
    guint64 nth_item  = 0;

    typedef boost::shared_ptr<ProgressDialog> ProgressDialogPtr;

    ProgressDialogPtr progress_dialog (ProgressDialog::create ());
    Library::Obj()->begin_txn ();

    g_atomic_int_set( &m_import_canceled, 0 );
    progress_dialog->signal_cancel().connect (sigc::mem_fun (*this, &Bmp::Preferences::import_tracks_cancel));

    for (Util::FileList::const_iterator i = list.begin (); i != list.end(); ++i)
    {
      AddOp op = LIBRARY_ADD_NOOP;

      try{
          op = Library::Obj()->insert (filename_to_uri (*i), folder);

          switch (op)
          {
            case LIBRARY_ADD_IMPORT:
              break;

            case LIBRARY_ADD_UPDATE:
              progress_dialog->add_file (op, *i, _("Updated"));
              break;

            default: break;
          }
        }
      catch (ConvertError & cxe)
        {
          progress_dialog->add_file (LIBRARY_ADD_ERROR, "",
                                     (boost::format (_("URI Conversion Error")) % cxe.what()).str());
          op = LIBRARY_ADD_ERROR;
        }
      catch (MetadataReadError & cxe)
        {
          progress_dialog->add_file (LIBRARY_ADD_ERROR, *i,
                                     (boost::format (_("No Metadata for this File: %s")) % cxe.what()).str());
          op = LIBRARY_ADD_ERROR;
        }
#ifdef HAVE_HAL
      catch (NoHALInformationError & cxe)
        {
          progress_dialog->add_file (LIBRARY_ADD_ERROR, *i,
                                     _("No HAL Volume/Device Information for this File"));
          op = LIBRARY_ADD_ERROR;
        }
#endif //HAVE_HAL

      switch (op)
      {
        case LIBRARY_ADD_IMPORT: new_items++; break;
        case LIBRARY_ADD_UPDATE: upd_items++; break;
        default: break;
      }
      progress_dialog->set_progress (n_items, ++nth_item);

      if(g_atomic_int_get(&m_import_canceled))
      {
        progress_dialog->done (0, 0, n_items, false);
        Library::Obj()->cancel_txn ();
        return;
      }
    }

    switch (progress_dialog->done (new_items, upd_items, n_items))
    {
      case Gtk::RESPONSE_CANCEL:
      {
        Library::Obj()->cancel_txn ();
        break;
      }

      case Gtk::RESPONSE_CLOSE:
      {
        Library::Obj()->close_txn ();
        Library::Obj()->vacuum_tables ();
#ifdef HAVE_HAL
        rescan_devices ();
#endif //HAVE_HAL
        Signals.LibraryUpdateRequest.emit();
        break;
      }
      default :;
    }
  }
  
  // Audio preferences
  void
  Preferences::audio_system_changed ()
  {
    m_notebook_audio_system->set_current_page ((*m_cbox_audio_system->get_active ())[audio_system_columns.tab]);
  }

  void
  Preferences::audio_system_apply_set_sensitive ()
  {
    m_button_audio_system_apply->set_sensitive (true);
    m_button_audio_system_reset->set_sensitive (true);
    m_warning_audio_system_changed->set_sensitive (true);
  }

  void
  Preferences::audio_system_apply ()
  {
    Play::Obj()->request_status (PLAYSTATUS_STOPPED);

    m_warning_audio_system_changed->set_sensitive (0);

    m_button_audio_system_apply->set_sensitive (0);
    m_button_audio_system_reset->set_sensitive (0);

    TreeModel::iterator iter (m_cbox_audio_system->get_active ());
    Sink sink = (*iter)[audio_system_columns.sink];
    std::string name = (*iter)[audio_system_columns.name];
    mcs->key_set<std::string> ("audio", "sink", name);

    switch (sink)
    {
#ifdef HAVE_ALSA
      case SINK_ALSA:
      {
        if (m_cbox_alsa_card->get_active_row_number () > 0)
        {
          TreeModel::iterator iter (m_cbox_alsa_device->get_active ());
          mcs->key_set<std::string> ("audio", "device-alsa", AlsaDevice ((*iter)[m_alsa_device_columns.device]).m_handle);
        }
        else
        {
          mcs->key_set<std::string> ("audio", "device-alsa", "default");
        }
      break;
      }
#endif //HAVE_ALSA
      default: ;
    }
    mcs->key_set <int> ("audio", "video-output", m_cbox_video_out->get_active_row_number());
    Play::Obj()->reset();
  }

#ifdef HAVE_ALSA
  void
  Preferences::on_alsa_card_changed ()
  {
    m_list_store_alsa_device->clear ();
    int row  = m_cbox_alsa_card->get_active_row_number ();

    if( row == -1 )
      return;

    AlsaCard const& card = (*m_cbox_alsa_card->get_active ())[m_alsa_card_columns.card];
    if ((row == 0) || card.m_devices.empty())
    {
      m_cbox_alsa_device->set_sensitive (false);
      return;
    }

    for (AlsaDevices::const_iterator i = card.m_devices.begin () ; i != card.m_devices.end() ; ++i)
    {
      TreeModel::iterator iter (m_list_store_alsa_device->append ());
      (*iter)[m_alsa_device_columns.name]   = i->m_name;
      (*iter)[m_alsa_device_columns.device] = *i;
    }

    m_cbox_alsa_device->set_sensitive (true);
    m_cbox_alsa_device->set_active (0);
  }

  Preferences::AlsaCards
  Preferences::get_alsa_cards ()
  {
    AlsaCards cards;
    int card_id (-1);
    while (!snd_card_next (&card_id) && (card_id > -1))
    {
      snd_ctl_t* control (0);
      if (!snd_ctl_open (&control, (boost::format ("hw:%d") % card_id).str().c_str(), SND_CTL_ASYNC))
      {
        snd_ctl_card_info_t * card_info (0);
        snd_ctl_card_info_malloc (&card_info);

        if (!snd_ctl_card_info (control, card_info))
        {
          using namespace std;

          string   card_handle   (snd_ctl_name (control));
          int      card_card_id  (snd_ctl_card_info_get_card (card_info));
          string   card_id       (snd_ctl_card_info_get_id (card_info));
          string   card_name     (snd_ctl_card_info_get_name (card_info));
          string   card_longname (snd_ctl_card_info_get_longname (card_info));
          string   card_driver   (snd_ctl_card_info_get_driver (card_info));
          string   card_mixer    (snd_ctl_card_info_get_mixername (card_info));

          AlsaDevices _devices;

          int device_id (-1);
          while (!snd_ctl_pcm_next_device (control, &device_id) && (device_id > -1))
          {
            snd_pcm_info_t * pcm_info (0);
            snd_pcm_info_malloc (&pcm_info);
            snd_pcm_info_set_device (pcm_info, device_id);

            if (!snd_ctl_pcm_info (control, pcm_info))
            {
              if (snd_pcm_info_get_stream (pcm_info) == SND_PCM_STREAM_PLAYBACK)
              {
                string device_handle  ((boost::format ("%s,%d") % snd_ctl_name (control) % device_id).str());
                string device_name    (snd_pcm_info_get_name (pcm_info));

                _devices.push_back (AlsaDevice (device_handle,
                                                card_card_id,
                                                device_id,
                                                device_name));
              }
            }
            if (pcm_info) snd_pcm_info_free (pcm_info);
          }

          if (_devices.size())
          {
            cards.push_back (AlsaCard ( card_handle,
                                        card_card_id,
                                        card_id,
                                        card_name,
                                        card_longname,
                                        card_driver,
                                        card_mixer,
                                        _devices ));
          }
        }
        if (card_info) snd_ctl_card_info_free (card_info);
      }
      if (control) snd_ctl_close (control);
    }
    return cards;
  }
#endif //HAVE_ALSA

#define PRESENT_SINK(n) ((m_sinks.find (n) != m_sinks.end()))
#define CURRENT_SINK(n) ((sink == n))
#define NONE_SINK (-1)

  Gtk::StockID
  Preferences::stock (bool truth)
  {
    return (truth ? StockID(BMP_STOCK_PLUGIN) : StockID(BMP_STOCK_PLUGIN_DISABLED));
  }

  void
  Preferences::setup_audio_widgets ()
  {
    audio_system_cbox_ids.resize(16);
 
    m_ref_xml->get_widget ("cbox_video_out", m_cbox_video_out);

    CellRendererText * cell;
    m_list_store_audio_systems = ListStore::create (audio_system_columns);
    cell = manage (new CellRendererText() );
    m_cbox_audio_system->clear ();
    m_cbox_audio_system->pack_start (*cell);
    m_cbox_audio_system->add_attribute (*cell, "text", 0);
    m_cbox_audio_system->set_model (m_list_store_audio_systems);

    std::string sink = mcs->key_get<std::string> ("audio", "sink");
    int cbox_counter = 0;
    for (unsigned int n = 0; n < G_N_ELEMENTS (audiosystems); n++)
    {
          if (test_element (audiosystems[n].name))
          {
 		audio_system_cbox_ids[n] = cbox_counter++;
                m_sinks.insert (audiosystems[n].name);
                TreeIter iter = m_list_store_audio_systems->append ();
                (*iter)[audio_system_columns.description] = audiosystems[n].description;
                (*iter)[audio_system_columns.name] = audiosystems[n].name;
                (*iter)[audio_system_columns.tab] = audiosystems[n].tab;
                (*iter)[audio_system_columns.sink] = audiosystems[n].sink;
          }
    }

#ifdef HAVE_ALSA
    if (PRESENT_SINK ("alsasink"))
    {
      m_list_store_alsa_cards = ListStore::create (m_alsa_card_columns);
      m_list_store_alsa_device = ListStore::create (m_alsa_device_columns);

      cell = manage (new CellRendererText () );
      m_cbox_alsa_card->clear ();
      m_cbox_alsa_card->pack_start (*cell);
      m_cbox_alsa_card->add_attribute (*cell, "text", 0);

      cell = manage (new CellRendererText () );
      m_cbox_alsa_device->clear ();
      m_cbox_alsa_device->pack_start (*cell);
      m_cbox_alsa_device->add_attribute (*cell, "text", 0);

      m_cbox_alsa_device->set_model (m_list_store_alsa_device);
      m_cbox_alsa_card->set_model (m_list_store_alsa_cards);

      m_cbox_alsa_card->signal_changed ().connect
        (sigc::mem_fun (*this, &Preferences::on_alsa_card_changed));

      mcs_bind->bind_spin_button (*m_alsa_buffer_time, "audio", "alsa-buffer-time");

      m_alsa_buffer_time->signal_value_changed().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));

      m_cbox_alsa_device->signal_changed().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));

      m_cbox_alsa_card->signal_changed().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
    }
#endif //HAVE_ALSA

#ifdef HAVE_SUN
    if (PRESENT_SINK("sunaudiosink"))
    {
      mcs_bind->bind_cbox_entry (*m_sun_cbe_device, "audio", "device-sun");
      mcs_bind->bind_spin_button (*m_sun_buffer_time, "audio", "sun-buffer-time");

      m_sun_buffer_time->signal_value_changed ().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
      m_sun_cbe_device->signal_changed ().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
    }
#endif //HAVE_SUN

    if (PRESENT_SINK("osssink"))
    {
      mcs_bind->bind_cbox_entry (*m_oss_cbe_device, "audio", "device-oss");
      mcs_bind->bind_spin_button (*m_oss_buffer_time, "audio", "oss-buffer-time");

      m_oss_cbe_device->signal_changed().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
      m_oss_buffer_time->signal_value_changed().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
    }

    if (PRESENT_SINK("esdsink"))
    {
      mcs_bind->bind_entry (*m_esd_host, "audio", "device-esd");
      mcs_bind->bind_spin_button (*m_esd_buffer_time, "audio", "esd-buffer-time");

      m_esd_buffer_time->signal_value_changed ().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
      m_esd_host->signal_changed ().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
    }

    if (PRESENT_SINK("pulsesink"))
    {
      mcs_bind->bind_entry (*m_pulse_server, "audio", "pulse-server");
      mcs_bind->bind_entry (*m_pulse_device, "audio", "pulse-device");
      mcs_bind->bind_spin_button (*m_pulse_buffer_time, "audio", "pulse-buffer-time");

      m_pulse_buffer_time->signal_value_changed ().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
      m_pulse_server->signal_changed ().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
      m_pulse_device->signal_changed ().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
    }

    if (PRESENT_SINK("jackaudiosink"))
    {
      mcs_bind->bind_entry (*m_jack_server, "audio", "jack-server");
      mcs_bind->bind_spin_button (*m_jack_buffer_time, "audio", "jack-buffer-time");

      m_jack_buffer_time->signal_value_changed ().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
      m_jack_server->signal_changed ().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
    }

#ifdef HAVE_HAL
    if (PRESENT_SINK("halaudiosink"))
    {
      mcs_bind->bind_entry (*m_halaudio_udi, "audio", "hal-udi");
      m_halaudio_udi->signal_changed ().connect
        (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));
    }
#endif // HAVE_HAL

    m_button_audio_system_apply->signal_clicked().connect
      (sigc::mem_fun (*this, &Preferences::audio_system_apply));

    m_button_audio_system_reset->signal_clicked().connect
      (sigc::mem_fun (*this, &Preferences::setup_audio));

    m_cbox_audio_system->signal_changed().connect
      (sigc::mem_fun (*this, &Preferences::audio_system_changed));

    m_cbox_audio_system->signal_changed().connect
      (sigc::mem_fun (*this, &Preferences::audio_system_apply_set_sensitive));

    // Setup Support Status Indicators
#if defined (HAVE_CDPARANOIA)
    bool has_cdda = test_element ("cdparanoiasrc");
#elif defined (HAVE_CDIO)
    bool has_cdda = test_element ("cdiocddasrc");
#endif
    bool has_mmsx = test_element ("mmssrc");
    bool has_http = true; // built-in http src 

    if (has_cdda)
      m_ref_xml->get_widget ("expander-cdda")->show_all ();

    dynamic_cast<Image*> (m_ref_xml->get_widget ("img_status_cdda"))->set
       (stock (has_cdda), ICON_SIZE_SMALL_TOOLBAR);

    dynamic_cast<Image*> (m_ref_xml->get_widget ("img_status_http"))->set
       (stock (has_http), ICON_SIZE_SMALL_TOOLBAR);

    dynamic_cast<Image*> (m_ref_xml->get_widget ("img_status_mms"))->set
       (stock (has_mmsx), ICON_SIZE_SMALL_TOOLBAR);

    struct SupportCheckTuple
    {
      char const* widget;
      char const* element;
    };

    const SupportCheckTuple support_check[] =
    {
      {"img_status_mpc",    "musepackdec"},
      {"img_status_flac",   "flacdec"},
      {"img_status_m4a",    "faad"},
      {"img_status_wma",    "ffdec_wmav2"},
      {"img_status_sid",    "siddec"},
      {"img_status_wav",    "wavparse"},
      {"img_status_mod",    "modplug"},
      {"img_status_spc",    "spcdec"},
    };

    // Check for either mad or flump3dec
    dynamic_cast<Image*> (m_ref_xml->get_widget ("img_status_mp3"))->set
        (stock ((test_element ("mad") ||
                  test_element ("flump3dec")) &&
                  test_element("id3demux") &&
                  test_element ("apedemux")),
        ICON_SIZE_SMALL_TOOLBAR);

    dynamic_cast<Image*> (m_ref_xml->get_widget ("img_status_ogg"))->set
      (stock (test_element ("oggdemux") && test_element("vorbisdec")), ICON_SIZE_SMALL_TOOLBAR);

    dynamic_cast<Image*> (m_ref_xml->get_widget ("img_status_vorbisenc"))->set
      (stock (test_element ("oggmux") && test_element("vorbisenc") && has_cdda), ICON_SIZE_SMALL_TOOLBAR);

    dynamic_cast<Image*> (m_ref_xml->get_widget ("img_status_mp3enc"))->set
      (stock (test_element ("lame") && has_cdda), ICON_SIZE_SMALL_TOOLBAR);

    dynamic_cast<Image*> (m_ref_xml->get_widget ("img_status_flacenc"))->set
      (stock (test_element ("flacenc") && has_cdda), ICON_SIZE_SMALL_TOOLBAR);

    for (unsigned int n = 0; n < G_N_ELEMENTS (support_check); ++n)
    {
      dynamic_cast<Image*> (m_ref_xml->get_widget (support_check[n].widget))->set
        (stock (test_element (support_check[n].element)), ICON_SIZE_SMALL_TOOLBAR);
    }

    bool video = (test_element ("filesrc") && test_element ("decodebin") && test_element ("queue") &&
                  test_element ("ffmpegcolorspace") && test_element ("xvimagesink"));
    
    dynamic_cast<Image*> (m_ref_xml->get_widget ("img_status_video"))->set (stock (video), ICON_SIZE_SMALL_TOOLBAR);
    m_cbox_video_out->signal_changed().connect
      (sigc::mem_fun( *this, &Preferences::audio_system_apply_set_sensitive) );

    dynamic_cast<ToggleButton *>(m_ref_xml->get_widget ("enable-eq"))->signal_toggled().connect
      (sigc::mem_fun( *this, &Preferences::audio_system_apply_set_sensitive) );
  }

  void
  Preferences::setup_audio ()
  {
    m_notebook_audio_system->set_current_page( 0 );

    std::string sink = mcs->key_get<std::string> ("audio", "sink");
    int x = NONE_SINK;

    for (unsigned int n = 0; n < G_N_ELEMENTS (audiosystems); n++)
    {
      if (PRESENT_SINK(audiosystems[n].name) && CURRENT_SINK(audiosystems[n].name))
      {
        x = n;
        break;
      }
    }

    if( x == NONE_SINK )
	x = SINK_AUTO; // we fallback to autoaudiosink if garbage or unavailable output is in the config file
	
    m_cbox_audio_system->set_active (audio_system_cbox_ids[x]);
  
    if( x != NONE_SINK )
    {
      m_notebook_audio_system->set_current_page (audiosystems[x].tab);
    }

#ifdef HAVE_ALSA
    if (PRESENT_SINK ("alsasink"))
    {
      AlsaCards cards (get_alsa_cards());

      m_list_store_alsa_cards->clear ();
      m_list_store_alsa_device->clear ();

      TreeModel::iterator iter = m_list_store_alsa_cards->append ();
      (*iter)[m_alsa_card_columns.name] = _("System Default");
      for (AlsaCards::iterator i = cards.begin (); i != cards.end (); ++i)
      {
        iter = m_list_store_alsa_cards->append ();
        (*iter)[m_alsa_card_columns.name] = ustring (*i);
        (*iter)[m_alsa_card_columns.card] = *i;
      }

      if (CURRENT_SINK ("alsasink"))
      {
        std::string device (mcs->key_get<std::string> ("audio", "device-alsa"));
        if (device == "default")
          m_cbox_alsa_card->set_active (0);
        else
        {
          StrV subs;

          using namespace boost::algorithm;
          split (subs, device, is_any_of (":,"));

          if (subs.size() && subs[0] == "hw")
          {
            int card (atoi (subs[1].c_str ()));
            int device (atoi (subs[2].c_str ()));
            m_cbox_alsa_card->set_active (card);
            m_cbox_alsa_device->set_active (device);
          }
        }
      }
    }
#endif //HAVE_ALSA

#ifdef HAVE_SUN
    if (PRESENT_SINK("sunaudiosink"))
    {
      mcs->key_push ("audio", "device-sun");
      mcs->key_push ("audio", "sun-buffer-time");
    }
#endif //HAVE_SUN

    if (PRESENT_SINK("osssink"))
    {
      mcs->key_push ("audio", "device-oss");
      mcs->key_push ("audio", "oss-buffer-time");
    }

    if (PRESENT_SINK("esdsink"))
    {
      mcs->key_push ("audio", "device-esd");
      mcs->key_push ("audio", "esd-buffer-time");
    }

    if (PRESENT_SINK("pulsesink"))
    {
      mcs->key_push ("audio", "pulse-server");
      mcs->key_push ("audio", "pulse-device");
      mcs->key_push ("audio", "pulse-buffer-time");
    }

    if (PRESENT_SINK("jackaudiosink"))
    {
      mcs->key_push ("audio", "jack-server");
      mcs->key_push ("audio", "jack-buffer-time");
    }

#ifdef HAVE_HAL
    if (PRESENT_SINK("halaudiosink"))
    {
      mcs->key_push ("audio", "hal-udi");
    }
#endif // HAVE_HAL

    m_cbox_video_out->set_active (mcs->key_get <int> ("audio", "video-output"));

    m_warning_audio_system_changed->set_sensitive (0);
    m_button_audio_system_apply->set_sensitive (0);
    m_button_audio_system_reset->set_sensitive (0);
  }



  //// Preferences

  Preferences::Preferences (BaseObjectType                 * obj,
                            RefPtr<Gnome::Glade::Xml> const& xml)
  : Window            (obj)
  , m_ref_xml         (xml)
  , m_import_canceled (0)
  {
      dynamic_cast<Button*>
        (m_ref_xml->get_widget ("close"))->signal_clicked().connect (sigc::mem_fun (*this, &Preferences::hide));

      m_throbber = Gdk::PixbufAnimation::create_from_file( build_filename( BMP_IMAGE_DIR, "throbber-small.gif" ) );
      m_state_ok = Gdk::Pixbuf::create_from_file( build_filename( BMP_IMAGE_DIR_STOCK, "approve.png") );
      m_state_err = Gdk::Pixbuf::create_from_file( build_filename( BMP_IMAGE_DIR_STOCK, "silk-exclamation.png") );

      /* version string */
      static boost::format version_f ("<span size='small' color='#606060'>BMPx %s:%s-R%s (%s / %s)</span>");
      std::string version = (version_f % PACKAGE_VERSION % RV_LAST_CHANGED_DATE % RV_REVISION % BUILD_DATE % BUILD_BUILDUSER).str ();
      dynamic_cast<Label*> (m_ref_xml->get_widget ("l_version"))->set_markup (version);

      m_ref_xml->get_widget ("cbox_audio_system", m_cbox_audio_system);

#ifdef HAVE_ALSA
      m_ref_xml->get_widget ("cbox_alsa_card", m_cbox_alsa_card);
      m_ref_xml->get_widget ("cbox_alsa_device", m_cbox_alsa_device);
      m_ref_xml->get_widget ("alsa_buffer_time", m_alsa_buffer_time);
#endif //HAVE_ALSA

#ifdef HAVE_SUN
      m_ref_xml->get_widget ("sun_cbe_device",  m_sun_cbe_device);
      m_ref_xml->get_widget ("sun_buffer_time", m_sun_buffer_time);
#endif //HAVE_SUN

      m_ref_xml->get_widget ("oss_cbe_device", m_oss_cbe_device);
      m_ref_xml->get_widget ("oss_buffer_time", m_oss_buffer_time);

      m_ref_xml->get_widget ("esd_host", m_esd_host);
      m_ref_xml->get_widget ("esd_buffer_time", m_esd_buffer_time);

      m_ref_xml->get_widget ("pulse_server", m_pulse_server);
      m_ref_xml->get_widget ("pulse_device", m_pulse_device);
      m_ref_xml->get_widget ("pulse_buffer_time", m_pulse_buffer_time);

      m_ref_xml->get_widget ("jack_server", m_jack_server);
      m_ref_xml->get_widget ("jack_buffer_time", m_jack_buffer_time);

#ifdef HAVE_HAL
      m_ref_xml->get_widget ("halaudio_udi", m_halaudio_udi);
#endif // HAVE_HAL

      m_ref_xml->get_widget ("notebook_audio_system", m_notebook_audio_system);
      m_ref_xml->get_widget ("audio-system-apply-changes", m_button_audio_system_apply);
      m_ref_xml->get_widget ("audio-system-reset-changes", m_button_audio_system_reset);
      m_ref_xml->get_widget ("audio-system-changed-warning", m_warning_audio_system_changed);

#ifdef HAVE_HAL
      m_ref_xml->get_widget_derived ("preferences-view-volumes", m_volumes_view);
      m_volumes_view->m_volume_manage_dialog->signal_scan_path().connect (sigc::mem_fun (*this, &Preferences::scan_path));
#endif // HAVE_HAL

#ifndef HAVE_HAL
      mcs_bind->bind_cbox_entry (*dynamic_cast<ComboBoxEntry*>(m_ref_xml->get_widget ("cbe_cdrom")), "audio", "cdrom-device");
      m_ref_xml->get_widget ("preferences-vbox-optical-device")->show_all ();
      m_ref_xml->get_widget ("preferences-library-vbox-hal")->hide ();
#endif // !HAVE_HAL

      /* audio */ 
      setup_audio_widgets ();
      setup_audio ();

      /* podcasts */
      if (!file_test (mcs->key_get<std::string> ("podcasts", "download-dir"), FILE_TEST_EXISTS))
        mcs->key_set<std::string> ("podcasts","download-dir", std::string (g_get_home_dir ()));
      mcs_bind->bind_filechooser (*dynamic_cast<FileChooser*> (m_ref_xml->get_widget ("podcast-download-filechooser")), "podcasts", "download-dir");
      mcs_bind->bind_cbox (*dynamic_cast<ComboBox*> (m_ref_xml->get_widget ("podcast-download-policy-cbox")), "podcasts", "download-policy");
      mcs_bind->bind_cbox (*dynamic_cast<ComboBox*> (m_ref_xml->get_widget ("podcast-update-interval-cbox")), "podcasts", "update-interval");
      mcs_bind->bind_cbox (*dynamic_cast<ComboBox*> (m_ref_xml->get_widget ("podcast-cache-policy-cbox")), "podcasts", "cache-policy");

      /* various toggle buttons */ 
      struct DomainKeyPair
      {
        char const * domain;
        char const * key;
        char const * widget;
      };

      DomainKeyPair buttons[] =
      {
        { "bmp",      "ui-esc-trayconify",      "ui-esc-trayconify"     },
        { "bmp",      "display-notifications",  "display-notifications" },
        { "bmp",      "force-rgba-enable",      "force-rgba-enable"     },
        { "bmp",      "spm-listen",             "spm-listen"            },
        { "audio",    "enable-eq",              "enable-eq"             },
      };

      for (unsigned int n = 0; n < G_N_ELEMENTS (buttons); ++n)
      {

        ToggleButton* button = dynamic_cast<ToggleButton*> (m_ref_xml->get_widget (buttons[n].widget));
        if (button)
          mcs_bind->bind_toggle_button (*button, buttons[n].domain, buttons[n].key);
        else
          g_warning ("%s: Widget '%s' not found in 'preferences-ui.glade'", G_STRLOC, buttons[n].widget);

      }

      /* library */
      mcs_bind->bind_filechooser (*dynamic_cast<FileChooser*>  (m_ref_xml->get_widget ("library-fcb-rootpath")), "library", "rootpath");
      dynamic_cast <Button *> (m_ref_xml->get_widget ("preferences-button-add-music"))->signal_clicked().connect (sigc::mem_fun (*this, &Preferences::import_tracks));
      dynamic_cast <Button *> (m_ref_xml->get_widget ("preferences-button-fetch-covers"))->signal_clicked().connect (sigc::mem_fun (*this, &Preferences::fetch_covers));
      
      /* mm-keys */
      for(int n = 1; n < 6; ++n)
      {
        Gtk::Entry * entry;
        m_ref_xml->get_widget ((boost::format ("mm-entry-%d") % n).str(), entry);
        entry->signal_key_press_event().connect( sigc::bind( sigc::mem_fun( *this, &Preferences::on_entry_key_press_event ), n));
        entry->signal_key_release_event().connect( sigc::bind( sigc::mem_fun( *this, &Preferences::on_entry_key_release_event ), n));
        Gtk::Button * button;
        m_ref_xml->get_widget ((boost::format ("mm-clear-%d") % n).str(), button);
        button->signal_clicked().connect( sigc::bind( sigc::mem_fun( *this, &Preferences::on_clear_keyboard ), n));
        m_mm_key_controls.resize( n );
      }

      int active = mcs->key_get<int>("hotkeys","system") + 1;
      for(int n = 1; n < 4; ++n)
      {
        Gtk::RadioButton * button;
        m_ref_xml->get_widget ((boost::format ("mm-rb-%d") % n).str(), button);
        button->signal_toggled().connect( sigc::bind( sigc::mem_fun( *this, &Preferences::on_mm_option_changed), n));
        if( n == active )
        {
          button->set_active ();
        }
      }

      dynamic_cast<Gtk::Button*>(m_ref_xml->get_widget ("mm-revert"))->signal_clicked().connect(
        sigc::mem_fun( *this, &Preferences::mm_load ));

      dynamic_cast<Gtk::Button*>(m_ref_xml->get_widget ("mm-apply"))->signal_clicked().connect(
        sigc::mem_fun( *this, &Preferences::mm_apply ));

      Gtk::ToggleButton * mm_enable;
      m_ref_xml->get_widget ("mm-enable", mm_enable);
      bool mm_enabled = mcs->key_get<bool>("hotkeys","enable");
      mm_enable->set_active(mm_enabled);
      m_ref_xml->get_widget("mm-vbox")->set_sensitive(mm_enabled);
      mm_enable->signal_toggled().connect(sigc::mem_fun( *this, &Preferences::mm_toggled ));

      mm_load ();
  }

  void
  Preferences::on_hide ()
  {
    setup_audio();
    Gtk::Widget::on_hide();
  }

  /* mm-keys */
  void
  Preferences::mm_toggled ()
  {
    Gtk::ToggleButton * b;
    m_ref_xml->get_widget ("mm-enable", b);
    bool active = b->get_active();

    Signals.HotkeyEditBegin.emit();
    mcs->key_set<bool>("hotkeys","enable", active);
    m_ref_xml->get_widget("mm-vbox")->set_sensitive(active);
    Signals.HotkeyEditEnd.emit();
  } 

  void
  Preferences::mm_apply ()
  {
    mcs->key_set<int>("hotkeys","system", m_mm_option);

    for(int n = 1; n < 6; ++n)
    {
      KeyControls & c = m_mm_key_controls[n-1];
      mcs->key_set<int>("hotkeys", (boost::format ("key-%d") % n).str(), c.key);
      mcs->key_set<int>("hotkeys", (boost::format ("key-%d-mask") % n).str(), c.mask);
    }

    m_ref_xml->get_widget ("mm-apply")->set_sensitive (false);
    m_ref_xml->get_widget ("mm-revert")->set_sensitive (false);

    Signals.HotkeyEditEnd.emit();
  }

  void
  Preferences::mm_load ()
  {
    for(int n = 1; n < 6; ++n)
    {
      KeyControls c;  
      c.key = mcs->key_get<int>("hotkeys", (boost::format ("key-%d") % n).str());
      c.mask = mcs->key_get<int>("hotkeys", (boost::format ("key-%d-mask") % n).str());
      m_mm_key_controls[n-1] = c;
      set_keytext( n, c.key, c.mask );
    }

    int sys = mcs->key_get<int>("hotkeys","system");
    if( sys < 0 || sys > 2) sys = 1;
    dynamic_cast<Gtk::RadioButton*>(m_ref_xml->get_widget ((boost::format ("mm-rb-%d") % (sys+1)).str()))->set_active();

    m_ref_xml->get_widget ("mm-apply")->set_sensitive (false);
    m_ref_xml->get_widget ("mm-revert")->set_sensitive (false);

    Signals.HotkeyEditEnd.emit();
  }

  void
  Preferences::set_keytext (gint entry_id, gint key, gint mask)
  {
    Signals.HotkeyEditBegin.emit();

    std::string text; 

    if (key == 0 && mask == 0)
    {
      text = (_("(none)"));
    } else {
      char const* modifier_string[] = { "Control", "Shift", "Alt", "Mod2", "Mod3", "Super", "Mod5" };
      const unsigned int modifiers[] = { ControlMask, ShiftMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask };

      std::string keytext;
      StrV strings;
      int i, j;
      KeySym keysym;

      keysym = XKeycodeToKeysym(gdk_x11_display_get_xdisplay (get_display()->gobj()), key, 0);
      if (keysym == 0 || keysym == NoSymbol)
      {
        keytext = (boost::format ("#%3d") % key).str();
      } else {
        keytext = XKeysymToString(keysym);
      }

      for (i = 0, j=0; j<7; j++)
      {
        if (mask & modifiers[j])
          strings.push_back (modifier_string[j]);
      }
      if (key != 0)
      {
        strings.push_back( keytext );
      }

      text = Util::stdstrjoin(strings, " + ");
    }

    Gtk::Entry * entry;
    m_ref_xml->get_widget ((boost::format ("mm-entry-%d") % entry_id).str(), entry); 

    entry->set_text (text);
    entry->set_position(-1);
  }

  bool 
  Preferences::on_entry_key_press_event(GdkEventKey * event, gint entry_id)
  {
    KeyControls & controls = m_mm_key_controls[entry_id-1]; 
    int is_mod;
    int mod;

    if (event->keyval == GDK_Tab) return false;
    mod = 0;
    is_mod = 0;

    if ((event->state & GDK_CONTROL_MASK) | (!is_mod && (is_mod = (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R))))
            mod |= ControlMask;

    if ((event->state & GDK_MOD1_MASK) | (!is_mod && (is_mod = (event->keyval == GDK_Alt_L || event->keyval == GDK_Alt_R))))
            mod |= Mod1Mask;

    if ((event->state & GDK_SHIFT_MASK) | (!is_mod && (is_mod = (event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R))))
            mod |= ShiftMask;

    if ((event->state & GDK_MOD5_MASK) | (!is_mod && (is_mod = (event->keyval == GDK_ISO_Level3_Shift))))
            mod |= Mod5Mask;

    if ((event->state & GDK_MOD4_MASK) | (!is_mod && (is_mod = (event->keyval == GDK_Super_L || event->keyval == GDK_Super_R))))
            mod |= Mod4Mask;

    if (!is_mod) {
      controls.key = event->hardware_keycode;
      controls.mask = mod;
    } else controls.key = 0;

    set_keytext (entry_id, is_mod ? 0 : event->hardware_keycode, mod);
    m_ref_xml->get_widget ("mm-apply")->set_sensitive (true);
    m_ref_xml->get_widget ("mm-revert")->set_sensitive (true);
    return false;
  }

  bool 
  Preferences::on_entry_key_release_event(GdkEventKey * event, gint entry_id)
  {
    KeyControls & controls = m_mm_key_controls[entry_id-1]; 
    if (controls.key == 0)
    {
      controls.mask = 0;
    }
    set_keytext (entry_id, controls.key, controls.mask);
    m_ref_xml->get_widget ("mm-apply")->set_sensitive (true);
    m_ref_xml->get_widget ("mm-revert")->set_sensitive (true);
    return false;
  }

  void
  Preferences::on_clear_keyboard (gint entry_id)
  {
    KeyControls & controls = m_mm_key_controls[entry_id-1]; 
    controls.key = 0;
    controls.mask = 0;
    set_keytext(entry_id, 0, 0);
    m_ref_xml->get_widget ("mm-apply")->set_sensitive (true);
    m_ref_xml->get_widget ("mm-revert")->set_sensitive (true);
  }

  void
  Preferences::on_mm_option_changed (gint option)
  {
    Signals.HotkeyEditBegin.emit();

    switch( option )
    {
      case 1:
      case 2:
        m_ref_xml->get_widget ("mm-table")->set_sensitive (false);
        break;  

      case 3:
        m_ref_xml->get_widget ("mm-table")->set_sensitive (true);
        break; 
    }
    m_mm_option = option - 1;
    m_ref_xml->get_widget ("mm-apply")->set_sensitive (true);
  }

} // namespace Bmp
