//  BMP
//  Copyright (C) 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 <signal.h>

#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <sstream>
#include <fstream>
#include <map>

#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>

#include <glibmm/i18n.h>
#include <glib/gstdio.h>
#include <gtkmm.h>
#include <cairomm/cairomm.h>

#include <gst/gst.h>
#include <gst/gstelement.h>
#include "dialog-gsterror.hh"

#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/optional.hpp>
#include <mcs/mcs.h>

#include <X11/Xlib.h>
#include <X11/XF86keysym.h>
#include <gdk/gdkx.h>

#undef BMP_PLUGIN_BUILD
#include "audio/play.hh"

#include "amazon.hh"
#define USING_DBUS 1
#include "core.hh"
#undef USING_DBUS
#ifdef HAVE_HAL
#include "hal.hh"
#endif //HAVE_HAL
#include "library.hh"

#include "debug.hh"
#include "lastfm.hh"
#include "main.hh"
#include "network.hh"
#include "paths.hh"
#include "stock.hh"
#include "ui-tools.hh"
#include "uri.hh"
#include "util.hh"
#include "util-string.hh"

#include "urihandler.hh"
#include "ui-part-cdda.hh"
#include "ui-part-jamendo.hh"
#include "ui-part-lastfm.hh"
#include "ui-part-library.hh"
#include "ui-part-playlist.hh"
#include "ui-part-podcasts.hh"
#include "ui-part-radio.hh"

#include "bmp/types/types-basic.hh"
#include "bmp/types/types-library.hh"
#include "bmp/types/types-basic.hh"

#include "widgets/bmp_tray_icon.h"
#include "widgets/bmp_status_icon.h"
#include "widgets/button.hh"
#include "widgets/cairoextensions.hh"
#include "widgets/ccwidgets.hh"
#include "widgets/popup.hh"
#include "widgets/taskdialog.hh"
#include "video-widget.hh"

#include "dialog-about.hh"
#include "dialog-equalizer.hh"
#include "dialog-filebrowser.hh"
#include "dialog-play-uri.hh"
#include "dialog-progress.hh"
#include "dialog-track-details.hh"

#include "lastfm-recommend-dialog.hh"
#include "lastfm-tag-dialog.hh"
#include "musicbrainz/mb-utils.hh"
#include "bmp/types/types-xspf.hh"

#include "preferences.hh"

#include "dbus-marshalers.h"
#include "shell.hh"

#include <libglademm.h>
#include "widgetloader.h"

using namespace Glib;
using namespace Gtk;
using namespace std;
using namespace Bmp::Util;

#define PLAYER_SHELL_ACTION_PLAY_FILES        "player-shell-action-add-files"
#define PLAYER_SHELL_ACTION_ENQUEUE_FILES     "player-shell-action-enqueue-files"
#define PLAYER_SHELL_ACTION_PLAY_STREAM       "player-shell-action-play-stream"

#define PLAYER_SHELL_ACTION_PLAY              "player-shell-action-play"
#define PLAYER_SHELL_ACTION_PAUSE             "player-shell-action-pause"
#define PLAYER_SHELL_ACTION_PREV              "player-shell-action-prev"
#define PLAYER_SHELL_ACTION_NEXT              "player-shell-action-next"
#define PLAYER_SHELL_ACTION_STOP              "player-shell-action-stop"

#define PLAYER_SHELL_ACTION_REPEAT            "player-shell-action-repeat"
#define PLAYER_SHELL_ACTION_SHUFFLE           "player-shell-action-shuffle"

#define PLAYER_SHELL_ACTION_TRACK_DETAILS     "player-shell-action-track-info"
#define PLAYER_SHELL_ACTION_EQ                "player-shell-action-eq"

#define PLAYER_SHELL_ACTION_CLOSE             "player-shell-action-close"
#define PLAYER_SHELL_ACTION_QUIT              "player-shell-action-quit"
#define PLAYER_SHELL_ACTION_PREFS             "player-shell-action-prefs"
#define PLAYER_SHELL_ACTION_ABOUT             "player-shell-action-about"
#define PLAYER_SHELL_ACTION_STATUSBAR         "player-shell-action-show-statusbar"
#define PLAYER_SHELL_ACTION_SOURCES           "player-shell-action-sources"
#define PLAYER_SHELL_ACTION_INFO              "player-shell-action-info"

#define PLAYER_SHELL_ACTION_LASTFM_PLAY       "player-shell-action-lastfm-play"
#define PLAYER_SHELL_ACTION_LASTFM_RECM       "player-shell-action-lastfm-recm"
#define PLAYER_SHELL_ACTION_LASTFM_TAG        "player-shell-action-lastfm-tag"
#define PLAYER_SHELL_ACTION_LASTFM_LOVE       "player-shell-action-lastfm-love"

#define PLAYER_SHELL_ACTION_BOOKMARKS         "player-shell-action-bookmarks"
#define PLAYER_SHELL_ACTION_CREATE_BOOKMARK   "player-shell-action-create-bookmark"

#define PLAYER_SHELL_ACTION_VIDEO_ASPECT_AUTO       "player-shell-action-video-aspect-auto"
#define PLAYER_SHELL_ACTION_VIDEO_ASPECT_43         "player-shell-action-video-aspect-43"
#define PLAYER_SHELL_ACTION_VIDEO_ASPECT_169        "player-shell-action-video-aspect-169"
#define PLAYER_SHELL_ACTION_VIDEO_ASPECT_DVB        "player-shell-action-video-aspect-dvb"

namespace
{
    const Bmp::DB::Variant dummy = double (2);
}

namespace Bmp
{
    Mcs::Bind* mcs_bind = 0;
}

namespace
{
  struct SourceInfo
  {
      const std::string name;
      const std::string icon;
      bool              netOnly;
      const std::string id;
      const std::string stock;
      bool              useTheme;
  } sources[] = {
    { N_("Music"),    "library.png",  false,  "library",  "audio-x-generic",    true    },
    { N_("Playlist"), "playlist.png", false,  "playlist", BMP_STOCK_PLAYLIST,   false   },
    { N_("Radio"),    "radio.png",    true,   "radio",    BMP_STOCK_XIPH,       false   },
    { N_("Last.fm"),  "lastfm.png",   true,   "lastfm",   BMP_STOCK_LASTFM,     false   },
    { N_("Podcast"),  "podcasts.png", true,   "podcast",  BMP_STOCK_FEED,       false   },
    { N_("Audio CD"), "audiocd.png",  false,  "audiocd",  BMP_STOCK_CDDA,       false   },
    { N_("Jamendo"),  "jamendo.png",  true,   "jamendo",  BMP_STOCK_JAMENDO,    false   },
  };
}

namespace
{
  static  boost::format time_f ("%02d:%02d .. %02d:%02d");
  static  boost::format format_int ("%llu");

  char const * ui_main_menubar =
  "<ui>"
  ""
  "<menubar name='MenuBarMain'>"
  ""
  "   <menu action='MenuPlayer'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PLAY_FILES "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PLAY_STREAM "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_ENQUEUE_FILES "'/>"
  "     <separator name='main-1'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PREFS "'/>"
  "     <separator name='main-11'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_CLOSE "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_QUIT "'/>"
  "   </menu>"
  ""
  "   <menu action='MenuPlayback'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_EQ "'/>"
  "     <separator name='main-4'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_REPEAT "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_SHUFFLE "'/>"
  "     <separator name='main-41'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PAUSE "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PREV "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_NEXT "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_STOP "'/>"
  "   </menu>"
  ""
  "   <menu action='MenuView'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_SOURCES "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_INFO "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_STATUSBAR "'/>"
  "     <separator name='main-52'/>"
  "         <menu action='MenuVideo'>"
  "             <menuitem action='" PLAYER_SHELL_ACTION_VIDEO_ASPECT_AUTO "'/>"
  "             <menuitem action='" PLAYER_SHELL_ACTION_VIDEO_ASPECT_43 "'/>"
  "             <menuitem action='" PLAYER_SHELL_ACTION_VIDEO_ASPECT_169 "'/>"
  "             <menuitem action='" PLAYER_SHELL_ACTION_VIDEO_ASPECT_DVB "'/>"
  "         </menu>"
  "   </menu>"
  ""
  "   <menu action='MenuTrack'>"
  "     <separator name='main-53'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_TRACK_DETAILS "'/>"
  "     <separator name='main-54'/>"
  "     <placeholder name='Merge__LastFmActions'/>"
  "   </menu>"
  ""
  "   <menu action='MenuBookmarks'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_CREATE_BOOKMARK "'/>"
  "     <separator name='main-55'/>"
  "     <menu action='MenuBookmarks-radio'/>"
  "     <menu action='MenuBookmarks-jamendo'/>"
  "   </menu>"
  ""
  "   <placeholder name='PlaceholderSource'/>"
  ""
  "   <menu action='MenuHelp'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_ABOUT "'/>"
  "   </menu>"
  ""
  "</menubar>"
  ""
  "</ui>";

  char const * ui_main_menubar_offline =
  "<ui>"
  "<menubar name='MenuBarMain'>"
  "   <menu action='MenuPlayer'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PLAY_FILES "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PLAY_STREAM "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_ENQUEUE_FILES "'/>"
  "     <separator name='main-1'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PREFS "'/>"
  "     <separator name='main-11'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_CLOSE "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_QUIT "'/>"
  "   </menu>"
  ""
  "   <menu action='MenuPlayback'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_EQ "'/>"
  "     <separator name='main-4'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_REPEAT "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_SHUFFLE "'/>"
  "     <separator name='main-41'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PAUSE "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PREV "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_NEXT "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_STOP "'/>"
  "   </menu>"

  "   <menu action='MenuView'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_SOURCES "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_INFO "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_STATUSBAR "'/>"
  "     <separator name='main-52'/>"
  "         <menu action='MenuVideo'>"
  "             <menuitem action='" PLAYER_SHELL_ACTION_VIDEO_ASPECT_AUTO "'/>"
  "             <menuitem action='" PLAYER_SHELL_ACTION_VIDEO_ASPECT_43 "'/>"
  "             <menuitem action='" PLAYER_SHELL_ACTION_VIDEO_ASPECT_169 "'/>"
  "             <menuitem action='" PLAYER_SHELL_ACTION_VIDEO_ASPECT_DVB "'/>"
  "         </menu>"
  "   </menu>"
  ""
  "   <menu action='MenuTrack'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_TRACK_DETAILS "'/>"
  "   </menu>"
  ""
  "   <menu action='MenuBookmarks'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_CREATE_BOOKMARK "'/>"
  "     <separator name='main-53'/>"
  "     <menu action='MenuBookmarks-radio'/>"
  "     <menu action='MenuBookmarks-jamendo'/>"
  "   </menu>"
  ""
  "   <placeholder name='PlaceholderSource'/>"
  ""
  "   <menu action='MenuHelp'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_ABOUT "'/>"
  "   </menu>"
  ""
  "</menubar>"
  "</ui>";

  char const * ui_tray_icon =
  "<ui>"
  " <menubar name='popup-tray'>"
  " <menu action='dummy' name='menu-tray'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PAUSE "'/>"
  "   <separator name='main-6'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_TRACK_DETAILS "'/>"
  "   <separator name='main-61'/>"
  "     <placeholder name='Merge__LastFmActions'/>"
  "   <separator name='main-8'/>"
  "   <placeholder name='PlaceholderSourceContext'/>"
  "   <separator name='main-9'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PREV "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_NEXT "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_STOP "'/>"
  "   <separator name='main-10'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_QUIT "'/>"
  " </menu>"
  " </menubar>"
  "</ui>";

  char const * ui_tray_icon_offline =
  "<ui>"
  " <menubar name='popup-tray'>"
  " <menu action='dummy' name='menu-tray'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PAUSE "'/>"
  "   <separator name='main-6'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_TRACK_DETAILS "'/>"
  "   <separator name='main-8'/>"
  "   <placeholder name='PlaceholderSourceContext'/>"
  "   <separator name='main-9'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_PREV "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_NEXT "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_STOP "'/>"
  "   <separator name='main-10'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_QUIT "'/>"
  " </menu>"
  " </menubar>"
  "</ui>";

  const char * ui_info_area_popup =
  "<ui>"
  "<menubar name='popup-shell'>"
  "   <menu action='dummy' name='menu-shell-context'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_TRACK_DETAILS "'/>"
  "   <separator name='main-101'/>"
  "     <placeholder name='Merge__LastFmActions'/>"
  "   <separator name='main-11'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_SOURCES "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_INFO "'/>"
  "   <separator name='main-12'/>"
  "   <placeholder name='PlaceholderSourceContext'/>"
  "   </menu>"
  "</menubar>"
  "</ui>";

  const char * ui_info_area_popup_offline =
  "<ui>"
  "<menubar name='popup-shell'>"
  "   <menu action='dummy' name='menu-shell-context'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_TRACK_DETAILS "'/>"
  "   <separator name='main-13'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_SOURCES "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_INFO "'/>"
  "   <separator name='main-14'/>"
  "   <placeholder name='PlaceholderSourceContext'/>"
  "   </menu>"
  "</menubar>"
  "</ui>";

  const char * ui_lastfm_actions =
  "<ui>"
  "<menubar name='popup-shell'>"
  "   <menu action='dummy' name='menu-shell-context'>"
  "   <placeholder name='Merge__LastFmActions'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_LASTFM_LOVE "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_LASTFM_TAG "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_LASTFM_PLAY "'/>"
  "   </placeholder>"
  "   </menu>"
  "</menubar>"
  ""
  "<menubar name='popup-tray'>"
  "   <menu action='dummy' name='menu-tray'>"
  "   <placeholder name='Merge__LastFmActions'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_LASTFM_LOVE "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_LASTFM_TAG "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_LASTFM_PLAY "'/>"
  "   </placeholder>"
  "   </menu>"
  "</menubar>"
  ""
  "<menubar name='MenuBarMain'>"
  "   <menu action='MenuTrack'>"
  "   <placeholder name='Merge__LastFmActions'>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_LASTFM_LOVE "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_LASTFM_TAG "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_LASTFM_PLAY "'/>"
  "         <menuitem action='" PLAYER_SHELL_ACTION_LASTFM_RECM "'/>"
  "   </placeholder>"
  "   </menu>"
  "</menubar>"
  "</ui>";

}

namespace Bmp
{
  enum LayoutID
  {
    L_TITLE,
    L_ARTIST,
    L_ALBUM,
    N_LAYOUTS
  };

  struct LayoutData
  {
    double alpha;
    double target;
    int    x;
    int    y;

    //FIXME: Encode size here as well
  };

  LayoutData const layout_info[] = {
    {-0.0, 1.0, 86,  8},
    {-0.4, 1.0, 86, 40},
    {-0.4, 0.8, 86, 58}
  };

  // WARNING: If you set the gravity or timescale too high, the cover
  // animation will never come to a stop. Visually it might well be but it may be
  // performing miniscule bounces.

  double const cover_anim_area_width       = 72.0;
  double const cover_anim_area_height      = 72.0;
  double const cover_anim_area_x0          = 6.0;
  double const cover_anim_area_y0          = 6.0;
  double const cover_anim_area_x1          = cover_anim_area_x0 + cover_anim_area_width;
  double const cover_anim_area_y1          = cover_anim_area_y0 + cover_anim_area_height;

  double const cover_anim_initial_pos      = 6.0 - 72.0;
  double /*const*/ cover_anim_initial_velocity = 223.3; 
  double /*const*/ cover_anim_gravity          = 359.1; 
  double const cover_anim_wall             = cover_anim_area_x1;
  double /*const*/ cover_anim_wall_elasticity  = 0.074;
  double const cover_anim_time_scale       = 1.0;

  int    const cover_anim_fps              = 25;
  int    const cover_anim_interval         = 1000 / cover_anim_fps;
  double const cover_anim_dt               = cover_anim_time_scale / cover_anim_fps;

#ifdef APP_MAINTENANCE

  class AnimParams
    : public WidgetLoader<Gtk::Window>
  {
      Glib::RefPtr<Gnome::Glade::Xml> m_ref_xml;
      Gtk::Range * m_range_velocity;
      Gtk::Range * m_range_gravity;
      Gtk::Range * m_range_elasticity;

      double & m_velocity; 
      double & m_gravity;
      double & m_elasticity;

      enum RangeId
      {
        RANGE_VELOCITY,
        RANGE_GRAVITY,
        RANGE_ELASTICITY
      };

      void
      on_range_value_changed (RangeId id)
      {
        switch(id)
        {
          case RANGE_VELOCITY:
            m_velocity = m_range_velocity->get_value()/1000.;
            break;

          case RANGE_GRAVITY:
            m_gravity = m_range_gravity->get_value()/1000.;
            break;

          case RANGE_ELASTICITY:
            m_elasticity = m_range_elasticity->get_value()/1000.;
            break;
        } 
      }

    public:

      static AnimParams*
      create (double & velocity, double & gravity, double & elasticity)
      {
        const std::string path (build_filename(DATA_DIR, build_filename("glade","dialog-bounce-adjust.glade")));
        AnimParams *p = new AnimParams(Gnome::Glade::Xml::create (path), velocity, gravity, elasticity);
        return p;
      }

      AnimParams(const Glib::RefPtr<Gnome::Glade::Xml>& xml,
              double & velocity,
              double & gravity,
              double & elasticity)
      : WidgetLoader<Gtk::Window>(xml, "anim-params")
      , m_ref_xml(xml)
      , m_velocity(velocity)
      , m_gravity(gravity)
      , m_elasticity(elasticity)
      {
        xml->get_widget("velocity", m_range_velocity);
        xml->get_widget("gravity", m_range_gravity);
        xml->get_widget("elasticity", m_range_elasticity);

        m_range_velocity->set_value(velocity*1000.);
        m_range_gravity->set_value(gravity*1000.);
        m_range_elasticity->set_value(elasticity*1000.);

        m_range_velocity->signal_value_changed().connect(
          sigc::bind( sigc::mem_fun( *this, &AnimParams::on_range_value_changed ), RANGE_VELOCITY));

        m_range_gravity->signal_value_changed().connect(
          sigc::bind( sigc::mem_fun( *this, &AnimParams::on_range_value_changed ), RANGE_GRAVITY));

        m_range_elasticity->signal_value_changed().connect(
          sigc::bind( sigc::mem_fun( *this, &AnimParams::on_range_value_changed ), RANGE_ELASTICITY));
      }
  };
#endif // APP_MAINTENANCE

  struct Color
  {
                guint8 red;
                guint8 green;
                guint8 blue;
  };

  Color colors[] =
  {
        { 0xff, 0xb1, 0x6f },
        { 0xff, 0xc8, 0x7f },
        { 0xff, 0xcf, 0x7e },
        { 0xf6, 0xe6, 0x99 },
        { 0xf1, 0xfc, 0xd4 },
        { 0xbd, 0xd8, 0xab },
        { 0xcd, 0xe6, 0xd0 },
        { 0xce, 0xe0, 0xea },
        { 0xd5, 0xdd, 0xea },
        { 0xee, 0xc1, 0xc8 },
        { 0xee, 0xaa, 0xb7 },
        { 0xec, 0xce, 0xb6 },
  };
 
