//  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 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 <glibmm/i18n.h>
#include <glib/gstdio.h>
#include <gtkmm.h>
#include <gdk/gdkkeysyms.h>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <sstream>
#include <fstream>

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

#include <mcs/mcs.h>

// BMP Musicbrainz
#include "musicbrainz/mbxml-v2.hh"
#include "mb-tagger.hh"

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

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

// BMP Misc
#include "dialog-export.hh"
#include "dialog-progress.hh"
#include "dialog-simple-progress.hh"
#include "dialog-track-details.hh"

#include "amazon.hh"
#include "debug.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-file.hh"

#include "audio/play.hh"

#ifdef HAVE_HAL
#  include "x_hal.hh"
#endif //HAVE_HAL

#include "x_mcsbind.hh"
#include "x_vfs.hh"

#include "ui-part-library.hh"

using namespace std;
using namespace boost;
using boost::algorithm::trim;

using namespace Gtk;
using namespace Gdk;
using namespace Glib;

using namespace Bmp::DB;
using namespace Bmp::Util;
using namespace Bmp::VFS;
using namespace Bmp::Audio;
using namespace Bmp::MusicBrainzXml;

#define LIBRARY_ACTION_PLAYLIST_TRACK_DETAILS         "library-action-track-details"
#define LIBRARY_ACTION_PLAYLIST_FILES_RETAG           "library-action-retag-tracks"
#define LIBRARY_ACTION_PLAYLIST_FILES_EXPORT          "library-action-playlist-export"
#define LIBRARY_ACTION_PLAYLIST_FILES_DELETE          "library-action-delete-files"
#define LIBRARY_ACTION_PLAYLIST_FILES_ENQUEUE         "library-action-enqueue-files"
#define LIBRARY_ACTION_PLAYLIST_HISTORY_CLEAR         "library-action-history-clear"

#define LIBRARY_ACTION_ALBUM_ENQUEUE                  "library-action-enqueue-album"

namespace
{
  const char * library_menu_playlist =
  "<ui>"
  ""
  "<menubar name='popup-library-playlist'>"
  ""
  "   <menu action='dummy' name='menu-library-playlist'>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_FILES_ENQUEUE "'/>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_FILES_RETAG "'/>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_FILES_EXPORT "'/>"
  "       <separator name='library-0'/>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_HISTORY_CLEAR "'/>"
  "       <separator name='library-1'/>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_TRACK_DETAILS "'/>"
  "       <separator name='library-2'/>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_FILES_DELETE "'/>"
  "   </menu>"
  ""
  "</menubar>"
  ""
  "</ui>";

  const char * library_menu_playlist_offline =
  "<ui>"
  ""
  "<menubar name='popup-library-playlist'>"
  ""
  "   <menu action='dummy' name='menu-library-playlist'>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_TRACK_DETAILS "'/>"
  "       <separator name='library-0'/>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_FILES_ENQUEUE "'/>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_FILES_EXPORT "'/>"
  "       <separator name='library-1'/>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_HISTORY_CLEAR "'/>"
  "       <separator name='library-2'/>"
  "     <menuitem action='" LIBRARY_ACTION_PLAYLIST_FILES_DELETE "'/>"
  "   </menu>"
  ""
  "</menubar>"
  ""
  "</ui>";

  const char * library_menu_albums =
  "<ui>"
  ""
  "<menubar name='popup-library-albums'>"
  ""
  "   <menu action='dummy' name='menu-library-albums'>"
  "     <menuitem action='" LIBRARY_ACTION_ALBUM_ENQUEUE "'/>"
  "   </menu>"
  ""
  "</menubar>"
  ""
  "</ui>";


  using namespace Bmp;
  template <class T>
  class TrackApply
  {
    public:

      TrackApply (const T&column, const Track &track) : m_track (track), m_column (column) {} 

      void
      operator()(const TreeIter&i)
      {
        (*i)[m_column] = m_track;
      }

    private:
    
      const Track &m_track;
      const T&m_column;
  };  

  template <class T>
  class PathCollect
  {
    public:

      PathCollect ( Glib::RefPtr<Gtk::TreeModel> const &model,
                    const T&column,
                    TrackV &tracks)
      : m_model   (model)
      , m_tracks  (tracks)
      , m_column  (column)
      {} 

      void
      operator()(const Gtk::TreePath&p)
      {
        m_tracks.push_back ((*m_model->get_iter (p))[m_column]);
      }

    private:
        
      Glib::RefPtr<Gtk::TreeModel> const &m_model; 
      TrackV &m_tracks;
      const T&m_column;
  };  

  class ReferenceCollect
  {
    public:

      ReferenceCollect (const Glib::RefPtr<Gtk::TreeModel> &model,
                        Bmp::ReferenceV &references)
      : m_model       (model)
      , m_references  (references)
      {} 

      void
      operator()(const Gtk::TreePath&p)
      {
        m_references.push_back (TreeRowReference (m_model, p));
      }

    private:
        
      Glib::RefPtr<Gtk::TreeModel> const &m_model; 
      Bmp::ReferenceV &m_references;
  };  

  template <class T>
  class UriCollect
  {
    public:

      UriCollect ( Glib::RefPtr<Gtk::TreeModel> const &model,
                    const T&column,
                    VUri &uris)
      : m_model   (model)
      , m_uris    (uris)
      , m_column  (column)
      {} 

      void
      operator()(const Gtk::TreePath&p)
      {
        m_uris.push_back( Track ((*m_model->get_iter (p))[m_column]).location.get() );
      }

    private:
        
      const Glib::RefPtr<Gtk::TreeModel> &m_model; 
      VUri &m_uris;
      const T &m_column;
  };  

  template <class T>
  inline T
  clamp (T min, T max, T val)
  {
    if( (val >= min) &&(val <= max) )
    {
      return val;
    }
    else if( val < min )
    {
      return min;
    }
    else
    {
      return max;
    }
  }


  void
  menu_item_set_markup (RefPtr<UIManager> uimanager,
                        ustring const & menupath,
                        ustring const & markup)
  {
    Bin * bin = 0;
    bin = dynamic_cast <Bin *> (uimanager->get_widget (menupath));

    if( bin )
      reinterpret_cast <Gtk::Label *> (bin->get_child())->set_markup (markup);
    else
      g_log (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Widget with path '%s' not found or not a Gtk::Bin", menupath.c_str());
  }


  std::string
  get_date_string_markup (std::string const &in)
  {
    int y = 0, m = 0, d = 0;

    const char * z (in.c_str());

    if( strlen (z) == 4 )
      sscanf (z, "%04d", &y);
    else
    if( strlen (z) == 8 )
      sscanf (z, "%04d%02d%02d", &y, &m, &d);
    else
    if( (strlen (z) == 10) ||  (strlen (z) == 19) )
      sscanf (z, "%04d-%02d-%02d", &y, &m, &d);



    struct tm * _tm = g_new0 (struct tm, 1);

    if (y) _tm->tm_year = (y - 1900);
    if (m) _tm->tm_mon  = m - 1;
    if (d) _tm->tm_mday = d;

    if( y )
    {
      char ys[256];
      strftime (ys, 255, "%Y", _tm);
      g_free (_tm);
      return std::string (ys); 
    }

    return std::string();
  }

  enum Renderer
  {
    R_TEXT,
    R_PIXBUF,
    R_TOGGLE,
    R_SURFACE
  };
}

namespace Bmp
{
  Artist_V::Artist_V (BaseObjectType                 *  obj,
                      RefPtr<Gnome::Glade::Xml> const & xml)
  : TreeView  (obj)
  {
    set_enable_tree_lines ();

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

    {
      Gtk::CellRendererText * cell = manage (new CellRendererText());
      cell->property_xpad() = 0;
      cell->property_ellipsize () = Pango::ELLIPSIZE_END;
      column->pack_start (*cell, true);
      column->set_cell_data_func (*cell, (sigc::bind (sigc::mem_fun (*this, &Bmp::Artist_V::cell_data_func), 0)));
    }

    append_column (*column);

    mStore = TreeStore::create (mStoreArtistCR);
    mStore->set_default_sort_func (sigc::mem_fun (*this, &Bmp::Artist_V::default_sort_func));
    mStore->set_sort_column (Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);

    get_selection()->set_mode (SELECTION_SINGLE);
    mConnChanged = get_selection()->signal_changed().connect (sigc::mem_fun (*this, &Bmp::Artist_V::on_selection_changed));
    set_search_equal_func( sigc::mem_fun( *this, &Artist_V::searchEqualFunc ) );
  }

  bool
  Artist_V::searchEqualFunc (const Glib::RefPtr<TreeModel> &model G_GNUC_UNUSED,
                            int G_GNUC_UNUSED, const Glib::ustring &text, const TreeModel::iterator& iter)
  {
    const ustring &val = (*iter)[mStoreArtistCR.value];
    return ! Util::match_keys (val, text); // http://library.gnome.org/devel/gtk/2.10/GtkTreeView.html#GtkTreeViewSearchEqualFunc
                                                     // "Note the return value is reversed from what you would normally expect, 
                                                     //  though it has some similarity to strcmp() returning 0 for equal strings."
  }

