//  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-simple-progress.hh"
#include "dialog-simple-entry.hh"
#include "dialog-export.hh"
#include "dialog-progress.hh"

#include "ui-tools.hh"

#include "debug.hh"
#include "lastfm.hh"
#include "main.hh"
#include "network.hh"
#include "paths.hh"
#include "stock.hh"

#include "uri.hh"
#include "util.hh"
#include "util-file.hh"

#ifdef HAVE_HAL
#  include "x_hal.hh"
#endif //HAVE_HAL
#include "x_library.hh"
#include "x_mcsbind.hh"
#include "audio/play.hh"
#include "x_vfs.hh"

// UiPart Playlist
#include "ui-part-playlist.hh"

using namespace std;
using namespace boost;
using namespace Gtk;
using namespace Glib;

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

#define ACTION_PLAYLIST_PLAYLIST_CLEAR                    "playlist-action-playlist-clear"
#define ACTION_PLAYLIST_PLAYLIST_EXPORT                   "playlist-action-playlist-export"
#define ACTION_PLAYLIST_PLAYLIST_REMOVE_ITEMS             "playlist-action-remove-items"
#define ACTION_PLAYLIST_PLAYLIST_HISTORY_CLEAR            "playlist-action-history-clear"
#define ACTION_PLAYLIST_PLAYLIST_CREATE_FROM_LASTFM_TAG   "playlist-action-playlist-create-from-lastfm-tag"
#define ACTION_PLAYLIST_PLAYLIST_MONITOR_PATH             "playlist-action-playlist-monitor-path"

namespace
{
  const char * ui_playlist_list_popup =
  "<ui>"
  ""
  "<menubar name='popup-playlist-list'>"
  ""
  "   <menu action='dummy' name='menu-playlist-list'>"
  "       <menuitem action='" ACTION_PLAYLIST_PLAYLIST_CREATE_FROM_LASTFM_TAG "'/>"
  "       <menuitem action='" ACTION_PLAYLIST_PLAYLIST_EXPORT "'/>"
  "     <separator name='playlist-1'/>"
  "       <menuitem action='" ACTION_PLAYLIST_PLAYLIST_HISTORY_CLEAR "'/>"
  "       <menuitem action='" ACTION_PLAYLIST_PLAYLIST_CLEAR "'/>"
  "       <menuitem action='" ACTION_PLAYLIST_PLAYLIST_REMOVE_ITEMS "'/>"
  "     <separator name='playlist-2'/>"
  "   </menu>"
  ""
  "</menubar>"
  ""
  "</ui>";

  const char *ui_string_playlist =
  "<ui>"
  ""
  "<menubar name='MenuBarMain'>"
  "   <placeholder name='PlaceholderSource'>"
  "   <menu action='MenuUiPartPlaylist'>"
  "     <menuitem action='" ACTION_PLAYLIST_PLAYLIST_MONITOR_PATH "'/>"
  "   </menu>"
  "   </placeholder>"
  "</menubar>"
  ""
  "</ui>";


  static boost::format int_f ("%d");
  static boost::format uint64_f ("%llu");
  static boost::format progress_f ("%d / %d");

  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 <Label*> (bin->get_child())->set_markup (markup);
    else
      g_warning ("%s: Widget with path '%s' not found or not a Gtk::Bin", G_STRLOC, menupath.c_str());
  }

  enum DnDTypes
  {
    DND_URI_TARGETS = 0,
    DND_ALBUM       = 1,
    DND_TRACK       = 2,
  };

  //// Various functors for for_each

  template <class T>
  class TrackApply
  {
    public:

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

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

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

  template <class T>
  class PathCollect
  {
    public:

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

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

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

  class ReferenceCollect
  {
    public:

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

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

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

  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();
  }

  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;
    }
  }

  enum Renderer
  {
    R_TEXT,
    R_PIXBUF,
    R_TOGGLE,
  };

  enum Column
  {
    COLUMN_PLAYING,
    COLUMN_NEW_ITEM,
    COLUMN_TRACK,
    COLUMN_TITLE,
    COLUMN_TIME,
    COLUMN_ALBUM,
    COLUMN_ARTIST,
    COLUMN_GENRE,
    COLUMN_BITRATE,
    COLUMN_SAMPLERATE,
    COLUMN_COUNT,
  };
}

namespace Bmp
{
  bool
  operator<(Track const& a, Track const& b)
  {
    if(a.tracknumber && b.tracknumber)
        return (a.tracknumber.get() < b.tracknumber.get());
    else if( a.tracknumber && !b.tracknumber )
        return false; 
    else if( !a.tracknumber && b.tracknumber )
        return true;
    return false;
  }
}

namespace Bmp
{
    PlaylistList::PlaylistList (BaseObjectType                 *  obj,
                                RefPtr<Gnome::Glade::Xml> const&  xml)
    : TreeView          (obj)
    , m_ref_xml         (xml)
    , mLocalUid         (0)
    , mTrackId          (0)
    , mButtonDepressed  (0)
    , mPlaying          (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;
      } cells[] = {
          { NULL,             0.5,   R_PIXBUF,   COLUMN_PLAYING        },
          { NULL,             0.5,   R_TOGGLE,   0                     },
          { N_("Title"),      0.0,   R_TEXT,     COLUMN_TITLE          },
          { N_("Artist"),     0.0,   R_TEXT,     COLUMN_ARTIST         },
          { N_("Album"),      0.0,   R_TEXT,     COLUMN_ALBUM          },
          { N_("Track"),      1.0,   R_TEXT,     COLUMN_TRACK          },
          { N_("Length"),     1.0,   R_TEXT,     COLUMN_TIME           },
          { N_("Genre"),      0.0,   R_TEXT,     COLUMN_GENRE          },
      };

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