  class InfoArea
    : public EventBox
  {
    private:

#ifdef APP_MAINTENANCE
      AnimParams * m_ParamControl;
#endif // APP_MAINTENANCE

      struct Text
      {
        Text (Gtk::Widget &  widget,
              ustring const& text,
              LayoutID       id)
        : alpha   (layout_info[id].alpha)
        , target  (layout_info[id].target)
        , x       (layout_info[id].x)
        , y       (layout_info[id].y)
        {
          layout = widget.create_pango_layout ("");
          layout->set_markup (text);
        }

        ~Text () {}

        RefPtr<Pango::Layout> layout;
        double                alpha;
        double                target;
        int                   x, y;
        sigc::connection      conn;
      };

      typedef boost::shared_ptr<Text>   TextP;
      typedef std::map<LayoutID, TextP> Layouts;

      Layouts     m_layouts;
      ustring     m_text[N_LAYOUTS];

      Cairo::RefPtr<Cairo::ImageSurface>  m_cover_surface;
      double                              m_cover_pos;
      double                              m_cover_velocity;
      double                              m_cover_accel;
      double                              m_cover_alpha;
      sigc::connection                    m_cover_anim_conn;

      Spectrum                            m_spectrum_data;
      Spectrum                            m_spectrum_peak;
      sigc::connection                    m_spectrum_decay_conn;

      RefPixbuf                           m_source_icon;

      bool m_compact;

    public:

      typedef sigc::signal<void, VUri const&> SignalUris;
      typedef sigc::signal<void> SignalCoverClicked;

      SignalUris &
      signal_uris_dropped()
      {
        return m_signal_uris_dropped;
      }

      SignalCoverClicked&
      signal_cover_clicked()
      {
        return m_signal_cover_clicked;
      }

    private:

      SignalUris         m_signal_uris_dropped;
      SignalCoverClicked m_signal_cover_clicked;

    protected:

      bool
      on_button_press_event (GdkEventButton * event)
      {
        int x = int (event->x);
        int y = int (event->y);

        if ((x >= 6) && (x <= 78) && (y >= 3) && (y <= 75))
        {
          int status = Play::Obj()->property_status();
          if (status != PLAYSTATUS_STOPPED && status != PLAYSTATUS_WAITING)
          {
            m_signal_cover_clicked.emit ();
          }
        }

        return false;
      }

      bool
      on_drag_drop (Glib::RefPtr<Gdk::DragContext> const& context,
                    int                                   x,
                    int                                   y,
                    guint                                 time)
      {
        ustring target (drag_dest_find_target (context));
        if( !target.empty() )
        {
          drag_get_data (context, target, time);
          context->drag_finish  (true, false, time);
          return true;
        }
        else
        {
          context->drag_finish  (false, false, time);
          return false;
        }
      }

      void
      on_drag_data_received (Glib::RefPtr<Gdk::DragContext> const& context,
                             int                                   x,
                             int                                   y,
                             Gtk::SelectionData const&             data,
                             guint                                 info,
                             guint                                 time)
      {
        if( data.get_data_type() == "text/uri-list")
        {
          VUri u = data.get_uris();
          m_signal_uris_dropped.emit (u);
        }
        else
        if( data.get_data_type() == "text/plain")
        {
          using boost::algorithm::split;
          using boost::algorithm::is_any_of;
          using boost::algorithm::replace_all;

          std::string text = data.get_data_as_string ();
          replace_all (text, "\r", "");

          StrV v;
          split (v, text, is_any_of ("\n"));

          if( v.empty ()) // we're taking chances here
          {
            v.push_back (text);
          }

          VUri u;

          for(StrV::const_iterator i = v.begin(); i != v.end(); ++i)
          {
            try{
              URI uri (*i);
              u.push_back (*i);
            }
            catch (URI::ParseError & cxe)
            {
                // seems like not it
            }
          }

          if( !u.empty() )
          {
            m_signal_uris_dropped.emit (u);
          }
        }
      }

    private:

      void
      enable_drag_dest ()
      {
        disable_drag_dest ();

        DNDEntries target_entries;
        target_entries.push_back (TargetEntry ("text/plain"));

        drag_dest_set (target_entries, Gtk::DEST_DEFAULT_MOTION);
        drag_dest_add_uri_targets ();
      }

      void
      disable_drag_dest ()
      {
        drag_dest_unset ();
      }

    public:

      InfoArea (BaseObjectType                 * obj,
                RefPtr<Gnome::Glade::Xml> const& xml)
      : EventBox          (obj)
      , m_cover_alpha     (1.0)
      , m_spectrum_data   (SPECT_BANDS, 0)
      , m_spectrum_peak   (SPECT_BANDS, 0)
      , m_source_icon     (RefPixbuf (0))
      , m_compact         (false)
      {
        add_events (Gdk::BUTTON_PRESS_MASK);

        Gdk::Color const color ("#000000");
        modify_bg (Gtk::STATE_NORMAL, color);
        modify_base (Gtk::STATE_NORMAL, color);

        Play::Obj ()->signal_spectrum ().connect (sigc::mem_fun (this, &InfoArea::play_update_spectrum));
        Play::Obj ()->property_status ().signal_changed ().connect (sigc::mem_fun (this, &InfoArea::play_status_changed));

        enable_drag_dest ();

#ifdef APP_MAINTENANCE
        m_ParamControl = AnimParams::create(cover_anim_initial_velocity, cover_anim_gravity, cover_anim_wall_elasticity);
#endif // APP_MAINTENANCE
      }

      ~InfoArea ()
      {}

      void
      set_source (Glib::RefPtr<Gdk::Pixbuf> const& source_icon)
      {
        m_source_icon = source_icon;
        queue_draw ();
      }

      void
      set_compact (bool compact)
      {
        m_compact = compact;
        queue_draw ();
      }

      void
      reset ()
      {
        std::fill (m_spectrum_data.begin (), m_spectrum_data.end (), 0);
        std::fill (m_spectrum_peak.begin (), m_spectrum_peak.end (), 0);

        remove_layout_if_exists (L_ARTIST);
        remove_layout_if_exists (L_ALBUM);
        remove_layout_if_exists (L_TITLE);

        m_text[0].clear ();
        m_text[1].clear ();
        m_text[2].clear ();

        m_cover_anim_conn.disconnect ();
        m_cover_surface = Cairo::RefPtr<Cairo::ImageSurface> (0);

        set_source (RefPixbuf (0));

        queue_draw ();
      }

      void
      set_text (LayoutID       id,
                ustring const& text)
      {
        if( text != m_text[id] )
        {
          m_text[id] = text;

          TextP p (new Text (*this, text, id));

          remove_layout_if_exists (id);
          insert_layout_and_connect (id, p);
        }
      }

      void
      set_paused (bool paused)
      {
        m_cover_alpha = (paused ? 0.5 : 1.0);
      }

      void
      set_image (RefPtr<Gdk::Pixbuf> const& pixbuf)
      {
        set_image (Util::cairo_image_surface_from_pixbuf (pixbuf));
      }

      void
      set_image (Cairo::RefPtr<Cairo::ImageSurface> const& surface)
      {
        m_cover_surface  = surface;

        m_cover_pos      = cover_anim_initial_pos;
        m_cover_velocity = cover_anim_initial_velocity;
        m_cover_accel    = cover_anim_gravity;

        m_cover_anim_conn.disconnect ();
        m_cover_anim_conn = signal_timeout ().connect (sigc::mem_fun (this, &InfoArea::slide_in_cover), cover_anim_interval);
      }

    private:

      void
      insert_layout_and_connect (LayoutID id,
                                 TextP &  p)
      {
        m_layouts[id] = p;
        p->conn = signal_timeout().connect (sigc::bind (sigc::mem_fun (this, &InfoArea::fade_in), id), 20);
      }

      void
      remove_layout_if_exists (LayoutID id)
      {
        Layouts::iterator i = m_layouts.find (id);
        if( i != m_layouts.end() )
        {
          TextP & p = i->second;

          p->conn.disconnect ();
          m_layouts.erase (i);
        }
      }

      bool
      fade_in (LayoutID id)
      {
        TextP & p = m_layouts[id];
        p->alpha += 0.1;

        bool r = p->alpha < p->target;

        queue_draw ();

        return r;
      }

      bool
      slide_in_cover ()
      {
        m_cover_pos      += m_cover_velocity * cover_anim_dt;
        m_cover_velocity += m_cover_accel    * cover_anim_dt;
        m_cover_accel     = cover_anim_gravity;

        bool r = true;

        if (m_cover_pos + m_cover_surface->get_width () >= cover_anim_wall)
        {
            m_cover_pos       = cover_anim_wall - m_cover_surface->get_width ();
            m_cover_velocity *= -cover_anim_wall_elasticity;

            // FIXME: Need a better test. This runs into stability problems when
            // dt or acceleration is too high

            double next_velocity = m_cover_velocity + m_cover_accel * cover_anim_dt;

            if (next_velocity >= 0.0)
            {
                m_cover_pos      = cover_anim_wall - m_cover_surface->get_width ();
                m_cover_velocity = 0.0;
                m_cover_accel    = 0.0;

                r = false;
            }
        }

        queue_draw ();

        return r;
      }

      bool
      decay_spectrum ()
      {
        for (int n = 0; n < SPECT_BANDS; ++n)
        {
          m_spectrum_data[n] = (((m_spectrum_data[n] - 6) < 0) ? 0 : (m_spectrum_data[n] - 6));
          m_spectrum_peak[n] = (((m_spectrum_peak[n] - 4) < 0) ? 0 : (m_spectrum_peak[n] - 4));
        }

        queue_draw ();

        return true;
      }

      void
      play_status_changed ()
      {
        BmpPlaystatus status = BmpPlaystatus (Play::Obj ()->property_status ().get_value ());
        if( status == PLAYSTATUS_PAUSED )
        {
          m_spectrum_decay_conn = Glib::signal_timeout ().connect (sigc::mem_fun (this, &InfoArea::decay_spectrum), 50);
        }
        else
        {
          m_spectrum_decay_conn.disconnect ();
        }
      }

      void
      play_update_spectrum (Spectrum const& spectrum)
      {
        for (int n = 0; n < SPECT_BANDS; ++n)
        {
          if( spectrum[n] < m_spectrum_data[n] )
          {
            m_spectrum_data[n] = (((m_spectrum_data[n] - 6) < 0) ? 0 : (m_spectrum_data[n] - 6));
            m_spectrum_peak[n] = (((m_spectrum_peak[n] - 2) < 0) ? 0 : (m_spectrum_peak[n] - 2));
          }
          else
          {
            m_spectrum_data[n] = spectrum[n];
            m_spectrum_peak[n] = spectrum[n];
          }
        }

        queue_draw ();
      }

      void
      draw_background (Cairo::RefPtr<Cairo::Context> const& cr)
      {
        Gtk::Allocation allocation = get_allocation ();

        Gdk::Cairo::set_source_color (cr, get_style()->get_bg (Gtk::STATE_SELECTED));
        cr->rectangle (0, 0, allocation.get_width (), allocation.get_height ());
        cr->stroke ();
      }

      void
      draw_cover (Cairo::RefPtr<Cairo::Context> const& cr)
      {
        if( m_cover_surface && (m_cover_pos >= cover_anim_area_x0 - m_cover_surface->get_width ()) )
        {
          cr->save ();

          cr->rectangle (cover_anim_area_x0, cover_anim_area_y0, cover_anim_area_width, cover_anim_area_height);
          cr->clip ();

          double y = (cover_anim_area_y0 + cover_anim_area_y1 - m_cover_surface->get_height ()) / 2;

          draw_cairo_image (cr, m_cover_surface, m_cover_pos, y, m_cover_alpha);

          cr->restore ();
        }
      }

      void
      draw_text (Cairo::RefPtr<Cairo::Context> const& cr)
      {
        Gtk::Allocation allocation = get_allocation ();

        for (Layouts::const_iterator i = m_layouts.begin(); i != m_layouts.end(); ++i)
        {
          TextP const& p (i->second);
          if( p->alpha < 0 )
            continue;

          cr->set_source_rgba (1.0, 1.0, 1.0, p->alpha);
          cr->set_operator (Cairo::OPERATOR_ATOP);
          cr->move_to (p->x, p->y);

          p->layout->set_single_paragraph_mode (true);
          p->layout->set_ellipsize (Pango::ELLIPSIZE_END);
          p->layout->set_width ((allocation.get_width() - p->x - 210) * PANGO_SCALE);
          p->layout->set_wrap (Pango::WRAP_CHAR);

          pango_cairo_show_layout (cr->cobj(), p->layout->gobj());
        }
      }

      void
      draw_spectrum (Cairo::RefPtr<Cairo::Context> const& cr)
      {
        Gtk::Allocation allocation = get_allocation ();

        for (int n = 0; n < SPECT_BANDS; ++n)
        {
          int x = 0, y = 0, w = 0, h = 0;

          // Bar
          x = allocation.get_width () - (8*SPECT_BANDS+8) + (n*8);
          y = 11 + (64 - (m_spectrum_data[n]));

          w = 6;
          h = (m_spectrum_data[n]);

          cr->set_source_rgba (0.7, 0.7, 0.7, 1.0);
          cr->rectangle (x,y,w,h);
          cr->fill ();

          // Peak
          if( m_spectrum_peak[n] > 0 )
          {
            y = 11 + (64 - m_spectrum_peak[n]);

            h = 1;

            cr->set_source_rgba (0.8, 0.8, 0.8, 1.0);
            cr->rectangle (x, y-1, w, h);
            cr->fill ();
          }
        }
      }

      void
      draw_source_icon (Cairo::RefPtr<Cairo::Context> const& cr)
      {
        if( m_source_icon && m_compact )
        {
          Gtk::Allocation allocation = get_allocation ();

          cr->set_operator (Cairo::OPERATOR_ATOP);
          Gdk::Cairo::set_source_pixbuf (cr, m_source_icon, allocation.get_width () - 38, 10);
          cr->rectangle (allocation.get_width () - 38, 0, 20, 20);
          cr->paint_with_alpha (0.6);
        }
      }

    protected:

      virtual bool
      on_expose_event (GdkEventExpose * event)
      {
        Widget::on_expose_event (event);

        Cairo::RefPtr<Cairo::Context> cr = get_window ()->create_cairo_context ();

        draw_background (cr);
        draw_cover (cr);
        draw_text (cr);
        draw_spectrum (cr);
        draw_source_icon (cr);

        return true;
      }
  };
}

namespace
{
  void
  parse_metadata (Bmp::TrackMetadata const &metadata,
                  ustring             &artist,
                  ustring             &album,
                  ustring             &title)
  {
    using namespace Bmp;

    static char const *
      text_b_f ("<b>%s</b>");

    static char const *
      text_b_f2 ("<b>%s</b> (%s)");

    static char const *
      text_big_f ("<big>%s</big>");

    static char const *
      text_album_artist_date_f ("%s (%s, <small>%s</small>)");

    static char const *
      text_album_artist_f ("%s (%s)");

    static char const *
      text_album_date_f ("%s (<small>%s</small>)");

    static char const *
      text_album_f ("%s");

    static char const *
      text_artist_date_f ("(%s, <small>%s</small>)");

    static char const *
      text_artist_f ("(%s)");

    static char const *
      text_date_f ("(<small>%s</small>)");

    opt_stdstring date;

#if 0
    if (metadata.mb_release_date)
      date = Util::get_date_string (metadata.mb_release_date.get());
    else if( metadata.date && (metadata.date.get() != 0) )
      date = (format_int % metadata.date.get()).str();
#endif

    if( (metadata.mb_album_artist_id != metadata.mb_artist_id) && metadata.mb_album_artist )
    {
#if 0
      if( (date) && (date.get().size()) )
      {
        if( metadata.album )
        {
          album = gprintf (text_album_artist_date_f,
                               Markup::escape_text (metadata.album.get()).c_str(),
                               Markup::escape_text (metadata.mb_album_artist.get()).c_str(),
                               date.get().c_str());
        }
        else
        {
          album = gprintf (text_artist_date_f,
                               Markup::escape_text (metadata.mb_album_artist.get()).c_str(),
                               date.get().c_str());
        }
      }
      else
#endif      
      {
        if( metadata.album )
        {
          album = gprintf (text_album_artist_f,
                               Markup::escape_text (metadata.album.get()).c_str(),
                               Markup::escape_text (metadata.mb_album_artist.get()).c_str());
        }
        else
        {
          album = gprintf (text_artist_f,
                               Markup::escape_text (metadata.mb_album_artist.get()).c_str());
        }
      }
    }
    else
    {
#if 0
      if( (date) && (date.get().size()) )
      {
        if( metadata.album )
        {
          album = gprintf (text_album_date_f,
                               Markup::escape_text (metadata.album.get()).c_str(),
                               date.get().c_str());
        }
        else
        {
          album = gprintf (text_date_f,
                               date.get().c_str());
        }
      }
      else
#endif
      {
        if( metadata.album )
        {
          album = gprintf (text_album_f,
                               Markup::escape_text (metadata.album.get()).c_str());
        }
      }
    }

    if ((metadata.mb_artist_sort_name && metadata.artist) &&
        (metadata.mb_artist_sort_name != metadata.artist)) /* let's display the artist if it's not identical to the sortname */
    {
      std::string a = metadata.mb_artist_sort_name.get();

      if( MusicBrainzUtil::reverse_sortname (a) )
      {
        std::string b = metadata.artist.get();

        if( a == b )
        {
          artist = gprintf (text_b_f, Markup::escape_text (metadata.artist.get()).c_str());
          goto artist_out;
        }
      }

      artist = gprintf (text_b_f2,
                            Markup::escape_text (metadata.mb_artist_sort_name.get()).c_str(),
                            Markup::escape_text (metadata.artist.get()).c_str());
    }
    else
    if( metadata.artist )
    {
      artist = gprintf (text_b_f, Markup::escape_text (metadata.artist.get()).c_str());
    }

    artist_out:

    if( metadata.title )
    {
      title = gprintf (text_big_f, Markup::escape_text (metadata.title.get()).c_str());
    }
  }
}

namespace
{
  bool video_type (std::string const& type)
  {
    return (type.size() && type.substr (0, 6) == std::string("video/"));
  }
}

namespace Bmp
{

#define TYPE_DBUS_OBJ_PLAYER (Player::get_type ())
#define DBUS_OBJ_PLAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_DBUS_OBJ_PLAYER, Player))

  enum MPRISPlayerSignals
  {
    SIGNAL_TRACK_CHANGE,
    SIGNAL_STATUS_CHANGE,
    SIGNAL_CAPS_CHANGE,

    LAST_SIGNAL
  };

  namespace MPRISPlayer
  {
    static guint mpris_player_signals[LAST_SIGNAL] = { 0 };
  }

  class PlayerShell::Player;
  struct PlayerClass
  {
    GObjectClass parent;

    void  (* track_change  )  ( PlayerShell::Player * player, GHashTable*);
    void  (* status_change )  ( PlayerShell::Player * player, int status);
    void  (* caps_change   )  ( PlayerShell::Player * player, int caps);
  };

  struct PlayerShell::Player
  {
    GObject parent;
    PlayerShell * m_shell;

    static gpointer parent_class;

    static GType
    get_type ();

    static Player *
    create (PlayerShell & shell);

    static void
    class_init (gpointer klass,
                gpointer class_data);

    static GObject *
    constructor (GType                   type,
                 guint                   n_construct_properties,
                 GObjectConstructParam * construct_properties);

    //// Remote signal invocation

    static bool
    emit_track_change (Player * self);

    static void
    emit_status_change (Player * self, int status);

    static void
    emit_caps_change (Player * self, int caps);

    //// Remote methods

    static gboolean
    volume_set (Player * self,
                int            volume,
                GError **      error);

    static gboolean
    volume_get (Player * self,
                int *          volume,
                GError **      error);

    static gboolean
    next (Player * self,
          GError ** error);

    static gboolean
    prev (Player * self,
          GError ** error);

    static gboolean
    pause  (Player * self,
            GError ** error);

    static gboolean
    play   (Player * self,
            GError ** error);

    static gboolean
    stop   (Player * self,
            GError ** error);

    static gboolean
    get_metadata (Player *        self,
                  GHashTable**    metadata,
                  GError**        error);

    static gboolean
    play_uris    (Player *        self,
                  char**          uris,
                  GError**        error);

    /// SessionPresence
    static void
    on_spm_status (DBusGProxy * proxy,
                   GValue * p1,
                   gpointer data);
  };