  void
  Artist_V::on_row_activated (Gtk::TreeModel::Path const &, Gtk::TreeViewColumn*)
  {
    Signals.Activated.emit();
  }

  void
  Artist_V::cell_data_func (CellRenderer * basecell, TreeModel::iterator const &iter, int cell)
  {
    CellRendererText * cell_t = dynamic_cast <CellRendererText*>(basecell);

    if( iter == mStore->children().begin() )
    {
      if( mUidIterMap.size() )
      {
        guint64 n_entries = guint64 (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (mStore->gobj()), NULL)-1);
        cell_t->property_markup() = ((boost::format (_("<b>%llu %s</b>"))
                                      % n_entries 
                                      % ((n_entries != 1) ? _("Artists") : _("Artist"))
                                     ).str());
      }
      else
      {
        cell_t->property_markup() = _("<b>No Listable Artists</b>"); 
      }
      return;
    }

    NodeType node = (*iter)[mStoreArtistCR.type];
    switch (node)
    {
      case NODE_BRANCH:
      {
        cell_t->property_text() = (*iter)[mStoreArtistCR.value];
        break;
      }

      case NODE_LEAF:
      {
        AlbumArtist aa ((*iter)[mStoreArtistCR.artist]);
        cell_t->property_text() = ustring (aa);
        break;
      }
    }
  }

  int
  Artist_V::default_sort_func (Gtk::TreeModel::iterator const &iter_a,
                                  Gtk::TreeModel::iterator const &iter_b)
  {
    // NOTE: for some reason, using brackets doesn't work:
    //   ustring str_a (Bmp::AlbumArtist ((*iter_a)[mStoreArtistCR.artist]));
    //   ustring str_b (Bmp::AlbumArtist ((*iter_b)[mStoreArtistCR.artist]));
    // gcc bug? or do the statements have some ambiguity I don't know about?
    // - Des

    //ustring str_a = Bmp::AlbumArtist ((*iter_a)[mStoreArtistCR.artist]);
    //ustring str_b = Bmp::AlbumArtist ((*iter_b)[mStoreArtistCR.artist]);
    
    if( iter_a ==  mStore->children().begin() )
    {
      return -1; // anything else (iter_b) is always "smaller"
    }

    if( iter_b ==  mStore->children().begin() )
    {
      return 1; // anything else (iter_a) is always "larger"
    }
    
    ustring str_a = (*iter_a)[mStoreArtistCR.value];
    ustring str_b = (*iter_b)[mStoreArtistCR.value];
    return str_a.lowercase().compare (str_b.lowercase());
  }
  
  void
  Artist_V::go_to_mbid (std::string const& mbid)
  {
    IndexMapT::iterator index_i = mMBIDIterMap.find (mbid);
    if( index_i != mMBIDIterMap.end() )
    {
      TreeIter iter = index_i->second;
      expand_to_path (mStore->get_path (iter));
      scroll_to_row (mStore->get_path (iter), 0.5);
      get_selection()->unselect_all ();
      get_selection()->select (iter);
    }
  }

  void
  Artist_V::clear ()
  {
    unset_model ();

    mStore = TreeStore::create (mStoreArtistCR);
    mStore->set_default_sort_func (sigc::mem_fun (*this, &Bmp::Artist_V::default_sort_func));
    mStore->set_sort_column (Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);

    mUidIterMap.clear ();
    mIndexMap.clear();
    mMBIDIterMap.clear();

    TreeIter i = mStore->append ();
    (*i)[mStoreArtistCR.type] = NODE_LEAF; // not really, but we handle all artist this way

    set_model (mStore);
    on_selection_changed ();
  }

  void 
  Artist_V::put_artist (Bmp::AlbumArtist const &artist)
  {
    std::string trimmed = ustring (artist);
    boost::algorithm::trim (trimmed);

    if( trimmed.empty() )
    {
          return;
    }
    
    ustring         a = Util::utf8_string_normalize (trimmed); 
    ustring         x = a.casefold (); 
    IndexMapIter    n = mIndexMap.find (x);
    TreeIter        i;

    if( n != mIndexMap.end() )
    {
      TreeIter const& i_toplevel = n->second;

      NodeType node = (*i_toplevel)[mStoreArtistCR.type];

      (*i_toplevel)[mStoreArtistCR.type] = NODE_BRANCH; 
      (*i_toplevel)[mStoreArtistCR.value] = a; 

      if( node != NODE_BRANCH )
      {
        // Clone this node as one child row
        UID uid (Bmp::AlbumArtist ((*i_toplevel)[mStoreArtistCR.artist]).bmpx_album_artist_id);
        Bmp::AlbumArtist aa ((*i_toplevel)[mStoreArtistCR.artist]);

        UidIterMapIter uid_i = mUidIterMap.find (uid);
        if( uid_i != mUidIterMap.end() )
        {
          mUidIterMap.erase (uid_i);
        }

        i = mStore->append (i_toplevel->children());
        (*i)[mStoreArtistCR.value] = ustring ((*i_toplevel)[mStoreArtistCR.value]);
        (*i)[mStoreArtistCR.artist] = Bmp::AlbumArtist ((*i_toplevel)[mStoreArtistCR.artist]);
        (*i)[mStoreArtistCR.uid] = UID ((*i_toplevel)[mStoreArtistCR.uid]);
        (*i)[mStoreArtistCR.type] = NODE_LEAF; 
        mUidIterMap.insert (std::make_pair (uid, i));

        if( aa.mb_album_artist_id )
        {
              IndexMapT::iterator index_i = mMBIDIterMap.find (aa.mb_album_artist_id.get());
              if( index_i != mMBIDIterMap.end() )
              {
                mMBIDIterMap.erase (index_i);
              }

              mMBIDIterMap.insert (std::make_pair (aa.mb_album_artist_id.get(), i));
        } 
      }

      i = mStore->append (i_toplevel->children());
    }
    else
    {
      if( mUidIterMap.find (artist.bmpx_album_artist_id) == mUidIterMap.end() )
      {
        i = mStore->append ();
        mIndexMap.insert (IndexMapPair (x, i)); 
      }
      else
      {
        return;
      } 
    }

    (*i)[mStoreArtistCR.value] = trimmed; 
    (*i)[mStoreArtistCR.artist] = artist;
    (*i)[mStoreArtistCR.uid] = artist.bmpx_album_artist_id;
    (*i)[mStoreArtistCR.type] = NODE_LEAF; 
    mUidIterMap.insert (std::make_pair (artist.bmpx_album_artist_id, i));
    if( artist.mb_album_artist_id )
    {
          mMBIDIterMap.insert (std::make_pair (artist.mb_album_artist_id.get(), i));
    }
  }

  void
  Artist_V::display ()
  {
    mConnChanged.block (true);
    unset_model ();

    mStore = TreeStore::create (mStoreArtistCR);
    mStore->set_default_sort_func (sigc::mem_fun (*this, &Bmp::Artist_V::default_sort_func));
    mStore->set_sort_column (Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);

    mUidIterMap.clear();
    mIndexMap.clear();
    mMBIDIterMap.clear();

    TreeIter i = mStore->append ();
    (*i)[mStoreArtistCR.type] = NODE_LEAF; 

    RowV r = ::Bmp::Library::Obj()->get_artists();
    for (RowV::const_iterator i = r.begin(); i != r.end(); ++i)
    {
      put_artist (*i);
    }

    set_model (mStore);
    columns_autosize ();
    mConnChanged.block (false);
    get_selection()->select (mStore->children().begin());
  }

  void
  Artist_V::on_selection_changed ()
  {
    if( get_selection()->count_selected_rows() == 0 )
    {
      Signals.Cleared.emit();
    }
    else
    if( get_selection()->get_selected() == mStore->children().begin() )
    {
      guint64 n_entries = guint64 (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (mStore->gobj()), NULL)-1);
      Signals.AllSelected.emit( n_entries );
    }
    else
    {
      UidSet        s;
      TreeIter      i = get_selection()->get_selected();
      NodeType      type = (*i)[mStoreArtistCR.type];

      switch (type)
      {
        case NODE_LEAF:
        {
          AlbumArtist aa ((*i)[mStoreArtistCR.artist]);
          s.insert (aa.bmpx_album_artist_id);
          break;
        }

        case NODE_BRANCH:
        {
          TreeModel::Row row = *i;
          for (TreeNodeChildren::const_iterator n = row.children().begin(); n != row.children().end(); ++n)
          {
            s.insert (UID ((*n)[mStoreArtistCR.uid]));
          }
          break;
        }
      }

      Signals.ArtistSelected.emit( s, (*i)[mStoreArtistCR.value] );
    }
  }
}

namespace Bmp
{
    UidList
    Album_V::get_albums ()
    {
      return mSelectedUids;
    }