        switch (cells[n].r)
        {
          case R_PIXBUF:
            r = manage (new CellRendererPixbuf());
            r->property_width() = 24;
            c->pack_start (*r, false);
            c->set_cell_data_func (*r, (sigc::bind (sigc::mem_fun (*this, &Bmp::PlaylistList::cell_data_func), cells[n].column, cells[n].r)));
            break;

          case R_TEXT:
            r = manage (new CellRendererText());
            r->property_xalign() = cells[n].xalign;
            dynamic_cast<CellRendererText*>(r)->property_ellipsize() = Pango::ELLIPSIZE_END;
            c->set_min_width(80);
            c->pack_end (*r);
            c->set_resizable (true);
            c->set_cell_data_func (*r, (sigc::bind (sigc::mem_fun (*this, &Bmp::PlaylistList::cell_data_func), cells[n].column, cells[n].r)));
            break;

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

        append_column (*c);
      }

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

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

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

      mListStore = Gtk::ListStore::create (mTrackCr);
      clear_current_iter ();
      set_model (mListStore);
      get_selection()->set_mode (SELECTION_MULTIPLE);
      set_search_column (mTrackCr.searchKey);
      enable_drag_dest ();
    }

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

      if ((*iter)[mTrackCr.present]) 
      {
        mSignals.Activated.emit ();
      }
    }

    void
    PlaylistList::put_track_at_iter (Bmp::Track const& track)
    {
      TreeIter i = mListStore->append();
      put_track_at_iter (track, i);
    }
 
    void
    PlaylistList::put_track_at_iter (Bmp::Track const& track, TreeIter & iter)
    {
      bool present = false;
      try{
       present = file_test (filename_from_uri (track.location.get()), FILE_TEST_EXISTS);
       }
      catch (Glib::ConvertError)
       {
        return;
       }

      (*iter)[mTrackCr.track] = track;
      (*iter)[mTrackCr.uid] = track.bmpx_track_id;
      (*iter)[mTrackCr.localUid] = mLocalUid;
      (*iter)[mTrackCr.searchKey] = track.title ? track.title.get() : std::string();
      (*iter)[mTrackCr.playTrack] = true; 

#ifdef HAVE_HAL
      (*iter)[mTrackCr.present] = present && (hal->volume_is_mounted (HAL::VolumeKey (track.volume_udi.get(), track.device_udi.get())));
#else
      (*iter)[mTrackCr.present] = present; 
#endif //HAVE_HAL

      if( mPlaying && mTrackId == track.bmpx_track_id && track.new_item )
      {
        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++;
    }

    void
    PlaylistList::enable_drag_dest ()
    {
      disable_drag_dest ();
      DNDEntries target_entries;
      target_entries.push_back (TargetEntry ("text/plain"));
      drag_dest_set (target_entries, Gtk::DEST_DEFAULT_MOTION);
      drag_dest_add_uri_targets ();
    }

    void
    PlaylistList::disable_drag_dest ()
    {
      drag_dest_unset ();  
    }

    bool
    PlaylistList::on_drag_motion (RefPtr<Gdk::DragContext> const& context, int x, int y, guint time)
    {
      TreeModel::Path path;
      TreeViewDropPosition pos;

      if( get_dest_row_at_pos (x, y, path, pos) )
      {
        switch (pos)
        {
            case TREE_VIEW_DROP_INTO_OR_BEFORE:
            case TREE_VIEW_DROP_BEFORE:
              set_drag_dest_row (path, TREE_VIEW_DROP_BEFORE);
              break;

            case TREE_VIEW_DROP_INTO_OR_AFTER:
            case TREE_VIEW_DROP_AFTER:
              set_drag_dest_row (path, TREE_VIEW_DROP_AFTER);
              break;
        }
      }
      return true;
    }

    void
    PlaylistList::on_drag_leave (RefPtr<Gdk::DragContext> const& context, guint time)
    {
      gtk_tree_view_set_drag_dest_row (gobj(), NULL, GTK_TREE_VIEW_DROP_BEFORE);
    }

    bool
    PlaylistList::on_drag_drop (RefPtr<Gdk::DragContext> const& context, int x, int y, guint time)
    {
      ustring target (drag_dest_find_target (context));

      if( !target.empty() )
      {
        drag_get_data (context, target, time);
        context->drag_finish  (true, false, time);
        return true;
      }
      else
      {
        context->drag_finish  (false, false, time);
        return false;
      }
    }

    void
    PlaylistList::on_drag_data_received (RefPtr<Gdk::DragContext> const& context, int x, int y,
                                          Gtk::SelectionData const& selection_data, guint info, guint time)
    {
      switch (info)
      {
        case DND_TRACK:
        {
          gconstpointer * data = (gconstpointer*)(selection_data.get_data());
          Bmp::Track track (*static_cast<Bmp::Track const*> (*data));

          TreeModel::Path       path;
          TreeModel::iterator   iter;
          TreeViewColumn      * c      G_GNUC_UNUSED;
          int                   cell_x G_GNUC_UNUSED,
                                cell_y G_GNUC_UNUSED;
          TreeViewDropPosition  pos;

          if( !get_dest_row_at_pos (x, y, path, pos) )
          {
            iter = mListStore->append ();
          }
          else
          {
            switch (pos)
            {
              case TREE_VIEW_DROP_BEFORE:
              case TREE_VIEW_DROP_INTO_OR_BEFORE:
                iter = mListStore->insert (mListStore->get_iter (path));
                break;

              case TREE_VIEW_DROP_AFTER:
              case TREE_VIEW_DROP_INTO_OR_AFTER:
                iter = mListStore->insert_after (mListStore->get_iter (path));
                break;
            }
          }

          put_track_at_iter (track, iter);
          break;
        }

        case DND_ALBUM:
        {
          gconstpointer* data = (gconstpointer*)(selection_data.get_data());

          Bmp::Album album (*(static_cast<Bmp::Album const*> (*data)));
          RowV rows (Library::Obj()->get_album_tracks (album.bmpx_album_id));

          TreeModel::Path       path;
          TreeViewColumn      * c       G_GNUC_UNUSED;
          int                   cell_x  G_GNUC_UNUSED,
                                cell_y  G_GNUC_UNUSED;
          TreeViewDropPosition  pos;

          if( !get_dest_row_at_pos (x, y, path, pos) )
          {
            path = Gtk::TreeModel::Path();
            path.append_index (mListStore->children().size());
          }
          else
          {
            switch (pos)
            {
              case TREE_VIEW_DROP_BEFORE:
              case TREE_VIEW_DROP_INTO_OR_BEFORE:
                break;

              case TREE_VIEW_DROP_AFTER:
              case TREE_VIEW_DROP_INTO_OR_AFTER:
                path.next ();
                break;
            }
          }

          TreeModel::iterator iter (mListStore->get_iter (path));

          if( !iter )
          {
            iter = mListStore->append ();
            for (RowV::size_type n = 0; n < rows.size()-1; ++n)
            {
              mListStore->insert (iter);
            }
          }
          else
          {
            for (RowV::size_type n = 0; n < rows.size(); ++n)
            {
              mListStore->insert (iter);
            }
          }

          for (RowV::const_iterator i = rows.begin(); i != rows.end(); ++i)
          {
            TreeModel::iterator iter = mListStore->get_iter (path);
            put_track_at_iter (*i, iter);
            path.next ();
          }
          break;
        }

        case DND_URI_TARGETS:
        {
          VUri uris (selection_data.get_uris());
          VUri v;

          for (VUri::const_iterator u = uris.begin() ; u != uris.end() ; ++u)
          {
            if( !vfs->has_container (*u) )
            {
              v.push_back (*u);
            }
            else
            {
              VFS::Handle h (*u);
              VUri list;

              try{
                if( file_test (filename_from_uri (*u), FILE_TEST_IS_DIR) )
                    vfs->read (h, list, VFS::CONTAINER);
                else
                    vfs->read (h, list, VFS::ProcessingFlags (VFS::TRANSPORT | VFS::CONTAINER));

                for (VUri::const_iterator u = list.begin(); u != list.end() ; ++u)
                  {
                    Bmp::URI uri (*u);
                    Bmp::URI::Protocol p (uri.get_protocol());

                    switch (p)
                    {
                      case URI::PROTOCOL_UNKNOWN:
                      case URI::PROTOCOL_CDDA:
                      case URI::PROTOCOL_LASTFM:
                      case URI::PROTOCOL_FTP:
                      case URI::PROTOCOL_QUERY:
                      case URI::PROTOCOL_TRACK:
                              break;

                      case URI::PROTOCOL_FILE:
                              if( file_test (filename_from_uri (*u), FILE_TEST_EXISTS) )
                              {
                                    v.push_back (*u);
                              }
                              break;

                      case URI::PROTOCOL_HTTP:
                      case URI::PROTOCOL_MMS:
                      case URI::PROTOCOL_MMSU:
                      case URI::PROTOCOL_MMST:
                      case URI::PROTOCOL_ITPC:
                              break;
                    }
                  }
                }
              catch (Glib::ConvertError) {}
            }
          }

          TreeModel::Path       path;
          int                   cell_x  G_GNUC_UNUSED, 
                                cell_y  G_GNUC_UNUSED; 
          TreeViewDropPosition  pos;
    
          TrackV xx;

          for (VUri::const_iterator i = v.begin(); i != v.end(); ++i)
          {
            try{
              if( file_test (filename_from_uri (*i), FILE_TEST_EXISTS) )
              {
                xx.push_back (Library::Obj()->get_track (*i));
              }
            }
            catch (Glib::ConvertError) {}
          }

          std::sort (xx.begin(), xx.end());

          if( !get_dest_row_at_pos (x, y, path, pos) )
          {
                path = TreePath(); 
                path.append_index (mListStore->children().size());

                for (TrackV::iterator i = xx.begin(); i != xx.end(); ++i)
                {
                    TreeIter iter = mListStore->append ();
                    put_track_at_iter (*i, iter);
                }
          }
          else
          {
            switch (pos)
            {
              case TREE_VIEW_DROP_BEFORE:
              case TREE_VIEW_DROP_INTO_OR_BEFORE:
                break;

              case TREE_VIEW_DROP_AFTER:
              case TREE_VIEW_DROP_INTO_OR_AFTER:
                path.next ();
                break;
            }

            TreeIter iter = mListStore->get_iter (path);

            if( !iter )
            {
                  iter = mListStore->append ();
                  TrackV::iterator i = xx.begin();
                  put_track_at_iter (*i, iter);
                  ++i;

                  for ( ; i != xx.end(); ++i)
                  {
                        iter = mListStore->insert (iter);
                        put_track_at_iter (*i, iter);
                  }
            }
            else
            {
                  for (TrackV::iterator i = xx.begin() ; i != xx.end(); ++i)
                  {
                        iter = mListStore->insert (iter);
                        put_track_at_iter (*i, iter);
                  }
            }
          }
        }
      }
      mSignals.RecheckCaps.emit ();
    }

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

    void
    PlaylistList::cell_play_track_toggled (Glib::ustring const& path)
    {
      if( bool ((*mListStore->get_iter (path))[mTrackCr.present]) )
      {
            TreeIter i (mListStore->get_iter (path));
            (*i)[mTrackCr.playTrack] = !((*i)[mTrackCr.playTrack]);
            if( !!bool ((*i)[mTrackCr.playTrack]))
            {
              mHistory.erase (UID ((*i)[mTrackCr.localUid]));
            }
      }
    }

    void
    PlaylistList::cell_data_func (CellRenderer * basecell, TreeModel::iterator const& iter, int column, int renderer)
    {
      basecell->property_sensitive() = bool ((*iter)[mTrackCr.present]);

      CellRendererText          * cell_t = 0;
      CellRendererPixbuf        * cell_p = 0;

      Bmp::Track const& track = (*iter)[mTrackCr.track];

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

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

      switch (column)
      {
        case COLUMN_PLAYING:
        {
          if( mCurrentIter && (mCurrentIter.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_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
    PlaylistList::has_playing ()
    {
      return bool (mCurrentIter);
    }

    void
    PlaylistList::set_first_iter ()
    {
      assign_current_iter (mListStore->get_iter (TreePath (TreePath::size_type (1), TreePath::value_type (0))));
    }

    Track
    PlaylistList::get_track ()
    {
      return Bmp::Track ((*mCurrentIter.get())[mTrackCr.track]);
    }

    ustring
    PlaylistList::get_uri ()
    {
      return Bmp::Track ((*mCurrentIter.get())[mTrackCr.track]).location.get();
    }

    ustring
    PlaylistList::get_type ()
    {
      Bmp::Track track ((*mCurrentIter.get())[mTrackCr.track]);
      return (track.type ? track.type.get() : std::string());
    }

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

    bool
    PlaylistList::notify_play ()
    {
      PathV paths (get_selection()->get_selected_rows ());

      if( paths.size() > 1 )
      { 
        return false;
      }
      else
      if( paths.size() < 1 )
      {
        if( mCurrentIter )
        {
          return true;
        }

        // Find the first playable row
        TreePath path = TreePath (TreePath::size_type (1), TreePath::value_type (0));
        TreeIter iter;

        bool has_track = false;       
        bool play = false;

        do {
          if( TreeNodeChildren::size_type( path.get_indices().data()[0] ) == (mListStore->children().size()-1) )
            break;
          iter = mListStore->get_iter (path); 
          bool q1 ((*iter)[mTrackCr.playTrack]);
          bool q2 ((*iter)[mTrackCr.present]);
          play = (q1 && q2); 
          has_track = (TreeNodeChildren::size_type( path.get_indices().data()[0] ) < (mListStore->children().size()-1));
          path.next ();
          }
        while (!play && has_track);
  
        if( play && has_track )
        {
          mHistory.set (UID ((*iter)[mTrackCr.localUid]));
          assign_current_iter (iter);
          return true;
        }
        else
          return false;
      }
      else
      {
        mHistory.set (UID ((*mListStore->get_iter (paths[0]))[mTrackCr.localUid]));
        assign_current_iter (mListStore->get_iter (paths[0]));
        return true;
      }
    }

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

      if( get_selection()->count_selected_rows() == 1 )
      {
        TreeIter iter = mListStore->get_iter (PathV (get_selection()->get_selected_rows())[0]);
        bool q1 ((*iter)[mTrackCr.playTrack]);
        bool q2 ((*iter)[mTrackCr.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] ) == (mListStore->children().size()-1) )
            break;
          iter = mListStore->get_iter (path); 
          bool q1 ((*iter)[mTrackCr.playTrack]);
          bool q2 ((*iter)[mTrackCr.present]);
          play = (q1 && q2); 
          last = (TreeNodeChildren::size_type( path.get_indices().data()[0] ) == (mListStore->children().size()-1));
          path.next ();
          }
        while (!play && !last);
        return play;
      }
    }

    void
    PlaylistList::assign_current_iter (TreeIter const& iter)
    {
      mCurrentIter = iter;
      mTrackId = Track ((*iter)[mTrackCr.track]).bmpx_track_id;

      g_assert (bool (iter));

      TreeModel::Path path1, path2, path3 (iter);
      if( get_visible_range (path1, path2) )
      {
        if( (path3 < path1) || (path3 > path2) )
        {
          scroll_to_row (path3, 0.5);
        }
      }

      queue_draw ();
    }

    void
    PlaylistList::bad_track ()
    {
      if(mCurrentIter && mCurrentIter.get())
      {
        get_selection()->unselect(mCurrentIter.get());
        (*mCurrentIter.get())[mTrackCr.present] = false;
        mListStore->row_changed(TreePath(mCurrentIter.get()), mCurrentIter.get());        
      }
    }

    bool
    PlaylistList::notify_next ()
    {
      const TreeNodeChildren &children (mListStore->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 (this should not be the case if the store was cleared previously, which
       * is not unimportant, but implict here
       */
      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 = (mListStore->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 = mListStore->get_iter (TreePath (TreePath::size_type (1), TreePath::value_type (index)));
          uid = ((*iter)[mTrackCr.localUid]);

          if (UidKeeper.find (uid) != UidKeeper.end()) 
          {
            continue;
          }
          else
          {
            UidKeeper.insert (uid);
            const Track &track = (*iter)[mTrackCr.track];
            bool q1 (track.active.get());
            bool q2 ((*iter)[mTrackCr.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( mCurrentIter )
      {
        iter = mCurrentIter.get();
        path = mListStore->get_path (iter);
        advance = 1;
      }
      else
      if( mcs->key_get <bool> ("bmp", "repeat") )
      {
        path = TreePath (TreePath::size_type (1), TreePath::value_type (0));
        iter = mListStore->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] ) < mListStore->children().size()) 
          {
            iter = mListStore->get_iter (path);
            const Track &track = (*iter)[mTrackCr.track];
            bool q1 (track.active.get());
            bool q2 ((*iter)[mTrackCr.present]);
            if( q1 && q2 )
            {
              mHistory.append (UID ((*iter)[mTrackCr.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
    PlaylistList::notify_prev ()
    {
      TreeModel::Children const& children (mListStore->children());
      TreeModel::Children::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 = mCurrentIter.get();
      TreePath path = mListStore->get_path (iter);

      do{
          path.prev ();
          if( path.get_indices().data()[0] >= 0 )
          {
            iter = mListStore->get_iter (path);
            bool q1 ((*iter)[mTrackCr.playTrack]);
            bool q2 ((*iter)[mTrackCr.present]);
            if( q1 && q2 )
            {
              mHistory.prepend (UID ((*iter)[mTrackCr.localUid]));
              assign_current_iter (iter);
              return true;
            }
          }
          else
            break;
        }
      while (1);

      clear_current_iter ();
      return false;
    }

    void
    PlaylistList::has_next_prev (bool & next, bool & prev)
    {
      TreeModel::Children const& children (mListStore->children());
      TreeModel::Children::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( mCurrentIter && mCurrentIter.get() )
      {
        path = TreePath (mListStore->get_path (mCurrentIter.get()));
        if (path.get_indices().data()[0] > 0) 
        do{
            path.prev ();
            if (path.get_indices().data()[0] >= 0) 
            {
              iter = mListStore->get_iter (path); 
              bool q1 ((*iter)[mTrackCr.playTrack]);
              bool q2 ((*iter)[mTrackCr.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( mCurrentIter )
      {
        path = TreePath (mListStore->get_path (mCurrentIter.get()));
        if( TreeNodeChildren::size_type( path.get_indices().data()[0] ) < (mListStore->children().size()-1) )
        do{
            path.next ();
            if( TreeNodeChildren::size_type( path.get_indices().data()[0] ) < mListStore->children().size() )
            {
              iter = mListStore->get_iter (path); 
              bool q1 ((*iter)[mTrackCr.playTrack]);
              bool q2 ((*iter)[mTrackCr.present]);
              next = (q1 && q2); 
            }
            else
              break;
          }
        while (!next);
      }
    }

    void
    PlaylistList::clear_current_iter ()
    {
      if( mCurrentIter && mListStore->children().size() )
      {
        TreeIter iter = mCurrentIter.get();
        mCurrentIter.reset ();
        if( iter )
        {
          mListStore->row_changed (mListStore->get_path (iter), iter);
        }
      }
      else
      {
        mCurrentIter.reset ();
      }
      mSignals.RecheckCaps.emit ();
    }

    void
    PlaylistList::on_library_track_modified (Bmp::Track const& 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> (mTrackCr.track, track));
      }
    }

    void
    PlaylistList::clear ()
    {
      mUidIterMap.clear();
      mLocalUidIterMap.clear();
      mListStore->clear();
      mHistory.clear ();
      clear_current_iter ();
    }

    void
    PlaylistList::on_playlist_export ()
    {
      TrackV v;
      PathV p = get_selection()->get_selected_rows ();
      std::for_each (p.begin(), p.end(), PathCollect<ColumnTrack> (mListStore, mTrackCr.track, v));
      ExportDialog * dialog = ExportDialog::create ();
      dialog->run (v);
      delete dialog;
    }

    void
    PlaylistList::add_uris (VUri const& uris, bool & playback)
    {
      VUri v;
      for (VUri::const_iterator u = uris.begin(); u != uris.end() ; ++u)
      {
        Bmp::URI uri (*u);
        Bmp::URI::Protocol p (uri.get_protocol());
        
        try{
          if( (p == URI::PROTOCOL_FILE) && !file_test (filename_from_uri (*u), FILE_TEST_EXISTS) )
          {
            continue; 
          }
        } catch (Glib::ConvertError)
          {
            continue;
          }

        if( !vfs->has_container (*u) )
        {
          v.push_back (*u);
        }
        else
        {
          VFS::Handle h (*u);
          VUri list;

          try{
            if( file_test (filename_from_uri (*u), FILE_TEST_IS_DIR) )
                vfs->read (h, list, VFS::CONTAINER);
            else
                vfs->read (h, list, VFS::ProcessingFlags (VFS::TRANSPORT | VFS::CONTAINER));

            for (VUri::const_iterator u = list.begin(); u != list.end() ; ++u)
            {
                  Bmp::URI uri (*u);
                  Bmp::URI::Protocol p (uri.get_protocol());

                  switch (p)
                  {
                        case URI::PROTOCOL_UNKNOWN:
                        case URI::PROTOCOL_CDDA:
                        case URI::PROTOCOL_LASTFM:
                        case URI::PROTOCOL_FTP:
                        case URI::PROTOCOL_QUERY:
                        case URI::PROTOCOL_TRACK:
                            break;

                        case URI::PROTOCOL_FILE:
                            v.push_back (*u);
                            break;

                        case URI::PROTOCOL_HTTP:
                        case URI::PROTOCOL_MMS:
                        case URI::PROTOCOL_MMSU:
                        case URI::PROTOCOL_MMST:
                        case URI::PROTOCOL_ITPC:
                            break;
                  }
            }
          }
          catch (Glib::ConvertError) 
          {
          }
        }
      }

      mHistory.clear();
      TreeModel::Path path_o (TreePath::size_type (1), TreePath::value_type (mListStore->children().size()));

      TrackV xx;
      for (VUri::const_iterator i = v.begin(); i != v.end(); ++i)
      {
        try{
          if( file_test (filename_from_uri (*i), FILE_TEST_EXISTS) )
          {
              try{
                xx.push_back (Library::Obj()->get_track (*i));
                }
              catch (...) {}
           }
        }
        catch (Glib::ConvertError){}
      }

      std::sort (xx.begin(), xx.end());

      for (TrackV::const_iterator i = xx.begin(); i != xx.end(); ++i)
      {
        put_track_at_iter (*i);
      }

      if( playback )
      {
        // deselect the list if playback is true because it's probably
        // likely that the user wants what they're adding. --nenolod
        get_selection()->unselect_all();
        clear_current_iter ();
        if (mListStore->get_iter (path_o))
          assign_current_iter (mListStore->get_iter (path_o));
      }

      mSignals.RecheckCaps.emit ();
    }

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

      m_actions = Gtk::ActionGroup::create ("Actions_UiPartPlaylist-PlaylistList");

      m_actions->add  (Gtk::Action::create ("MenuUiPartPlaylist", _("Playlist")));

#ifdef HAVE_LINUX
      m_actions->add  (Gtk::ToggleAction::create (ACTION_PLAYLIST_PLAYLIST_MONITOR_PATH,
                                            _("Watch Path...")),
                                            sigc::mem_fun (*this, &PlaylistList::on_playlist_watch_path_toggled));
      dynamic_cast<Button*>(m_ref_xml->get_widget ("playlist-button-watch-cancel"))->signal_clicked().connect
        (sigc::mem_fun( *this, &Bmp::PlaylistList::on_playlist_watch_cancel));
#endif // HAVE_LINUX

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

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

      m_actions->add  (Gtk::Action::create (ACTION_PLAYLIST_PLAYLIST_CLEAR,
                                            Gtk::StockID (GTK_STOCK_CLEAR),
                                            _("Clear Playlist")),
                                            sigc::mem_fun (*this, &PlaylistList::clear));

      m_actions->add  (Gtk::Action::create (ACTION_PLAYLIST_PLAYLIST_REMOVE_ITEMS,
                                            Gtk::StockID (GTK_STOCK_REMOVE),
                                            _("Remove Selected Tracks")),
                                            sigc::mem_fun (*this, &PlaylistList::on_playlist_remove_items));

      m_actions->add  (Gtk::Action::create (ACTION_PLAYLIST_PLAYLIST_CREATE_FROM_LASTFM_TAG,
                                            Gtk::StockID (BMP_STOCK_LASTFM),
                                            _("Create Playlist from Last.fm Tag")),
                                            sigc::mem_fun (*this, &PlaylistList::on_playlist_create_from_lastfm_tag));

      if (!NM::Obj()->Check_Status())
      {
        m_actions->get_action (ACTION_PLAYLIST_PLAYLIST_CREATE_FROM_LASTFM_TAG)->set_sensitive (0);
      }

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

#ifdef HAVE_LINUX
    void
    PlaylistList::on_new_inotify_events()
    {
      VUri uris;

      m_inotify->acquire_queue_lock();

      IEventQueue & queue = m_inotify->get_queue();
      while( !queue.empty() ) 
      {
        INotifyEvent const& event = queue.back();

        if( ( event.mask & IN_MOVED_TO ) || ( event.mask & IN_CREATE ) )
        {
          if (!event.name.empty())
          {
            uris.push_back( filename_to_uri( build_filename( m_watch_path, event.name) ) );
          }
        } 

        queue.pop_back ();
      }

      m_inotify->release_queue_lock();

      if( !uris.empty() )
      {
        bool playback = false;
        add_uris( uris, playback );
      }
    }

    void
    PlaylistList::on_playlist_watch_cancel ()
    {
      RefPtr<ToggleAction>::cast_static (m_actions->get_action (ACTION_PLAYLIST_PLAYLIST_MONITOR_PATH))->set_active( false );
    }

    void
    PlaylistList::on_playlist_watch_path_toggled ()
    {
      bool active = RefPtr<ToggleAction>::cast_static (m_actions->get_action (ACTION_PLAYLIST_PLAYLIST_MONITOR_PATH))->get_active();
      if (!active)
      {
        dynamic_cast<Label*>(m_ref_xml->get_widget ("playlist-label-watch"))->set_text( "" ); 
        m_ref_xml->get_widget ("playlist-hbox-watch")->hide();
        m_inotify.clear();
      }
      else
      {
        FileChooserDialog dialog (_("Select Folder to Watch - BMP (Playlist)"), FILE_CHOOSER_ACTION_SELECT_FOLDER);
        dialog.add_button (Stock::CANCEL, RESPONSE_CANCEL);
        dialog.add_button (Stock::OPEN, RESPONSE_OK);
        dialog.set_current_folder (mcs->key_get<string>("bmp", "file-chooser-path"));
        dialog.set_default_response (RESPONSE_CANCEL);

        if (dialog.run () == GTK_RESPONSE_OK)
        {
          try{
            m_watch_path = filename_from_uri (dialog.get_current_folder_uri());
            dynamic_cast<Label*>(m_ref_xml->get_widget ("playlist-label-watch"))->set_text( filename_to_utf8( m_watch_path ));
            m_ref_xml->get_widget ("playlist-hbox-watch")->show();
            m_inotify = Bmp::INotify::create (m_watch_path);
            m_inotify->signal_new_events().connect( sigc::mem_fun( *this, &Bmp::PlaylistList::on_new_inotify_events ) );
          } catch (Glib::ConvertError)
            {
                RefPtr<ToggleAction>::cast_static (m_actions->get_action (ACTION_PLAYLIST_PLAYLIST_MONITOR_PATH))->set_active( false );
            }
        }
        else
        {
          // stop_watch above after setting this here will return false since this watch will not exist, but it's
          // tolerable behaviour which doesn't need a workaround at the moment (nothing will crash)
          RefPtr<ToggleAction>::cast_static (m_actions->get_action (ACTION_PLAYLIST_PLAYLIST_MONITOR_PATH))->set_active( false );
        }
      }
    }
#endif // HAVE_LINUX

    void
    PlaylistList::on_playlist_create_from_lastfm_tag ()
    {
      DialogSimpleEntry * d = DialogSimpleEntry::create ();
      d->set_title (_("Please enter a Last.fm tag to create the Playlist from"));
      ustring tag;
      int response = d->run (tag);
      delete d;

      if (response == GTK_RESPONSE_OK)
      {
        using namespace LastFM;
        using namespace WS;

        SimpleProgress * d = SimpleProgress::create ();
        d->set_title (_("Creating Last.fm Tag Playlist - BMP"));
        d->present ();

        d->step (5, 1);
        d->set_label (_("Fetching Last.fm Information..."));

        LastFMArtistV v;
        artists (AT_TAG_TOP, tag, v);

        if (v.empty())
        {
          delete d;
          MessageDialog dialog ((boost::format (_("There is not enough Last.fm data available to create a Playlist based on the Tag '%s'")) 
                                  % tag.c_str()).str().c_str(),
                                false, MESSAGE_QUESTION, BUTTONS_OK, true);
          dialog.run ();
          return;
        }

        d->step (5, 2);
        d->set_label (_("Accessing Library..."));

        RowV rows;
        for (LastFMArtistV::const_iterator i = v.begin(); i != v.end(); ++i)
        {
          AttributeV  a;
          Query       q;

          a.push_back  (Attribute (FUZZY, get_attribute_info (ATTRIBUTE_ARTIST).id, std::string (i->name)));
          q.add_attr_v (a, CHAIN_OR);
          q.seq_chain_append (CHAIN_AND);
          q.set_suffix  (" ORDER BY ifnull(mb_album_artist_sort_name, mb_album_artist), ifnull(mb_release_date, album), tracknumber ");
          q.set_columns (" * ");
          Library::Obj()->query (q, rows, true); 
        }

        d->step (5, 3);
        d->set_label (_("Reticulating Splines..."));

        std::random_shuffle (rows.begin(), rows.end());

        d->step (5, 4);
        d->set_label (_("Creating Playlist..."));

        RowV::size_type n = 0;
        for (RowV::const_iterator r = rows.begin(); r != rows.end(); ++r)
        {
          put_track_at_iter (Track (*r));
          d->step (rows.size(), ++n);
        }
    
        d->step (5, 5);
        d->set_label (_("Starting Playback"));

        delete d;
      }
    }

    void
    PlaylistList::on_playlist_remove_items ()
    {
      PathV p = get_selection()->get_selected_rows();
      ReferenceV v;
      std::for_each (p.begin(), p.end(), ReferenceCollect (mListStore, v));

      ReferenceVIter i;
      for (i = v.begin() ; !v.empty() ; )
      {
        TreeIter iter = mListStore->get_iter (i->get_path());

        if(mCurrentIter && mCurrentIter.get() == iter)
        {
          mCurrentIter.reset();
        }

        UID uid ((*iter)[mTrackCr.localUid]);

        mHistory.erase (uid);

        mLocalUidIterMap.erase (mLocalUidIterMap.find (uid));

        mListStore->erase (iter);

        i = v.erase (i);
      }

      mHistory.integrity ();
      mSignals.RecheckCaps.emit();
    }

    bool
    PlaylistList::on_event (GdkEvent * ev)
    {
      if ((ev->type == GDK_BUTTON_RELEASE) ||
          (ev->type == GDK_2BUTTON_PRESS) ||
          (ev->type == GDK_3BUTTON_PRESS))
      {
        mButtonDepressed = false;
        return false;
      }
      else
      if( ev->type == GDK_BUTTON_PRESS )
      {
        GdkEventButton * event = reinterpret_cast <GdkEventButton *> (ev);

        if( event->button == 1 && (event->window == get_bin_window()->gobj()))
        {
          TreeViewColumn      * column;
          int                   cell_x,
                                cell_y;
          int                   tree_x,
                                tree_y;
          TreePath              path;

          widget_to_tree_coords (int (event->x), int (event->y), tree_x, tree_y);
          bool have_path = get_path_at_pos (tree_x, tree_y, path, column, cell_x, cell_y) ;

          mButtonDepressed  = have_path;
          mPathButtonPress = path;  
        }
        else
        if( event->button == 3 )
        {
          m_actions->get_action (ACTION_PLAYLIST_PLAYLIST_HISTORY_CLEAR)->set_sensitive
            (!mHistory.empty());

          m_actions->get_action (ACTION_PLAYLIST_PLAYLIST_CLEAR)->set_sensitive
            (mListStore->children().size());

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

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

          Gtk::Menu * menu = dynamic_cast < Gtk::Menu* > (Util::get_popup (m_ui_manager, "/popup-playlist-list/menu-playlist-list"));
          if (menu) // better safe than screwed
          {
            menu->popup (event->button, event->time);
          }
          return true;
        }
      }
      return false;
    }

    bool
    PlaylistList::on_motion_notify_event (GdkEventMotion * event)
    {
      TreeView::on_motion_notify_event (event);
      if( mButtonDepressed )
      {
        TreeViewColumn    * column;
        TreeModel::Path     path;

        int                 cell_x,
                            cell_y;

        int                 tree_x,
                            tree_y;

        widget_to_tree_coords (int (event->x), int (event->y), tree_x, tree_y);
        if( get_path_at_pos (tree_x, tree_y, path, column, cell_x, cell_y) )
        {
          if( path != mPathButtonPress )
          {
            if( path < mPathButtonPress )
              move_selected_rows_up ();
            else
            if( path > mPathButtonPress )
              move_selected_rows_down ();

            mPathButtonPress = path;
          }
        }
      }
      return false;
    }

    void
    PlaylistList::move_selected_rows_up ()
    {
      PathV p = get_selection()->get_selected_rows();

      if( p.empty() )
        return;

      if( p[0].get_indices().data()[0] < 1 )
        return; // we don't move rows if the uppermost would be pushed out of the list

      ReferenceV r;
      std::for_each (p.begin(), p.end(), ReferenceCollect (mListStore, r));
      ReferenceVIter i = r.begin();

      for ( ; !r.empty() ; )
      {
        TreeModel::Path path_a (i->get_path());
        TreeModel::Path path_b (i->get_path());

        path_a.prev ();
        path_b.next ();

        TreeModel::iterator iter_a (mListStore->get_iter (path_a));
        TreeModel::iterator iter_b (mListStore->get_iter (path_b));

        if( G_UNLIKELY( TreeNodeChildren::size_type( path_b.get_indices().data()[0] ) == mListStore->children().size()) )
        {
          mListStore->iter_swap (iter_a, mListStore->get_iter (i->get_path()));
        }
        else
        {
          mListStore->move (iter_a, iter_b);
        }

        i = r.erase (i);
      }

      mSignals.RecheckCaps.emit ();
    }

    void
    PlaylistList::move_selected_rows_down ()
    {
      PathV p = get_selection()->get_selected_rows();
    
      if( p.empty() )
        return;

      if( TreeNodeChildren::size_type( p[p.size()-1].get_indices().data()[0] + 1 ) == (mListStore->children().size()) )
        return; // we don't move rows if the LOWER-most would be pushed out of the list, either

      ReferenceV r;
      std::for_each (p.begin(), p.end(), ReferenceCollect (mListStore, r));
      ReferenceVIter i = r.begin();

      for ( ; !r.empty() ; )
      {
        TreeModel::Path path_a (i->get_path());
        TreeModel::Path path_b (i->get_path());

        path_b.next ();

        TreeModel::iterator iter_a (mListStore->get_iter (path_a));
        TreeModel::iterator iter_b (mListStore->get_iter (path_b));

        mListStore->move (iter_b, iter_a);
        i = r.erase (i);
      }
      mSignals.RecheckCaps.emit();
    }

    PlaylistList::~PlaylistList ()
    {}

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

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

    void
    PlaylistList::unselect_missing ()
    {
      PathV list (get_selection()->get_selected_rows());
      for (PathV::const_iterator i = list.begin(); i != list.end(); ++i)
      {
        Bmp::Track const& track = (*mListStore->get_iter (*i))[mTrackCr.track];
        if( !hal->volume_is_mounted (HAL::VolumeKey (track.volume_udi.get(), track.device_udi.get())) )
        {
          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
  {
      StrV  
      Playlist::get_schemes ()
      {
        StrV v;
        v.push_back ("file");
        return v;
      }

      void
      Playlist::process (VUri const& uris)
      {
        add_uris (uris, true);
      }

      Playlist::Playlist (RefPtr<Gnome::Glade::Xml> const& xml,RefPtr<UIManager> ui_manager)

      : PlaybackSource (_("Playlist"), PlaybackSource::CAN_SEEK, flags)
      , Base (xml, ui_manager)
      , mPlaying (0)
      {
        m_ref_xml->get_widget_derived ("playlist-view-playlist", m_playlist);

        m_playlist->set_ui_manager (m_ui_manager);
        m_playlist->get_selection()->signal_changed()
          .connect (sigc::mem_fun (*this, &Bmp::UiPart::Playlist::on_playlist_selection_changed)); // could be merged with sig caps recheck ?
        m_playlist->signal_activated()
          .connect (sigc::mem_fun (*this, &Bmp::UiPart::Playlist::on_playlist_activated));
        m_playlist->signal_recheck_caps()
          .connect (sigc::mem_fun (*this, &Bmp::UiPart::Playlist::query_playlist_caps)); // something changed

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

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

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

      }

      Playlist::~Playlist () {}

      guint
      Playlist::add_ui ()
      {
#ifdef HAVE_LINUX
        return m_ui_manager->add_ui_from_string( ui_string_playlist );
#else
        return 0;
#endif // HAVE_LINUX
      }

      void
      Playlist::add_uris (VUri const& uris, bool playback)
      {
        if (playback)
          mSignalStopRequest.emit ();

        m_playlist->add_uris (uris, playback);

        if (playback)
          mSignalPlayRequest.emit ();
      }

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

      void
      Playlist::query_playlist_caps ()
      {
        if( mPlaying )
        {
          bool next, prev;
          m_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);
        }
        mSignalCaps.emit (m_caps);
      }

      void
      Playlist::on_playlist_selection_changed ()
      {
        if( m_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
      Playlist::on_shuffle_repeat_toggled (MCS_CB_DEFAULT_SIGNATURE)
      {
        query_playlist_caps ();
      }

      ustring
      Playlist::get_uri ()
      {
        return m_playlist->get_uri ();
      }

      ustring
      Playlist::get_type ()
      {
        return m_playlist->get_type ();
      }

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

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

      bool
      Playlist::go_next ()
      {
        if( !m_playlist->notify_next () )
          return false;

        return true;
      }

      bool
      Playlist::go_prev ()
      {
        if( !m_playlist->notify_prev () )
          return false;

        return true;
      }

      void
      Playlist::stop ()
      {
        if ( !m_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);

        mPlaying = 0;
        m_playlist->mPlaying = 0;
        m_playlist->notify_stop ();
      }

      bool
      Playlist::play ()
      {
        return m_playlist->notify_play ();
      }

      void
      Playlist::prev_post ()
      {
        query_playlist_caps ();
        send_metadata ();
      }

      void
      Playlist::next_post ()
      {
        query_playlist_caps ();
        send_metadata ();
      }

      void
      Playlist::play_post ()
      {
        m_caps = Caps (m_caps | PlaybackSource::CAN_PAUSE);
        m_caps = Caps (m_caps | PlaybackSource::CAN_PROVIDE_METADATA);
        mPlaying = 1;
        m_playlist->mPlaying = 1;
        query_playlist_caps ();
        send_metadata ();
      }

      void
      Playlist::bad_track ()
      {
        m_playlist->bad_track();
      }
  }
}