  gpointer PlayerShell::Player::parent_class = 0;

// HACK: Hackery to rename functions in glue
#define player_next                 next
#define player_prev                 prev
#define player_pause                pause
#define player_stop                 stop
#define player_play                 play
#define player_volume_set           volume_set
#define player_volume_get           volume_get
#define player_get_metadata         get_metadata
#define player_play_uris            play_uris

#include "dbus-obj-player-glue.h"

  void
  PlayerShell::Player::class_init (gpointer klass,
                                   gpointer class_data)
  {
    parent_class = g_type_class_peek_parent (klass);

    GObjectClass *gobject_class = reinterpret_cast<GObjectClass*>(klass);
    gobject_class->constructor  = &Player::constructor;

    ::Bmp::MPRISPlayer::mpris_player_signals[SIGNAL_TRACK_CHANGE] =
      g_signal_new ("track-change",
                    G_OBJECT_CLASS_TYPE (klass),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (PlayerClass, track_change),
                    NULL, NULL,
                    g_cclosure_marshal_VOID__BOXED,
                    G_TYPE_NONE, 1, dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE));

    ::Bmp::MPRISPlayer::mpris_player_signals[SIGNAL_STATUS_CHANGE] =
      g_signal_new ("status-change",
                    G_OBJECT_CLASS_TYPE (klass),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (PlayerClass, status_change),
                    NULL, NULL,
                    g_cclosure_marshal_VOID__INT,
                    G_TYPE_NONE, 1, G_TYPE_INT);