    Album_V::Album_V (BaseObjectType                 *  obj,
                      RefPtr<Gnome::Glade::Xml> const & xml)
    : TreeView            (obj)
    , m_ref_xml           (xml)
    {
      mCover = Util::cairo_image_surface_from_pixbuf (Pixbuf::create_from_file (build_filename
                    (BMP_IMAGE_DIR,BMP_COVER_IMAGE_DEFAULT))->scale_simple (52, 52, Gdk::INTERP_BILINEAR));

      TreeViewColumn * column = 0; 
      {
        column = manage (new TreeViewColumn ());
        CellRendererCairoSurface * cell = manage (new CellRendererCairoSurface());
        cell->property_xalign() = 1.0; 
        cell->property_yalign() = 0.0;
        cell->property_xpad() = 0;
        cell->property_ypad() = 2;
        cell->set_fixed_size (60, 56);

        column->pack_start (*cell, false);
        column->set_resizable (false);
        column->set_expand (false);
        column->set_sizing (TREE_VIEW_COLUMN_FIXED);
        column->set_fixed_width (60);
        column->add_attribute (*cell, "surface", mStoreAlbumCR.cover);
        append_column (*column);
      }
      {
        column = manage (new TreeViewColumn (_("Title")));
        CellRendererText * cell = manage (new CellRendererText());
        cell = manage (new CellRendererText());
        cell->property_xalign() = 0.0;
        cell->property_yalign() = 0.5;
        cell->property_xpad() = 4;
        cell->property_ypad() = 2;
        cell->property_ellipsize () = Pango::ELLIPSIZE_END;
        column->pack_start (*cell, true);
        column->set_resizable (true);
        column->set_expand (false);
        column->set_min_width (150);
        column->set_cell_data_func (*cell, (sigc::mem_fun (*this, &Bmp::Album_V::album_cell_data_func)));
        append_column (*column);
      }

      mStore = ListStore::create (mStoreAlbumCR);
      set_model (mStore);
      get_selection()->set_mode (SELECTION_MULTIPLE);
      get_selection()->set_select_function (sigc::mem_fun (*this, &Bmp::Album_V::slot_select));

      set_search_equal_func( sigc::mem_fun( *this, &Album_V::searchEqualFunc ) );
    }

    void
    Album_V::set_ui_manager (Glib::RefPtr<Gtk::UIManager> const& ui_manager)
    {
      m_ui_manager = ui_manager;

      m_actions = Gtk::ActionGroup::create ("Actions_UiPartLibrary-Album_V");

      m_actions->add  (Gtk::Action::create (LIBRARY_ACTION_ALBUM_ENQUEUE,
                                            Gtk::StockID (BMP_STOCK_PLAYLIST),
                                            _("Enqueue Album to Playlist")),
                                            sigc::mem_fun (*this, &Album_V::on_enqueue_album));

      m_ui_manager->insert_action_group (m_actions);
      m_ui_manager->add_ui_from_string (library_menu_albums); 
    }

    bool
    Album_V::searchEqualFunc (const Glib::RefPtr<TreeModel> &model G_GNUC_UNUSED,
                              int G_GNUC_UNUSED, const Glib::ustring &text, const TreeModel::iterator& iter)
    {
      const Album &a = (*iter)[mStoreAlbumCR.album];
      AlbumArtist aa = a.m_row;

      ustring match;
      if( a.album )
        match = a.album.get() + " " + ustring (aa);
      else
        match = ustring( aa ); 

      return ! Util::match_keys (match, text); // http://library.gnome.org/devel/gtk/2.10/GtkTreeView.html#GtkTreeViewSearchEqualFunc
                                               // "Note the return value is reversed from what you would normally expect, 
                                               //  though it has some similarity to strcmp() returning 0 for equal strings."
    }

    void
    Album_V::album_cell_data_func (Gtk::CellRenderer * basecell, Gtk::TreeModel::iterator const &iter)
    {
      CellRendererText * cell = dynamic_cast <CellRendererText *>(basecell);

      const Album &a = (*iter)[mStoreAlbumCR.album];
      AlbumArtist aa = a.m_row;

      char const* format_f ("<b>%s</b>\n%s\n<small>%s</small>");

      std::string album;
      if( a.album )
      {
        album = a.album.get(); 
        trim (album);
      }

      cell->property_markup() = (gprintf (format_f, (Markup::escape_text (ustring (aa)).c_str()),
                                                    (Markup::escape_text (ustring (album)).c_str()),
                                                    (a.mb_release_date ? get_date_string_markup (a.mb_release_date.get()).c_str()
                                                                       : "")));
    }

    void
    Album_V::clear ()
    {
      mUidAlbumMap.clear();
      mSelectedUids.clear ();
      mStore->clear ();
    }

    void
    Album_V::change ()
    {
      signal_changed_.emit ();
    }

    bool
    Album_V::slot_select (const Glib::RefPtr<Gtk::TreeModel>&model, const Gtk::TreePath &path, bool was_selected)
    {
      TreeIter iter (mStore->get_iter (path));
      UID uid ((*iter)[mStoreAlbumCR.uid]);
  
      if( was_selected )
      {
        UidList::iterator i = mSelectedUids.begin();
        for ( ; i != mSelectedUids.end(); ++i)
        {
          if ( *i == uid) break;
        }

        if( i != mSelectedUids.end() )
        {
          mSelectedUids.erase (i);
        }
        else
        {
          debug("ui-library","%s: System claims Path %d was selected, but it's not on our list!", G_STRLOC, path.get_indices().data()[0]);
        }
      }
      else
      {
        mSelectedUids.push_back (uid);
      }

      signal_changed_.emit ();
      return true;
    }

    void
    Album_V::on_artist_cleared ()
    {
      clear ();
      signal_changed_.emit ();
    }

    void
    Album_V::on_artist_all_selected (guint64 n_artists)
    {
      unset_model ();
      clear ();

      RowV r = ::Bmp::Library::Obj()->get_albums ();
      for (RowV::const_iterator i = r.begin(); i != r.end(); ++i)
      {
        insert (*i);
      }

      set_model (mStore);
      columns_autosize ();
      signal_changed_.emit ();
    }
 
    void
    Album_V::on_artist_selected (UidSet const &x, ustring const &name)
    {
      unset_model ();
      clear ();

      for (UidSet::const_iterator xx = x.begin(); xx != x.end(); ++xx)
      {
        RowV r = ::Bmp::Library::Obj()->get_albums_by_artist (*xx);
        for (RowV::const_iterator a = r.begin(); a != r.end(); ++a)
        {
          insert (*a);
        }
      }

      set_model (mStore);
      columns_autosize ();
      signal_changed_.emit ();
    }

    void
    Album_V::insert (const Album&album)
    {
      TreeModel::iterator iter = mStore->append();

      if( !album.album )
      {
        Album insert_album = album;
        insert_album.album = (_("(Unnamed Album)"));
        (*iter)[mStoreAlbumCR.uid] = insert_album.bmpx_album_id;
        (*iter)[mStoreAlbumCR.album] = insert_album; 
        mUidAlbumMap.insert (std::make_pair (insert_album.bmpx_album_id, iter));
      }
      else
      {
        (*iter)[mStoreAlbumCR.uid] = album.bmpx_album_id;
        (*iter)[mStoreAlbumCR.album] = album; 
        mUidAlbumMap.insert (std::make_pair (album.bmpx_album_id, iter));
      }

      UidList uid_list;
      uid_list.push_back (album.bmpx_album_id);
      get_cover (uid_list);
    }

    void
    Album_V::get_cover (UidList const &uid_list)
    {
      for (UidList::const_iterator i_uid = uid_list.begin(); i_uid != uid_list.end(); ++i_uid)
      {
        UidIterMap::iterator i (mUidAlbumMap.find (*i_uid));
        if( i != mUidAlbumMap.end() )
        {
          TreeModel::iterator const &iter (i->second);
          Album const &album = (*iter)[mStoreAlbumCR.album];

          if( album.asin ) 
          {
            try{
              ::Cairo::RefPtr< ::Cairo::ImageSurface> surface = Amazon::Covers::Obj()->fetch (album.asin.get(), COVER_SIZE_ALBUM_LIST, true);
              cairo_image_surface_border (surface, 1.);
              (*iter)[mStoreAlbumCR.cover] = surface; 
              return;
            }
            catch (...) {}
          }
          (*iter)[mStoreAlbumCR.cover] = mCover;
        }
        else
          g_warning ("%s: Iter for uid '%llu' was not found in the map!", G_STRLOC, *i_uid);
      }
    }

    void
    Album_V::on_row_activated (Gtk::TreeModel::Path const &__attribute__ ((unused)), Gtk::TreeViewColumn* __attribute__ ((unused)) )
    {
      signal_activated_.emit ();
    }

    bool
    Album_V::on_event (GdkEvent * ev)
    {
      if( ev->type == GDK_BUTTON_PRESS )
      {
        GdkEventButton * event = reinterpret_cast <GdkEventButton *> (ev);
        if( event->button == 3 )
        {
          m_actions->get_action (LIBRARY_ACTION_ALBUM_ENQUEUE)->set_sensitive
            (get_selection()->count_selected_rows() != 0);

          Gtk::Menu * menu = dynamic_cast < Gtk::Menu* >
                                (Util::get_popup (m_ui_manager, "/popup-library-albums/menu-library-albums"));

          if (menu) // better safe than screwed
          {
            menu->popup (event->button, event->time);
          }
          return true;
        }
      }
      return false;
    }

