//  BMP
//  Copyright (C) 2005-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 Version 2
//  as published by the Free Software Foundation.
//
//  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 <glibmm.h>
#include <glibmm/i18n.h>
#include <gtkmm.h>
#include <libglademm.h>

#include <cstring>
#include <iostream>
#include <sstream>
#include <string>

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

// BMP Widgets
#include "widgets/banner-image.hh"
#include "widgets/cell-renderer-cairo-surface.hh"

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

// BMP Misc
#include "core.hh"
#include "x_library.hh"
#include "audio/play.hh"
#include "x_vfs.hh"
#include "x_mcsbind.hh"

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

using namespace Glib;
using namespace Gtk;
using namespace std;
using namespace Bmp;
using namespace Util;
using namespace LastFM;
using namespace LastFM::XMLRPC;
using namespace LastFM::WS;

namespace
{
  static boost::format f_neighbours ("lastfm://user/%s/neighbours");
  static boost::format f_personal   ("lastfm://user/%s/personal");
  static boost::format f_loved      ("lastfm://user/%s/loved");
  static boost::format f_recommend  ("lastfm://user/%s/recommended/%i");
  static boost::format f_globaltag  ("lastfm://globaltags/%s");
  static boost::format f_artist     ("lastfm://artist/%s/similarartists");

  static Glib::ustring t_personal  (_("Personal"));
  static Glib::ustring t_loved     (_("Loved Tracks"));
  static Glib::ustring t_neighbour (_("Neighbourhood"));
  static Glib::ustring t_recommend (_("Recommended"));
}

namespace
{
  const char * ui_string_lastfm =
  "<ui>"
  ""
  "<menubar name='MenuBarMain'>"
  "   <placeholder name='PlaceholderSource'>"
  "   <menu action='MenuUiPartLastFm'>"
  "     <menuitem action='" LASTFM_ACTION_DISCOVERY "'/>" 
  "     <separator name='lastfm-1'/>"
  "     <menuitem action='" LASTFM_ACTION_BAN "'/>"
  "     <separator name='lastfm-3'/>"
  "     <menuitem action='" LASTFM_ACTION_UPDATE_LIST "'/>"
  "     <menuitem action='" LASTFM_ACTION_MATCH_TAGS "'/>"
  "   </menu>"
  "   </placeholder>"
  "</menubar>"
  ""
  "</ui>";

  const char * ui_string_lastfm_context =
  "<ui>"
  "<menubar name='popup-shell'>"
  " <menu action='dummy' name='menu-shell-context'>"
  "   <placeholder name='PlaceholderSourceContext'>"
  "     <menuitem action='" LASTFM_ACTION_BAN "'/>"
  "   </placeholder>"
  " </menu>"
  "</menubar>"
  "</ui>";

  char const * ui_string_lastfm_tray =
  "<ui>"
  " <menubar name='popup-tray'>"
  " <menu action='dummy' name='menu-tray'>"
  "   <placeholder name='PlaceholderSourceContext'>"
  "     <menuitem action='" LASTFM_ACTION_BAN "'/>"
  "   </placeholder>"
  " </menu>"
  " </menubar>"
  "</ui>";

  const char * ui_string_tav =
  "<ui>"
  ""
  "<menubar name='popup-lastfm-tav'>"
  "   <menu action='dummy' name='menu-lastfm-tav'>"
  "     <menuitem action='lastfm-action-tav-play-lastfm'/>"
  "     <menuitem action='lastfm-action-tav-view-in-library'/>"
  "   </menu>"
  "</menubar>"
  ""
  "</ui>";
}

namespace Bmp
{
  namespace UiPart
  {
    guint
    LASTFM::add_ui ()
    {
      return m_ui_manager->add_ui_from_string  (ui_string_lastfm);
    };

    guint
    LASTFM::add_context_ui ()
    {
      return m_ui_manager->add_ui_from_string  (ui_string_lastfm_context);
    }

    guint
    LASTFM::add_tray_ui ()
    {
      return m_ui_manager->add_ui_from_string  (ui_string_lastfm_tray);
    }

    LASTFM::~LASTFM ()
    {
      Widgets.TagInfoView->clear();
      StateData.TagsReq.clear();
    }

    LASTFM::LASTFM (RefPtr <Gnome::Glade::Xml> const& xml, RefPtr <UIManager> ui_manager)
    : PlaybackSource  (_("Last.fm"), Caps (0), Flags (F_ALWAYS_IMAGE_FRAME | F_HANDLE_LASTFM_ACTIONS | F_ASYNC))
    , Base (xml, ui_manager)
    {
      m_actions = Gtk::ActionGroup::create ("Actions_LastFm");
      m_actions->add (Gtk::Action::create ("MenuUiPartLastFm", _("_Last.fm")));
      m_actions->add  (Gtk::ToggleAction::create (LASTFM_ACTION_DISCOVERY,
                                            _("Discovery Mode")));

      mcs_bind->bind_toggle_action (RefPtr<ToggleAction>::cast_static (m_actions->get_action( LASTFM_ACTION_DISCOVERY )),
        "lastfm", "discoverymode");

      m_actions->add  (Gtk::Action::create (LASTFM_ACTION_BAN,
                                            Gtk::StockID (BMP_STOCK_LASTFM_BAN),
                                            _("I Hate this Track!")),
                                            (sigc::mem_fun (*this, &UiPart::LASTFM::on_ban)));

      m_actions->add  (Gtk::Action::create (LASTFM_ACTION_UPDATE_LIST,
                                            Gtk::Stock::REFRESH,
                                            _("Reload Stations")),
                                            (sigc::bind (sigc::mem_fun (*this, &UiPart::LASTFM::on_update_list), false)));

      m_actions->add  (Gtk::Action::create (LASTFM_ACTION_MATCH_TAGS,
                                            Gtk::Stock::REFRESH,
                                            _("Reload Stations (with matching tags)")),
                                            (sigc::bind (sigc::mem_fun (*this, &UiPart::LASTFM::on_update_list), true)));

      m_actions->get_action( LASTFM_ACTION_BAN )->set_sensitive (false);

      m_ui_manager->insert_action_group (m_actions);

      dynamic_cast<Gtk::Image *>(m_ref_xml->get_widget ("lastfm-i-station"))->set
        (Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_LASTFM, "lastfm-station.png")));
      dynamic_cast <Gtk::Image *>(m_ref_xml->get_widget ("lastfm-stations-throbber"))->set
        (Glib::build_filename (BMP_IMAGE_DIR, BMP_THROBBER));
      dynamic_cast <Gtk::Image *>(m_ref_xml->get_widget ("lastfm-tags-throbber"))->set
        (Glib::build_filename (BMP_IMAGE_DIR, BMP_THROBBER));
      dynamic_cast <Gtk::Image *>(m_ref_xml->get_widget ("lastfm-artists-throbber"))->set
        (Glib::build_filename (BMP_IMAGE_DIR, BMP_THROBBER));