    ::Bmp::MPRISPlayer::mpris_player_signals[SIGNAL_CAPS_CHANGE] =
      g_signal_new ("caps-change",
                    G_OBJECT_CLASS_TYPE (klass),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (PlayerClass, caps_change),
                    NULL, NULL,
                    g_cclosure_marshal_VOID__INT,
                    G_TYPE_NONE, 1, G_TYPE_INT);
  }

  GObject *
  PlayerShell::Player::constructor (GType                   type,
                                    guint                   n_construct_properties,
                                    GObjectConstructParam*  construct_properties)
  {
    GObject *object = G_OBJECT_CLASS (parent_class)->constructor (type, n_construct_properties, construct_properties);
    return object;
  }

  void
  PlayerShell::Player::on_spm_status (DBusGProxy * proxy,
                                      GValue * p1,
                                      gpointer data)
  {
    if( ::mcs->key_get <bool> ("bmp", "spm-listen") )
    {
      PlayerShell::Player & player = *(reinterpret_cast<PlayerShell::Player*>(data));

      GType target_type = dbus_g_type_get_struct ("GValueArray", G_TYPE_STRV, G_TYPE_STRING, G_TYPE_INVALID);
      g_return_if_fail (p1->g_type == target_type);

      GValue * v1 = g_value_array_get_nth ((GValueArray*)(g_value_get_boxed (p1)), 0);
      //GValue * v2 = g_value_array_get_nth ((GValueArray*)(g_value_get_boxed (p1)), 1);

      char ** strv = (char**)(g_value_get_boxed (v1));

      if( strv && strv[0] )
      {
        if( (std::string (strv[0]) == "absent") && Play::Obj()->property_status() == PLAYSTATUS_PLAYING )
        {
          Play::Obj()->request_status (PLAYSTATUS_PAUSED);
          player.m_shell->m_spm_paused = true;
        }
        else if( (std::string (strv[0]) == "present") && Play::Obj()->property_status() == PLAYSTATUS_PAUSED && player.m_shell->m_spm_paused )
        {
          Play::Obj()->request_status (PLAYSTATUS_PLAYING);
        }
      }
    }

    g_value_unset (p1);
  }

  PlayerShell::Player *
  PlayerShell::Player::create (PlayerShell & shell)
  {
    dbus_g_object_type_install_info (TYPE_DBUS_OBJ_PLAYER, &dbus_glib_player_object_info);

    Player * self = DBUS_OBJ_PLAYER (g_object_new (TYPE_DBUS_OBJ_PLAYER, NULL));
    self->m_shell = &shell;

    if( ::Bmp::DBus::dbus_connected )
    {
      dbus_connection_setup_with_g_main (dbus_g_connection_get_connection (::Bmp::DBus::dbus_session_bus), g_main_context_default());

      dbus_g_connection_register_g_object (::Bmp::DBus::dbus_session_bus, BMP_DBUS_PATH__MPRIS_PLAYER, G_OBJECT(self));
      debug("shell","object: '/%s', interface '%s', service '%s'", G_OBJECT_TYPE_NAME(self), BMP_DBUS_INTERFACE__MPRIS, BMP_DBUS_SERVICE);

#if 0
      // Connect SessionPresence stuff
      DBusGProxy * spm_proxy = dbus_g_proxy_new_for_name (::Bmp::DBus::dbus_session_bus, "com.weej.SessionPresenceManager",
            "/com/weej/SessionPresenceManager", "com.weej.SessionPresenceManager");

      dbus_g_object_register_marshaller (bmp_dbus_marshal_VOID__BOXED, G_TYPE_NONE, G_TYPE_VALUE, G_TYPE_INVALID);
      dbus_g_proxy_add_signal (spm_proxy, "StatusChanged", G_TYPE_VALUE, G_TYPE_INVALID);
      dbus_g_proxy_connect_signal (spm_proxy, "StatusChanged", G_CALLBACK (PlayerShell::Player::on_spm_status), self, NULL);
#endif
    }

    return self;
  }

  GType
  PlayerShell::Player::get_type ()
  {
    static GType type = 0;

    if( G_UNLIKELY (type == 0) )
      {
        static GTypeInfo const type_info =
          {
            sizeof (PlayerClass),
            NULL,
            NULL,
            &class_init,
            NULL,
            NULL,
            sizeof (Player),
            0,
            NULL
          };

        type = g_type_register_static (G_TYPE_OBJECT, "Player", &type_info, GTypeFlags (0));
      }

    return type;
  }

  gboolean
  PlayerShell::Player::get_metadata (Player *       self,
                                     GHashTable**   metadata,
                                     GError**       error)
  {
    if( self->m_shell->m_selection->m_active_source == SOURCE_NONE )
    {
      g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "GetMetadata RMI: No Active Source");
      *error = g_error_new (g_quark_from_string ("org.mpris.bmp.DBusError"), 0, "No Active Source");
      return FALSE;
    }

    *metadata = self->m_shell->get_metadata_hash_table ();
    if( !*metadata )
    {
      g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "Invalid source specified: %d", self->m_shell->m_selection->m_active_source);
      *error = g_error_new (g_quark_from_string ("org.mpris.bmp.DBusError"), 0, "No Metadata Available");
      return FALSE;
    }

    return TRUE;
  }

  gboolean
  PlayerShell::Player::play_uris     (Player*   self,
                                      char**    uris,
                                      GError**  error)
  {
    VUri uriv;

    if( uris )
    {
      while (*uris)
      {
        uriv.push_back (*uris);
        ++uris;
      }
      self->m_shell->play_uris (uriv);
    }
    return TRUE;
  }

  gboolean
  PlayerShell::Player::volume_set (Player*   self,
                                   int      volume,
                                   GError** error)
  {
    mcs->key_set<int> ("bmp", "volume", volume);
    return TRUE;
  }

  gboolean
  PlayerShell::Player::volume_get (Player*   self,
                                   int*     volume,
                                   GError** error)
  {
    *volume = mcs->key_get<int> ("bmp", "volume");
    return TRUE;
  }

  gboolean
  PlayerShell::Player::next  (Player*     self,
                              GError**   error)
  {
    if( self->m_shell->m_selection->m_active_source == SOURCE_NONE )
      return TRUE;

    if( self->m_shell->m_source_caps[self->m_shell->m_selection->m_active_source] & PlaybackSource::CAN_GO_NEXT )
      self->m_shell->next ();

    return TRUE;
  }

  gboolean
  PlayerShell::Player::prev  (Player*     self,
                              GError**   error)
  {
    if( self->m_shell->m_selection->m_active_source == SOURCE_NONE )
      return TRUE;

    if( self->m_shell->m_source_caps[self->m_shell->m_selection->m_active_source] & PlaybackSource::CAN_GO_PREV )
      self->m_shell->prev ();

    return TRUE;
  }

  gboolean
  PlayerShell::Player::pause   (Player*      self,
                                GError**    error)
  {
    PlaybackSource::Caps c = self->m_shell->m_source_caps[self->m_shell->m_selection->m_active_source];

    if( c & PlaybackSource::CAN_PAUSE )
      self->m_shell->pause ();

    return TRUE;
  }

  gboolean
  PlayerShell::Player::stop   (Player*       self,
                               GError**     error)
  {
    self->m_shell->stop ();
    return TRUE;
  }

  gboolean
  PlayerShell::Player::play   (Player*       self,
                               GError**     error)
  {
    self->m_shell->play ();
    return TRUE;
  }

  bool
  PlayerShell::Player::emit_track_change (Player* self)
  {
    g_signal_emit (self, ::Bmp::MPRISPlayer::mpris_player_signals[SIGNAL_TRACK_CHANGE], 0, self->m_shell->get_metadata_hash_table ());
    return false;
  }

  void
  PlayerShell::Player::emit_caps_change (Player* self, int caps)
  {
    g_signal_emit (self, ::Bmp::MPRISPlayer::mpris_player_signals[SIGNAL_CAPS_CHANGE], 0, caps);
  }

  void
  PlayerShell::Player::emit_status_change (Player* self, int status)
  {
    using namespace Bmp;

    int mpris_playstatus = -1;

    switch (status)
    {
      case PLAYSTATUS_PLAYING:
        mpris_playstatus = 0;
        break;

      case PLAYSTATUS_PAUSED:
        mpris_playstatus = 1;
        break;

      case PLAYSTATUS_STOPPED:
        mpris_playstatus = 2;
        break;
    };

    g_signal_emit (self, ::Bmp::MPRISPlayer::mpris_player_signals[SIGNAL_STATUS_CHANGE], 0, mpris_playstatus);
  }

  //// SourceSelection

  SourceSelection::SourceSelection (BaseObjectType                 * obj,
                                    RefPtr<Gnome::Glade::Xml> const& xml)
  : VBox              (obj)
  , m_active_source   (SOURCE_NONE)
  , m_selected_source (SOURCE_NONE)
  {
    bool network_present = NM::Obj()->Check_Status();
    for (unsigned int n = 0; n < N_SOURCES; ++n)
    {
      if( sources[n].netOnly && !network_present )
      {
        continue;
      }
      else
      {
        SourceButton * b = manage (new SourceButton());
        b->set_label ((boost::format ("<span weight='600'>%s</span> <small>(_%u)</small>") % _(sources[n].name.c_str()) % (n+1)).str(), true);

        if(sources[n].useTheme)
        {
            try{
                b->set_image(IconTheme::get_default()->load_icon(sources[n].stock, 32, ICON_LOOKUP_GENERIC_FALLBACK));
            } catch(...)
              {
                b->set_image(Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_SOURCES, sources[n].icon))->scale_simple(32,32,Gdk::INTERP_BILINEAR));

              }
        }
        else
        {
                b->set_image(Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_SOURCES, sources[n].icon))->scale_simple(32,32,Gdk::INTERP_BILINEAR));
        }

        b->show_all ();
        b->signal_clicked().connect (sigc::bind (sigc::mem_fun (*this, &SourceSelection::on_source_button_clicked), PlaybackSourceID (n)));
        pack_start (*b, false, false);
        m_source_selection_buttons_map.insert (make_pair (PlaybackSourceID (n), b));
        m_notebook_tab_map.insert (make_pair (PlaybackSourceID (n), n));
      }
    }
    set_homogeneous (false);
    get_parent()->signal_configure_event().connect( sigc::mem_fun( *this, &SourceSelection::on_parent_configure ) );
  }

  bool
  SourceSelection::on_expose_event (GdkEventExpose * event)
  {
    Cairo::RefPtr<Cairo::Context> cr = get_window ()->create_cairo_context ();

    int xoff = get_allocation().get_x();
    int yoff = get_allocation().get_y();

    cr->rectangle (xoff,
                   yoff,
                   get_allocation().get_width(),
                   get_allocation().get_height());

    Gdk::Color c (get_style()->get_base (Gtk::STATE_NORMAL));
    cr->set_source_rgba (c.get_red_p(), c.get_green_p(), c.get_blue_p(), 1.);
    cr->fill ();

    Widget::on_expose_event (event);

    gtk_paint_shadow (GTK_WIDGET (gobj())->style,
                      GTK_WIDGET (gobj())->window,
                      GTK_STATE_NORMAL,
                      GTK_SHADOW_IN,
                      NULL,
                      GTK_WIDGET (gobj()),
                      "scrolled_window",
                      get_allocation().get_x(),
                      get_allocation().get_y(),
                      get_allocation().get_width(),
                      get_allocation().get_height());
    return true;
  }

  void
  SourceSelection::source_activate (PlaybackSourceID source)
  {
    if( m_active_source != SOURCE_NONE )
      m_source_selection_buttons_map.find (m_active_source)->second->set_playing (0);

    m_active_source = source;

    if( m_active_source != SOURCE_NONE )
      m_source_selection_buttons_map.find (m_active_source)->second->set_playing (1);

    queue_draw ();
  }

  void
  SourceSelection::source_select (PlaybackSourceID new_source)
  {
    PlaybackSourceID old_source = m_selected_source;
    m_selected_source = new_source;

    if( old_source != SOURCE_NONE )
        m_source_selection_buttons_map.find (old_source)->second->set_active (0);
    if( new_source != SOURCE_NONE )
        m_source_selection_buttons_map.find (new_source)->second->set_active (1);

    source_selected_.emit (new_source);
    queue_draw ();
  }

  void
  SourceSelection::on_source_button_clicked (PlaybackSourceID source)
  {
    source_select (source);
  }

  void
  SourceSelection::on_size_allocate (Gtk::Allocation & alloc)
  {
    Gtk::Widget::on_size_allocate (alloc);
    queue_draw ();
  }

  bool
  SourceSelection::on_parent_configure (GdkEventConfigure * event)
  {
    queue_draw ();
    return false;
  }

  ///////////////////////////////////////////
  //// PlayerShell                         //
  ///////////////////////////////////////////

  PlayerShell::PlayerShell (BaseObjectType                 *  obj,
                            RefPtr<Gnome::Glade::Xml> const&  xml)
  : Window                  (obj)
  , m_ref_xml               (xml)
  , m_player_dbus_obj       (PlayerShell::Player::create (*this))
  , m_ui_merge_id           (0)
  , m_ui_merge_id_context   (0)
  , m_ui_merge_id_tray      (0)
  , m_ui_merge_id_lastfm    (0)
  , m_popup                 (0)
  , m_n_status_messages     (0)
  , m_visible               (1)
  , m_seeking               (0)
  , m_spm_paused            (0)
  , m_IgnorePauseToggled    (0)
  , m_mmkeys_dbusproxy      (0)
  {
    int network_present = NM::Obj()->Check_Status();

    register_stock_icons ();
    window_set_icon_list (*this, "player");

    m_ref_xml->get_widget ("statusbar", m_statusbar);

    mcs_bind = new Mcs::Bind (mcs);
    m_about = new Bmp::AboutDialog;
    m_prefs = Preferences::create ();
    m_simple_progress = SimpleProgress::create ();

    m_throbber = Gdk::PixbufAnimation::create_from_file( build_filename( BMP_IMAGE_DIR, "throbber.gif" ));

    dynamic_cast<Button*>(m_ref_xml->get_widget("statusbar-clear"))->signal_clicked().connect(
      sigc::mem_fun (*this, &Bmp::PlayerShell::status_message_clear ) );

    m_audio_default = Gdk::Pixbuf::create_from_file( build_filename( BMP_IMAGE_DIR, "audio-default.png" ));
    m_audio_default_64 = m_audio_default->scale_simple( 72, 72, Gdk::INTERP_HYPER );

    try{
        m_video_default = IconTheme::get_default()->load_icon("video-x-generic", 256, ICON_LOOKUP_GENERIC_FALLBACK);
        m_video_default_64 = IconTheme::get_default()->load_icon("video-x-generic", 72, ICON_LOOKUP_GENERIC_FALLBACK);
     }
    catch (...)
    {
        m_video_default = Gdk::Pixbuf::create_from_file( build_filename( BMP_IMAGE_DIR, "video-default.png" ));
        m_video_default_64 = m_video_default->scale_simple( 72, 72, Gdk::INTERP_HYPER );
    }

    // Source Images
    {
      for (unsigned int n = 0; n < N_SOURCES; ++n)
      {
        if(sources[n].useTheme)
        {
            try{
                m_source_icons[n] = IconTheme::get_default()->load_icon(sources[n].stock, 256, ICON_LOOKUP_GENERIC_FALLBACK);
            } catch(...)
                {
                    m_source_icons[n] = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_SOURCES, sources[n].icon));
                }
        }
        else
        {
                m_source_icons[n] = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_SOURCES, sources[n].icon));
        }
        m_source_icons_med[n] = m_source_icons[n]->scale_simple (72, 72, Gdk::INTERP_HYPER);
        m_source_icons_small[n] = m_source_icons[n]->scale_simple (32, 32, Gdk::INTERP_HYPER);
      }
    }

    // Seeking
    {
      m_ref_xml->get_widget ("seek-progress", m_seek_progress);
      m_seek_progress->set_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK | Gdk::POINTER_MOTION_HINT_MASK);
      m_seek_progress->signal_event().connect( sigc::mem_fun( *this, &Bmp::PlayerShell::on_seek_event ) );
      m_seek_progress->signal_expose_event().connect( sigc::mem_fun( *this, &Bmp::PlayerShell::on_seek_expose ) );
      m_seek_progress->set_sensitive( 0 );
      m_seek_progress->set_text(" ");
    }

    // Metadata Black Bar
    {
      m_ref_xml->get_widget_derived ("eventbox-info", m_infoarea);

      m_infoarea->signal_uris_dropped().connect
        ( sigc::mem_fun( *this, &Bmp::PlayerShell::play_uris ) );

      m_infoarea->signal_cover_clicked().connect
        ( sigc::mem_fun( *this, &Bmp::PlayerShell::on_shell_display_track_details) );
    }

    // Volume
    {
      m_ref_xml->get_widget_derived ("scale-volume", m_volume);
      mcs_bind->bind_range (*dynamic_cast<Range*>(m_volume), "bmp", "volume");
      mcs->subscribe ("PlayerShell", "bmp", "volume", sigc::mem_fun (*this, &Bmp::PlayerShell::on_volume_changed));
    }

    // Navigation
    {
      m_ref_xml->get_widget ("notebook-main", m_notebook_main);
      m_ref_xml->get_widget_derived ("vbox-sources", m_selection);
    }

    // Playback Engine Signals
    {
      Play::Obj()->property_status().signal_changed().connect
            (sigc::mem_fun (*this, &Bmp::PlayerShell::on_play_status_changed));
      Play::Obj()->signal_position().connect
            (sigc::mem_fun (*this, &Bmp::PlayerShell::on_play_position));
      Play::Obj()->signal_buffering().connect
            (sigc::mem_fun (*this, &Bmp::PlayerShell::on_play_buffering));
      Play::Obj()->signal_error().connect
            (sigc::mem_fun (*this, &Bmp::PlayerShell::on_play_error));
      Play::Obj()->signal_eos().connect
            (sigc::mem_fun (*this, &Bmp::PlayerShell::on_play_eos));
      Play::Obj()->signal_metadata().connect
            (sigc::mem_fun (*this, &Bmp::PlayerShell::on_gst_metadata));
    }

    // UIManager + Menus + Proxy Widgets
    {
      m_ui_manager = UIManager::create ();
      m_actions = ActionGroup::create ("ActionsMain");

      m_actions->add (Action::create ("dummy",
            "dummy"));
      m_actions->add (Action::create ("MenuPlayer",
          _("_BMP")));
      m_actions->add (Action::create ("MenuView",
          _("Vi_ew")));
      m_actions->add (Action::create ("MenuTrack",
          _("_Track")));
      m_actions->add (Action::create ("MenuPlayback",
          _("_Playback")));
      m_actions->add (Action::create ("MenuHelp",
          _("_Help")));
      m_actions->add (Action::create ("MenuVideo",
          _("_Video Aspect Ratio")));

      ///////////////////////////////
      // BOOKMARKS
      ///////////////////////////////

      m_actions->add (Action::create ("MenuBookmarks",
                                      _("Boo_kmarks")));

      m_actions->add (Action::create ("MenuBookmarks-radio",
                                      Gtk::StockID (BMP_STOCK_XIPH),
                                      "Radio"));

      m_actions->add (Action::create ("MenuBookmarks-jamendo",
                                      Gtk::StockID (BMP_STOCK_JAMENDO),
                                      "Jamendo"));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_CREATE_BOOKMARK,
                                      Gtk::Stock::ADD,
                                      _("_Create Bookmark")),
                                      AccelKey ("<control>d"),
                                      sigc::mem_fun (*this, &PlayerShell::on_bookmark_create));

      ////////////////////////////////
      // PLAYBACK ACTIONS
      ////////////////////////////////

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_PLAY,
                                      Gtk::Stock::MEDIA_PLAY,
                                      _("_Play")),
                                      AccelKey ("<alt>z"),
                                      sigc::mem_fun (*this, &PlayerShell::play));

      m_actions->add (ToggleAction::create (PLAYER_SHELL_ACTION_PAUSE,
                                      Gtk::Stock::MEDIA_PAUSE,
                                      _("P_ause")),
                                      AccelKey ("<Alt>x"),
                                      sigc::mem_fun (*this, &PlayerShell::pause));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_PREV,
                                      Gtk::Stock::MEDIA_PREVIOUS,
                                      _("P_rev")),
                                      AccelKey ("<Alt>Left"),
                                      sigc::mem_fun (*this, &PlayerShell::prev));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_NEXT,
                                      Gtk::Stock::MEDIA_NEXT,
                                      _("_Next")),
                                      AccelKey ("<Alt>Right"),
                                      sigc::mem_fun (*this, &PlayerShell::next));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_STOP,
                                      Gtk::Stock::MEDIA_STOP,
                                      _("_Stop")),
                                      AccelKey ("<Alt>c"),
                                     sigc::mem_fun (*this, &PlayerShell::stop));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_EQ,
                                      Gtk::StockID (BMP_STOCK_EQ),
                                      _("_Equalizer")),
                                      AccelKey ("<control>e"),
                                      sigc::mem_fun (*this, &PlayerShell::on_shell_display_eq));

      m_actions->add (ToggleAction::create (PLAYER_SHELL_ACTION_REPEAT,
                                      Gtk::StockID (BMP_STOCK_REPEAT),
                                      _("_Repeat")),
                                      AccelKey ("<control>r"));

      m_actions->add (ToggleAction::create (PLAYER_SHELL_ACTION_SHUFFLE,
                                      Gtk::StockID (BMP_STOCK_SHUFFLE),
                                      _("_Shuffle")),
                                      AccelKey ("<control>s"));

      //////////////////////////////
      // MISC ACTIONS
      //////////////////////////////

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_PLAY_FILES,
                                      Stock::OPEN,
                                      _("Play Files...")),
                                      sigc::mem_fun (*this, &PlayerShell::on_play_files));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_ENQUEUE_FILES,
                                      Stock::ADD,
                                      _("Enqueue Files...")),
                                      AccelKey (""),
                                      sigc::mem_fun (*this, &PlayerShell::on_enqueue_files));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_PLAY_STREAM,
                                      StockID (BMP_STOCK_HTML_LINK),
                                      _("Play Location...")),
                                      AccelKey (""),
                                      sigc::mem_fun (*this, &PlayerShell::on_play_stream));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_ABOUT,
                                      Stock::ABOUT,
                                      _("_About")),
                                      sigc::mem_fun (*m_about, &AboutDialog::present));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_PREFS,
                                      Stock::PREFERENCES,
                                      _("_Preferences")),
                                      AccelKey ("<control>p"),
                                      sigc::mem_fun (*this, &PlayerShell::on_shell_display_preferences));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_CLOSE,
                                      Stock::CLOSE,
                                      _("_Close")),
                                      AccelKey ("<control>w"),
                                      sigc::mem_fun (*this, &PlayerShell::on_close_window));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_QUIT,
                                      Stock::QUIT,
                                      _("_Quit")),
                                      AccelKey ("<control>q"),
                                      sigc::mem_fun (Core::Obj(), &Core::stop));

      m_actions->add (ToggleAction::create (PLAYER_SHELL_ACTION_SOURCES,
                                      _("Sources")),
                                      AccelKey ("<control>1"),
                                      sigc::mem_fun (*this, &PlayerShell::on_shell_toggle_sources));

      m_actions->add (ToggleAction::create (PLAYER_SHELL_ACTION_INFO,
                                      _("Info Area")),
                                      AccelKey ("<control>2"),
                                      sigc::mem_fun (*this, &PlayerShell::on_shell_toggle_info));

      m_actions->add (ToggleAction::create (PLAYER_SHELL_ACTION_STATUSBAR,
                                      _("Statusbar")),
                                      AccelKey ("<control>3"),
                                      sigc::mem_fun (*this, &PlayerShell::on_shell_toggle_statusbar));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_TRACK_DETAILS,
                                      Stock::INFO,
                                      _("Playing Track Info")),
                                      AccelKey ("<control>t"),
                                      sigc::mem_fun (*this, &PlayerShell::on_shell_display_track_details));

      ///////////////////////////
      // Video                 //
      ///////////////////////////

      Gtk::RadioButtonGroup gr1;
      m_actions->add  (Gtk::RadioAction::create (gr1, PLAYER_SHELL_ACTION_VIDEO_ASPECT_AUTO,
                                                _("Auto")),
                                                (sigc::mem_fun (*this, &PlayerShell::set_video_ar)));
      RefPtr<Gtk::RadioAction>::cast_static (m_actions->get_action
        (PLAYER_SHELL_ACTION_VIDEO_ASPECT_AUTO))->property_value() = 0;

      m_actions->add  (Gtk::RadioAction::create (gr1, PLAYER_SHELL_ACTION_VIDEO_ASPECT_43,
                                                _("4:3 (TV)")),
                                                (sigc::mem_fun (*this, &PlayerShell::set_video_ar)));
      RefPtr<Gtk::RadioAction>::cast_static (m_actions->get_action
        (PLAYER_SHELL_ACTION_VIDEO_ASPECT_43))->property_value() = 2;

      m_actions->add  (Gtk::RadioAction::create (gr1, PLAYER_SHELL_ACTION_VIDEO_ASPECT_169,
                                                _("16:9 (Widescreen)")),
                                                (sigc::mem_fun (*this, &PlayerShell::set_video_ar)));
      RefPtr<Gtk::RadioAction>::cast_static (m_actions->get_action
        (PLAYER_SHELL_ACTION_VIDEO_ASPECT_169))->property_value() = 3;

      m_actions->add  (Gtk::RadioAction::create (gr1, PLAYER_SHELL_ACTION_VIDEO_ASPECT_DVB,
                                                _("2.11:1 (DVB)")),
                                                (sigc::mem_fun (*this, &PlayerShell::set_video_ar)));
      RefPtr<Gtk::RadioAction>::cast_static (m_actions->get_action
        (PLAYER_SHELL_ACTION_VIDEO_ASPECT_DVB))->property_value() = 4;

      ///////////////////////////
      // Last.fm based actions //
      ///////////////////////////

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_LASTFM_LOVE,
                                      Gtk::StockID (BMP_STOCK_LASTFM),
                                      _("I Love This Track!")),
                                      sigc::mem_fun (*this, &PlayerShell::on_shell_lastfm_love));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_LASTFM_TAG,
                                      Gtk::StockID (BMP_STOCK_LASTFM),
                                      _("Tag Track...")),
                                      sigc::mem_fun (*this, &PlayerShell::on_shell_lastfm_tag));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_LASTFM_RECM,
                                      StockID (BMP_STOCK_LASTFM),
                                      _("Recommend...")),
                                      sigc::mem_fun (*this, &PlayerShell::on_shell_lastfm_recm));

      m_actions->add (Action::create (PLAYER_SHELL_ACTION_LASTFM_PLAY,
                                      Gtk::StockID (BMP_STOCK_LASTFM),
                                      _("Play similar Music")),
                                      AccelKey ("<control><shift>s"),
                                      sigc::mem_fun (*this, &PlayerShell::on_shell_lastfm_play));

      RefPtr<ToggleAction>::cast_static (m_actions->get_action (PLAYER_SHELL_ACTION_SOURCES))->set_active( true );
      RefPtr<ToggleAction>::cast_static (m_actions->get_action (PLAYER_SHELL_ACTION_INFO))->set_active( true );

      m_actions->get_action (PLAYER_SHELL_ACTION_TRACK_DETAILS)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_CREATE_BOOKMARK)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_RECM)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_PLAY)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_TAG)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_LOVE)->set_sensitive( 0 );
      m_actions->get_action("MenuVideo")->set_sensitive( 0 );

      m_ui_manager->insert_action_group (m_actions);
      m_ui_manager->add_ui_from_string (network_present ? ui_main_menubar : ui_main_menubar_offline);
      m_ui_manager->add_ui_from_string (network_present ? ui_info_area_popup : ui_info_area_popup_offline);
      m_ui_manager->add_ui_from_string (network_present ? ui_tray_icon : ui_tray_icon_offline);

      if( !network_present )
      {
        m_actions->get_action ("MenuBookmarks-radio")->set_visible (0);
        m_actions->get_action ("MenuBookmarks-jamendo")->set_visible (0);
      }

      m_menu_bar = m_ui_manager->get_widget ("/MenuBarMain");
      dynamic_cast<Alignment *>(m_ref_xml->get_widget("alignment-menu-default"))->add (*(m_menu_bar));
    }

    // Playback Proxies
    {
      m_actions->get_action (PLAYER_SHELL_ACTION_PLAY)->connect_proxy
            (*(dynamic_cast<Button *>(m_ref_xml->get_widget ("b_play"))));
      m_actions->get_action (PLAYER_SHELL_ACTION_STOP)->connect_proxy
            (*(dynamic_cast<Button *>(m_ref_xml->get_widget ("b_stop"))));
      m_actions->get_action (PLAYER_SHELL_ACTION_PAUSE)->connect_proxy
            (*(dynamic_cast<ToggleButton *>(m_ref_xml->get_widget ("b_pause"))));
      m_actions->get_action (PLAYER_SHELL_ACTION_PREV)->connect_proxy
            (*(dynamic_cast<Button *>(m_ref_xml->get_widget ("b_prev"))));
      m_actions->get_action (PLAYER_SHELL_ACTION_NEXT)->connect_proxy
            (*(dynamic_cast<Button *>(m_ref_xml->get_widget ("b_next"))));

      dynamic_cast<Gtk::Image *>(m_ref_xml->get_widget ("image-button-repeat"))
        ->set (render_icon (Gtk::StockID (BMP_STOCK_REPEAT), Gtk::ICON_SIZE_BUTTON));
      dynamic_cast<Gtk::Image *>(m_ref_xml->get_widget ("image-button-shuffle"))
        ->set (render_icon (Gtk::StockID (BMP_STOCK_SHUFFLE), Gtk::ICON_SIZE_BUTTON));

      m_actions->get_action (PLAYER_SHELL_ACTION_REPEAT)->connect_proxy
            (*(dynamic_cast<ToggleButton *>(m_ref_xml->get_widget ("button-repeat"))));
      m_actions->get_action (PLAYER_SHELL_ACTION_SHUFFLE)->connect_proxy
            (*(dynamic_cast<ToggleButton *>(m_ref_xml->get_widget ("button-shuffle"))));

      mcs_bind->bind_toggle_action
        (RefPtr<ToggleAction>::cast_static (m_actions->get_action (PLAYER_SHELL_ACTION_SHUFFLE)), "bmp", "shuffle");
      mcs_bind->bind_toggle_action
        (RefPtr<ToggleAction>::cast_static (m_actions->get_action (PLAYER_SHELL_ACTION_REPEAT)), "bmp", "repeat");

      m_actions->get_action (PLAYER_SHELL_ACTION_PLAY)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_PAUSE)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_PREV)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_NEXT)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_STOP)->set_sensitive( 0 );
    }

    add_accel_group (m_ui_manager->get_accel_group());

    ////////////////////////////////// Sources

    if( (sources[SOURCE_RADIO].netOnly && network_present) || (!sources[SOURCE_RADIO].netOnly) )
    {
      m_sources[SOURCE_RADIO] = boost::shared_ptr<UiPart::Radio> (new UiPart::Radio (m_ref_xml, m_ui_manager));
      install_source (SOURCE_RADIO);
    }

    if( (sources[SOURCE_CDDA].netOnly && network_present) || (!sources[SOURCE_CDDA].netOnly) )
    {
      m_sources[SOURCE_CDDA] = boost::shared_ptr<UiPart::CDDA> (new UiPart::CDDA (m_ref_xml, m_ui_manager));
      install_source (SOURCE_CDDA);
    }

    if( (sources[SOURCE_PODCASTS].netOnly && network_present) || (!sources[SOURCE_PODCASTS].netOnly) )
    {
      m_sources[SOURCE_PODCASTS] = boost::shared_ptr<UiPart::Podcasts> (new UiPart::Podcasts (m_ref_xml, m_ui_manager));
      install_source (SOURCE_PODCASTS);
    }

    if( (sources[SOURCE_LIBRARY].netOnly && network_present) || (!sources[SOURCE_LIBRARY].netOnly) )
    {
      m_sources[SOURCE_LIBRARY] = boost::shared_ptr<UiPart::Library> (new UiPart::Library (m_ref_xml, m_ui_manager));
      install_source (SOURCE_LIBRARY);
    }

    if( (sources[SOURCE_PLAYLIST].netOnly && network_present) || (!sources[SOURCE_PLAYLIST].netOnly) )
    {
      m_sources[SOURCE_PLAYLIST] = boost::shared_ptr<UiPart::Playlist> (new UiPart::Playlist (m_ref_xml, m_ui_manager));
      install_source (SOURCE_PLAYLIST);
    }

    if( (sources[SOURCE_LASTFM].netOnly && network_present) || (!sources[SOURCE_LASTFM].netOnly) )
    {
      m_sources[SOURCE_LASTFM] = boost::shared_ptr<UiPart::LASTFM> (new UiPart::LASTFM (m_ref_xml, m_ui_manager));
      install_source (SOURCE_LASTFM);
      UiPart::LASTFM * l = boost::dynamic_pointer_cast <UiPart::LASTFM> (m_sources[SOURCE_LASTFM]).get();
      l->Widgets.TagInfoView->signalGoToMBID().connect( sigc::mem_fun( *this, &Bmp::PlayerShell::on_go_to_mbid));
    }

    if( (sources[SOURCE_JAMENDO].netOnly && network_present) || (!sources[SOURCE_JAMENDO].netOnly) )
    {
      m_sources[SOURCE_JAMENDO] = boost::shared_ptr<UiPart::Jamendo> (new UiPart::Jamendo (m_ref_xml, m_ui_manager));
      install_source (SOURCE_JAMENDO);
    }


    m_selection->source_selected().connect( sigc::mem_fun( *this, &Bmp::PlayerShell::source_changed));

    for (int n = 0; n < N_SOURCES; ++n)
    {
      SourceSP source  = m_sources[n];
      if( source )
      {
        source->send_caps ();
        source->send_flags ();
      }
    }

    show ();
    while (gtk_events_pending()) gtk_main_iteration();

    // Setup EQ
    if( Audio::test_element ("equalizer-10bands") )
    {
      m_equalizer = Equalizer::create ();
      m_actions->get_action (PLAYER_SHELL_ACTION_EQ)->set_sensitive( mcs->key_get<bool>("audio","enable-eq"));
      mcs->subscribe ("PlayerShell", "audio", "enable-eq", sigc::mem_fun (*this, &Bmp::PlayerShell::on_enable_eq_changed));
    }
    else
    {
      m_actions->get_action (PLAYER_SHELL_ACTION_EQ)->set_sensitive( 0 );
    }

    // Tray icon
    {
      m_status_icon_image_default =
        Gdk::Pixbuf::create_from_file (BMP_TRAY_ICON_DIR G_DIR_SEPARATOR_S "tray-icon-default.png");
      m_status_icon_image_paused =
        Gdk::Pixbuf::create_from_file (BMP_TRAY_ICON_DIR G_DIR_SEPARATOR_S "tray-icon-paused.png");
      m_status_icon_image_playing =
        Gdk::Pixbuf::create_from_file (BMP_TRAY_ICON_DIR G_DIR_SEPARATOR_S "tray-icon-playing.png");

      m_status_icon = bmp_status_icon_new_from_pixbuf (m_status_icon_image_default->gobj());
      bmp_status_icon_set_visible (m_status_icon, TRUE);

      g_object_connect (G_OBJECT (m_status_icon),
                        "signal::click",
                        G_CALLBACK(PlayerShell::status_icon_click),
                        this,
                        "signal::popup-menu",
                        G_CALLBACK(PlayerShell::status_icon_popup_menu),
                        this,
                        "signal::scroll-up",
                        G_CALLBACK(PlayerShell::status_icon_scroll_up),
                        this,
                        "signal::scroll-down",
                        G_CALLBACK(PlayerShell::status_icon_scroll_down),
                        this,
                        NULL);

      GtkWidget *tray_icon = bmp_status_icon_get_tray_icon (m_status_icon);
      gtk_widget_realize (GTK_WIDGET (tray_icon));
      g_object_connect (G_OBJECT (tray_icon),
                        "signal::embedded",
                        G_CALLBACK (Bmp::PlayerShell::on_tray_embedded),
                        this,
                        NULL);
      g_object_connect (G_OBJECT (tray_icon),
                        "signal::destroyed",
                        G_CALLBACK (Bmp::PlayerShell::on_tray_destroyed),
                        this,
                        NULL);
    }

    Library::Obj()->signal_modify_start().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::progress_start), _("Modifying Tracks")));
    Library::Obj()->signal_modify_end().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::progress_end), false));
    Library::Obj()->signal_modify_step().connect
      (sigc::mem_fun (*this, &Bmp::PlayerShell::progress_step));

    Library::Obj()->signal_vacuum_begin().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::progress_start), "Reticulating Splines"));
    Library::Obj()->signal_vacuum_end().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::progress_end), false));
    Library::Obj()->signal_vacuum_step().connect
      (sigc::mem_fun (*this, &Bmp::PlayerShell::progress_step));

    UiPart::Library * p = dynamic_cast<UiPart::Library*>(m_sources[SOURCE_LIBRARY].get());
    m_prefs->signal_library_update_request().connect
      (sigc::mem_fun (*p, &Bmp::UiPart::Library::update));

    m_bookmark_manager.signal_bookmark_loaded().connect
      (sigc::mem_fun (*this, &Bmp::PlayerShell::on_bookmark_loaded));
    m_bookmark_manager.load_bookmarks ();

    m_infoarea->signal_event().connect
      (sigc::mem_fun (*this, &Bmp::PlayerShell::on_info_area_event));

    if( network_present )
    {
      LastFM::Radio::Obj()->signal_disconnected().connect
        (sigc::mem_fun (*this, &Bmp::PlayerShell::on_last_fm_radio_disconnected));
      LastFM::Radio::Obj()->signal_connected().connect
        (sigc::mem_fun (*this, &Bmp::PlayerShell::on_last_fm_radio_connected));
    }

    LastFM::Scrobbler::Obj()->signal_disconnected().connect
      (sigc::mem_fun (*this, &Bmp::PlayerShell::on_scrobbler_disconnected));
    LastFM::Scrobbler::Obj()->signal_connected().connect
      (sigc::mem_fun (*this, &Bmp::PlayerShell::on_scrobbler_connected));

    m_scrolllock_mask = 0;
    m_numlock_mask = 0;
    m_capslock_mask = 0;
    mmkeys_get_offending_modifiers ();

    on_mm_edit_done (); // bootstraps the settings
    m_prefs->signal_mm_edit_begin().connect( sigc::mem_fun( *this, &PlayerShell::on_mm_edit_begin ) );
    m_prefs->signal_mm_edit_done().connect( sigc::mem_fun( *this, &PlayerShell::on_mm_edit_done ) );

    mVideoWidget = manage( new VideoWindow() );
    dynamic_cast<Gtk::Alignment*>(m_ref_xml->get_widget ("alignment-video"))->add( * mVideoWidget );
    mVideoWidget->show ();
    gtk_widget_realize (GTK_WIDGET (mVideoWidget->gobj()));

    m_ref_xml->get_widget ("main-notebook", mMainNotebook);

    // md FIXME: In case it re-requests, i'm not entirely sure how this works really
    if( Play::Obj()->has_video() )
    {
      Play::Obj()->signal_request_window_id ().connect
        (sigc::mem_fun (*this, &Bmp::PlayerShell::set_da_window_id));
      Play::Obj()->signal_video_geom ().connect
        (sigc::mem_fun (*this, &Bmp::PlayerShell::on_gst_window_geom));
    }
  }

  void
  PlayerShell::init ()
  {
    m_prefs->setup_lastfm ();
  }

  void
  PlayerShell::set_video_ar ()
  {
    AspectRatio ar = AspectRatio(RefPtr<Gtk::RadioAction>::cast_static (m_actions->get_action (PLAYER_SHELL_ACTION_VIDEO_ASPECT_AUTO))->
        get_current_value());
    if(( Play::Obj()->property_status().get_value() == PLAYSTATUS_PLAYING ) && (mVideoWidget->property_playing().get_value()))
    {
        mVideoWidget->property_aspect_ratio() = ar;
    }
  }

  ::Window
  PlayerShell::set_da_window_id ()
  {
    mMainNotebook->set_current_page( 1 );
    mVideoWidget->property_playing() = true;
    mVideoWidget->queue_draw();
    while (gtk_events_pending()) gtk_main_iteration();
    return GDK_WINDOW_XID (mVideoWidget->mVideo->gobj());
  }

  void
  PlayerShell::on_gst_window_geom (int width, int height, GValue * par)
  {
    mVideoWidget->property_geometry() = Geometry (width, height);
    mVideoWidget->mPar = par;
    mVideoWidget->queue_resize ();
  }

  void
  PlayerShell::message_set( const Glib::ustring& message )
  {
  }

  void
  PlayerShell::message_clear()
  {
  }

  void
  PlayerShell::install_source (PlaybackSourceID source)
  {
    m_sources[source]->signal_caps().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::on_source_caps), source));

    m_sources[source]->signal_flags().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::on_source_flags), source));

    m_sources[source]->signal_track_metadata().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::on_source_track_metadata), source, PARSE_FLAGS_ALL));

    m_sources[source]->signal_playback_request().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::on_source_play_request), source));

    m_sources[source]->signal_segment().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::on_source_segment), source));

    m_sources[source]->signal_stop_request().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::on_source_stop), source));

    m_sources[source]->signal_next_request().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::on_source_next), source));

    m_sources[source]->signal_message().connect
      (sigc::mem_fun (*this, &Bmp::PlayerShell::on_source_message_set));

    m_sources[source]->signal_message_clear().connect
      (sigc::mem_fun (*this, &Bmp::PlayerShell::on_source_message_clear));

    m_sources[source]->signal_play_async().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::play_async_cb), source));

    m_sources[source]->signal_next_async().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::next_async_cb), source));

    m_sources[source]->signal_prev_async().connect
      (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::prev_async_cb), source));

    m_sources[source]->signal_enqueue_request().connect
      (sigc::mem_fun (*this, &Bmp::PlayerShell::enqueue));

    UriHandler * p = boost::dynamic_pointer_cast <UriHandler> (m_sources[source]).get();

    if( p )
    {
      StrV v = p->get_schemes ();
      for(StrV::const_iterator i = v.begin(); i != v.end(); ++i)
      {
        m_uri_map.insert (std::make_pair (*i, source));
      }
    }
  }

  bool
  PlayerShell::display_popup_delayed ()
  {
    m_popup->tooltip_mode ();
    return false;
  }

  gboolean
  PlayerShell::status_icon_enter (BmpStatusIcon *icon, GdkEventCrossing *event, gpointer data)
  {
    PlayerShell & shell = (*(reinterpret_cast<PlayerShell*>(data)));

    if( shell.m_selection->m_active_source != SOURCE_NONE )
    {
      shell.m_popup_delay_conn = signal_timeout().connect (sigc::mem_fun (&shell, &Bmp::PlayerShell::display_popup_delayed), 400);
    }

    return FALSE;
  }

  gboolean
  PlayerShell::status_icon_leave (BmpStatusIcon *icon, GdkEventCrossing *event, gpointer data)
  {
    PlayerShell & shell = (*(reinterpret_cast<PlayerShell*>(data)));

    shell.m_popup_delay_conn.disconnect ();
    shell.m_popup->tooltip_mode (false);
    return FALSE;
  }

  void
  PlayerShell::status_icon_popup_menu (BmpStatusIcon *icon, guint arg1, guint arg2, gpointer data)
  {
    PlayerShell & shell = (*(reinterpret_cast<PlayerShell*>(data)));

    shell.m_popup_delay_conn.disconnect ();

    if( shell.m_popup )
    {
      shell.m_popup->tooltip_mode (false, true);
    }

    GtkWidget* menu = Util::ui_manager_get_popup (shell.m_ui_manager->gobj(), "/popup-tray/menu-tray");
    gtk_widget_realize (menu);

    if( menu )
    {
      gtk_menu_popup (GTK_MENU (menu), /* parent shell */ NULL,
                                       /* parent item  */ NULL,
                                       GtkMenuPositionFunc (bmp_status_icon_position_menu), icon, arg1, arg2);
    }
  }

  void
  PlayerShell::status_icon_click (BmpStatusIcon *icon, gpointer data)
  {
    PlayerShell & shell = (*(reinterpret_cast<Bmp::PlayerShell*>(data)));

    shell.m_popup_delay_conn.disconnect ();

    if( shell.m_popup )
    {
        shell.m_popup->tooltip_mode (false, true);
    }

    if( shell.property_has_toplevel_focus() )
    {
        shell.on_close_window ();
    }
    else
    {
        shell.set_skip_taskbar_hint (false);
        shell.set_skip_pager_hint (false);
        shell.show ();
        shell.present ();
        shell.m_visible = true;
    }
  }

  void
  PlayerShell::status_icon_scroll_up (BmpStatusIcon *icon, gpointer data)
  {
    int volume;
    volume = mcs->key_get<int>("bmp", "volume");
    volume += 4;
    volume = (volume > 100) ? 100 : volume;
    mcs->key_set<int>("bmp", "volume", volume);
  }

  void
  PlayerShell::status_icon_scroll_down (BmpStatusIcon *icon, gpointer data)
  {
    int volume;
    volume = mcs->key_get<int>("bmp", "volume");
    volume -= 4;
    volume = (volume < 0) ? 0 : volume;
    mcs->key_set<int>("bmp", "volume", volume);
  }

  void
  PlayerShell::on_tray_embedded (GtkPlug* plug, gpointer data)
  {
    Bmp::PlayerShell & shell = (*(reinterpret_cast<Bmp::PlayerShell*>(data)));

    GtkWidget *widget = GTK_WIDGET (plug);

    ustring text ("");
    Popup * saveptr = new Bmp::Popup (widget, shell.m_metadata);
    g_atomic_pointer_set ((gpointer*)&shell.m_popup, saveptr);
    g_object_connect (G_OBJECT (plug),
                      "signal::enter-notify-event",
                      G_CALLBACK(PlayerShell::status_icon_enter),
                      &shell,
                      "signal::leave-notify-event",
                      G_CALLBACK(PlayerShell::status_icon_leave),
                      &shell,
                      NULL);

    if (Play::Obj()->property_status().get_value() != PLAYSTATUS_STOPPED)
    {
      shell.m_popup->metadata_updated (shell.m_parse_flags);
    }
  }

  void
  PlayerShell::on_tray_destroyed (BmpTrayIcon* tray, gpointer data)
  {
    Bmp::PlayerShell & shell = (*(reinterpret_cast<Bmp::PlayerShell*>(data)));

    if (shell.m_popup)
    {
      g_signal_handlers_disconnect_by_func (gpointer(tray),
                                            gpointer(PlayerShell::status_icon_enter),
                                            gpointer(&shell));

      g_signal_handlers_disconnect_by_func (gpointer(tray),
                                            gpointer(PlayerShell::status_icon_leave),
                                            gpointer(&shell));
      Popup * saveptr = shell.m_popup;
      g_atomic_pointer_set ((gpointer*)&shell.m_popup, 0);
      delete saveptr;
    }
  }

  void
  PlayerShell::on_enable_eq_changed (MCS_CB_DEFAULT_SIGNATURE)
  {
    m_actions->get_action (PLAYER_SHELL_ACTION_EQ)->set_sensitive( mcs->key_get<bool>("audio","enable-eq"));
  }

  void
  PlayerShell::on_volume_changed (MCS_CB_DEFAULT_SIGNATURE)
  {
    Play::Obj()->property_volume() = boost::get <int> (value);
  }

  void
  PlayerShell::on_close_window ()
  {
    if( m_popup && mcs->key_get<bool>("bmp", "ui-esc-trayconify") )
    {
          set_skip_taskbar_hint (true);
          set_skip_pager_hint (true);
          hide ();
          m_visible = false;
    }
    else
    {
          iconify ();
    }
  }

  bool
  PlayerShell::on_delete_event (GdkEventAny *event)
  {
    on_close_window ();
    return true;
  }

  bool
  PlayerShell::on_info_area_event (GdkEvent * ev)
  {
    if( ev->type == GDK_BUTTON_PRESS )
    {
      GdkEventButton * event = reinterpret_cast <GdkEventButton *> (ev);
      if( event->button == 3 )
      {
        Gtk::Menu * menu = dynamic_cast < Gtk::Menu* > (Util::get_popup (m_ui_manager, "/popup-shell/menu-shell-context"));
        if (menu) // better safe than screwed
        {
          menu->popup (event->button, event->time);
        }
        return true;
      }
    }
    return false;
  }

  void
  PlayerShell::progress_start (int n_items, const char * title)
  {
    m_n_progress_items = n_items;
    m_nth_progress_item = 0;

    m_simple_progress->step  (n_items, 0);
    m_simple_progress->set_label (title);

    m_simple_progress->show ();
  }

  void
  PlayerShell::progress_step ()
  {
    ++m_nth_progress_item;
    m_simple_progress->step  (m_n_progress_items, m_nth_progress_item);
  }

  void
  PlayerShell::progress_end (bool wait_for_close)
  {
    m_simple_progress->done (wait_for_close);
    m_simple_progress->hide ();
  }

  bool
  PlayerShell::source_restore_context (GdkEventButton *event)
  {
    if( m_selection->m_active_source != SOURCE_NONE )
    {
      m_selection->source_select (m_selection->m_active_source);
      source_changed (m_selection->m_active_source);
      m_sources[m_selection->m_active_source]->restore_context();
    }
    return false;
  }

  void
  PlayerShell::on_play_stream ()
  {
    DialogPlayUri * d = DialogPlayUri::create();
    ustring uri;
    int response = d->run (uri);
    delete d;

    if( response == GTK_RESPONSE_OK )
    {
      m_selection->source_select (SOURCE_RADIO);
      source_changed (SOURCE_RADIO);

      UiPart::Radio * r = boost::dynamic_pointer_cast <UiPart::Radio> (m_sources[SOURCE_RADIO]).get();
      r->play_uri (uri);
    }
  }

  void
  PlayerShell::on_go_to_mbid (ustring const& mbid)
  {
    m_selection->source_select (SOURCE_LIBRARY);
    source_changed (SOURCE_LIBRARY);
    UiPart::Library * l = boost::dynamic_pointer_cast <UiPart::Library> (m_sources[SOURCE_LIBRARY]).get();
    l->go_to_mbid (mbid);
  }

  void
  PlayerShell::enqueue (VUri const& uris)
  {
    m_selection->source_select (SOURCE_PLAYLIST);
    source_changed (SOURCE_PLAYLIST);
    UiPart::Playlist * p = boost::dynamic_pointer_cast <UiPart::Playlist> (m_sources[SOURCE_PLAYLIST]).get();
    p->add_uris (uris, false);
  }

  void
  PlayerShell::play_uris (VUri const& uris)
  {
    if( uris.empty())
      return;

    try{
        URI u (uris[0]);
        URISchemeMap::const_iterator i = m_uri_map.find (u.scheme);
        if( i != m_uri_map.end ())
        {
          m_selection->source_select (i->second);
          source_changed (i->second);
          UriHandler * p = boost::dynamic_pointer_cast <UriHandler> (m_sources[i->second]).get();
          p->process (uris);
        }
      }
    catch (URI::ParseError & cxe)
      {
        g_warning (G_STRLOC ": Couldn't parse URI %s", uris[0].c_str());
      }
    catch (bad_cast & cxe)
      {
        g_critical (G_STRLOC ": Source isn't URI handler (dynamic_cast<> failed)");
      }
  }

  void
  PlayerShell::on_enqueue_files ()
  {
    FileBrowser * p = FileBrowser::create ();
    int response = p->run ();
    p->hide ();

    if( response == GTK_RESPONSE_OK )
    {
      VUri uris = p->get_uris ();
      enqueue (uris);
    }
    delete p;
  }

  void
  PlayerShell::on_play_files ()
  {
    FileBrowser * p = FileBrowser::create ();
    int response = p->run ();
    p->hide ();

    if( response == GTK_RESPONSE_OK )
    {
      VUri uris = p->get_uris ();
      play_uris (uris);
    }
    delete p;
  }

  // EQ
  void
  PlayerShell::on_shell_display_eq ()
  {
    m_equalizer->present ();
  }

  // Preferences
  void
  PlayerShell::on_shell_display_preferences ()
  {
    m_prefs->present ();
  }

  // Statusbar API
  void
  PlayerShell::status_message_clear ()
  {
    m_statusbar->pop (0);
    m_n_status_messages--;
    if( m_n_status_messages == 0 )
    {
      dynamic_cast <Button *>(m_ref_xml->get_widget ("statusbar-clear"))->set_sensitive( 0 );
      RefPtr<ToggleAction>::cast_static (m_actions->get_action (PLAYER_SHELL_ACTION_STATUSBAR))->set_active (false);
    }
  }

  bool
  PlayerShell::status_push_message_idle (ustring const& message)
  {
    dynamic_cast <Button *>(m_ref_xml->get_widget ("statusbar-clear"))->set_sensitive ();
    m_n_status_messages++;
    RefPtr<ToggleAction>::cast_static (m_actions->get_action (PLAYER_SHELL_ACTION_STATUSBAR))->set_active();
    m_statusbar->push (message, 0);
    return false;
  }

  void
  PlayerShell::status_push_message (ustring const& message)
  {
    signal_idle().connect (sigc::bind (sigc::mem_fun (*this, &PlayerShell::status_push_message_idle), message));
  }

  void
  PlayerShell::on_shell_lastfm_love ()
  {
    using namespace LastFM::XMLRPC;

    XSPF::Item item;
    item.creator  = (m_metadata.artist    ? m_metadata.artist.get()   : std::string());
    item.album    = (m_metadata.album     ? m_metadata.album.get()    : std::string());
    item.title    = (m_metadata.title     ? m_metadata.title.get()    : std::string());
    item.rating = "L";

    m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_LOVE)->set_sensitive (0);

    if( m_selection->m_active_source == SOURCE_LASTFM )
    {
      UiPart::LASTFM * p = boost::dynamic_pointer_cast <UiPart::LASTFM> (m_sources[SOURCE_LASTFM]).get();
      p->disable_ban ();
    }

    TrackAction action ("loveTrack", item);
    action.run ();
  }

  void
  PlayerShell::on_shell_lastfm_tag ()
  {
    boost::shared_ptr<LastFM::TagDialog> dialog (LastFM::TagDialog::create());
    dialog->run (m_metadata);
  }

  void
  PlayerShell::on_shell_lastfm_recm ()
  {
    boost::shared_ptr<LastFM::RecommendDialog> dialog (LastFM::RecommendDialog::create());
    dialog->run (m_metadata);
  }

  void
  PlayerShell::on_shell_lastfm_play ()
  {
    static boost::format
      f_artist ("lastfm://artist/%s/similarartists");

    m_metadata_lock.lock ();
    std::string artist = m_metadata.artist.get();
    m_metadata_lock.unlock ();

    m_infoarea->reset ();
    VUri v (1, ((f_artist % artist.c_str()).str()));
    play_uris (v);
  }

  void
  PlayerShell::on_shell_display_track_details ()
  {
    if (m_actions->get_action (PLAYER_SHELL_ACTION_TRACK_DETAILS)->is_sensitive())
    {
          TrackDetails * p = TrackDetails::create ();
          p->display (m_metadata);
    }
  }

  void
  PlayerShell::reset_seek_range ()
  {
    m_seek_progress->set_fraction (0.);
    m_seek_progress->set_text (" ");
    m_seek_progress->set_sensitive (false);
  }

  bool
  PlayerShell::on_seek_expose (GdkEventExpose *event)
  {
    if(m_seek_progress->has_focus())
    {
        Gdk::Rectangle r = m_seek_progress->get_allocation();
        m_seek_progress->get_style()->paint_focus(m_seek_progress->get_window(),
                                                  m_seek_progress->get_state(),
                                                  get_allocation(),
                                                  *m_seek_progress,
                                                  "",
                                                  r.get_x(),
                                                  r.get_y(),
                                                  r.get_width(),
                                                  r.get_height());
    }
    return false;
  }

  bool
  PlayerShell::on_seek_event (GdkEvent *event)
  {
    int x, y;

    if( event->type == GDK_KEY_PRESS )
    {
            GdkEventKey * ev = ((GdkEventKey*)(event));
            gint64 status = Play::Obj()->property_status().get_value();
            if((status == PLAYSTATUS_PLAYING) || (status == PLAYSTATUS_PAUSED))
            {
                    gint64 pos = Play::Obj()->property_position().get_value();
                    if(ev->keyval == GDK_Left)
                    {
                        Play::Obj()->seek( pos - 15 );
                        return true;
                    }
                    else if(ev->keyval == GDK_Right)
                    {
                        Play::Obj()->seek( pos + 15 );
                        return true;
                    }
            }
            return false;
    }
    else if( event->type == GDK_BUTTON_PRESS )
    {
      m_seeking = 1;
      x = int (((GdkEventButton*)(event))->x);
      y = int (((GdkEventButton*)(event))->y);
      goto SET_SEEK_POSITION;
    }
    else if( event->type == GDK_BUTTON_RELEASE && m_seeking)
    {
      m_seeking = 0;
      Play::Obj()->seek (guint64 (Play::Obj()->property_duration().get_value() * m_seek_position));
    }
    else if( event->type == GDK_MOTION_NOTIFY && m_seeking)
    {
      GdkModifierType state;
      if( ((GdkEventMotion*)(event))->is_hint )
      {
        gdk_window_get_pointer (((GdkEventMotion*)(event))->window, &x, &y, &state);
      }
      else
      {
        x = int (((GdkEventMotion*)(event))->x);
        y = int (((GdkEventMotion*)(event))->y);
      }

      SET_SEEK_POSITION:

      m_seek_position = double (x) / m_seek_progress->get_allocation().get_width();

      guint64 duration = Play::Obj()->property_duration().get_value();
      guint64 position = guint64 (duration * m_seek_position);

      guint64 m_pos = position / 60;
      guint64 m_dur = duration / 60;
      guint64 s_pos = position % 60;
      guint64 s_dur = duration % 60;

      m_seek_progress->set_text ((time_f % m_pos % s_pos % m_dur % s_dur).str());
      m_seek_progress->set_fraction( m_seek_position );
    }
    return false;
  }

  void
  PlayerShell::on_play_error (ustring const& element, ustring const& uri, ustring const& debug, GError * error, GstElement const* src)
  {
    ustring text, help;

    if( error->domain == GST_CORE_ERROR )
    {
      switch (error->code)
      {
        case GST_CORE_ERROR_MISSING_PLUGIN:
        {
          text = _("No plugin available to play stream");
          break;
        }

        case GST_CORE_ERROR_SEEK:
        {
          text = _("Error during Seek");
          break;
        }

        case GST_CORE_ERROR_STATE_CHANGE:
        {
          struct StringPairs
          {
            ustring state_pre;
            ustring state_post;
          };

          static StringPairs const string_pairs[] =
          {
            {"0",       "READY"},
            {"READY",   "PAUSED"},
            {"PAUSED",  "PLAYING"},
            {"PLAYING", "PAUSED"},
            {"PAUSED",  "READY"},
            {"READY",   "0"},
          };

          text = _("Error occured during state change from ");
          text += string_pairs[GST_STATE_PENDING(src)].state_pre  + " -> ";
          text += string_pairs[GST_STATE_PENDING(src)].state_post;
          break;
        }

        case GST_CORE_ERROR_PAD:
        {
          text = _("Error while performing pad linking");
          break;
        }

        case GST_CORE_ERROR_NEGOTIATION:
        {
          text = _("Error occured during element(s) negotiation");
          break;
        }

        default:
        {
          text = (boost::format (_("Unhandled error of domain GST_CORE_ERROR with code %d")) % error->code).str();
          break;
        }

      }
    }
    else if( error->domain == GST_RESOURCE_ERROR )
    {
      switch (error->code)
      {
        case GST_RESOURCE_ERROR_SEEK:
        {
          text = _("Error during Seek (resource; end of stream?)");
          break;
        }

        case GST_RESOURCE_ERROR_NOT_FOUND:
        case GST_RESOURCE_ERROR_OPEN_READ:
        case GST_RESOURCE_ERROR_OPEN_READ_WRITE:
        case GST_RESOURCE_ERROR_READ:
        {
          text = _("Unable to Open Resource (stream)");
          help = _("The requested resource was not found (The file/stream does not exist/is invalid?)");
          break;
        }

        case GST_RESOURCE_ERROR_SYNC:
        {
          text = _("Synchronization Error");
          break;
        }

        case GST_RESOURCE_ERROR_BUSY:
        {
          text = _("Device is in use");
          if( element == "sink" )
          {
            help = _("The audio device seems to be in use. (Maybe another audio/media app is running?)");
          }
          break;
        }

        case GST_RESOURCE_ERROR_FAILED:
        {
          text = _("Operation Failed");
          break;
        }

        default:
        {
          text = (boost::format (_("Unhandled error of domain GST_RESOURCE_ERROR with code %d")) % error->code).str();
        }
      }
    }
    else if( error->domain == GST_STREAM_ERROR )
    {
        switch (error->code)
        {
          case GST_STREAM_ERROR_FAILED:
          {
            text = _("Unable to play/continue playing stream (generic error)");
            break;
          }

          case GST_STREAM_ERROR_TYPE_NOT_FOUND:
          {
            text = _("Unable to Determine Stream Data");
            help = _("GStreamer was unable to determine the stream/file data\nand was unable to play it back");
            break;
          }

          case GST_STREAM_ERROR_WRONG_TYPE:
          {
            text = _("Element is unable to handle this stream type");
            break;
          }

          case GST_STREAM_ERROR_CODEC_NOT_FOUND:
          {
            text = _("No Codec installed to Handle this Stream");
            help = _("GStreamer is not able to play this kind of stream (Maybe missing plugin)");
            break;
          }

          case GST_STREAM_ERROR_DECODE:
          {
            text = _("Error Decoding the Stream");
            break;
          }

          case GST_STREAM_ERROR_DEMUX:
          {
            text = _("Error Demultiplexing the Stream");
            break;
          }

          case GST_STREAM_ERROR_FORMAT:
          {
            text = _("Error Parsing the Stream Format");
            break;
          }

          default:
          {
            text = (boost::format (_("Unhandled error of domain GST_STREAM_ERROR with code %d")) % error->code).str();
            break;
          }
        }
    }

    DialogGstError * d = DialogGstError::create ();
    d->run (text, element, uri, debug, help, error->domain, error->code);
    delete d;

    mMainNotebook->set_current_page( 0 );
    Play::Obj()->reset ();
  }

  void
  PlayerShell::on_play_buffering (double buffered)
  {
    if( buffered == 1. )
    {
      if( m_selection->m_active_source != SOURCE_NONE )
      {
        m_sources[m_selection->m_active_source]->buffering_done();
      }
      message_clear ();
      return;
    }

    int buffered_i (int(buffered * 100.));
    if( buffered_i < 100 )
    {
      message_set ((boost::format (_("Prebuffering: %d%%")) % buffered_i).str());
    }
  }

  void
  PlayerShell::on_gst_metadata (BmpGstMetadataField field)
  {
    BmpGstMetadata const& m = Play::Obj()->get_metadata();
    BmpMetadataParseFlags parse_flags = BmpMetadataParseFlags (0);

    switch (field)
    {
      case FIELD_IMAGE:
        parse_flags = PARSE_FLAGS_IMAGE;
        m_metadata.image = m.m_image.get()->copy();
        reparse_metadata (parse_flags);
        return;

      case FIELD_TITLE:
        if (m_source_flags[m_selection->m_active_source] & PlaybackSource::F_HANDLE_STREAMINFO)
        {
          parse_flags = PARSE_FLAGS_TEXT;
          m_metadata.title = m.m_title.get();
          reparse_metadata (parse_flags);
          set_current_source_image ();
        }
      return;

      case FIELD_ALBUM:
        if (m_source_flags[m_selection->m_active_source] & PlaybackSource::F_HANDLE_STREAMINFO)
        {
          parse_flags = PARSE_FLAGS_TEXT;
          m_metadata.album = m.m_album.get();
          reparse_metadata (parse_flags);
          set_current_source_image ();
        }
      break;

      case FIELD_AUDIO_BITRATE:
        parse_flags = PARSE_FLAGS_TEXT;
        m_metadata.bitrate = m.m_audio_bitrate.get();
        reparse_metadata (parse_flags);
      break;

      case FIELD_AUDIO_CODEC:
        parse_flags = PARSE_FLAGS_TEXT;
        m_metadata.m_row.insert (VariantPair ("audio-codec", m.m_audio_codec.get()));
        reparse_metadata (parse_flags);
      break;

      case FIELD_VIDEO_CODEC:
        parse_flags = PARSE_FLAGS_TEXT;
        m_metadata.m_row.insert (VariantPair ("video-codec", m.m_video_codec.get()));
        reparse_metadata (parse_flags);
      break;
    }
  }

  void
  PlayerShell::on_play_position (guint64 position)
  {
    if( m_seeking ) //FIXME: Provides timing excludes seek (? )
      return;

    guint64 duration = Play::Obj()->property_duration().get_value();

    if( (duration > 0) && (position <= duration) && (position >= 0) )
    {
      if (duration <= 0)
        return;

      if (position < 0)
        return;

      guint64 m_pos = position / 60;
      guint64 s_pos = position % 60;
      guint64 m_dur = duration / 60;
      guint64 s_dur = duration % 60;

      m_seek_progress->set_text ((time_f % m_pos % s_pos % m_dur % s_dur).str());
      m_seek_progress->set_fraction (double (position) / double (duration));

      if( m_popup )
      {
        m_popup->set_position (position, duration);
      }
    }
  }

  void
  PlayerShell::reparse_metadata (BmpMetadataParseFlags parse_flags)
  {
    struct NoCurrentImage {};

    try{
        if( parse_flags & PARSE_FLAGS_IMAGE )
        {
          m_metadata.image_known = true;

          if( m_metadata.image )
          {
            m_infoarea->set_image (m_metadata.image->scale_simple (72, 72, Gdk::INTERP_HYPER));
            m_asin.reset ();
          }
          else
          if( m_metadata.asin )
          {
            if( m_metadata.asin != m_asin )
            {
              try{
                  m_infoarea->set_image (Amazon::Covers::Obj()->fetch (m_metadata.asin.get(), COVER_SIZE_INFO_AREA, true));
                  Amazon::Covers::Obj()->fetch (m_metadata.asin.get(), m_metadata.image, true); //FIXME: Redundancy here ^
                  m_asin = m_metadata.asin;
                }
              catch (Bmp::Amazon::NoCoverError & cxe)
                {
                  throw NoCurrentImage ();
                }
            }
            else
            {
              // Same ASIN, so same cover, save the reparsing
              parse_flags = BmpMetadataParseFlags (parse_flags & ~PARSE_FLAGS_IMAGE);
            }
          }
          else
          {
            throw NoCurrentImage();
          }
        }
      }
    catch (NoCurrentImage & cxe)
      {
        m_asin.reset ();
        set_current_source_image ();
      }

    if( parse_flags & PARSE_FLAGS_TEXT )
    {
      ustring artist, album, title;
      parse_metadata (m_metadata, artist, album, title);
      m_infoarea->set_text (L_TITLE, title);
      m_infoarea->set_text (L_ARTIST, artist);
      m_infoarea->set_text (L_ALBUM, album);
      m_actions->get_action (PLAYER_SHELL_ACTION_TRACK_DETAILS)->set_sensitive( 1 );
    }

    m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_LOVE)->set_sensitive( 0 );
    m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_TAG)->set_sensitive( 0 );
    m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_RECM)->set_sensitive( 0 );
    m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_PLAY)->set_sensitive( 0 );

    signal_idle().connect (sigc::bind (sigc::mem_fun (*this, &Bmp::PlayerShell::metadata_update_when_idle), parse_flags));
  }

  void
  PlayerShell::on_last_fm_radio_connected ()
  {
    if( Play::Obj()->property_status().get_value() != PLAYSTATUS_STOPPED )
      m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_PLAY)->set_sensitive (bool (m_metadata.artist));
  }

  void
  PlayerShell::on_last_fm_radio_disconnected ()
  {
      m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_PLAY)->set_sensitive( 0 );
  }

  void
  PlayerShell::on_scrobbler_connected ()
  {
    if ((Play::Obj()->property_status().get_value() != PLAYSTATUS_STOPPED) &&
          (m_metadata.artist && m_metadata.album && m_metadata.title))
    {
        m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_RECM)->set_sensitive (true);
    }
  }

  void
  PlayerShell::on_scrobbler_disconnected ()
  {
    m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_RECM)->set_sensitive( 0 );
  }

  void
  PlayerShell::check_lastfm_actions ()
  {
    if( NM::Obj()->Check_Status() )
    {
          bool qual_1 = LastFM::Scrobbler::Obj()->connected ();
          bool qual_2 = m_metadata.artist && m_metadata.title;

          m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_LOVE)->set_sensitive( qual_1 && qual_2 );
          m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_TAG)->set_sensitive( qual_1 && qual_2 );
          m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_RECM)->set_sensitive( qual_1 && qual_2 );

          m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_PLAY)->set_sensitive( m_metadata.artist && LastFM::Radio::Obj()->connected() );
    }
  }

  bool
  PlayerShell::metadata_update_when_idle (BmpMetadataParseFlags parse_flags)
  {
    m_parse_flags = parse_flags;
    if( m_popup )
    {
      m_popup->metadata_updated (parse_flags);
      if( mcs->key_get<bool>("bmp", "display-notifications") )
      {
        m_popup->show ();
      }
    }

    check_lastfm_actions ();
    return false;
  }

  void
  PlayerShell::set_current_source_image ()
  {
    m_metadata.image_known = false;

    if(( m_selection->m_active_source != SOURCE_LIBRARY ) && ( m_selection->m_active_source != SOURCE_PLAYLIST ))
    {
      m_metadata.image = m_source_icons[m_selection->m_active_source];
      m_infoarea->set_image (m_source_icons_med[m_selection->m_active_source]);
    }
    else
    {
      m_metadata.image = mVideoWidget->property_playing().get_value() ? m_video_default : m_audio_default;
      m_infoarea->set_image (mVideoWidget->property_playing().get_value() ? m_video_default_64 : m_audio_default_64);
    }
  }

  void
  PlayerShell::on_source_caps (Bmp::PlaybackSource::Caps caps, PlaybackSourceID source)
  {
    Mutex::Lock L (m_source_cf_lock);

    m_source_caps[source] = caps;
    m_player_dbus_obj->emit_caps_change (m_player_dbus_obj, int (caps));

    if( (source == m_selection->m_active_source) || (m_notebook_main->get_current_page() == source) )
    {
      m_actions->get_action (PLAYER_SHELL_ACTION_PREV)->set_sensitive (caps & PlaybackSource::CAN_GO_PREV);
      m_actions->get_action (PLAYER_SHELL_ACTION_NEXT)->set_sensitive (caps & PlaybackSource::CAN_GO_NEXT);
      m_actions->get_action (PLAYER_SHELL_ACTION_PLAY)->set_sensitive (caps & PlaybackSource::CAN_PLAY);
      m_actions->get_action (PLAYER_SHELL_ACTION_CREATE_BOOKMARK)->set_sensitive (caps & PlaybackSource::CAN_BOOKMARK);
    }

    if( source == m_selection->m_active_source )
    {
          if( Play::Obj()->property_status().get_value() == PLAYSTATUS_PLAYING )
          {
                m_actions->get_action (PLAYER_SHELL_ACTION_PAUSE)->set_sensitive (caps & PlaybackSource::CAN_PAUSE);
                m_seek_progress->set_sensitive( caps & PlaybackSource::CAN_SEEK );
          }
    }
  }

  void
  PlayerShell::on_source_flags (PlaybackSource::Flags flags, PlaybackSourceID source)
  {
    Mutex::Lock L (m_source_cf_lock);

    m_source_flags[source] = flags;
    m_actions->get_action (PLAYER_SHELL_ACTION_REPEAT)->set_sensitive (flags & PlaybackSource::F_USES_REPEAT);
    m_actions->get_action (PLAYER_SHELL_ACTION_SHUFFLE)->set_sensitive (flags & PlaybackSource::F_USES_SHUFFLE);
  }

  void
  PlayerShell::on_source_track_metadata (TrackMetadata const& metadata, PlaybackSourceID source, BmpMetadataParseFlags parse_flags)
  {
    Mutex::Lock L (m_metadata_lock);

    m_metadata = metadata;

    if( !m_metadata.location )
    {
      m_metadata.location = Play::Obj()->property_stream().get_value();
      m_metadata.m_row.insert (VariantPair ("location", m_metadata.location.get()));
    }

    reparse_metadata ();
  }

  void
  PlayerShell::on_source_play_request (PlaybackSourceID source_id)
  {
    if( !m_playback_lock.trylock() )
      return;

    if( m_selection->m_active_source != SOURCE_NONE )
    {
          if( m_source_flags[m_selection->m_active_source] & PlaybackSource::F_HANDLE_LASTFM )
          {
                shell_lastfm_scrobble_current (); // scrobble the current item before switching
          }

          if( m_selection->m_active_source != source_id)
          {
                m_sources[m_selection->m_active_source]->stop ();
                Play::Obj()->request_status (PLAYSTATUS_STOPPED);
          }
    }

    SourceSP source = m_sources[source_id];

    if( m_source_flags[source_id] & PlaybackSource::F_ASYNC)
    {
          source->play_async ();
          m_actions->get_action( PLAYER_SHELL_ACTION_STOP )->set_sensitive (true);
    }
    else
    {
          if( source->play () )
          {
                  std::string type = source->get_type ();
                  enable_video_check(type);

                  ustring uri = source->get_uri();
                  if((uri.substr(0,7) == "file://") && (!file_test(filename_from_uri(uri), FILE_TEST_EXISTS)))
                  {
                    source->bad_track();
                    next();
                    return;
                  }

                  safe_pause_unset();
                  Play::Obj()->switch_stream (uri, type);

                  if( Play::Obj()->property_sane() == true )
                  {
                        m_selection->source_select (source_id);
                        play_post_internal (source_id);
                        return;
                  }
          }
          else
          {
            source->stop ();
            Play::Obj()->request_status (PLAYSTATUS_STOPPED);
            m_selection->source_activate (SOURCE_NONE);
          }
    }

    m_playback_lock.unlock ();
  }

  void
  PlayerShell::on_source_message_set (Glib::ustring const& message)
  {
    message_set (message);
  }

  void
  PlayerShell::on_source_message_clear ()
  {
    message_clear ();
  }

  void
  PlayerShell::on_source_segment (PlaybackSourceID source_id)
  {
    if( source_id == m_selection->m_active_source )
    {
      m_infoarea->reset ();
      m_sources[source_id]->segment ();
    }
    else
    {
      g_warning (_("%s: Source '%s' requested segment, but is not the active source"), G_STRLOC, sources[source_id].name.c_str());
    }
  }

  void
  PlayerShell::on_source_stop (PlaybackSourceID source_id)
  {
    if( source_id == m_selection->m_active_source )
    {
      stop ();
    }
    else
    {
      g_warning (_("%s: Source '%s' requested stop, but is not the active source"), G_STRLOC, sources[source_id].name.c_str());
    }
  }

  void
  PlayerShell::on_source_next (PlaybackSourceID source_id)
  {
    if( source_id == m_selection->m_active_source )
    {
      next ();
    }
    else
    {
      g_warning (_("%s: Source '%s' requested next, but is not the active source"), G_STRLOC, sources[source_id].name.c_str());
    }
  }

  void
  PlayerShell::source_changed (int n)
  {
    m_notebook_main->set_current_page (n);

    SourceSP source = m_sources[n];
    if( source )
    {
      source->send_caps ();
      source->send_flags ();

      if( m_ui_merge_id )
      {
        m_ui_manager->remove_ui (m_ui_merge_id);
      }

      m_ui_merge_id = boost::dynamic_pointer_cast <Bmp::UiPart::Base> (source)->add_ui();
    }
    else
    {
      m_actions->get_action (PLAYER_SHELL_ACTION_PLAY)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_PREV)->set_sensitive( 0 );
      m_actions->get_action (PLAYER_SHELL_ACTION_NEXT)->set_sensitive( 0 );
    }
  }

  void
  PlayerShell::enable_video_check (std::string const& type)
  {
    if(video_type(type))
    {
        m_actions->get_action("MenuVideo")->set_sensitive( true );
        mMainNotebook->set_current_page( 1 );
        mVideoWidget->property_playing() = true;
        Play::Obj()->set_window_id (GDK_WINDOW_XID (mVideoWidget->mVideo->gobj()));
    }
    else
    {
        m_actions->get_action("MenuVideo")->set_sensitive( false );
        mMainNotebook->set_current_page( 0 );
        mVideoWidget->property_playing() = false;
    }
  }

  //////////////////////////////// Internal Playback Control

  void
  PlayerShell::play_async_cb (int source_id)
  {
    SourceSP source = m_sources[source_id];

    std::string type = source->get_type ();
    enable_video_check (type);

    ustring uri = source->get_uri();
    if((uri.substr(0,7) == "file://") && (!file_test(filename_from_uri(uri), FILE_TEST_EXISTS)))
    {
        source->bad_track();
        source->stop ();
        Play::Obj()->request_status (PLAYSTATUS_STOPPED);
        m_selection->source_activate (SOURCE_NONE);
        return;
    }

    Play::Obj()->switch_stream (uri, type);
    if( Play::Obj()->property_sane() == true )
    {
          play_post_internal (source_id);
    }
  }

  void
  PlayerShell::play_post_internal (int source_id)
  {
    m_selection->source_activate (PlaybackSourceID (source_id));
    SourceSP source = m_sources[source_id];

    source->play_post ();
    source->send_caps ();

    if( m_popup )
    {
          m_popup->set_source_icon (m_source_icons_small[source_id]);
    }

    if( m_source_flags[source_id] & PlaybackSource::F_HANDLE_LASTFM )
    {
          shell_lastfm_now_playing ();
    }

    m_player_dbus_obj->emit_track_change (m_player_dbus_obj);

    if( m_ui_merge_id_context )
    {
          m_ui_manager->remove_ui (m_ui_merge_id_context);
          m_ui_merge_id_context = boost::dynamic_pointer_cast <Bmp::UiPart::Base> (source)->add_context_ui();
    }

    if( m_ui_merge_id_tray )
    {
          m_ui_manager->remove_ui (m_ui_merge_id_tray);
          m_ui_merge_id_tray = boost::dynamic_pointer_cast <Bmp::UiPart::Base> (source)->add_tray_ui();
    }

    if( m_ui_merge_id_lastfm )
    {
          m_ui_manager->remove_ui (m_ui_merge_id_lastfm);
          m_ui_merge_id_lastfm = 0;
    }

    m_infoarea->set_source (m_source_icons_small[source_id]->scale_simple (20, 20, Gdk::INTERP_BILINEAR));

    if( (m_ui_merge_id_lastfm == 0) && LastFM::Scrobbler::Obj()->connected() && (m_source_flags[source_id] & PlaybackSource::F_HANDLE_LASTFM_ACTIONS) )
    {
          m_ui_merge_id_lastfm = m_ui_manager->add_ui_from_string (ui_lastfm_actions);
    }
    else
    {
          if (m_ui_merge_id_lastfm != 0)
          {
                m_ui_manager->remove_ui (m_ui_merge_id_lastfm);
          }
    }
  }

  void
  PlayerShell::play ()
  {
    Glib::RecMutex::Lock L (m_playback_lock);

    PlaybackSourceID source_id = PlaybackSourceID (m_notebook_main->get_current_page());
    PlaybackSource::Caps c = m_source_caps[source_id];

    if( c & PlaybackSource::CAN_PLAY )
    {
      if( m_selection->m_active_source != SOURCE_NONE )
      {
            m_sources[m_selection->m_active_source]->stop ();

            if( m_selection->m_active_source != source_id)
            {
                  Play::Obj()->request_status (PLAYSTATUS_STOPPED);
            }
      }

      message_clear ();

      SourceSP source = m_sources[source_id];

      m_selection->source_activate (PlaybackSourceID (source_id));

      if( m_source_flags[source_id] & PlaybackSource::F_ASYNC)
      {
            source->play_async ();
            m_actions->get_action( PLAYER_SHELL_ACTION_STOP )->set_sensitive (true);
      }
      else
      {
            safe_pause_unset();
            if( source->play() )
            {
                    std::string type = source->get_type ();
                    enable_video_check (type);

                    ustring uri = source->get_uri();
                    if((uri.substr(0,7) == "file://") && (!file_test(filename_from_uri(uri), FILE_TEST_EXISTS)))
                    {
                        source->bad_track();
                        next();
                        return;
                    }

                    Play::Obj()->switch_stream (uri, type);
                    if( Play::Obj()->property_sane() == true )
                    {
                          play_post_internal (source_id);
                          return;
                    }
            }

            source->stop ();
            Play::Obj()->request_status (PLAYSTATUS_STOPPED);
            m_selection->source_activate (SOURCE_NONE);
      }
    }
  }

  void
  PlayerShell::safe_pause_unset ()
  {
    bool paused = RefPtr<ToggleAction>::cast_static (m_actions->get_action
        (PLAYER_SHELL_ACTION_PAUSE))->get_active();
    m_IgnorePauseToggled = paused;
    RefPtr<ToggleAction>::cast_static (m_actions->get_action
        (PLAYER_SHELL_ACTION_PAUSE))->set_active(false);
  }

  void
  PlayerShell::pause ()
  {
    bool paused = RefPtr<ToggleAction>::cast_static (m_actions->get_action
        (PLAYER_SHELL_ACTION_PAUSE))->get_active();

    m_infoarea->set_paused(paused);

    if(m_IgnorePauseToggled)
    {
        m_IgnorePauseToggled = false;

        if(paused)
        {
            return;
        }
    }

    PlaybackSource::Caps c = m_source_caps[m_selection->m_active_source];
    if( paused && (c & PlaybackSource::CAN_PAUSE ))
    {
      Play::Obj()->request_status (PLAYSTATUS_PAUSED);
    }
    else
    if( !paused && (Play::Obj()->property_status() == PLAYSTATUS_PAUSED))
    {
      Play::Obj()->request_status (PLAYSTATUS_PLAYING);
    }
  }

  void
  PlayerShell::prev_async_cb (int source_id)
  {
    SourceSP source = m_sources[source_id];
    PlaybackSource::Flags f = m_source_flags[source_id];

    if( (f & PlaybackSource::F_PHONY_NEXT) == 0 )
    {
        std::string type = source->get_type ();
        enable_video_check (type);

        ustring uri = source->get_uri();
        if((uri.substr(0,7) == "file://") && (!file_test(filename_from_uri(uri), FILE_TEST_EXISTS)))
        {
            source->bad_track();
            source->stop ();
            Play::Obj()->request_status (PLAYSTATUS_STOPPED);
            m_selection->source_activate (SOURCE_NONE);
            return;
        }

        safe_pause_unset();
        Play::Obj()->switch_stream (uri, type);

        if( Play::Obj()->property_sane() == true )
        {
                source->prev_post ();
                source->send_caps ();

                if( f & PlaybackSource::F_HANDLE_LASTFM )
                {
                      shell_lastfm_now_playing ();
                }

                m_player_dbus_obj->emit_track_change (m_player_dbus_obj);
                return;
        }

        source->stop ();
        Play::Obj()->request_status (PLAYSTATUS_STOPPED);
        m_selection->source_activate (SOURCE_NONE);
    }
  }

  void
  PlayerShell::prev ()
  {
    Glib::RecMutex::Lock L (m_playback_lock);

    PlaybackSourceID source_id = PlaybackSourceID (m_selection->m_active_source);
    SourceSP source = m_sources[source_id];
    PlaybackSource::Flags f = m_source_flags[source_id];
    PlaybackSource::Caps c = m_source_caps[source_id];

    if( f & PlaybackSource::F_HANDLE_LASTFM )
    {
          shell_lastfm_scrobble_current ();
    }

    if( c & PlaybackSource::CAN_GO_PREV )
    {
          if( f & PlaybackSource::F_ASYNC )
          {
                m_actions->get_action (PLAYER_SHELL_ACTION_PREV)->set_sensitive( false );
                source->go_prev_async ();
                return;
          }
          else
          if( (f & PlaybackSource::F_PHONY_PREV) == 0 )
          {
                if( source->go_prev () )
                {
                  std::string type = source->get_type ();
                  enable_video_check (type);

                  ustring uri = source->get_uri();
                  if((uri.substr(0,7) == "file://") && (!file_test(filename_from_uri(uri), FILE_TEST_EXISTS)))
                  {
                    prev();
                    return;
                  }

                  Play::Obj()->switch_stream (uri, type);
                  prev_async_cb (m_selection->m_active_source);
                }
                else
                {
                  g_warning(_("%s: Source '%s' indicated CAN_GO_PREV, but can not"), G_STRLOC, sources[source_id].name.c_str());
                  stop ();
                }
          }
    }
    else
    {
      source->stop ();
      Play::Obj()->request_status (PLAYSTATUS_STOPPED);
      m_selection->source_activate (SOURCE_NONE);
    }
  }

  void
  PlayerShell::next_async_cb (int source_id)
  {
    SourceSP source = m_sources[source_id];
    PlaybackSource::Flags f = m_source_flags[source_id];

    if( (f & PlaybackSource::F_PHONY_NEXT) == 0 )
    {
          std::string type = source->get_type ();
          enable_video_check (type);

          ustring uri = source->get_uri();
          if((uri.substr(0,7) == "file://") && (!file_test(filename_from_uri(uri), FILE_TEST_EXISTS)))
          {
            next();
            return;
          }

          safe_pause_unset();
          Play::Obj()->switch_stream (uri, type);

          if( Play::Obj()->property_sane() == true )
          {
                source->next_post ();
                source->send_caps ();

                if( f & PlaybackSource::F_HANDLE_LASTFM )
                {
                      shell_lastfm_now_playing ();
                }

                m_player_dbus_obj->emit_track_change (m_player_dbus_obj);
                return;
          }
    }
  }

  void
  PlayerShell::on_play_eos ()
  {
    Glib::RecMutex::Lock L (m_playback_lock);

    PlaybackSourceID source_id = PlaybackSourceID (m_selection->m_active_source);
    SourceSP source = m_sources[source_id];
    PlaybackSource::Flags f = m_source_flags[source_id];
    PlaybackSource::Caps c = m_source_caps[source_id];

    if( f & PlaybackSource::F_HANDLE_LASTFM )
    {
          shell_lastfm_scrobble_current ();
    }

    if( c & PlaybackSource::CAN_GO_NEXT )
    {
          if( f & PlaybackSource::F_ASYNC )
          {
            m_actions->get_action (PLAYER_SHELL_ACTION_NEXT)->set_sensitive( false );
            source->go_next_async ();
            return;
          }
          else
          {
            if( source->go_next () )
            {
              next_async_cb (m_selection->m_active_source);
              return;
            }
            else
            {
              g_warning(_("%s: Source %s indicated CAN_GO_NEXT, but can not"), G_STRLOC, sources[source_id].name.c_str());
              stop ();
            }
          }
    }
    else
    {
      stop ();
    }
  }

  void
  PlayerShell::next ()
  {
    Glib::RecMutex::Lock L (m_playback_lock);

    if( m_selection->m_active_source != SOURCE_NONE )
    {
      m_sources[m_selection->m_active_source]->skipped();
    }

    on_play_eos ();
  }

  void
  PlayerShell::shell_lastfm_scrobble_current ()
  {
    if( mcs->key_get<bool>("lastfm","queue-enable") && Play::Obj()->lastfm_qualifies() )
    {
      Mutex::Lock L (m_metadata_lock);
      TrackQueueItem item (m_metadata);
      LastFM::Scrobbler::Obj()->scrobble (item);
    }
  }

  void
  PlayerShell::shell_lastfm_now_playing ()
  {
    Mutex::Lock L (m_metadata_lock);
    TrackQueueItem item (m_metadata);
    LastFM::Scrobbler::Obj()->now_playing (item);
  }

  void
  PlayerShell::stop ()
  {
    if( m_selection->m_active_source != SOURCE_NONE )
    {
      PlaybackSourceID source_id = PlaybackSourceID( m_selection->m_active_source );
      PlaybackSource::Flags f = m_source_flags[source_id];
      if( f & PlaybackSource::F_ASYNC )
      {
        m_sources[source_id]->stop ();
      }
      Play::Obj()->request_status( PLAYSTATUS_STOPPED );
      m_sources[source_id]->send_caps();
    }
    message_clear ();
    m_actions->get_action (PLAYER_SHELL_ACTION_STOP)->set_sensitive( false );
  }

  void
  PlayerShell::on_play_status_changed ()
  {
    BmpPlaystatus status = BmpPlaystatus (Play::Obj()->property_status().get_value());
    Bmp::URI u;

    switch (status)
    {
      case PLAYSTATUS_STOPPED:
      {
        m_seeking = false;

        if( NM::Obj()->Check_Status() )
        {
          m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_TAG)->set_sensitive( 0 );
          m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_LOVE)->set_sensitive( 0 );
          m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_RECM)->set_sensitive( 0 );
          m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_PLAY)->set_sensitive( 0 );
        }

        if( m_ui_merge_id_context )
        {
          m_ui_manager->remove_ui (m_ui_merge_id_context);
          m_ui_merge_id_context = 0;
        }

        if( m_ui_merge_id_tray )
        {
          m_ui_manager->remove_ui (m_ui_merge_id_tray);
          m_ui_merge_id_tray = 0;
        }

        if( m_ui_merge_id_lastfm )
        {
          m_ui_manager->remove_ui (m_ui_merge_id_lastfm);
          m_ui_merge_id_lastfm = 0;
        }

        break;
      }

      case PLAYSTATUS_PLAYING:
      {
        m_seeking = false;
        m_actions->get_action( PLAYER_SHELL_ACTION_STOP )->set_sensitive (true);
        break;
      }

      default: ;
    }

    switch (status)
    {
        case PLAYSTATUS_NONE:
          /* ... */
          break;

        case PLAYSTATUS_SEEKING:
          /* ... */
          break;

        case PLAYSTATUS_STOPPED:
        {
          mVideoWidget->property_playing() = false;
          RefPtr<RadioAction>::cast_static (m_actions->get_action(PLAYER_SHELL_ACTION_VIDEO_ASPECT_AUTO))->set_current_value( 0 );
          m_actions->get_action("MenuVideo")->set_sensitive( false );
          mMainNotebook->set_current_page( 0 );

          if( m_selection->m_active_source != SOURCE_NONE )
          {
            m_sources[m_selection->m_active_source]->stop ();
            m_sources[m_selection->m_active_source]->send_caps ();
          }

          m_selection->source_activate (SOURCE_NONE);

          m_infoarea->reset ();

          m_asin.reset ();
          m_metadata = TrackMetadata();

          reset_seek_range ();
          message_clear ();

          if( m_popup )
          {
            m_popup->hide ();
          }

          bmp_status_icon_set_from_pixbuf (m_status_icon, m_status_icon_image_default->gobj());

          m_actions->get_action (PLAYER_SHELL_ACTION_LASTFM_RECM)->set_sensitive( 0 );
          m_actions->get_action (PLAYER_SHELL_ACTION_STOP)->set_sensitive( 0 );
          m_actions->get_action (PLAYER_SHELL_ACTION_PAUSE)->set_sensitive( 0 );
          m_actions->get_action (PLAYER_SHELL_ACTION_TRACK_DETAILS)->set_sensitive( 0 );
          m_actions->get_action (PLAYER_SHELL_ACTION_CREATE_BOOKMARK)->set_sensitive( 0 );
          break;
        }

        case PLAYSTATUS_WAITING:
        {
          reset_seek_range ();
          bmp_status_icon_set_from_pixbuf (m_status_icon, m_status_icon_image_default->gobj());
          break;
        }

        case PLAYSTATUS_PLAYING:
        {
          bmp_status_icon_set_from_pixbuf (m_status_icon, m_status_icon_image_playing->gobj());
          m_spm_paused = false;
          m_seek_progress->set_sensitive (true);
          break;
        }

        case PLAYSTATUS_PAUSED:
        {
          bmp_status_icon_set_from_pixbuf (m_status_icon, m_status_icon_image_paused->gobj());
          m_spm_paused = false;
          break;
        }
    }

    if( m_popup )
    {
      m_popup->set_playstatus (status);
    }

    m_player_dbus_obj->emit_status_change (m_player_dbus_obj, int (status));
  }

  bool
  PlayerShell::on_key_press_event (GdkEventKey *event)
  {
    if( event->keyval == GDK_Escape )
    {
          if( m_popup && mcs->key_get<bool>("bmp", "ui-esc-trayconify") )
          {
                set_skip_taskbar_hint (true);
                set_skip_pager_hint (true);
                hide ();
                m_visible = false;
          }
          else
          {
                iconify ();
          }
          return true;
    }
    return Widget::on_key_press_event (event);
  }

  PlayerShell*
  PlayerShell::create ()
  {
    const string path (BMP_GLADE_DIR G_DIR_SEPARATOR_S "main.glade");
    RefPtr<Gnome::Glade::Xml> glade_xml = Gnome::Glade::Xml::create (path);
    PlayerShell *p = 0;
    glade_xml->get_widget_derived ("main-ui", p);
    return p;
  }

  PlayerShell::~PlayerShell ()
  {
    mmkeys_deactivate ();
    g_object_unref (m_status_icon);
    delete mcs_bind;
    delete m_about;
  }

  GHashTable*
  PlayerShell::get_metadata_hash_table ()
  {
    int source_id = m_selection->m_active_source;

    if( source_id == SOURCE_NONE )
      return NULL;

    GHashTable * table = m_sources[source_id]->get_metadata();
    if( table )
      return table;

    // Nope.

    table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, GDestroyNotify (Util::free_gvalue_ptr));
    GValue* value = 0;

    if( m_metadata.bitrate )
    {
      value = g_new0 (GValue,1);
      g_value_init (value, G_TYPE_INT);
      g_value_set_int (value, int(m_metadata.bitrate.get()));
      g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_BITRATE).id), value);
    }

    if( m_metadata.duration )
    {
      value = g_new0 (GValue,1);
      g_value_init (value, G_TYPE_INT);
      g_value_set_int (value, int(m_metadata.duration.get()/1000));
      g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_TIME).id), value);
    }

    if( m_metadata.artist )
    {
      value = g_new0 (GValue,1);
      g_value_init (value, G_TYPE_STRING);
      g_value_set_string (value, m_metadata.artist.get().c_str());
      g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_ARTIST).id), value);
    }

    if( m_metadata.mb_artist_id )
    {
      value = g_new0 (GValue,1);
      g_value_init (value, G_TYPE_STRING);
      g_value_set_string (value, m_metadata.mb_artist_id.get().c_str());
      g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_MB_ARTIST_ID).id), value);
    }

    if( m_metadata.album )
    {
      value = g_new0 (GValue,1);
      g_value_init (value, G_TYPE_STRING);
      g_value_set_string (value, m_metadata.album.get().c_str());
      g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_ALBUM).id), value);
    }

    if( m_metadata.mb_album_id )
    {
      value = g_new0 (GValue,1);
      g_value_init (value, G_TYPE_STRING);
      g_value_set_string (value, m_metadata.mb_album_id.get().c_str());
      g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_MB_ALBUM_ID).id), value);
    }

    if( m_metadata.title )
    {
      value = g_new0 (GValue,1);
      g_value_init (value, G_TYPE_STRING);
      g_value_set_string (value, m_metadata.title.get().c_str());
      g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_TITLE).id), value);
    }

    if( m_metadata.mb_track_id )
    {
      value = g_new0 (GValue,1);
      g_value_init (value, G_TYPE_STRING);
      g_value_set_string (value, m_metadata.mb_track_id.get().c_str());
      g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_MB_TRACK_ID).id), value);
    }

    if( m_metadata.asin )
    {
      value = g_new0 (GValue,1);
      g_value_init (value, G_TYPE_STRING);
      g_value_set_string (value, m_metadata.asin.get().c_str());
      g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_ASIN).id), value);
    }

    value = g_new0 (GValue,1);
    g_value_init (value, G_TYPE_STRING);
    if( m_metadata.genre )
      g_value_set_string (value, m_metadata.genre.get().c_str());
    else if( source_id != SOURCE_NONE )
      g_value_set_string (value, m_sources[m_selection->m_active_source]->get_name().c_str());
    g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_GENRE).id), value);

    // we conceal Last.fm location on purpose here
    if( source_id != SOURCE_LASTFM )
    {
      value = g_new0 (GValue,1);
      g_value_init (value, G_TYPE_STRING);
      g_value_set_string (value, Play::Obj()->property_stream().get_value().c_str());
      g_hash_table_insert (table, g_strdup(get_attribute_info(ATTRIBUTE_LOCATION).id), value);
    }

    return table;
  }

  void
  PlayerShell::raise ()
  {
    if( !m_visible )
    {
      set_skip_taskbar_hint (false);
      set_skip_pager_hint (false);
      show ();
      m_visible = true;
    }
    deiconify ();
    Gtk::Window::raise ();
  }

  void
  PlayerShell::on_shell_toggle_info ()
  {
    bool active (RefPtr<ToggleAction>::cast_static (m_actions->get_action (PLAYER_SHELL_ACTION_INFO))->get_active());
    if( active )
      m_ref_xml->get_widget ("eventbox-info")->show ();
    else
      m_ref_xml->get_widget ("eventbox-info")->hide();
  }

  void
  PlayerShell::on_shell_toggle_sources ()
  {
    bool active (RefPtr<ToggleAction>::cast_static (m_actions->get_action (PLAYER_SHELL_ACTION_SOURCES))->get_active());
    if( active )
      m_ref_xml->get_widget ("vbox-sources")->show ();
    else
      m_ref_xml->get_widget ("vbox-sources")->hide();

    m_infoarea->set_compact (!active);
  }

  void
  PlayerShell::on_shell_toggle_statusbar ()
  {
    Gtk::Window * window = dynamic_cast<Gtk::Window*>(m_ref_xml->get_widget ("main-ui"));
    int w, h;

    bool active (RefPtr<ToggleAction>::cast_static (m_actions->get_action (PLAYER_SHELL_ACTION_STATUSBAR))->get_active());
    if( active )
    {
      m_ref_xml->get_widget ("hbox-status")->show ();
    }
    else
    {
      Gtk::Allocation alloc = m_ref_xml->get_widget ("hbox-status")->get_allocation ();
      window->get_size (w, h);
      m_ref_xml->get_widget ("hbox-status")->hide ();
      window->resize (w, h - alloc.get_height());
    }
  }

  ////////////////////////////////////////
  // BOOKMARKS API
  ////////////////////////////////////////

  void
  PlayerShell::bookmark_append (Bookmark const& bookmark, guint64 b_id)
  {
    static boost::format name_f ("bookmark-%llu");
    static boost::format path_f ("/MenuBarMain/MenuBookmarks/MenuBookmarks-%s");
    ustring name = (name_f % b_id).str();
    m_actions->add (Action::create (name, Gtk::StockID (sources[bookmark.m_id].stock), bookmark.m_title),
                                    sigc::bind (sigc::mem_fun (*this, &PlayerShell::on_bookmark_activate), bookmark.m_url));
    m_ui_manager->add_ui (bookmark.m_merge_id,
                          (path_f % sources[bookmark.m_id].id).str(),
                          bookmark.m_title,
                          name,
                          Gtk::UI_MANAGER_AUTO,
                          false);
  }

  void
  PlayerShell::on_bookmark_loaded (Bookmark & b, guint64 id)
  {
    b.m_merge_id = m_ui_manager->new_merge_id ();
    bookmark_append (b, id);
  }

  void
  PlayerShell::on_bookmark_create ()
  {
    PlaybackSourceID id = PlaybackSourceID (m_selection->m_active_source);

    if( id != SOURCE_NONE )
    {
          PlaybackSource::Caps caps (m_source_caps[id]);
          if( caps & PlaybackSource::CAN_BOOKMARK )
          {
                BookmarkHandler * handler = dynamic_cast<BookmarkHandler*>(m_sources[id].get());

                ustring url, title;
                handler->get_bookmark (sources[id].id, url, title);

                UIManager::ui_merge_id merge_id = m_ui_manager->new_merge_id ();
                Bookmark b (title, id, url, merge_id);
                guint64 b_id = m_bookmark_manager.add_bookmark (b);
                bookmark_append (b, b_id);
          }
    }
  }

  void
  PlayerShell::on_bookmark_activate (std::string const& url)
  {
    Bmp::URI u (url);
    BookmarkHandler * handler = 0;

    if( u.hostname == "radio" )
    {
          handler = dynamic_cast<BookmarkHandler*>(m_sources[SOURCE_RADIO].get());
    }

    if( u.hostname == "jamendo" )
    {
          handler = dynamic_cast<BookmarkHandler*>(m_sources[SOURCE_JAMENDO].get());
    }

    if( handler )
    {
          handler->set_bookmark (url);
    }
  }

  // MM-Keys stuff (C) Rhythmbox Developers 2007

  /*static*/ void
  PlayerShell::media_player_key_pressed (DBusGProxy *proxy,
                                         const gchar *application,
                                         const gchar *key,
                                         gpointer data)
  {
    PlayerShell & shell = *reinterpret_cast<PlayerShell*>(data);

    if (strcmp (application, "BMPx"))
      return;

    if( shell.m_selection->m_active_source == SOURCE_NONE )
      return;

    PlaybackSource::Caps c = shell.m_source_caps[shell.m_selection->m_active_source];

    if (strcmp (key, "Play") == 0) {
      if( Play::Obj()->property_status() == PLAYSTATUS_PAUSED)
        shell.pause ();
      else
      if( Play::Obj()->property_status() != PLAYSTATUS_WAITING)
        shell.play ();
    } else if (strcmp (key, "Pause") == 0) {
      if( c & PlaybackSource::CAN_PAUSE )
        shell.pause ();
    } else if (strcmp (key, "Stop") == 0) {
      shell.stop ();
    } else if (strcmp (key, "Previous") == 0) {
      if( c & PlaybackSource::CAN_GO_PREV )
        shell.prev ();
    } else if (strcmp (key, "Next") == 0) {
      if( c & PlaybackSource::CAN_GO_NEXT )
        shell.next ();
    }
  }

  /*static*/ bool
  PlayerShell::window_focus_cb (GdkEventFocus *event)
  {
    dbus_g_proxy_call (m_mmkeys_dbusproxy,
           "GrabMediaPlayerKeys", NULL,
           G_TYPE_STRING, "BMPx",
           G_TYPE_UINT, 0,
           G_TYPE_INVALID, G_TYPE_INVALID);

    return false;
  }

  /* Taken from xbindkeys */
  void
  PlayerShell::mmkeys_get_offending_modifiers ()
  {
    Display * dpy = gdk_x11_display_get_xdisplay (gdk_display_get_default());
    int i;
    XModifierKeymap *modmap;
    KeyCode nlock, slock;
    static int mask_table[8] = {
      ShiftMask, LockMask, ControlMask, Mod1Mask,
      Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
    };

    nlock = XKeysymToKeycode (dpy, XK_Num_Lock);
    slock = XKeysymToKeycode (dpy, XK_Scroll_Lock);

    /*
    * Find out the masks for the NumLock and ScrollLock modifiers,
    * so that we can bind the grabs for when they are enabled too.
    */
    modmap = XGetModifierMapping (dpy);

    if (modmap != NULL && modmap->max_keypermod > 0)
    {
      for (i = 0; i < 8 * modmap->max_keypermod; i++)
      {
        if (modmap->modifiermap[i] == nlock && nlock != 0)
          m_numlock_mask = mask_table[i / modmap->max_keypermod];
        else if (modmap->modifiermap[i] == slock && slock != 0)
          m_scrolllock_mask = mask_table[i / modmap->max_keypermod];
      }
    }

    m_capslock_mask = LockMask;

    if (modmap)
      XFreeModifiermap (modmap);
  }

  /*static*/ void
  PlayerShell::grab_mmkey (int key_code,
                           GdkWindow *root)
  {
    gdk_error_trap_push ();

    XGrabKey (GDK_DISPLAY (), key_code,
        0,
        GDK_WINDOW_XID (root), True,
        GrabModeAsync, GrabModeAsync);
    XGrabKey (GDK_DISPLAY (), key_code,
        Mod2Mask,
        GDK_WINDOW_XID (root), True,
        GrabModeAsync, GrabModeAsync);
    XGrabKey (GDK_DISPLAY (), key_code,
        Mod5Mask,
        GDK_WINDOW_XID (root), True,
        GrabModeAsync, GrabModeAsync);
    XGrabKey (GDK_DISPLAY (), key_code,
        LockMask,
        GDK_WINDOW_XID (root), True,
        GrabModeAsync, GrabModeAsync);
    XGrabKey (GDK_DISPLAY (), key_code,
        Mod2Mask | Mod5Mask,
        GDK_WINDOW_XID (root), True,
        GrabModeAsync, GrabModeAsync);
    XGrabKey (GDK_DISPLAY (), key_code,
        Mod2Mask | LockMask,
        GDK_WINDOW_XID (root), True,
        GrabModeAsync, GrabModeAsync);
    XGrabKey (GDK_DISPLAY (), key_code,
        Mod5Mask | LockMask,
        GDK_WINDOW_XID (root), True,
        GrabModeAsync, GrabModeAsync);
    XGrabKey (GDK_DISPLAY (), key_code,
        Mod2Mask | Mod5Mask | LockMask,
        GDK_WINDOW_XID (root), True,
        GrabModeAsync, GrabModeAsync);

    gdk_flush ();

    if (gdk_error_trap_pop ())
    {
      g_message(G_STRLOC ": Error grabbing key");
    }
  }

  /*static*/ void
  PlayerShell::grab_mmkey (int key_code,
                           int modifier,
                           GdkWindow *root)
  {
    gdk_error_trap_push ();

    modifier &= ~(m_numlock_mask | m_capslock_mask | m_scrolllock_mask);

    XGrabKey (GDK_DISPLAY (), key_code, modifier, GDK_WINDOW_XID (root),
      False, GrabModeAsync, GrabModeAsync);

    if (modifier == AnyModifier)
      return;

    if (m_numlock_mask)
      XGrabKey (GDK_DISPLAY (), key_code, modifier | m_numlock_mask,
        GDK_WINDOW_XID (root),
        False, GrabModeAsync, GrabModeAsync);

    if (m_capslock_mask)
      XGrabKey (GDK_DISPLAY (), key_code, modifier | m_capslock_mask,
        GDK_WINDOW_XID (root),
        False, GrabModeAsync, GrabModeAsync);

    if (m_scrolllock_mask)
      XGrabKey (GDK_DISPLAY (), key_code, modifier | m_scrolllock_mask,
        GDK_WINDOW_XID (root),
        False, GrabModeAsync, GrabModeAsync);

    if (m_numlock_mask && m_capslock_mask)
      XGrabKey (GDK_DISPLAY (), key_code, modifier | m_numlock_mask | m_capslock_mask,
        GDK_WINDOW_XID (root),
        False, GrabModeAsync, GrabModeAsync);

    if (m_numlock_mask && m_scrolllock_mask)
      XGrabKey (GDK_DISPLAY (), key_code, modifier | m_numlock_mask | m_scrolllock_mask,
        GDK_WINDOW_XID (root),
        False, GrabModeAsync, GrabModeAsync);

    if (m_capslock_mask && m_scrolllock_mask)
      XGrabKey (GDK_DISPLAY (), key_code, modifier | m_capslock_mask | m_scrolllock_mask,
        GDK_WINDOW_XID (root),
        False, GrabModeAsync, GrabModeAsync);

    if (m_numlock_mask && m_capslock_mask && m_scrolllock_mask)
      XGrabKey (GDK_DISPLAY (), key_code,
        modifier | m_numlock_mask | m_capslock_mask | m_scrolllock_mask,
        GDK_WINDOW_XID (root), False, GrabModeAsync,
        GrabModeAsync);

    gdk_flush ();

    if (gdk_error_trap_pop ())
    {
      g_message(G_STRLOC ": Error grabbing key");
    }
  }

  /*static*/ void
  PlayerShell::ungrab_mmkeys (GdkWindow *root)
  {
    gdk_error_trap_push ();

    XUngrabKey (GDK_DISPLAY (), AnyKey, AnyModifier, GDK_WINDOW_XID (root));

    gdk_flush ();

    if (gdk_error_trap_pop ())
    {
      g_message(G_STRLOC ": Error grabbing key");
    }
  }


  /*static*/ GdkFilterReturn
  PlayerShell::filter_mmkeys (GdkXEvent *xevent,
                              GdkEvent *event,
                              gpointer data)
  {
    PlayerShell & shell = *reinterpret_cast<PlayerShell*>(data);

    if( shell.m_selection->m_active_source == SOURCE_NONE )
      return GDK_FILTER_CONTINUE;

    PlaybackSource::Caps c = shell.m_source_caps[shell.m_selection->m_active_source];

    XEvent * xev = (XEvent *) xevent;

    if (xev->type != KeyPress)
    {
      return GDK_FILTER_CONTINUE;
    }

    XKeyEvent * key = (XKeyEvent *) xevent;

    guint keycodes[] = {0, 0, 0, 0, 0};
    int sys = mcs->key_get<int>("hotkeys","system");

    if( sys == 0)
    {
      keycodes[0] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPlay);
      keycodes[1] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPause);
      keycodes[2] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPrev);
      keycodes[3] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioNext);
      keycodes[4] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioStop);
    }
    else
    {
      keycodes[0] = mcs->key_get<int>("hotkeys","key-1");
      keycodes[1] = mcs->key_get<int>("hotkeys","key-2");
      keycodes[2] = mcs->key_get<int>("hotkeys","key-3");
      keycodes[3] = mcs->key_get<int>("hotkeys","key-4");
      keycodes[4] = mcs->key_get<int>("hotkeys","key-5");
    }

    if (keycodes[0] == key->keycode) {
      if( Play::Obj()->property_status() == PLAYSTATUS_PAUSED)
        shell.pause ();
      else
      if( Play::Obj()->property_status() != PLAYSTATUS_WAITING)
        shell.play ();
      return GDK_FILTER_REMOVE;
    } else if (keycodes[1] == key->keycode) {
      if( c & PlaybackSource::CAN_PAUSE )
        shell.pause ();
      return GDK_FILTER_REMOVE;
    } else if (keycodes[2] == key->keycode) {
      if( c & PlaybackSource::CAN_GO_PREV )
        shell.prev ();
      return GDK_FILTER_REMOVE;
    } else if (keycodes[3] == key->keycode) {
      if( c & PlaybackSource::CAN_GO_NEXT )
        shell.next ();
      return GDK_FILTER_REMOVE;
    } else if (keycodes[4] == key->keycode) {
      shell.stop ();
      return GDK_FILTER_REMOVE;
    } else {
      return GDK_FILTER_CONTINUE;
    }
  }

  /*static*/ void
  PlayerShell::mmkeys_grab (bool grab)
  {
    gint keycodes[] = {0, 0, 0, 0, 0};
    keycodes[0] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPlay);
    keycodes[1] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioStop);
    keycodes[2] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPrev);
    keycodes[3] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioNext);
    keycodes[4] = XKeysymToKeycode (GDK_DISPLAY (), XF86XK_AudioPause);

    GdkDisplay *display;
    GdkScreen *screen;
    GdkWindow *root;

    display = gdk_display_get_default ();
    int sys = mcs->key_get<int>("hotkeys","system");

    for (int i = 0; i < gdk_display_get_n_screens (display); i++) {
      screen = gdk_display_get_screen (display, i);

      if (screen != NULL) {
        root = gdk_screen_get_root_window (screen);
        if(!grab)
        {
          ungrab_mmkeys (root);
          continue;
        }

        for (guint j = 1; j < 6 ; ++j)
        {
          if( sys == 2 )
          {
            int key = mcs->key_get<int>("hotkeys", (boost::format ("key-%d") % j).str());
            int mask = mcs->key_get<int>("hotkeys", (boost::format ("key-%d-mask") % j).str());

            if (key)
            {
              grab_mmkey (key, mask, root);
            }
          }
          else
          if( sys == 0 )
          {
            grab_mmkey (keycodes[j-1], root);
          }
        }

        if (grab)
          gdk_window_add_filter (root, filter_mmkeys, this);
        else
          gdk_window_remove_filter (root, filter_mmkeys, this);
      }
    }
  }

  /*static*/ void
  PlayerShell::mmkeys_activate ()
  {
    if( mm_active )
      return;

    DBusGConnection *bus;

    g_message(G_STRLOC ": Activating media player keys");

    if (m_mmkeys_grab_type == SETTINGS_DAEMON)
    {
      bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
      if(bus != NULL)
      {
        GError *error = NULL;

        m_mmkeys_dbusproxy = dbus_g_proxy_new_for_name (bus,
            "org.gnome.SettingsDaemon",
            "/org/gnome/SettingsDaemon",
            "org.gnome.SettingsDaemon");

        if (m_mmkeys_dbusproxy)
        {
          dbus_g_proxy_call (m_mmkeys_dbusproxy,
                 "GrabMediaPlayerKeys", &error,
                 G_TYPE_STRING, "Rhythmbox",
                 G_TYPE_UINT, 0,
                 G_TYPE_INVALID,
                 G_TYPE_INVALID);

          if (error == NULL)
          {
            g_message(G_STRLOC ": created dbus proxy for org.gnome.SettingsDaemon; grabbing keys");

            dbus_g_object_register_marshaller (bmp_dbus_marshal_VOID__STRING_STRING,
                G_TYPE_NONE, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);

            dbus_g_proxy_add_signal (m_mmkeys_dbusproxy,
                   "MediaPlayerKeyPressed",
                   G_TYPE_STRING,G_TYPE_STRING,G_TYPE_INVALID);

            dbus_g_proxy_connect_signal (m_mmkeys_dbusproxy,
                       "MediaPlayerKeyPressed",
                       G_CALLBACK (media_player_key_pressed),
                       this, NULL);

            mWindowFocusConn = signal_focus_in_event().connect( sigc::mem_fun( *this, &PlayerShell::window_focus_cb ) );
          }
          else if (error->domain == DBUS_GERROR &&
                     (error->code != DBUS_GERROR_NAME_HAS_NO_OWNER ||
                      error->code != DBUS_GERROR_SERVICE_UNKNOWN))
          {
            /* settings daemon dbus service doesn't exist.
             * just silently fail.
             */
            g_message(G_STRLOC ": org.gnome.SettingsDaemon dbus service not found");
            g_error_free (error);
          }
          else
          {
            g_warning (G_STRLOC ": Unable to grab media player keys: %s", error->message);
            g_error_free (error);
          }
        }
      }
      else
      {
        g_message(G_STRLOC ": couldn't get dbus session bus");
      }
    }
    else
    if (m_mmkeys_grab_type == X_KEY_GRAB)
    {
      g_message(G_STRLOC ": attempting old-style key grabs");
      mmkeys_grab (true);
    }

    mm_active = true;
  }

  /*static*/ void
  PlayerShell::mmkeys_deactivate ()
  {
    if( !mm_active )
      return;

    if (m_mmkeys_dbusproxy)
    {
      GError *error = NULL;

      if (m_mmkeys_grab_type == SETTINGS_DAEMON)
      {
        dbus_g_proxy_call (m_mmkeys_dbusproxy,
               "ReleaseMediaPlayerKeys", &error,
               G_TYPE_STRING, "BMPx",
               G_TYPE_INVALID, G_TYPE_INVALID);
        if (error != NULL)
        {
          g_warning (G_STRLOC ": Could not release media player keys: %s", error->message);
          g_error_free (error);
        }
        mWindowFocusConn.disconnect ();
        m_mmkeys_grab_type = NONE;
      }

      g_object_unref (m_mmkeys_dbusproxy);
      m_mmkeys_dbusproxy = 0;
    }

    if (m_mmkeys_grab_type == X_KEY_GRAB)
    {
      g_message(G_STRLOC ": undoing old-style key grabs");
      mmkeys_grab (false);
      m_mmkeys_grab_type = NONE;
    }

    mm_active = false;
  }

  void
  PlayerShell::on_mm_edit_begin ()
  {
    mmkeys_deactivate ();
  }

  void
  PlayerShell::on_mm_edit_done ()
  {
    if( mcs->key_get<bool>("hotkeys","enable") )
    {
      int sys = mcs->key_get<int>("hotkeys","system");
      if( (sys == 0) || (sys == 2))
        m_mmkeys_grab_type = X_KEY_GRAB;
      else
        m_mmkeys_grab_type = SETTINGS_DAEMON;
      mmkeys_activate ();
    }
  }
}