    void
    Album_V::on_enqueue_album ()
    {
      PathV p = get_selection()->get_selected_rows();
      TreeIter iter (mStore->get_iter (p[0]));
      Album a ((*iter)[mStoreAlbumCR.album]);
      DB::RowV x = Library::Obj()->get_album_tracks (a.bmpx_album_id);
      VUri v;
      for(DB::RowV::const_iterator i = x.begin(); i != x.end(); ++i)
      {
        v.push_back (boost::get<std::string>(i->find("location")->second));
      }
      signal_enqueue_request_.emit (v);
    }
}

namespace
{
    char const * gradient[] =
    {
      NULL, /* dummy for 0 rating */
      "#EDB315",
      "#10FF10",
      "#FF1010"
    };

    enum Column
    {
      COLUMN_PLAYING,
      COLUMN_NEW_ITEM,
/*
      COLUMN_LIBRARY_TRACK,
*/
      COLUMN_ACTIVE,
      COLUMN_TRACK,
      COLUMN_TITLE,
      COLUMN_TIME,
      COLUMN_ALBUM,
      COLUMN_ARTIST,
      COLUMN_GENRE,
      COLUMN_BITRATE,
      COLUMN_SAMPLERATE,
      COLUMN_RATING,
      COLUMN_COUNT,
/*
      COLUMN_UID,
      COLUMN_UID_OK,
*/
    };
}

namespace Bmp
{
    Playlist_V::Playlist_V (BaseObjectType                 *  obj,
                            RefPtr<Gnome::Glade::Xml> const & xml)
    : TreeView            (obj)
    , m_ref_xml           (xml)
    , mLocalUid           (0)
    , m_bmpx_track_id     (0)
    , m_playing           (0)
    {
      m_pb_playing = Gdk::Pixbuf::create_from_file (build_filename (BMP_IMAGE_DIR_STOCK, "play.png"));

      struct {

        char const*             title;
        double                  xalign;
        Renderer                r;
        int                     column;
        Pango::EllipsizeMode    ellipsize;

      } cells[] = {
          { " ",              0.5,   R_PIXBUF,   COLUMN_PLAYING,    Pango::ELLIPSIZE_NONE   },
          { " ",              0.5,   R_TOGGLE,   COLUMN_ACTIVE,     Pango::ELLIPSIZE_NONE   },
          { N_("Title"),      0.0,   R_TEXT,     COLUMN_TITLE,      Pango::ELLIPSIZE_END    },
          { N_("Artist"),     0.0,   R_TEXT,     COLUMN_ARTIST,     Pango::ELLIPSIZE_END    },
          { N_("Album"),      0.0,   R_TEXT,     COLUMN_ALBUM,      Pango::ELLIPSIZE_END    },
          { N_("Track"),      1.0,   R_TEXT,     COLUMN_TRACK,      Pango::ELLIPSIZE_NONE   },
          { N_("Length"),     0.5,   R_TEXT,     COLUMN_TIME,       Pango::ELLIPSIZE_NONE   },
          { N_("Genre"),      0.0,   R_TEXT,     COLUMN_GENRE,      Pango::ELLIPSIZE_NONE   },
      };

      for (unsigned int n = 0 ; n < G_N_ELEMENTS (cells); ++n)
      {
        TreeView::Column * c = manage (new TreeView::Column (_(cells[n].title)));
        CellRenderer     * r = 0;

        switch (cells[n].r)
        {
          case R_SURFACE:
            r = manage (new CellRendererCairoSurface());
            r->property_xpad() = 0; 
            c->set_fixed_width (64);
            c->set_sizing (Gtk::TREE_VIEW_COLUMN_FIXED);
            c->pack_end (*r);
          break;

          case R_PIXBUF:
            r = manage (new CellRendererPixbuf());
            r->property_width() = 24;
            c->pack_start (*r, false);
          break;

          case R_TEXT:
            r = manage (new CellRendererText());
            r->property_xalign() = cells[n].xalign;
            dynamic_cast<CellRendererText*>(r)->property_ellipsize() = cells[n].ellipsize; 
            c->pack_end (*r);
            c->set_resizable (true);

            if ((cells[n].column != COLUMN_TRACK) && (cells[n].column != COLUMN_TIME))
            {
              c->set_min_width (80);
            }
          break;

          case R_TOGGLE:
            r = manage (new CellRendererToggle());
            c->pack_end (*r, false);
            c->set_resizable (false);
            dynamic_cast<CellRendererToggle*>(r)->signal_toggled().connect (sigc::mem_fun (*this, &Bmp::Playlist_V::cell_play_track_toggled));
          break;
        }

        append_column (*c);
        c->set_cell_data_func (*r, (sigc::bind (sigc::mem_fun (*this, &Bmp::Playlist_V::cell_data_func), cells[n].column, cells[n].r)));
      }

#ifdef HAVE_HAL
      // Connect Bmp::HAL
      if( hal->is_initialized() )
      {
        hal->signal_volume_added().connect
          (sigc::mem_fun (*this, &Bmp::Playlist_V::hal_volume_add));
        hal->signal_volume_removed().connect
          (sigc::mem_fun (*this, &Bmp::Playlist_V::hal_volume_del));
      }
#endif //HAVE_HAL

      get_selection()->set_select_function
        (sigc::mem_fun (*this, &Bmp::Playlist_V::slot_select));

      ::Bmp::Library::Obj()->signal_track_modified().connect
        (sigc::mem_fun (*this, &Bmp::Playlist_V::on_library_track_modified));

      mStore = Gtk::ListStore::create (m_track_cr);
      clear_current_iter ();
      set_model (mStore);
      get_selection()->set_mode (SELECTION_MULTIPLE);
      set_search_column (m_track_cr.searchKey);
    }

    void
    Playlist_V::on_row_activated (Gtk::TreeModel::Path const &path, Gtk::TreeViewColumn* G_GNUC_UNUSED )
    {
      TreeIter iter (mStore->get_iter(path));

      if ((*iter)[m_track_cr.present]) 
      {
        signal_activated_.emit ();
      }
    }

    void
    Playlist_V::append_album (guint64 bmpx_album_id)
    {
      RowV r = ::Bmp::Library::Obj()->get_album_tracks (bmpx_album_id);

      for (RowV::const_iterator t = r.begin(); t != r.end(); ++t)
      {
        put_track_at_iter (*t);
      }
    }

    void
    Playlist_V::put_track_at_iter (const Track &track)
    {
      TreeIter i = mStore->append();
      put_track_at_iter (track, i);
    }
 
    void
    Playlist_V::put_track_at_iter (const Track &track, Gtk::TreeModel::iterator &iter)
    {
      (*iter)[m_track_cr.track] = track;
      (*iter)[m_track_cr.uid] = track.bmpx_track_id;
      (*iter)[m_track_cr.localUid] = mLocalUid;
      (*iter)[m_track_cr.searchKey] = track.title ? track.title.get() : std::string();
      (*iter)[m_track_cr.playTrack] = track.active.get();

      bool present = file_test (filename_from_uri (track.location.get()), FILE_TEST_EXISTS);
#ifdef HAVE_HAL
      try{
          (*iter)[m_track_cr.present] = present && (hal->volume_is_mounted (HAL::VolumeKey (track.volume_udi.get(), track.device_udi.get())));
      }
      catch (HAL::InvalidVolumeSpecifiedError & cxe)
      {
          (*iter)[m_track_cr.present] = false;
      }
#else
      (*iter)[m_track_cr.present] = present; 
#endif //HAVE_HAL

      if( m_playing && m_bmpx_track_id == track.bmpx_track_id )
      {
        assign_current_iter (iter);
      }

      mLocalUidIterMap.insert (std::make_pair (mLocalUid, iter));

      UidIterSetMap::iterator i (mUidIterMap.find (track.bmpx_track_id));
      if( i == mUidIterMap.end() )
      {
        IterSet x;
        x.insert (iter);
        mUidIterMap.insert (std::make_pair (track.bmpx_track_id, x));
      }
      else
      {
        IterSet &x (i->second);
        x.insert (iter);
      }
      mLocalUid++;
    }

    bool
    Playlist_V::slot_select (RefPtr < Gtk::TreeModel > const &model, Gtk::TreeModel::Path const &path, bool was_selected)
    {
      return bool ((*mStore->get_iter (path))[m_track_cr.present]);
    }

    void
    Playlist_V::cell_play_track_toggled (ustring const &path)
    {
      if( bool ((*mStore->get_iter (path))[m_track_cr.present]) )
      {
            TreeIter iter = mStore->get_iter (path);
            const Track &track = (*iter)[m_track_cr.track];
            ::Bmp::Library::Obj()->track_set_active (track.bmpx_track_id, ! track.active.get()); 
            if( !!track.active.get() )
            {
              mHistory.erase(UID ((*iter)[m_track_cr.localUid]));
            }
      }
    }