      m_ref_xml->get_widget ("lastfm-current-station", Widgets.Station);
      m_ref_xml->get_widget ("lastfm-station", Widgets.StationEntry);
      m_ref_xml->get_widget_derived ("lastfm-tag-info-view", Widgets.TagInfoView);
      Widgets.TagInfoView->set_ui_manager(m_ui_manager);

      Widgets.TagInfoView->signalArtistActivated().connect( sigc::mem_fun( *this, &LASTFM::display_tag_artists_activated));

      Widgets.StationEntry->set_text("");
      Widgets.StationEntry->signal_activate().connect (sigc::mem_fun (*this, &LASTFM::start_playback));

      m_ref_xml->get_widget ("lastfm-cbox-stationchoice", Widgets.StationChoice);
      dynamic_cast <CellLayout*> (Widgets.StationChoice)->clear ();
      CellRendererText * cell = manage (new Gtk::CellRendererText());
      cell->property_xalign() = 1.0;
      Widgets.StationChoice->pack_start (*cell, false);
      Widgets.StationChoice->add_attribute (*cell, "text", 0);
      Widgets.StationChoice->set_active (0);

      m_ref_xml->get_widget_derived ("lastfm-stations", Widgets.ProfileView);
      Widgets.ProfileView->station_activated().connect
        (sigc::mem_fun (*this, &LASTFM::play_uri));

      m_ref_xml->get_widget_derived ("lastfm-textview-tags", Widgets.TagView);
      TagBuffer = Widgets.TagView->get_buffer (); 

      m_ref_xml->get_widget ("lastfm-limiter", Widgets.TagLimiter);
      Widgets.TagLimiter->signal_value_changed().connect
        (sigc::mem_fun (*this, &LASTFM::display_tags_limited));
      Widgets.TagLimiter->set_value (10);
      Widgets.TagLimiter->set_sensitive (0);

      m_ref_xml->get_widget ("vbox-lastfm")->set_sensitive (false);
      LastFM::Radio::Obj()->signal_connected().connect
        (sigc::mem_fun (*this, &UiPart::LASTFM::on_radio_connected));
      LastFM::Radio::Obj()->signal_disconnected().connect
        (sigc::mem_fun (*this, &UiPart::LASTFM::on_radio_disconnected));
      LastFM::Radio::Obj()->signal_playlist().connect
        (sigc::mem_fun (*this, &UiPart::LASTFM::on_playlist));
      LastFM::Radio::Obj()->signal_no_playlist().connect
        (sigc::mem_fun (*this, &UiPart::LASTFM::on_no_playlist));
      LastFM::Radio::Obj()->signal_tuned().connect
        (sigc::mem_fun (*this, &UiPart::LASTFM::on_tuned));
      LastFM::Radio::Obj()->signal_tune_error().connect
        (sigc::mem_fun (*this, &UiPart::LASTFM::on_tune_error));