    void
    Playlist_V::cell_data_func (CellRenderer * basecell, TreeModel::iterator const &iter, int column, int renderer)
    {
      static boost::format uint64_f ("%llu");

      basecell->property_sensitive() = bool ((*iter)[m_track_cr.present]);

      CellRendererText          * cell_t = 0;
      CellRendererPixbuf        * cell_p = 0;
      CellRendererCairoSurface  * cell_s = 0;
      CellRendererToggle        * cell_g = 0;

      const Track &track = (*iter)[m_track_cr.track];




      switch (renderer)
      {
        case R_TEXT:
          cell_t = dynamic_cast <CellRendererText *>    (basecell);
          break;

        case R_PIXBUF:
          cell_p = dynamic_cast <CellRendererPixbuf *>  (basecell);
          break;

        case R_SURFACE:
          cell_s = dynamic_cast <CellRendererCairoSurface *>  (basecell);
          break;

        case R_TOGGLE:
          cell_g = dynamic_cast <CellRendererToggle *>  (basecell);
          break;
      }

      switch (column)
      {
        case COLUMN_PLAYING:
        {
          if( m_current_iter &&(m_current_iter.get() == iter) )
            cell_p->property_pixbuf() = m_pb_playing;
          else
            cell_p->property_pixbuf() = RefPtr <Gdk::Pixbuf> (0);

          break;
        }

        case COLUMN_TRACK:
        {
          if( track.tracknumber &&(track.tracknumber.get() != 0) )
            cell_t->property_text() = (uint64_f % track.tracknumber.get()).str();
          else
            cell_t->property_text() = "";
          break;
        }

        case COLUMN_ACTIVE:
        {
          cell_g->property_active() = track.active.get(); 
          break;
        }

        case COLUMN_RATING:
        {
          int rating = int (track.rating ? track.rating.get() : 0);

          ::Cairo::RefPtr< ::Cairo::ImageSurface> surface = ::Cairo::ImageSurface::create (::Cairo::FORMAT_ARGB32, 64, 16);
          ::Cairo::RefPtr< ::Cairo::Context> cr = ::Cairo::Context::create (surface); 
          cr->set_operator (::Cairo::OPERATOR_CLEAR);
          cr->paint ();

          if ((rating > 0) &&(rating <= 3)) // don't overflow the gradient array
          {
            Gdk::Color color (gradient[rating]);
            cr->set_operator (::Cairo::OPERATOR_SOURCE);
            cr->set_source_rgba (color.get_red_p(), color.get_green_p(), color.get_blue_p(), 1.);
            cr->rectangle (0, 0, 64, 16);
            cr->fill ();
          }

          cell_s->property_surface() = surface;
        
          break;
        }

        /*case COLUMN_LIBRARY_TRACK:
        {
          if( track.new_item )
            cell_p->property_pixbuf() = render_icon (Gtk::StockID (BMP_STOCK_LIBRARY_TRACK), Gtk::ICON_SIZE_MENU);
          else
            cell_p->property_pixbuf() = RefPtr <Gdk::Pixbuf> (0);
          break;
        }*/

        case COLUMN_TITLE:
        {
          cell_t->property_text() = track.title
                                    ? track.title.get().c_str()           
                                    : "";
          break;
        }

        case COLUMN_ARTIST:
        {
          cell_t->property_text() = track.artist
                                    ? track.artist.get().c_str()
                                    : ""; 
          break;
        }

        case COLUMN_ALBUM:
        {
          cell_t->property_text() = track.album             ? track.album.get().c_str()           
                                                            : "";
          break;
        }

        case COLUMN_TIME:
        {
          if( track.duration )
          {
            guint64 duration (track.duration.get());
            cell_t->property_text() = (boost::format ("%d:%02d") % (duration / 60) % (duration % 60)).str();
          }
          else
            cell_t->property_text() = "";
          break;
        }

        case COLUMN_GENRE:
        {
          cell_t->property_text() = track.genre       ? track.genre.get().c_str() 
                                                      : "";
          break;
        }

        case COLUMN_BITRATE:
        {
          cell_t->property_text() = track.bitrate     ? (uint64_f % track.bitrate.get()).str()    
                                                      : "";
          break;
        }

        case COLUMN_SAMPLERATE:
        {
          cell_t->property_text() = track.samplerate  ? (uint64_f % track.samplerate.get()).str() 
                                                      : "";
          break;
        }

        case COLUMN_COUNT:
        {
          cell_t->property_text() = track.count   ? (uint64_f % track.count.get()).str() 
                                                  : "";
          break;
        }
      }
    }

    // PlaybackSource
    bool
    Playlist_V::has_playing ()
    {
      return bool (m_current_iter);
    }

    void
    Playlist_V::set_first_iter ()
    {
      //assign_current_iter (mStore->get_iter (TreePath (TreePath::size_type (1), TreePath::value_type (0))));

      const TreeNodeChildren &children (mStore->children());
      TreeNodeChildren::size_type size (children.size());

      /* There is no next either way if the datastore is empty
       */
      if( !size )
      {
        return;
      }

      if( mcs->key_get <bool> ("bmp", "shuffle") )
      {
        TreeNodeChildren::size_type n1 = 0; 
        TreeNodeChildren::size_type n2 = (mStore->children().size()-1);

        // Find a random iter until history doesn't have it
        Glib::Rand rand;
        std::set<UID> UidKeeper;
        UID uid = 0;
        while (n1 != n2)
        {
          guint32 index = rand.get_int_range (0, n2+1); 
          TreeIter iter = mStore->get_iter (TreePath (TreePath::size_type (1), TreePath::value_type (index)));
          uid = ((*iter)[m_track_cr.localUid]);

          if (UidKeeper.find (uid) != UidKeeper.end()) 
          {
            continue;
          }
          else
          {
            UidKeeper.insert (uid);
            const Track &track = (*iter)[m_track_cr.track];
            bool q1 (track.active.get());
            bool q2 ((*iter)[m_track_cr.present]);
            if( !(q1 &&q2) )
            {
              ++n1;
              continue;
            }

            if( mHistory.has_uid (uid) )
            {
              ++n1; 
              continue;
            }
          }
          break;
        }

        UidIterMap::iterator i = mLocalUidIterMap.find (uid);
        if( i != mLocalUidIterMap.end() )
        {
          mHistory.append (uid);
          assign_current_iter (i->second);
          return;
        }
      }

      TreeIter  iter;
      TreePath  path (TreePath::size_type (1), TreePath::value_type (0));
      bool      advance = 0;

      do{
          if( advance )
            path.next ();
          else
            advance = 1;
  
          if (TreeNodeChildren::size_type( path.get_indices().data()[0] ) < mStore->children().size()) 
          {
            iter = mStore->get_iter (path);
            const Track &track = (*iter)[m_track_cr.track];
            bool q1 (track.active.get());
            bool q2 ((*iter)[m_track_cr.present]);
            if( q1 && q2 )
            {
              mHistory.append (UID ((*iter)[m_track_cr.localUid]));
              assign_current_iter (iter);
              return;
            }
          }
          else
            break;
        }
      while (1);
    }

    Track
    Playlist_V::get_track ()
    {
      return Track ((*m_current_iter.get())[m_track_cr.track]);
    }

    ustring
    Playlist_V::get_uri ()
    {
      return Track ((*m_current_iter.get())[m_track_cr.track]).location.get();
    }

    void
    Playlist_V::notify_stop ()
    {
      clear_current_iter ();
    }

    bool
    Playlist_V::notify_play ()
    {
      if( !mStore->children().size() )
        return false;

      PathV paths (get_selection()->get_selected_rows ());

      if( paths.size() > 1 )
      { 
        return false;
      }
      else
      if( paths.size() < 1 )
      {
        if( m_current_iter )
        {
          return true;
        }
        set_first_iter ();
        return true;
      }
      else
      {
        mHistory.set (UID ((*mStore->get_iter (paths[0]))[m_track_cr.localUid]));
        assign_current_iter (mStore->get_iter (paths[0]));
        return true;
      }
    }

    bool
    Playlist_V::check_play ()
    {
      if( !mStore->children().size() )
        return false;

      if( get_selection()->count_selected_rows() == 1 )
      {
        TreeIter iter = mStore->get_iter (PathV (get_selection()->get_selected_rows())[0]);
        const Track &track = (*iter)[m_track_cr.track];
        bool q1 (track.active.get());
        bool q2 ((*iter)[m_track_cr.present]);
        return (q1 &&q2);
      }
      else
      {
        // Find the first playable row
        TreePath path = TreePath (TreePath::size_type (1), TreePath::value_type (0));
        TreeIter iter;

        bool last = false;       
        bool play = false;

        do {
          if( TreeNodeChildren::size_type( path.get_indices().data()[0] ) == (mStore->children().size()-1) )
            break;
          iter = mStore->get_iter (path); 
          const Track &track = (*iter)[m_track_cr.track];
          bool q1 (track.active.get());
          bool q2 ((*iter)[m_track_cr.present]);
          play = (q1 &&q2); 
          last = (TreeNodeChildren::size_type( path.get_indices().data()[0] ) == (mStore->children().size()-1));
          path.next ();
          }
        while (!play &&!last);
        return play;
      }
    }

    void
    Playlist_V::assign_current_iter (Gtk::TreeModel::iterator const &iter)
    {
      m_current_iter  = iter;
      m_bmpx_track_id = Track ((*iter)[m_track_cr.track]).bmpx_track_id;

      g_assert (bool (iter));

      TreeModel::Path path1 (iter);
      scroll_to_row (path1, 0.5);
      queue_draw ();
    }

    bool
    Playlist_V::notify_next ()
    {
      const TreeNodeChildren &children (mStore->children());
      TreeNodeChildren::size_type size (children.size());

      /* There is no next either way if the datastore is empty
       */
      if( !size )
      {
        return false;
      }

      /* First let's see if the history has a next item
       */
      if( mHistory.have_next() )
      {
        UID uid (mHistory.get_next());
        assign_current_iter (mLocalUidIterMap.find (uid)->second);
        return true;
      }

      if( mcs->key_get <bool> ("bmp", "shuffle") )
      {
        TreeNodeChildren::size_type n1 = 0; 
        TreeNodeChildren::size_type n2 = (mStore->children().size()-1);

        // Find a random iter until history doesn't have it
        Glib::Rand rand;
        std::set<UID> UidKeeper;
        UID uid = 0;
        while (n1 != n2)
        {
          guint32 index = rand.get_int_range (0, n2+1); 
          TreeIter iter = mStore->get_iter (TreePath (TreePath::size_type (1), TreePath::value_type (index)));
          uid = ((*iter)[m_track_cr.localUid]);

          if (UidKeeper.find (uid) != UidKeeper.end()) 
          {
            continue;
          }
          else
          {
            UidKeeper.insert (uid);
            const Track &track = (*iter)[m_track_cr.track];
            bool q1 (track.active.get());
            bool q2 ((*iter)[m_track_cr.present]);
            if( !(q1 &&q2) )
            {
              ++n1;
              continue;
            }

            if( mHistory.has_uid (uid) )
            {
              ++n1; 
              continue;
            }
          }
          break;
        }

        if( (uid == 0) || (n1 == n2) )
        {
          // History is exhausted
          if( mcs->key_get <bool> ("bmp", "repeat") )
          {
            if( !mHistory.empty() )
            {
              mHistory.rewind ();
              UID uid;
              if( mHistory.get (uid) )
              {
                UidIterMap::iterator i = mLocalUidIterMap.find (uid);
                if( i != mLocalUidIterMap.end() )
                {
                  assign_current_iter (i->second);
                  return true;
                }
                else
                  g_warning ("%s: History is not empty but uid %llu is not present in current view", G_STRLOC, uid);
              }
              else
                g_warning ("%s: History claims to be not empty, but can not deliver an uid (this is EXTREMELY strange!)", G_STRLOC);
            }
          }
          mHistory.clear();
          return false;
        }
        else
        {
          UidIterMap::iterator i = mLocalUidIterMap.find (uid);
          if( i != mLocalUidIterMap.end() )
          {
            mHistory.append (uid);
            assign_current_iter (i->second);
            return true;
          }
          else
          {
            g_warning ("%s: History is not empty but uid %llu is not present in current view", G_STRLOC, uid);
            return false;
          }
        }
      }

      TreeIter  iter;
      TreePath  path;
      bool      advance = 0;

      if( m_current_iter )
      {
        /* I can has current iter? KTHX */
        iter = m_current_iter.get();
        path = mStore->get_path (iter);
        advance = 1;
      }
      else
      if( mcs->key_get <bool> ("bmp", "repeat") )
      {
        path = TreePath (TreePath::size_type (1), TreePath::value_type (0));
        iter = mStore->get_iter (path);
        advance = 0;
      }
      else
      {
        return false;
      }

      do{
          if( advance )
            path.next ();
          else
            advance = 1;
  
          if (TreeNodeChildren::size_type( path.get_indices().data()[0] ) < mStore->children().size()) 
          {
            iter = mStore->get_iter (path);
            const Track &track = (*iter)[m_track_cr.track];
            bool q1 (track.active.get());
            bool q2 ((*iter)[m_track_cr.present]);
            if( q1 && q2 )
            {
              mHistory.append (UID ((*iter)[m_track_cr.localUid]));
              assign_current_iter (iter);
              return true;
            }
          }
          else
            break;
        }
      while (1);

      if( mcs->key_get <bool> ("bmp", "repeat") )
      {
        if( !mHistory.empty() )
        {
          mHistory.rewind ();
          UID uid;
          if( mHistory.get (uid) )
          {
            UidIterMap::iterator i = mLocalUidIterMap.find (uid);
            if( i != mLocalUidIterMap.end() )
            {
              assign_current_iter (i->second);
              return true;
            }
            else
              g_warning ("%s: History is not empty but uid %llu is not present in current view", G_STRLOC, uid);
          }
          else
            g_warning ("%s: History claims to be not empty, but can not deliver an uid (this is EXTREMELY strange!)", G_STRLOC);
        }
      }

      clear_current_iter ();
      return false;
    }

    bool
    Playlist_V::notify_prev ()
    {
      const TreeNodeChildren &children (mStore->children());
      TreeNodeChildren::size_type size (children.size());

      if( !size )
      {
        return false;
      }

      if( mHistory.have_prev() )
      {
        UID uid (mHistory.get_prev());
        assign_current_iter (mLocalUidIterMap.find (uid)->second);
        return true;
      }

      TreeIter iter = m_current_iter.get();
      TreePath path = mStore->get_path (iter);

      do{
          path.prev ();
          if( path.get_indices().data()[0] >= 0 )
          {
            iter = mStore->get_iter (path);
            const Track &track = (*iter)[m_track_cr.track];
            bool q1 (track.active.get());
            bool q2 ((*iter)[m_track_cr.present]);
            if( q1 &&q2 )
            {
              mHistory.prepend (UID ((*iter)[m_track_cr.localUid]));
              assign_current_iter (iter);
              return true;
            }
          }
          else
            break;
        }
      while (1);

      clear_current_iter ();
      return false;
    }

    void
    Playlist_V::has_next_prev (bool &next, bool &prev)
    {
      const TreeNodeChildren &children (mStore->children());
      TreeNodeChildren::size_type size = children.size();

      next = false;
      prev = false;

      if( !size )
        return;

      if( !mHistory.boundary() )
      {
        next = mHistory.have_next();
        prev = mHistory.have_prev();
        return;
      }

      TreePath path;
      TreeIter iter;

      if( m_current_iter && m_current_iter.get() )
      {
        path = TreePath (mStore->get_path (m_current_iter.get()));
        if (path.get_indices().data()[0] > 0) 
        do{
            path.prev ();
            if (path.get_indices().data()[0] >= 0) 
            {
              iter = mStore->get_iter (path); 
              const Track &track = (*iter)[m_track_cr.track];
              bool q1 (track.active.get());
              bool q2 ((*iter)[m_track_cr.present]);
              prev = (q1 && q2); 
            }
          }
        while (!prev &&(path.get_indices().data()[0] > 0));
      }

      if( mcs->key_get<bool>("bmp","repeat") || mcs->key_get<bool>("bmp","shuffle") )
      {
        next = true;
      }
      else if( m_current_iter )
      {
        path = TreePath (mStore->get_path (m_current_iter.get()));
        if( TreeNodeChildren::size_type( path.get_indices().data()[0] ) < (mStore->children().size()-1) )
        do{
            path.next ();
            if( TreeNodeChildren::size_type( path.get_indices().data()[0] ) < mStore->children().size() )
            {
              iter = mStore->get_iter (path); 
              const Track &track = (*iter)[m_track_cr.track];
              bool q1 (track.active.get());
              bool q2 ((*iter)[m_track_cr.present]);
              next = (q1 && q2); 
            }
            else
              break;
          }
        while (!next);
      }
    }

    void
    Playlist_V::clear_current_iter ()
    {
      if( m_current_iter &&mStore->children().size() )
      {
        TreeIter iter = m_current_iter.get();
        m_current_iter.reset ();
        if( iter )
        {
          mStore->row_changed (mStore->get_path (iter), iter);
        }
      }
      else
      {
        m_current_iter.reset ();
      }
      signal_recheck_caps_.emit ();
    }

    void
    Playlist_V::on_library_track_modified (const Track &track)
    {
      UidIterSetMap::iterator m (mUidIterMap.find (track.bmpx_track_id));
      if( m != mUidIterMap.end() )
      {
        IterSet &x = m->second;
        std::for_each (x.begin(), x.end(), TrackApply<ColumnTrack> (m_track_cr.track, track));
      }
    }

    void
    Playlist_V::clear ()
    {
      mUidIterMap.clear();
      mLocalUidIterMap.clear();
      mStore->clear();
      mHistory.clear ();
      clear_current_iter ();
    }

    void
    Playlist_V::on_tracks_retag ()
    {
      TrackV  v,
              m;

      PathV p = get_selection()->get_selected_rows ();
      std::for_each (p.begin(), p.end(), PathCollect<ColumnTrack> (mStore, m_track_cr.track, v));

      boost::shared_ptr<MusicBrainzTagger> tagger (MusicBrainzTagger::Create ());
      int response = tagger->run (v, m);
      tagger->hide ();

      // FIXME: Don't do this here in the playlist's code
      if( response == Gtk::RESPONSE_OK )
      {
        ::Bmp::Library::Obj()->modify (m, TrackModFlags (TRACK_MOD_RETAG | TRACK_MOD_UPDATE_DB));
        ::Bmp::Library::Obj()->vacuum_tables ();
        signal_updated_.emit ();
      }
    }