      m_caps = Caps (m_caps & ~PlaybackSource::CAN_SEEK);
      send_caps ();
    }
  
    void
    LASTFM::disable_ban ()
    {
      m_actions->get_action( LASTFM_ACTION_BAN )->set_sensitive (0);
    }

    void
    LASTFM::on_update_list (bool match_tags)
    {
      Widgets.ProfileView->user_set (mcs->key_get <string> ("lastfm","username"), LastFM::Radio::Obj()->session().subscriber, match_tags); 
    }

    void
    LASTFM::on_radio_connected ()
    {
      Widgets.ProfileView->user_set (mcs->key_get<string>("lastfm","username"),LastFM::Radio::Obj()->session().subscriber, false);
      m_ref_xml->get_widget ("vbox-lastfm")->set_sensitive (true);
      m_caps = Caps (m_caps | PlaybackSource::CAN_PLAY);
      send_caps ();
    }

    void
    LASTFM::on_radio_disconnected ()
    {
      m_ref_xml->get_widget ("vbox-lastfm")->set_sensitive (false);
      m_caps = Caps (m_caps & ~PlaybackSource::CAN_PLAY);
      send_caps ();
      Widgets.ProfileView->user_clear ();
    }

    void
    LASTFM::clear_metadata_info ()
    {
      Widgets.Station->set_text ("");
      Widgets.TagView->clear();
    }

    //////////////////////////////////////////////
    // URIHANDLER                               //
    //////////////////////////////////////////////

    // UriHandler
    StrV  
    LASTFM::get_schemes ()
    {
      StrV v;
      v.push_back ("lastfm");
      return v;
    }

    void
    LASTFM::process (VUri const& uris)
    {
      play_uri (uris[0]);
    }
 
    //////////////////////////////////////////////
    // PLAYBACKSOURCE                           //
    //////////////////////////////////////////////

    ustring
    LASTFM::get_uri ()
    {
      XSPF::Item const& item = (*StateData.Iter);
      return item.location;
    }

    ustring
    LASTFM::get_type ()
    {
      return "audio/mpeg";
    }
    
    void
    LASTFM::on_playlist (XSPF::Playlist const& playlist)
    {
      bool empty = StateData.Playlist.Items.empty();

      StateData.Playlist = playlist;

      if (!StateData.Playlist.Items.empty())
      {
        StateData.Iter = StateData.Playlist.Items.begin();
        mSignalMessageClear.emit ();
        m_actions->get_action( LASTFM_ACTION_BAN )->set_sensitive( true );
        if( empty )
        {
          mSignalPlayAsync.emit ();
          m_caps = Caps (m_caps | PlaybackSource::CAN_PLAY);
          send_caps ();
        }
        else
          mSignalNextAsync.emit ();
      }
      else
      {
        mSignalStopRequest.emit ();
        //Core::Obj()->status_push_message (new_message ("No Items Available for this Station"));
        //return false;
      }
    }

    void
    LASTFM::on_no_playlist ()
    {
      mSignalStopRequest.emit ();
      //Core::Obj()->status_push_message (new_message ("No Items Available for this Station"));
      //return false;
    }

    void
    LASTFM::go_next_async ()
    {
      if( Play::Obj()->lastfm_qualifies_duration( StateData.Iter->duration / 1000))
      {
        LastFM::Scrobbler::Obj()->scrobble (*StateData.Iter);
      }

      StateData.Iter++;
      if( StateData.Iter == StateData.Playlist.Items.end() )
      {
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_NEXT);
        send_caps();
        LastFM::Radio::Obj()->get_xspf_playlist ();
      }
      else
      {
        m_actions->get_action( LASTFM_ACTION_BAN )->set_sensitive( true );
        mSignalNextAsync.emit ();
      }
    }

    void
    LASTFM::stop ()
    {
      PlayConnection.disconnect ();

      m_actions->get_action( LASTFM_ACTION_BAN )->set_sensitive( 0 );
      Widgets.TagLimiter->set_sensitive( 0 ); 

      if (StateData.TagsReq) StateData.TagsReq->cancel ();
      dynamic_cast <Gtk::Notebook *>(m_ref_xml->get_widget ("lastfm-tags-notebook"))->set_current_page (0);
      LastFM::Radio::Obj()->get_xspf_playlist_cancel ();

      StateData = StateDataT ();
      clear_metadata_info ();

      m_caps = Caps (m_caps & ~PlaybackSource::CAN_PROVIDE_METADATA);
      m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_NEXT);
      send_caps ();
    }

    void
    LASTFM::play_async ()
    {
      m_caps = Caps (m_caps & ~PlaybackSource::CAN_PLAY);
      send_caps ();

      PlayConnection = Play::Obj()->signal_http_status().connect
        (sigc::mem_fun (*this, &LASTFM::on_play_http_status));

      if(!LastFM::Radio::Obj()->is_playlist(StateData.Uri))
      {
        mSignalMessage.emit (_("Getting Playlist"));
        LastFM::Radio::Obj()->get_xspf_playlist();
      }
      else
      {
        m_actions->get_action( LASTFM_ACTION_BAN )->set_sensitive( true );
      }

      StateData.Uri = ustring(); 
    }

    void
    LASTFM::play_post ()
    {
      Widgets.TagLimiter->set_sensitive( true );
      m_actions->get_action( LASTFM_ACTION_BAN )->set_sensitive( true );

      XSPF::Item const& item = (*StateData.Iter);
      Widgets.Station->set_text ((boost::format ("%s: %s - %s") 
                                  % StateData.Playlist.Title.c_str()
                                  % item.creator.c_str()
                                  % item.title.c_str()).str());
 
      TrackMetadata metadata;
      metadata.artist = item.creator; 
      metadata.album = item.album; 
      metadata.title = item.title; 
      metadata.duration = item.duration; 
      metadata.image = Util::get_image_from_uri (item.image); 
      metadata.genre = ustring ("Last.fm: " + StateData.Playlist.Title);
      mSignalMetadata.emit (metadata);

      TrackQueueItem tqi (metadata);
      tqi.source = "L";
      tqi.length /= 1000;
      LastFM::Scrobbler::Obj()->now_playing (tqi);
      get_track_tags ();

      if( StateData.Iter != StateData.Playlist.Items.end() )
        m_caps = Caps (m_caps | PlaybackSource::CAN_GO_NEXT);
      else
        m_caps = Caps (m_caps & ~PlaybackSource::CAN_GO_NEXT);
    }

    void
    LASTFM::display_tags_limited ()
    {
      Widgets.TagView->clear();
      TextBuffer::iterator iter = TagBuffer->begin();
      guint64 value = (100 - guint64 (Widgets.TagLimiter->get_value() * 10));
      for (TagV::const_iterator i = StateData.Tags.begin(); i != StateData.Tags.end(); ++i)
      {
        if (i->count >= value)
        {
          RefPtr<LastFmLinkTag> tag = LastFmLinkTag::create (i->name, i->count);
          TagBuffer->get_tag_table()->add (tag);
          tag->signalUrlActivated().connect( sigc::mem_fun( *this, &LASTFM::display_tag_artists ) );
          iter = TagBuffer->insert_with_tag (iter, i->name, tag);
          iter = TagBuffer->insert (iter, "        ");
        }
        else
        {
          RefPtr<LastFmLinkTag> tag = LastFmLinkTag::create (i->name, i->count, "#eaf3ff");
          TagBuffer->get_tag_table()->add (tag);
          tag->signalUrlActivated().connect( sigc::mem_fun( *this, &LASTFM::display_tag_artists ) );
          iter = TagBuffer->insert_with_tag (iter, i->name, tag);
          iter = TagBuffer->insert (iter, "        ");
        }
      }
    }

    void
    LASTFM::display_tag_artists (ustring const& tag)
    {
      Widgets.TagInfoView->idSet( ID_TAG, tag );
    }

    void
    LASTFM::display_tag_artists_clear ()
    {
      dynamic_cast<Gtk::Entry*>(m_ref_xml->get_widget ( "lastfm-tag-entry" ))->set_text("");
    }

    void
    LASTFM::display_tag_artists_activated (ustring const& uri)
    {
      display_tag_artists_clear ();
      play_uri (uri);
    }

    void
    LASTFM::got_track_tags (TagV const& tags)
    {
      StateData.Tags = tags;

      if( StateData.Tags.size() > 1 )
      {
        Tag const& t = StateData.Tags[0];

        if(Widgets.TagInfoView->isEmpty())
          display_tag_artists( t.name );

        std::random_shuffle( StateData.Tags.begin(), StateData.Tags.end()-1 );
        std::iter_swap( StateData.Tags.begin()+StateData.Tags.size()/2, StateData.Tags.end()-1);
      }

      display_tags_limited ();
      dynamic_cast <Gtk::Notebook *>(m_ref_xml->get_widget ("lastfm-tags-notebook"))->set_current_page (0);
    }

    void
    LASTFM::get_track_tags ()
    {
      dynamic_cast <Gtk::Notebook *>(m_ref_xml->get_widget ("lastfm-tags-notebook"))->set_current_page (1);

      XSPF::Item const& item = (*StateData.Iter);
      StateData.TagsReq = TagsGlobReq::create (TAGS_GLOBAL_TRACK, item.creator, item.title);     
      StateData.TagsReq->tags().connect (sigc::mem_fun (*this, &LASTFM::got_track_tags));
      StateData.TagsReq->run ();
    }

    void
    LASTFM::next_post ()
    {
      play_post ();
    }

    void
    LASTFM::prev_post ()
    {
    }

    void
    LASTFM::restore_context ()
    {
    }

    void
    LASTFM::segment ()
    {
      clear_metadata_info ();
    }

    void
    LASTFM::skipped ()
    {
      XSPF::Item & item = (*StateData.Iter);
      item.rating = "S";
    }

    ////////////////////////////// !PLAYBACKSOURCE

    void
    LASTFM::on_play_http_status (int status)
    {
      switch (status)
      {
        case 200: break;
        case 401:
          {
            Core::Obj()->status_push_message (new_message ("Session expired. Please reconnect."));
            mSignalStopRequest.emit ();
            break;
          }

        case 503:
          {
            Core::Obj()->status_push_message (new_message ("The radio server is too busy at the moment, please try "
                                         "again in a few minutes."));
            mSignalStopRequest.emit ();
            break;
          }

        case 666:
          {
            Core::Obj()->status_push_message (new_message ("Server is down for maintenance, please try again in a few minutes."));
            mSignalStopRequest.emit ();
            break;
          }

        case 667:
          {
            Core::Obj()->status_push_message (new_message ("Not enough content (left) to play this station; please tune "
                                         "in to a different one."));
            mSignalStopRequest.emit ();
            break;
          }
      }
    }

    void
    LASTFM::on_tuned ()
    {
      mSignalMessageClear.emit ();
      mSignalPlayRequest.emit ();
    }
  
    void
    LASTFM::on_tune_error (Glib::ustring const& error)
    {
      Core::Obj()->status_push_message (new_message (error));
      mSignalMessageClear.emit ();
      mSignalStopRequest.emit();
    }

    void
    LASTFM::start_playback ()
    {
      std::string text = Widgets.StationEntry->get_text ();
      Widgets.StationEntry->set_text ("");
      if( !text.empty() )
      {
        int choice = Widgets.StationChoice->get_active_row_number();
        if( (text.length() >= 13) && (text.substr (0, 13) == "lastfm://tag/") )
          {
            text.erase (text.begin(), text.begin() + 13);
            choice = 0;
          }
        else if( (text.length() >= 16) && (text.substr (0, 16) == "lastfm://artist/") )
          {
            text.erase (text.begin(), text.begin() + 16);
            choice = 1;
          }
        else if( (text.length() >= 14) && (text.substr (0, 14) == "lastfm://user/") )
          {
            text.erase (text.begin(), text.begin() + 14);
            choice = 2;
          }
        else if( (text.length() >= 9) && (text.substr (0, 9) == "lastfm://") )
          {
            choice = 3;
          }

        switch( choice ) 
        {
          case 0: 
            StateData.Uri = (f_globaltag % text.c_str()).str();
            break;

          case 1:
            StateData.Uri = (f_artist % text.c_str()).str();
            break;

          case 2:
            StateData.Uri = (f_neighbours % text.c_str()).str();
            break;

          case 3:
            if( text.substr (0, 9) != "lastfm://" )
              text = "lastfm://" + text;
            StateData.Uri = text;
            break;

        }

        LastFM::Radio::Obj()->handshake ();  

        mSignalSegment.emit ();
        mSignalMessage.emit (_("Adjusting Station"));
        LastFM::Radio::Obj()->playurl (StateData.Uri);
      }
      else
      {
        mSignalPlayRequest.emit ();
      }
    }

    void
    LASTFM::play_uri (ustring const& uri)
    {
      mSignalStopRequest.emit ();
      try{
          StateData.Uri = uri;
          mSignalSegment.emit ();
          if(! LastFM::Radio::Obj()->connected() )
          {
            mSignalMessage.emit (_("Last.fm Handshake"));
          }
          LastFM::Radio::Obj()->handshake ();  
          mSignalMessage.emit (_("Adjusting Station"));
          LastFM::Radio::Obj()->playurl (uri);
        }
      catch (LastFMNotConnectedError& cxe)
        {
          Core::Obj()->status_push_message (new_error (cxe));
          mSignalStopRequest.emit();
        }
    }

    void
    LASTFM::on_ban ()
    {
      using namespace XMLRPC;
      try{
          XSPF::Item & item = (*StateData.Iter);
          m_actions->get_action( LASTFM_ACTION_BAN )->set_sensitive( 0 );
          item.rating = "B";
          TrackAction action ("banTrack", item);
          mSignalNextRequest.emit ();
        }
      catch (LastFMNotConnectedError& cxe)
        {
          Core::Obj()->status_push_message (new_error (cxe));
          mSignalStopRequest.emit();
        }
    }

    ////////////////////////////////////////////////////////////////////////

    LASTFM::TagInfoViewT::TagInfoViewT (BaseObjectType                 * obj,
                                              RefPtr<Gnome::Glade::Xml> const& xml)
    : TreeView    (obj)
    , mEndDisplay (false)
    {
      Glib::RefPtr<Gdk::Pixbuf> image = Gdk::Pixbuf::create_from_file (build_filename( BMP_IMAGE_DIR_LASTFM, "artist-unknown.png" ));
      mUnknownArtist = Util::cairo_image_surface_from_pixbuf (image);

      xml->get_widget ("lastfm-artists-notebook", mNotebook);
      xml->get_widget ("lastfm-tag-entry", mEntry);
      xml->get_widget ("lastfm-cbox-tag-match", mCbox);

      signal_event().connect( sigc::mem_fun( *this, &TagInfoViewT::on_event_cb ));

      mCbox->set_active( 0 );
      mCbox->signal_changed().connect( sigc::compose( sigc::mem_fun( *this, &TagInfoViewT::idSetAuto),
                                                      sigc::mem_fun( *mEntry, &Gtk::Entry::get_text )));

      mEntry->signal_changed().connect( sigc::mem_fun( *this, &TagInfoViewT::clear ) );
      mEntry->signal_activate().connect( sigc::compose( sigc::mem_fun( *this, &TagInfoViewT::idSetAuto),
                                                        sigc::mem_fun( *mEntry, &Gtk::Entry::get_text )));
      mStore = ListStore::create (mColumns);

      TreeViewColumn *column = 0; 
      CellRendererCairoSurface *cell1 = 0; 
      CellRendererText *cell2 = 0; 

      column = manage (new TreeViewColumn ());
      column->set_resizable (false);
      column->set_expand (false);

      cell1 = manage (new CellRendererCairoSurface ());
      cell1->property_xpad() = 8;
      cell1->property_ypad() = 4;
      cell1->property_yalign() = 0.;
      column->pack_start (*cell1, false);
      column->set_cell_data_func (*cell1, sigc::bind( sigc::mem_fun( *this, &LASTFM::TagInfoViewT::cellDataFunc ), 0));
       
      cell2 = manage (new CellRendererText ());
      cell2->property_ellipsize() = Pango::ELLIPSIZE_END;
      cell2->property_xpad() = 4;
      cell2->property_yalign() = 0.;
      cell2->property_height() = 58;
      column->pack_start (*cell2, true);
      column->set_cell_data_func (*cell2, sigc::bind( sigc::mem_fun( *this, &LASTFM::TagInfoViewT::cellDataFunc ), 1));

      append_column (*column);

      get_selection()->set_mode (SELECTION_BROWSE);
      get_selection()->signal_changed().connect( sigc::mem_fun( *this, &LASTFM::TagInfoViewT::on_selection_changed ) );
      set_model (mStore);
    }

    bool
    LASTFM::TagInfoViewT::isEmpty ()
    {
        return mEntry->get_text().empty();
    }

    void
    LASTFM::TagInfoViewT::set_ui_manager (RefPtr<Gtk::UIManager> const &ui_manager)
    {
      m_ui_manager = ui_manager;
      m_actions = Gtk::ActionGroup::create ("Actions_UiPartLASTFM-TagArtistsView");
      m_actions->add  (Gtk::Action::create ("lastfm-action-tav-view-in-library",
                                            _("View in Library")),
                                            sigc::mem_fun (*this, &TagInfoViewT::on_view_in_library));
      m_actions->add  (Gtk::Action::create ("lastfm-action-tav-play-lastfm",
                                            Gtk::StockID (BMP_STOCK_LASTFM),
                                            _("Play on Last.fm")),
                                            sigc::mem_fun (*this, &TagInfoViewT::on_play_lastfm));
      m_ui_manager->insert_action_group (m_actions);
      m_ui_manager->add_ui_from_string (ui_string_tav);
    }

    bool
    LASTFM::TagInfoViewT::on_event_cb (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-lastfm-tav/menu-lastfm-tav"));
          if (menu) // better safe than screwed
          {
            menu->popup (event->button, event->time);
          }
        }
      }
      return false;
    }

    void
    LASTFM::TagInfoViewT::on_selection_changed ()
    {
      if( get_selection()->count_selected_rows() )
      {
        TreeIter iter = get_selection()->get_selected();
        bool hasAlbums = ((*iter)[mColumns.hasAlbums]);
        m_actions->get_action("lastfm-action-tav-view-in-library")->set_sensitive( hasAlbums );
      }
    }

    void
    LASTFM::TagInfoViewT::on_view_in_library ()
    {
        TreeIter iter = get_selection()->get_selected();
        LastFMArtist a = ((*iter)[mColumns.artist]);
        Signals.GoToMBID.emit( a.mbid );
    }

    void
    LASTFM::TagInfoViewT::on_play_lastfm ()
    {
        TreeIter iter = get_selection()->get_selected();
        LastFMArtist a = ((*iter)[mColumns.artist]);
        Signals.ArtistActivated.emit((f_artist % a.name.c_str()).str());
    }

    void
    LASTFM::TagInfoViewT::clear ()
    {
      mDisplayLock.lock ();
      mEndDisplay = true;
      mDisplayLock.unlock ();
      mProcessIdler.disconnect ();
      mStore->clear ();
      mEndDisplay = false;
      mNotebook->set_current_page( 0 );
      m_actions->get_action("lastfm-action-tav-view-in-library")->set_sensitive( false );
    }

    bool
    LASTFM::TagInfoViewT::idler ()
    {
      Glib::Mutex::Lock L (mDisplayLock);
      if( mEndDisplay )
        return false;

      if( mIterator->streamable )
      {
        if( !mIterator->thumbnail.empty())
        {
              Glib::RefPtr<Gdk::Pixbuf> image = Util::get_image_from_uri (mIterator->thumbnail);
              if( image )
              {
                    mImage = Util::cairo_image_surface_from_pixbuf (image);
                    Util::cairo_image_surface_border (mImage, 1.);
                    (*mTreeIterator)[mColumns.image] = mImage;
              }
        }

        LastFMArtist a = ((*mTreeIterator)[mColumns.artist]);
        if( !a.mbid.empty() )
        {
              DB::RowV v; 
              Library::Obj()->get (v, (boost::format ("SELECT count(*) AS cnt FROM album JOIN "
                                                      "album_artist ON album.album_artist_j = album_artist.id WHERE "
                                                      "mb_album_artist_id ='%s'") % a.mbid).str(), false);  
              guint64 count = boost::get<guint64>(v[0].find("cnt")->second);
              if( count > 0 )
              {
                    (*mTreeIterator)[mColumns.hasAlbums] = true;
        
                    if( count > 1 )
                      (*mTreeIterator)[mColumns.info] = (boost::format ("<small><b>%llu</b> %s</small>") % count % _("Albums in Library")).str();
                    else
                      (*mTreeIterator)[mColumns.info] = (boost::format ("<small><b>%llu</b> %s</small>") % count % _("Album in Library")).str();
              }
              else
              {
                    (*mTreeIterator)[mColumns.hasAlbums] = false;
              }
        }
        ++mTreeIterator;
      }

      return (++mIterator != mArtists.end());
    }

    void  
    LASTFM::TagInfoViewT::data_cb (char const * data, guint size, guint code)
    {
      if( code == 200 )
      {
        std::string chunk;
        chunk.append( data, size );

        mArtists = LastFMArtistV();

        try{
            LastFM::WS::ArtistParser p (mArtists);
            Markup::ParseContext context (p);
            context.parse (chunk);
            context.end_parse ();
          }
        catch (Glib::MarkupError & cxe)
          {
            debug("lastfm","%s: Markup Error: '%s'", G_STRFUNC, cxe.what().c_str());
            return;
          }
        catch (Glib::ConvertError & cxe)
          {
            debug("lastfm","%s: Conversion Error: '%s'", G_STRFUNC, cxe.what().c_str());
            return;
          }

        if( mArtists.empty () )
        {
          debug("lastfm","%s: No Artists Parsed", G_STRFUNC); 
          mNotebook->set_current_page( 0 );
          return;
        }

        mIterator = mArtists.begin();
        mTopRank = mIterator->count;

        for(LastFMArtistV::const_iterator i = mArtists.begin(); i != mArtists.end(); ++i)
        {
          if( i->streamable )
          { 
            TreeIter iter = mStore->append ();
            (*iter)[mColumns.artist] = *i;
            (*iter)[mColumns.image] = mUnknownArtist;
            (*iter)[mColumns.hasAlbums] = false;
          }
        }

        mNotebook->set_current_page( 0 );
        mTreeIterator = mStore->children().begin();
        mProcessIdler = signal_idle().connect( sigc::mem_fun( *this, &TagInfoViewT::idler ) );
      }
      else
        debug("lastfm","%s: HTTP Response Code is not 200 OK", G_STRFUNC); 
    }

    void
    LASTFM::TagInfoViewT::idSetAuto (ustring const& id)
    {
      idSet( ID_AUTO, id );
    }

    void
    LASTFM::TagInfoViewT::idSet (IdType type, ustring const& id)
    {
      if( mEntry->get_text() != id )
        mEntry->set_text( id ); 
      else
        clear ();

      if( mEntry->get_text().empty() ) 
        return;

      mNotebook->set_current_page( 1 );
      std::string uri; 

      int row = mCbox->get_active_row_number();
      if( (row != int( type )) && ( type != ID_AUTO ))
      {
        mCbox->set_active( int( type ) );
      }

      switch( row ) 
      {
        case 0:
          uri = (boost::format ("http://ws.audioscrobbler.com/1.0/tag/%s/topartists.xml") % URI::escape_string(id).c_str()).str();
          break;
  
        case 1:
          uri = (boost::format ("http://ws.audioscrobbler.com/1.0/artist/%s/similar.xml") % URI::escape_string(id).c_str()).str();
          break;
      } 

      mArtistReq = Soup::Request::create (uri);
      mArtistReq->request_callback().connect (sigc::mem_fun (*this, &LASTFM::TagInfoViewT::data_cb));
      mArtistReq->run ();
    }

    void
    LASTFM::TagInfoViewT::cellDataFunc (CellRenderer * baseCell, TreeModel::iterator const& i, int column)
    {
      CellRendererCairoSurface * pix = 0; 
      CellRendererText * text = 0; 

      switch( column )
      {
        case 0:
          pix = dynamic_cast<CellRendererCairoSurface*>(baseCell);
          pix->property_surface() = (*i)[mColumns.image];
          return;
      
        case 1:
          text = dynamic_cast<CellRendererText*>(baseCell);
          LastFMArtist a = ((*i)[mColumns.artist]);
          ustring info = ((*i)[mColumns.info]);
          text->property_markup() = (boost::format ("<b>%s</b>\n%.2f %%\n%s")
                                        % Markup::escape_text (a.name).c_str()
                                        % (100. * (double (a.count) / double (mTopRank))) 
                                        % info).str(); // info goes in raw, is escaped above
          return;
      }
    }

    ////////////////////////////////////////////////////////////////////////

    LASTFM::ProfileViewT::ProfileViewT (BaseObjectType                 * obj,
                                        RefPtr<Gnome::Glade::Xml> const& xml)
    : TreeView (obj)
    {
      m_store = TreeStore::create (columns);
      xml->get_widget ("lastfm-stations-notebook", m_notebook);

      // md FIXME This is all somewhat strange here because we just assume the sizes of the images are as we need them,
      //          and while they currently in fact are it's not guaranteed for all times ever

      m_pb_artist = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_LASTFM , "lastfm-artist.png"));
      m_pb_rec = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_LASTFM , "lastfm-recommended-radio.png"));
      m_pb_url = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_LASTFM , "lastfm-station-small.png"));
      m_pb_neighbour = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_LASTFM , "lastfm-neighbour.png"));
      m_pb_friend = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_LASTFM , "lastfm-friend.png"));
      m_pb_user = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_LASTFM , "lastfm-user.png"));
      m_pb_mainuser = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_LASTFM , "lastfm-mainuser.png"));
      m_pb_tag = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_LASTFM , "lastfm-tag.png"));

      TreeViewColumn *column = 0; 
      CellRendererPixbuf *cell1 = 0; 
      CellRendererText *cell2 = 0; 

      column = manage (new TreeViewColumn ());
      column->set_resizable (false);
      column->set_expand (false);

      cell1 = manage (new CellRendererPixbuf ());
      column->pack_start (*cell1, false);
      column->set_cell_data_func (*cell1, sigc::mem_fun (*this, &LASTFM::ProfileViewT::cell_data_func));
       
      cell2 = manage (new CellRendererText ());
      column->pack_start (*cell2, false);
      column->add_attribute (*cell2, "markup", columns.name);

      append_column (*column);

      column = manage (new TreeViewColumn ());
      cell2 = manage (new CellRendererText ());
      column->pack_start (*cell2, false);
      column->add_attribute (*cell2, "markup", columns.score);
      append_column (*column);

      get_selection()->set_mode (SELECTION_SINGLE);
      set_enable_tree_lines ();
      set_model (m_store);
    }

    LASTFM::ProfileViewT::~ProfileViewT () {}

    void
    LASTFM::ProfileViewT::user_clear ()
    {
      m_request[R_USERTAGS].clear();
      m_request[R_FRIENDS].clear();
      m_request[R_NEIGHBOURS].clear();
      m_store->clear ();
      set_sensitive (false);
      while (gtk_events_pending()) gtk_main_iteration ();
    }

    void
    LASTFM::ProfileViewT::request_cb (char const* data, guint size, guint status_code, RootIter ri)
    {
      if( status_code != 200) 
        return;

      std::string chunk;
      chunk.append(data, size);

      bool subscriber = LastFM::Radio::Obj()->session().subscriber;

      TreeIter iter;

      if(ri == R_USERTAGS)
      {
          TagV v;
          LastFM::WS::TagParser p (v);
          Markup::ParseContext context (p);
          context.parse (chunk);
          context.end_parse ();
          for (TagV::const_iterator i = v.begin(); i != v.end(); ++i)
          {
                Tag const& x (*i);
                iter = m_store->append (m_iroot[R_USERTAGS]->children());
                (*iter)[columns.name] = Markup::escape_text (x.name);
                (*iter)[columns.type] = ProfileViewT::ROW_TAG;
                (*iter)[columns.station] = (f_globaltag % x.name.c_str()).str();

                while (gtk_events_pending()) gtk_main_iteration ();
          }
      }
      else if(ri == R_FRIENDS)
      {
          UserV v;
          LastFM::WS::UserParser p (v);
          Markup::ParseContext context (p);
          context.parse (chunk);
          context.end_parse ();
          for (UserV::const_iterator i = v.begin(); i != v.end(); ++i)
          {
                User const& x (*i);
                iter = m_store->append (m_iroot[R_FRIENDS]->children());
                (*iter)[columns.name] = Markup::escape_text (x.username);
                (*iter)[columns.type] = ProfileViewT::ROW_FRIEND;

                TreeModel::iterator n, r, p, l;
                n = m_store->append (iter->children());
                r = m_store->append (iter->children());

                (*n)[columns.name] = Markup::escape_text (t_neighbour);
                (*n)[columns.station] = (f_neighbours % x.username.c_str()).str();
                (*n)[columns.type] = ProfileViewT::ROW_URL;

                (*r)[columns.name] = Markup::escape_text (t_recommend);
                (*r)[columns.station] = (f_recommend % x.username.c_str() % 100).str();
                (*r)[columns.type] = ProfileViewT::ROW_URL;

                if( subscriber )
                {
                      p = m_store->append (iter->children());
                      l = m_store->append (iter->children());

                      (*p)[columns.name] = Markup::escape_text (t_personal);
                      (*p)[columns.station] = (f_personal % x.username.c_str()).str();
                      (*p)[columns.type] = ProfileViewT::ROW_URL;

                      (*l)[columns.name] = Markup::escape_text (t_loved);
                      (*l)[columns.station] = (f_loved % x.username.c_str()).str();
                      (*l)[columns.type] = ProfileViewT::ROW_URL;
                }
                while (gtk_events_pending()) gtk_main_iteration ();
          }
        }
        else if(ri ==  R_NEIGHBOURS)
        {
          UserV v;
          std::string chunk;
          chunk.append(data, size);
          LastFM::WS::UserParser p (v);
          Markup::ParseContext context (p);
          context.parse (chunk);
          context.end_parse ();
          for (UserV::const_iterator i = v.begin(); i != v.end(); ++i)
          {
                User const& x (*i);
                iter = m_store->append (m_iroot[R_NEIGHBOURS]->children());
                (*iter)[columns.name] = Markup::escape_text (x.username);
                (*iter)[columns.type] = ProfileViewT::ROW_NEIGHBOUR;
                (*iter)[columns.score] = ((boost::format ("<small>%.2f</small>") % x.match).str()); 

                TreeModel::iterator n, p, l, r;
                n = m_store->append (iter->children());
                r = m_store->append (iter->children());

                (*n)[columns.name] = Markup::escape_text (t_neighbour);
                (*n)[columns.station] = (f_neighbours % x.username.c_str()).str();
                (*n)[columns.type] = ProfileViewT::ROW_URL;

                (*r)[columns.name] = Markup::escape_text (t_recommend);
                (*r)[columns.station] = (f_recommend % x.username.c_str() % 100).str();
                (*r)[columns.type]  = ProfileViewT::ROW_URL;

                if( subscriber )
                {
                      p = m_store->append (iter->children());
                      l = m_store->append (iter->children());

                      (*p)[columns.name] = Markup::escape_text (t_personal);
                      (*p)[columns.station] = (f_personal % x.username.c_str()).str();
                      (*p)[columns.type] = ProfileViewT::ROW_URL;

                      (*l)[columns.name] = Markup::escape_text (t_loved);
                      (*l)[columns.station] = (f_loved % x.username.c_str()).str();
                      (*l)[columns.type] = ProfileViewT::ROW_URL;
                }
                while (gtk_events_pending()) gtk_main_iteration ();
          }
        }
    }

    void
    LASTFM::ProfileViewT::user_set (ustring const& username, bool subscriber, bool match_tags)
    {
      user_clear ();  

      m_iroot[R_STATIONS] = m_store->append ();
      (*m_iroot[R_STATIONS])[columns.name] = (boost::format (_("%s's stations")) % username.c_str()).str();
      (*m_iroot[R_STATIONS])[columns.type] = ProfileViewT::ROW_MAINUSER;

      m_iroot[R_NEIGHBOURS] = m_store->append ();
      (*m_iroot[R_NEIGHBOURS])[columns.name] = (boost::format (_("%s's neighbours")) % username.c_str()).str();
      (*m_iroot[R_NEIGHBOURS])[columns.type] = ProfileViewT::ROW_NEIGHBOUR_ROOT;

      m_iroot[R_FRIENDS] = m_store->append ();
      (*m_iroot[R_FRIENDS])[columns.name] = (boost::format (_("%s's friends")) % username.c_str()).str();
      (*m_iroot[R_FRIENDS])[columns.type] = ProfileViewT::ROW_FRIEND_ROOT;

      m_iroot[R_USERTAGS] = m_store->append ();
      (*m_iroot[R_USERTAGS])[columns.name] = (boost::format (_("%s's personal tags")) % username.c_str()).str();
      (*m_iroot[R_USERTAGS])[columns.type] = ProfileViewT::ROW_TAG_ROOT;

      TreeIter iter = m_store->append (m_iroot[R_STATIONS]->children());
      (*iter)[columns.name] = Markup::escape_text (t_neighbour);
      (*iter)[columns.station]  = (f_neighbours % username.c_str()).str();
      (*iter)[columns.type]  = ProfileViewT::ROW_URL;

      iter = m_store->append (m_iroot[R_STATIONS]->children());
      (*iter)[columns.name] = Markup::escape_text (t_recommend);
      (*iter)[columns.station]  = (f_recommend % username.c_str() % 100).str();
      (*iter)[columns.type]  = ProfileViewT::ROW_URL;

      if( subscriber )
      {
            iter = m_store->append (m_iroot[R_STATIONS]->children());
            (*iter)[columns.name] = Markup::escape_text (t_personal);
            (*iter)[columns.station]  = (f_personal % username.c_str()).str();
            (*iter)[columns.type]  = ProfileViewT::ROW_URL;

            iter = m_store->append (m_iroot[R_STATIONS]->children());
            (*iter)[columns.name] = Markup::escape_text (t_loved);
            (*iter)[columns.station]  = (f_loved % username.c_str()).str();
            (*iter)[columns.type]  = ProfileViewT::ROW_URL;
      }

      Bmp::URI u;

      // Append Neighbours
      u = Bmp::URI ((boost::format ("http://ws.audioscrobbler.com/1.0/user/%s/neighbours.xml") % username).str(), true);
      m_request[R_NEIGHBOURS] = Soup::Request::create(ustring(u));
      m_request[R_NEIGHBOURS]->request_callback().connect(sigc::bind( sigc::mem_fun (*this, &LASTFM::ProfileViewT::request_cb), R_NEIGHBOURS ));
      m_request[R_NEIGHBOURS]->run();

      // Append Friends 
      u = Bmp::URI ((boost::format ("http://ws.audioscrobbler.com/1.0/user/%s/friends.xml") % username).str(), true);
      m_request[R_FRIENDS] = Soup::Request::create(ustring(u));
      m_request[R_FRIENDS]->request_callback().connect(sigc::bind( sigc::mem_fun (*this, &LASTFM::ProfileViewT::request_cb), R_FRIENDS ));
      m_request[R_FRIENDS]->run();

      // Append User's Tags 
      u = Bmp::URI ((boost::format ("http://ws.audioscrobbler.com/1.0/user/%s/tags.xml") % username).str(), true);
      m_request[R_USERTAGS] = Soup::Request::create(ustring(u));
      m_request[R_USERTAGS]->request_callback().connect(sigc::bind( sigc::mem_fun (*this, &LASTFM::ProfileViewT::request_cb), R_USERTAGS ));
      m_request[R_USERTAGS]->run();

      m_match_tags.clear ();
      set_sensitive (true);
      if( match_tags )
      {
            m_notebook->set_current_page (1);
            while (gtk_events_pending()) gtk_main_iteration();

            m_iroot[R_MATCHTAGS] = m_store->append ();
            (*m_iroot[R_MATCHTAGS])[columns.name] = _("Matching Tags");
            (*m_iroot[R_MATCHTAGS])[columns.type] = ProfileViewT::ROW_TAG_ROOT;

            disp.connect (sigc::mem_fun (*this, &UiPart::LASTFM::ProfileViewT::user_set_match_tags));
            Glib::Thread::create (sigc::bind (sigc::mem_fun (*this, &UiPart::LASTFM::ProfileViewT::user_get_match_tags_tags), username), false);
      }
    }

    void
    LASTFM::ProfileViewT::user_get_match_tags_tags (std::string const& username)
    {
      WS::matchtags (username, m_match_tags);
      disp.emit();
    }

    void
    LASTFM::ProfileViewT::user_set_match_tags ()
    {
      m_notebook->set_current_page (0);
      while (gtk_events_pending()) gtk_main_iteration();

      TreeIter iter;
      std::sort (m_match_tags.begin(), m_match_tags.end());

      for (RankedTagV::const_iterator i = m_match_tags.begin(); i != m_match_tags.end(); ++i)
      {
            Tag const& tag (i->first);

            iter = m_store->append (m_iroot[R_MATCHTAGS]->children());

            (*iter)[columns.name]     = Markup::escape_text (tag.name); 
            (*iter)[columns.score]    = ((boost::format ("<small><b>%s: %.2f</b></small>")  % _("Score") % (double (i->second) / 100.)).str());
            (*iter)[columns.station]  = (f_globaltag % tag.name).str();
            (*iter)[columns.type]     = ProfileViewT::ROW_TAG;

            if( i != m_match_tags.begin() )
            {
                  expand_row (m_store->get_path (m_iroot[R_MATCHTAGS]), false);
            }

            while (gtk_events_pending()) gtk_main_iteration ();
      }
    }

    void
    LASTFM::ProfileViewT::cell_data_func (CellRenderer * _cell, TreeModel::iterator const& iter)
    {
      CellRendererPixbuf * cell = dynamic_cast<CellRendererPixbuf *>(_cell);
      switch ((*iter)[columns.type])
      {
            case ROW_MAINUSER:
                cell->property_pixbuf() = m_pb_mainuser;
                return;

            case ROW_SYSREC_ROOT:
                cell->property_pixbuf() = m_pb_rec;
                return;

            case ROW_ARTIST:
                cell->property_pixbuf() = m_pb_artist;
                return;

            case ROW_URL:
                cell->property_pixbuf() = m_pb_url;
                return;

            case ROW_TAG_ROOT:
                cell->property_pixbuf() = m_pb_tag;
                return;
            case ROW_TAG:
                cell->property_pixbuf() = m_pb_url;
                return;

            case ROW_NEIGHBOUR_ROOT:
                cell->property_pixbuf() = m_pb_neighbour;
                return;
            case ROW_NEIGHBOUR:
                cell->property_pixbuf() = m_pb_user;
                return;

            case ROW_FRIEND_ROOT:
                cell->property_pixbuf() = m_pb_friend;
                return;
            case ROW_FRIEND:
                cell->property_pixbuf() = m_pb_user;
                return;

          default: return;
      }
    }

    void
    LASTFM::ProfileViewT::on_row_activated (TreeModel::Path const& path, TreeViewColumn * column)
    {
      RowType type ((m_store->get_iter(path).operator*().operator[](columns.type)));
      if( (type == ROW_URL) || (type == ROW_TAG) || (type == ROW_ARTIST) )
      {
        station_activated_.emit (m_store->get_iter (path).operator*().operator[](columns.station));
        get_selection()->unselect_all ();
      }
    }
  } // end namespace UiPart
} // end namespace Bmp