    void
    Playlist_V::on_playlist_export ()
    {
      PathV   p = get_selection()->get_selected_rows ();
      TrackV  v;
      std::for_each (p.begin(), p.end(), PathCollect<ColumnTrack> (mStore, m_track_cr.track, v));

      ExportDialog * dialog = ExportDialog::create ();
      dialog->run (v);
      delete dialog;
    }

    void
    Playlist_V::on_delete_files ()
    {
      PathV p = get_selection()->get_selected_rows();
      ReferenceV r;
      std::for_each (p.begin(), p.end(), ReferenceCollect (mStore, r));
      ReferenceVIter i;

      if( m_current_iter )
      {
        for (i = r.begin() ; i != r.end(); ++i) 
        {
          TreeIter iter = mStore->get_iter (i->get_path());
          if( m_current_iter.get() == iter )
          {
            MessageDialog dialog (_("You can not delete the currently playing track.\n"
                                    "Stop playback, or exclude it from the selection of\n"
                                    "tracks to be deleted."), false, MESSAGE_ERROR, BUTTONS_OK, true);
            dialog.run ();
            return;
          }
        }
      }

      MessageDialog * dialog = new MessageDialog (_("Are you <b>sure</b> you want to delete the selected tracks <b>permanently</b>?"),
        true, MESSAGE_QUESTION, BUTTONS_YES_NO, true);
      dialog->set_title (_("Delete Tracks - BMP"));
      int response = dialog->run ();
      delete dialog;

      if( response == GTK_RESPONSE_YES )
      {
        for (i = r.begin() ; !r.empty() ; )
        {
          TreeIter iter = mStore->get_iter (i->get_path());
          Track track ((*iter)[m_track_cr.track]);
          if( g_unlink (filename_from_uri (track.location.get()).c_str()) == 0 )
          {
            ::Bmp::Library::Obj()->remove (track.bmpx_track_id);
            mStore->erase (iter);
          }
          i = r.erase (i);
        }

        ::Bmp::Library::Obj()->vacuum_tables ();
        signal_updated_.emit ();
      }
    }

    void
    Playlist_V::on_enqueue_files ()
    {
      PathV p = get_selection()->get_selected_rows();
      VUri v;
      std::for_each (p.begin(), p.end(), UriCollect<ColumnTrack>( mStore, m_track_cr.track, v ) );
      signal_enqueue_request_.emit (v);
    }

    void
    Playlist_V::on_track_info ()
    {
      PathV p = get_selection()->get_selected_rows();
      for(PathV::const_iterator i = p.begin(); i != p.end(); ++i)
      {
        TreeIter iter = mStore->get_iter(*i);
        const Track &track = (*iter)[m_track_cr.track];
        TrackDetails * p = TrackDetails::create (); 
        TrackMetadata m (track);
        if( track.asin )
        {
          try{
            Amazon::Covers::Obj()->fetch (track.asin.get(), m.image, true); 
          } catch (...) {}
        }
        p->display (m);
      }
    }

    void
    Playlist_V::set_ui_manager (RefPtr<Gtk::UIManager> const &ui_manager)
    {
      m_ui_manager = ui_manager;

      m_actions = Gtk::ActionGroup::create ("Actions_UiPartLibrary-Playlist_V");

      m_actions->add  (Gtk::Action::create (LIBRARY_ACTION_PLAYLIST_FILES_RETAG,
                                            Gtk::Stock::EDIT,
                                            _("Re-Tag Tracks")),
                                            sigc::mem_fun (*this, &Playlist_V::on_tracks_retag));

      m_actions->add  (Gtk::Action::create (LIBRARY_ACTION_PLAYLIST_FILES_EXPORT,
                                            Gtk::Stock::SAVE_AS,
                                            _("Export Selected")),
                                            sigc::mem_fun (*this, &Playlist_V::on_playlist_export));

      m_actions->add  (Gtk::Action::create (LIBRARY_ACTION_PLAYLIST_HISTORY_CLEAR,
                                            Gtk::StockID (GTK_STOCK_CLEAR),
                                            _("Clear Playback History")),
                                            sigc::mem_fun (mHistory, &Playlist_V::History::clear));

      m_actions->add  (Gtk::Action::create (LIBRARY_ACTION_PLAYLIST_FILES_DELETE,
                                            Gtk::StockID (GTK_STOCK_DELETE),
                                            _("Delete Files Physically")),
                                            sigc::mem_fun (*this, &Playlist_V::on_delete_files));

      m_actions->add  (Gtk::Action::create (LIBRARY_ACTION_PLAYLIST_FILES_ENQUEUE,
                                            Gtk::StockID (BMP_STOCK_PLAYLIST),
                                            _("Enqueue Tracks to Playlist")),
                                            sigc::mem_fun (*this, &Playlist_V::on_enqueue_files));

      m_actions->add  (Gtk::Action::create (LIBRARY_ACTION_PLAYLIST_TRACK_DETAILS,
                                            Gtk::StockID (GTK_STOCK_INFO),
                                            _("Track Info")),
                                            sigc::mem_fun (*this, &Playlist_V::on_track_info));

      m_ui_manager->insert_action_group (m_actions);
      m_ui_manager->add_ui_from_string (NM::Obj()->Check_Status() ? library_menu_playlist : library_menu_playlist_offline);
    }

    bool
    Playlist_V::on_event (GdkEvent * ev)
    {
      if( ev->type == GDK_BUTTON_PRESS )
      {
        GdkEventButton * event = reinterpret_cast <GdkEventButton *> (ev);
        if( event->button == 3 )
        {
          m_actions->get_action (LIBRARY_ACTION_PLAYLIST_FILES_ENQUEUE)->set_sensitive
            (get_selection()->count_selected_rows() != 0);

          m_actions->get_action (LIBRARY_ACTION_PLAYLIST_FILES_DELETE)->set_sensitive
            (get_selection()->count_selected_rows() != 0);

          m_actions->get_action (LIBRARY_ACTION_PLAYLIST_FILES_RETAG)->set_sensitive
            (get_selection()->count_selected_rows() != 0);

          m_actions->get_action (LIBRARY_ACTION_PLAYLIST_HISTORY_CLEAR)->set_sensitive
            (!mHistory.empty());

          m_actions->get_action (LIBRARY_ACTION_PLAYLIST_FILES_EXPORT)->set_sensitive
            (get_selection()->count_selected_rows());

          m_actions->get_action (LIBRARY_ACTION_PLAYLIST_TRACK_DETAILS)->set_sensitive
            (get_selection()->count_selected_rows() != 0);

          Gtk::Menu * menu = dynamic_cast < Gtk::Menu* >
                                (Util::get_popup (m_ui_manager, "/popup-library-playlist/menu-library-playlist"));

          if (menu) // better safe than screwed
          {
            menu->popup (event->button, event->time);
          }
          return true;
        }
      }

      return false;
    }

    bool
    Playlist_V::on_motion_notify_event (GdkEventMotion * event)
    {
      TreeView::on_motion_notify_event (event);
      return false;
    }

    Playlist_V::~Playlist_V ()
    {}

#ifdef HAVE_HAL
    void
    Playlist_V::hal_volume_del  (HAL::Volume const &volume)
    {
      unselect_missing ();
      TreeNodeChildren nodes = mStore->children();
      for (TreeNodeChildren::iterator i = nodes.begin(); i != nodes.end(); ++i)
      {
        const Track &track = (*i)[m_track_cr.track];
        (*i)[m_track_cr.present] = file_test (filename_from_uri (track.location.get()), FILE_TEST_EXISTS); 
      }
    }

    void
    Playlist_V::hal_volume_add  (HAL::Volume const &volume)
    {
      TreeNodeChildren nodes = mStore->children();
      for (TreeNodeChildren::iterator i = nodes.begin(); i != nodes.end(); ++i)
      {
        const Track &track = (*i)[m_track_cr.track];
        (*i)[m_track_cr.present] = file_test (filename_from_uri (track.location.get()), FILE_TEST_EXISTS); 
      }
    }

    void
    Playlist_V::unselect_missing ()
    {
      PathV list (get_selection()->get_selected_rows());
      for (PathV::const_iterator i = list.begin(); i != list.end(); ++i)
      {
            const Track & track = (*mStore->get_iter (*i))[m_track_cr.track];

            try{
                    if( !hal->volume_is_mounted (HAL::VolumeKey (track.volume_udi.get(), track.device_udi.get())) )
                    {
                          get_selection()->unselect (*i);
                    }
                }
            catch (HAL::InvalidVolumeSpecifiedError & cxe)
                { 
                          get_selection()->unselect (*i);
                }
      }
    }
#endif //HAVE_HAL
}

namespace
{
  using namespace Bmp;
  const PlaybackSource::Flags flags =
    PlaybackSource::Flags (PlaybackSource::F_ALWAYS_IMAGE_FRAME | PlaybackSource::F_HANDLE_LASTFM | PlaybackSource::F_HANDLE_LASTFM_ACTIONS |
                           PlaybackSource::F_USES_REPEAT | PlaybackSource::F_USES_SHUFFLE);
}

namespace Bmp
{
  namespace UiPart
  {
      Library::Library (RefPtr<Gnome::Glade::Xml> const &xml,
                        RefPtr<UIManager> ui_manager)
      : PlaybackSource          (_("Library/Playlist"), PlaybackSource::CAN_SEEK, flags)
      , Base                    (xml, ui_manager)
      , m_playing               (0)
      , m_queue_playback        (0)
      , m_queue_block_changes   (0)

      {
        dynamic_cast <Gtk::Image *>(m_ref_xml->get_widget ("i_throbber-1"))->set (BMP_IMAGE_DIR G_DIR_SEPARATOR_S BMP_THROBBER);

        m_ref_xml->get_widget ("notebook-library", m_library_notebook);
        m_library_notebook->set_current_page (1);

        m_ref_xml->get_widget_derived ("library-view-artists", m_view_artists);
        m_ref_xml->get_widget_derived ("library-view-albums", m_view_albums);
        m_ref_xml->get_widget_derived ("library-view-playlist", m_view_playlist);

  // Playlist

        m_view_playlist->set_ui_manager (m_ui_manager);
        m_view_playlist->get_selection()->signal_changed()
          .connect (sigc::mem_fun (*this, &Bmp::UiPart::Library::on_playlist_selection_changed)); // could be merged with sig caps recheck ?
        m_view_playlist->get_model()->signal_rows_reordered()
          .connect (sigc::hide (sigc::hide (sigc::hide (sigc::mem_fun (*this, &Bmp::UiPart::Library::on_playlist_rows_reordered)))));
        m_view_playlist->signal_activated()
          .connect (sigc::mem_fun (*this, &Bmp::UiPart::Library::on_playlist_activated));

        m_view_playlist->signal_recheck_caps()
          .connect (sigc::mem_fun (*this, &Bmp::UiPart::Library::query_playlist_caps)); // something changed
        m_view_playlist->signal_updated()
          .connect (sigc::mem_fun (*this, &Bmp::UiPart::Library::update)); // metadata editing

        m_view_playlist->signal_enqueue_request()
          .connect (sigc::mem_fun (mSignalEnqueueRequest, &Bmp::PlaybackSource::SignalEnqueueRequest::emit));

  // Albums

        m_view_albums->set_ui_manager (m_ui_manager);
        m_view_albums->signal_activated()
          .connect (sigc::mem_fun (*this, &Bmp::UiPart::Library::on_albums_activated));
        m_view_albums->signal_changed()
          .connect (sigc::mem_fun (*this, &Bmp::UiPart::Library::on_albums_changed));
        m_view_albums->signal_enqueue_request()
          .connect (sigc::mem_fun (mSignalEnqueueRequest, &Bmp::PlaybackSource::SignalEnqueueRequest::emit));

  // Artists

        m_view_artists->signal_artist_selected()
          .connect (sigc::mem_fun (*m_view_albums, &Bmp::Album_V::on_artist_selected));
        m_view_artists->signal_artist_cleared()
          .connect (sigc::mem_fun (*m_view_albums, &Bmp::Album_V::on_artist_cleared));
        m_view_artists->signal_all_selected()
          .connect (sigc::mem_fun (*m_view_albums, &Bmp::Album_V::on_artist_all_selected));

        m_view_artists->signal_activated()
          .connect (sigc::mem_fun (*this, &Bmp::UiPart::Library::on_artists_activated));

        m_view_artists->display ();

  // Gtk::Actions 

        m_actions = Gtk::ActionGroup::create ("Actions_UiPartLibrary");
        m_ui_manager->insert_action_group (m_actions);

        ::Bmp::Library::Obj()->signal_modified().connect
          (sigc::mem_fun (*this, &Bmp::UiPart::Library::on_library_backend_modified));

        mcs->subscribe ("UiPart::Library", "bmp", "shuffle",
          sigc::mem_fun (*this, &Bmp::UiPart::Library::on_shuffle_repeat_toggled));

        mcs->subscribe ("UiPart::Library", "bmp", "repeat",
          sigc::mem_fun (*this, &Bmp::UiPart::Library::on_shuffle_repeat_toggled));

        m_library_notebook->set_current_page (0);
      }

      Library::~Library () {}
      guint Library::add_ui () { return 0; }

      void
      Library::update ()
      {
        send_metadata ();
        m_library_notebook->set_current_page (1);
        m_view_artists->display ();
        m_library_notebook->set_current_page (0);
      }

      void
      Library::go_to_mbid (std::string const& mbid)
      {
        m_view_artists->go_to_mbid (mbid);
      }

      void
      Library::on_albums_changed ()
      {
        m_view_playlist->clear ();
        UidList l = m_view_albums->get_albums();
        if( l.size() )
        {
          for (UidList::const_iterator i = l.begin(); i != l.end(); ++i)
          {
            m_view_playlist->append_album (*i); 
          }

          m_view_playlist->columns_autosize ();

          query_playlist_caps ();

          if( m_queue_playback )
          {
            m_queue_playback = false;
            m_view_playlist->set_first_iter ();
            mSignalPlayRequest.emit();
          }
        }
      }

      ///////////// ACTIVATED

      void
      Library::on_playlist_activated ()
      {
        mSignalPlayRequest.emit();
      }

      void
      Library::on_artists_activated ()
      {
        if( m_view_albums->get_selection()->count_selected_rows() )
        {
          stop ();
          m_queue_playback = true;
          m_view_albums->change ();
        }
      }

      void
      Library::on_albums_activated ()
      {
        stop ();
        m_queue_playback = true;
        m_view_albums->change ();
      }

      //// MISC (CAPS ETC)

      void
      Library::query_playlist_caps ()
      {
        if( m_playing )
        {
          bool next, prev;
          m_view_playlist->has_next_prev (next, prev);

          if( next )
            m_caps = Caps (m_caps |  PlaybackSource::CAN_GO_NEXT);
          else
            m_caps = Caps (m_caps &~PlaybackSource::CAN_GO_NEXT);

          if( prev )
            m_caps = Caps (m_caps |  PlaybackSource::CAN_GO_PREV);
          else
            m_caps = Caps (m_caps &~PlaybackSource::CAN_GO_PREV);
        }
        else
        {
            m_caps = Caps (m_caps &~PlaybackSource::CAN_GO_NEXT);
            m_caps = Caps (m_caps &~PlaybackSource::CAN_GO_PREV);
        }

        if( !m_view_playlist->get_model()->children().empty() )
            m_caps = Caps (m_caps | PlaybackSource::CAN_PLAY);
        else
            m_caps = Caps (m_caps &~PlaybackSource::CAN_PLAY);

        mSignalCaps.emit (m_caps);
      }

      void
      Library::on_playlist_selection_changed ()
      {
        if( m_view_playlist->check_play() )
          m_caps = Caps (m_caps | PlaybackSource::CAN_PLAY);
        else
          m_caps = Caps (m_caps &~PlaybackSource::CAN_PLAY);

        mSignalCaps.emit (m_caps);
      }

      void
      Library::on_playlist_rows_reordered ()
      {
      }

      /// Misc

      void
      Library::on_library_backend_modified ()
      {
        update ();
      }

      void
      Library::on_shuffle_repeat_toggled (MCS_CB_DEFAULT_SIGNATURE)
      {
        query_playlist_caps ();
      }

      /// PlaybackSource

      ustring
      Library::get_uri ()
      {
        return m_view_playlist->get_uri ();
      }

      GHashTable*
      Library::get_metadata ()
      {
        return ::Bmp::Library::Obj()->get_metadata_hash_table_for_uri (m_view_playlist->get_uri());
      }

      void
      Library::send_metadata ()
      {
        if( m_view_playlist->has_playing() )
        {
          mSignalMetadata.emit (m_view_playlist->get_track ());
        }
      }

      bool
      Library::go_next ()
      {
        if( !m_view_playlist->notify_next () )
          return false;

        return true;
      }

      void
      Library::next_post ()
      {
        send_metadata ();
      }

      bool
      Library::go_prev ()
      {
        if( !m_view_playlist->notify_prev () )
          return false;

        return true;
      }

      void
      Library::prev_post ()
      {
        send_metadata ();
      }

      void
      Library::stop ()
      {
        if ( !m_view_playlist->get_selection()->count_selected_rows())
        {
          m_caps = Caps (m_caps &~PlaybackSource::CAN_PLAY);
        }

        m_caps = Caps (m_caps &~PlaybackSource::CAN_PAUSE);
        m_caps = Caps (m_caps &~PlaybackSource::CAN_PROVIDE_METADATA);
        m_caps = Caps (m_caps &~PlaybackSource::CAN_GO_NEXT);
        m_caps = Caps (m_caps &~PlaybackSource::CAN_GO_PREV);

        m_playing = 0;
        m_view_playlist->m_playing = 0;

        m_view_playlist->notify_stop ();
      }

      bool
      Library::play ()
      {
        return m_view_playlist->notify_play ();
      }

      void
      Library::play_post ()
      {
        m_caps = Caps (m_caps | PlaybackSource::CAN_PAUSE);
        m_caps = Caps (m_caps | PlaybackSource::CAN_PROVIDE_METADATA);

        m_playing = 1;
        m_view_playlist->m_playing = 1;

        send_metadata ();
      }
        
      void
      Library::send_caps ()
      {
        query_playlist_caps ();
      }
  }
}
