// Copyright (c) 2019 Giorgos Vougioukas
//
// The license can be found in the LICENSE file.

#include "euterpe/playlist_wnd.h"
#include "euterpe/definitions.h"
#include "euterpe/app_info.h"
#include "euterpe/plugin.h"
#include "euterpe/main_wnd.h"

#include <string.h>
#include <stdlib.h>

#include "WDL/fpcmp.h"
#include "WDL/lice/lice.h"
#include "WDL/win32_utf8.h"
#include "WDL/filebrowse.h"
#include "WDL/projectcontext.h"
#include "WDL/lineparse.h"
#include "WDL/wdlcstring.h"
#include "WDL/filewrite.h"
#include "WDL/fileread.h"

#if defined(_WIN32)
#include <shlwapi.h>
#define atoll(x) _atoi64(x)
#define strcasestr(a, b) StrStrIA(a, b)
#endif

static WNDPROC PrevListProc = NULL;
static HWND s_list = NULL;

static int sort_by_file_path(const void *a, const void *b)
{
  WDL_FastString *s1, *s2;
  s1 = *(WDL_FastString **)a;
  s2 = *(WDL_FastString **)b;
  return stricmp(s1->Get(), s2->Get());
}

static int cmp_ints(const void *a, const void *b)
{
  int aa = *(const int *)a;
  int bb = *(const int *)b;

  if (aa < bb) return -1;
  if (aa > bb) return 1;

  return 0;
}

int RSE_PlayListWnd::DoCompare(char *s1, char *s2)
{
  ColumnHeader *ch = m_column_header.Get();

  if (ch[m_column].sort_type)
  {
    int r = atoi(s1) - atoi(s2);
    if (ch[m_column].sort_dir == -1)
    {
      return -r;
    }
    return r;
  }

  int r = stricmp(s1, s2);
  if (ch[m_column].sort_dir == -1)
  {
    return -r;
  }
  return r;
}

int RSE_PlayListWnd::ListViewCompareProc(LPARAM lparam1, LPARAM lparam2, LPARAM lparam)
{
  RSE_PlayListWnd *self = (RSE_PlayListWnd *)lparam;

  char text1[2048], text2[2048];

  ListView_GetItemText(self->m_list, lparam1, self->m_column, text1, sizeof(text1));
  ListView_GetItemText(self->m_list, lparam2, self->m_column, text2, sizeof(text2));

  return self->DoCompare(text1, text2);
}

RSE_PlayListWnd::RSE_PlayListWnd()
  : m_hwnd(NULL)
  , m_cycle_found(0)
  , m_list(NULL)
  , m_column(1)
  , m_drag_started(false)
{}

RSE_PlayListWnd::~RSE_PlayListWnd()
{}

HWND RSE_PlayListWnd::Handle() const
{
  return m_hwnd;
}

HWND RSE_PlayListWnd::PropertiesHandle() const
{
  return m_prop_wnd.Handle();
}

HWND RSE_PlayListWnd::PreviewHandle() const
{
  return m_prevart_wnd.Handle();
}

bool RSE_PlayListWnd::SavePlayList(const char *fn)
{
  ProjectStateContext *psc = ProjectCreateFileWrite(fn);
  if (!psc) return false;

  char text[2048];
  WDL_FastString strbuf;

  psc->AddLine("<EUTERPE_PLAYLIST 0.3");

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    psc->AddLine("<PLAYLIST_ITEM %d", i);

    for (int j = 0; j < m_column_header.GetSize(); j++)
    {
      ColumnHeader *ch = m_column_header.Get();

      switch (ch[j].id)
      {
        case ID_PL_HEADER_TYPE:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "TYPE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_INDEX:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "INDEX", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_TITLE:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "TITLE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_ARTIST:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "ARTIST", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_ALBUM:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "ALBUM", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_LENGTH:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "LENGTH", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_GENRE:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "GENRE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_TRACK:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "TRACK", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_YEAR:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "YEAR", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_BITRATE:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "BITRATE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_CHANNELS:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "CHANNELS", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_SAMPLERATE:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "SAMPLERATE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_FILENAME:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "FILENAME", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_FILEEXTENSION:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "FILEEXTENSION", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_FILESIZE:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "FILESIZE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_SHA:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "SHA", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_FILEPATH:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "FILEPATH", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_COMMENT:
          {
            ListView_GetItemText(m_list, i, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "COMMENT", strbuf.Get());
          }
          break;
      }
    }

    psc->AddLine(">");
  }

  psc->AddLine(">");
  delete psc;
  return true;
}

bool RSE_PlayListWnd::LoadPlayList(const char *fn)
{
  ProjectStateContext *psc = ProjectCreateFileRead(fn);
  if (!psc) return false;

  LineParser lp;

  LVITEM lvi = { 0, };
  WDL_String strbuf;
  int item_count = 0;

  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

  bool is_euterpe_pl = false;

  while (ProjectContext_GetNextLine(psc, &lp))
  {
    //if (lp.gettoken_str(0)[0] == '>') break;
    if (lp.gettoken_str(0)[0] == '<')
    {
      if (!stricmp(lp.gettoken_str(0), "<EUTERPE_PLAYLIST"))
      {
        if (lp.gettoken_float(1) < 0.3) break;

        is_euterpe_pl = true;
      }
      if (!stricmp(lp.gettoken_str(0), "<PLAYLIST_ITEM"))
      {
        //item_count = lp.gettoken_int(1);
        item_count = ListView_GetItemCount(m_list);
      }
    }
    else
    {
      if (!is_euterpe_pl)
      {
        delete psc;
        return false;
      }

      if (!stricmp(lp.gettoken_str(0), "TYPE"))
      {
        lvi.mask = LVIF_TEXT | LVIF_PARAM;
        lvi.lParam = (LPARAM)item_count;
        lvi.iItem = item_count;
        lvi.iSubItem = 0;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_InsertItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "INDEX"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 1;

        //strbuf.Set(lp.gettoken_str(1));
        strbuf.SetFormatted(64, "%d", item_count + 1);
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "TITLE"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 2;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "ARTIST"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 3;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "ALBUM"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 4;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "LENGTH"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 5;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "GENRE"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 6;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "TRACK"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 7;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "YEAR"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 8;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "BITRATE"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 9;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "CHANNELS"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 10;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "SAMPLERATE"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 11;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "FILENAME"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 12;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "FILEEXTENSION"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 13;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "FILESIZE"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 14;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "SHA"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 15;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "FILEPATH"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 16;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);

        g_main_loop_mutex.Enter();
        g_main_loop->AddTrack(strbuf.Get());
        g_main_loop_mutex.Leave();
      }
      if (!stricmp(lp.gettoken_str(0), "COMMENT"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 17;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      WDL_UTF8_HookListView(m_list);
    }
  }

  delete psc;

  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);

  if (!is_euterpe_pl)
  {
    return false;
  }

  return true;
}

void RSE_PlayListWnd::PlaySelected()
{
  int pos = ListView_GetNextItem(m_list, -1, LVIS_SELECTED | LVIS_FOCUSED);

  if (pos >= 0)
  {
    g_main_loop_mutex.Enter();
    g_main_loop->Stop();
    g_main_loop->Play(pos);
    g_main_loop_mutex.Leave();
  }
}

void RSE_PlayListWnd::RemoveAll()
{
  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);
  ListView_DeleteAllItems(m_list);
  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);

  g_main_loop_mutex.Enter();
  g_main_loop->RemoveAll();
  g_main_loop_mutex.Leave();
}

void RSE_PlayListWnd::SelectAll()
{
  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_SetItemState(m_list, i, LVIS_SELECTED, LVIS_SELECTED);
  }

  SetFocus(m_list);
}

void RSE_PlayListWnd::SelectNone()
{
  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_SetItemState(m_list, i, 0, LVIS_SELECTED);
    ListView_SetItemState(m_list, i, 0, LVIS_FOCUSED);
  }
}

void RSE_PlayListWnd::InvertSelection()
{
  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    if (ListView_GetItemState(m_list, i, LVIS_SELECTED))
    {
      ListView_SetItemState(m_list, i, 0, LVIS_SELECTED);
    }
    else
    {
      ListView_SetItemState(m_list, i, LVIS_SELECTED, LVIS_SELECTED);
    }
  }

  SetFocus(m_list);
}

void RSE_PlayListWnd::AddItem(const char *filename)
{
  LVITEM lvi = { 0, };

  WDL_String strbuf;
  WDL_FastString sql, shastr;
  RSE_IFileTagger *ft = NULL;

  WDL_ASSERT(filename != NULL);

  ft = CreateFileTagger(filename);

  if (!ft)
  {
    return;
  }

  int item_count = ListView_GetItemCount(m_list);
  ColumnHeader *ch = m_column_header.Get();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    lvi.mask = LVIF_TEXT;
    lvi.iItem = item_count;
    //lvi.lParam = item_count;
    lvi.iSubItem = i;
    switch (ch[i].id)
    {
    case ID_PL_HEADER_TYPE:
      lvi.mask = LVIF_TEXT | LVIF_PARAM;
      lvi.lParam = (LPARAM)item_count;

      lvi.pszText = (char *)ft->GetType();
      lvi.cchTextMax = (int)strlen(ft->GetType());

      ListView_InsertItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_INDEX:
      strbuf.SetFormatted(32, "%d", item_count + 1);
      lvi.pszText = strbuf.Get();
      lvi.cchTextMax = strbuf.GetLength();
      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_TITLE:
      lvi.pszText = (char *)ft->GetTitle();
      lvi.cchTextMax = (int)strlen(ft->GetTitle());

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_ARTIST:
      lvi.pszText = (char *)ft->GetArtist();
      lvi.cchTextMax = (int)strlen(ft->GetArtist());

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_ALBUM:
      lvi.pszText = (char *)ft->GetAlbum();
      lvi.cchTextMax = (int)strlen(ft->GetAlbum());

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_LENGTH:
      {
        int minutes = 0;
        int seconds = 0;
        double time = 0.0;

        time = ft->GetLength();

        if (WDL_DefinitelyGreaterThan(time, 0.0))
        {
          minutes = (int)time / 60;
          seconds = (int)time % 60;
        }

        strbuf.SetFormatted(32, "%01d:%02d", minutes, seconds);

        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();
        ListView_SetItem(m_list, &lvi);
      }
      break;
    case ID_PL_HEADER_GENRE:
      lvi.pszText = (char *)ft->GetGenre();
      lvi.cchTextMax = (int)strlen(ft->GetGenre());

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_TRACK:
      if (ft->GetTrack() == 0)
      {
        strbuf.Set("");
      }
      else
      {
        strbuf.SetFormatted(32, "%d", ft->GetTrack());
      }
      lvi.pszText = strbuf.Get();
      lvi.cchTextMax = strbuf.GetLength();

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_YEAR:
      if (ft->GetYear() == 0)
      {
        strbuf.Set("");
      }
      else
      {
        strbuf.SetFormatted(32, "%d", ft->GetYear());
      }
      lvi.pszText = strbuf.Get();
      lvi.cchTextMax = strbuf.GetLength();

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_BITRATE:
      strbuf.SetFormatted(32, "%d kbit/s", ft->GetBitRate());

      lvi.pszText = strbuf.Get();
      lvi.cchTextMax = strbuf.GetLength();
      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_CHANNELS:
      strbuf.SetFormatted(32, "%d", ft->GetChannels());
      lvi.pszText = strbuf.Get();
      lvi.cchTextMax = strbuf.GetLength();

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_SAMPLERATE:
      {
        strbuf.SetFormatted(32, "%.0f Hz", ft->GetSampleRate());

        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();
        ListView_SetItem(m_list, &lvi);
      }
      break;
    case ID_PL_HEADER_FILENAME:
      lvi.pszText = (char *)ft->GetFileName();
      lvi.cchTextMax = (int)strlen(ft->GetFileName());

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_FILEEXTENSION:
      lvi.pszText = (char *)ft->GetFileExtension();
      lvi.cchTextMax = (int)strlen(ft->GetFileExtension());

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_FILESIZE:
      {
        double mb = 0.0;

        mb = (double)ft->GetFileSize() / 1024 / 1024;

        strbuf.SetFormatted(32, "%.2f MB", mb);

        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();
        ListView_SetItem(m_list, &lvi);
      }
      break;
    case ID_PL_HEADER_SHA:
      lvi.pszText = (char *)ft->GetSHA();
      lvi.cchTextMax = (int)strlen(ft->GetSHA());

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_COMMENT:
      lvi.pszText = (char *)ft->GetComment();
      lvi.cchTextMax = (int)strlen(ft->GetComment());

      ListView_SetItem(m_list, &lvi);
      break;
    case ID_PL_HEADER_FILEPATH:
      lvi.pszText = (char *)ft->GetFilePath();
      lvi.cchTextMax = (int)strlen(ft->GetFilePath());

      ListView_SetItem(m_list, &lvi);
      break;
    }
    WDL_UTF8_HookListView(m_list);
  }

  g_main_loop_mutex.Enter();
  g_main_loop->AddTrack(filename);
  g_main_loop_mutex.Leave();

  delete ft;
}

int RSE_PlayListWnd::Find(const char *str)
{
  char text[2048];
  int title_subitem = 0;

  m_found.Resize(0);
  m_cycle_found = 0;

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    ColumnHeader *ch = m_column_header.Get();

    if (ch[i].id == ID_PL_HEADER_TITLE)
    {
      title_subitem = i;
      break;
    }
  }

  SelectNone();

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, title_subitem, text, sizeof(text));

    if (strcasestr(text, str))
    {
      ListView_SetItemState(m_list, i, LVIS_SELECTED, LVIS_SELECTED);

      m_found.Add(i);
    }
  }

  if (m_found.GetSize())
  {
    ListView_SetItemState(m_list, m_found.Get()[0], LVIS_FOCUSED, LVIS_FOCUSED);
    ListView_EnsureVisible(m_list, m_found.Get()[0], FALSE);
  }

  return m_found.GetSize();
}

void RSE_PlayListWnd::PreviousFind()
{
  m_cycle_found--;
  if (m_cycle_found < 0) m_cycle_found = m_found.GetSize() - 1;

  if (m_found.GetSize())
  {
    ListView_SetItemState(m_list, m_found.Get()[m_cycle_found],
      LVIS_FOCUSED, LVIS_FOCUSED);
    ListView_EnsureVisible(m_list, m_found.Get()[m_cycle_found],
      FALSE);
  }
}

void RSE_PlayListWnd::NextFind()
{
  m_cycle_found++;
  if (m_cycle_found >= m_found.GetSize()) m_cycle_found = 0;

  if (m_found.GetSize())
  {
    ListView_SetItemState(m_list, m_found.Get()[m_cycle_found],
      LVIS_FOCUSED, LVIS_FOCUSED);
    ListView_EnsureVisible(m_list, m_found.Get()[m_cycle_found],
      FALSE);
  }
}

void RSE_PlayListWnd::InfoFind(int *current, int *total, int *pl_index)
{
  if (m_found.GetSize())
  {
    *current = m_cycle_found + 1;
    *total = m_found.GetSize();
    *pl_index = m_found.Get()[m_cycle_found] + 1;
  }
  else
  {
    *pl_index = *current = *total = 0;
  }
}

void RSE_PlayListWnd::MarkFind()
{
  SelectNone();

  if (m_found.GetSize())
  {
    ListView_SetItemState(m_list, m_found.Get()[m_cycle_found],
      LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
    ListView_EnsureVisible(m_list, m_found.Get()[m_cycle_found],
      FALSE);
  }

  m_found.Resize(0, true);
}

void RSE_PlayListWnd::GetSelected(WDL_TypedBuf<int> *items, int *focus)
{
  if (items)
  {
    items->Resize(0);
    *focus = -1;

    for (int i = 0; i < ListView_GetItemCount(m_list); i++)
    {
      if (ListView_GetItemState(m_list, i, LVIS_SELECTED))
      {
        items->Add(i);
      }

      if (ListView_GetItemState(m_list, i, LVIS_FOCUSED))
      {
        *focus = i;
      }
    }
  }
}

void RSE_PlayListWnd::SetSelection(WDL_TypedBuf<int> *items, int focus)
{
  if (items)
  {
    SelectNone();

    for (int i = 0; i < items->GetSize(); i++)
    {
      ListView_SetItemState(m_list, items->Get()[i],
        LVIS_SELECTED, LVIS_SELECTED);
    }

    if (focus >= 0)
    {
      ListView_SetItemState(m_list, focus,
        LVIS_FOCUSED, LVIS_FOCUSED);
      ListView_EnsureVisible(m_list, focus, FALSE);
    }
  }
}

void RSE_PlayListWnd::SortMenu()
{
  HWND h = ListView_GetHeader(GetDlgItem(m_hwnd, IDC_LIST1));
  RECT r, s;
  GetWindowRect(h, &r);
#if defined(_WIN32)
  r.top += r.bottom - r.top;
#else
  r.top += SWELL_GetListViewHeaderHeight(m_list);
#endif

  GetWindowRect(m_hwnd, &s);
  r.left = s.left;

  ColumnHeader *ch = m_column_header.Get();

  EnableMenuItem(m_sort_menu, ID_PLAYLIST_SORT_INFO, MF_GRAYED);

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    CheckMenuItem(m_sort_menu, ch[i].id, MF_UNCHECKED);
  }

  TrackPopupMenu(m_sort_menu, TPM_LEFTALIGN, r.left, r.top, 0, m_hwnd, NULL);
}

void RSE_PlayListWnd::Reverse()
{
  int subitem = 0;

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    ColumnHeader *ch = m_column_header.Get();

    if (ch[i].id == ID_PL_HEADER_INDEX)
    {
      subitem = i;
      break;
    }
  }

  Sort(subitem);
}

void RSE_PlayListWnd::Randomize()
{
  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

  MTRand mt;
  char text[2048];
  int subitem = 0;
  WDL_FastString strbuf;

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    ColumnHeader *ch = m_column_header.Get();

    if (ch[i].id == ID_PL_HEADER_INDEX)
    {
      subitem = i;
      break;
    }
  }

  WDL_TypedBuf<int> old_order, new_order;

  old_order.Resize(ListView_GetItemCount(m_list));

  for (int i = 0; i < old_order.GetSize(); i++)
  {
    old_order.Get()[i] = i;
  }

  while (old_order.GetSize())
  {
    int range = old_order.GetSize() - 1;
    int pick = (int)mt.randInt(range);

    new_order.Add(old_order.Get()[pick]);
    old_order.Delete(pick);
  }

  g_main_loop_mutex.Enter();
  int cur = g_main_loop->GetCurrentTrack();
  g_main_loop_mutex.Leave();

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    int val = new_order.Get()[i];
    sprintf(text, "%d", val + 1);
    ListView_SetItemText(m_list, i, subitem, text);

    if (i == cur)
    {
      g_main_loop_mutex.Enter();
      g_main_loop->SetCurrentTrack(val);
      g_main_loop_mutex.Leave();
    }
  }

  //Sort(subitem);

  m_column = subitem;

  LVITEM lvi = { 0, };

  ColumnHeader *ch = m_column_header.Get();

  if (ch[m_column].id == ID_PL_HEADER_INDEX)
  {
    ch[m_column].sort_dir = -1;
  }

  ListView_SortItems(m_list, ListViewCompareProc, (LPARAM)this);

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      subitem = i;
      break;
    }
  }

  WDL_PtrList_DeleteOnDestroy<char> *pl = new WDL_PtrList_DeleteOnDestroy<char>(free);

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, subitem, text, sizeof(text));

    pl->Add(strdup(text));
  }

  g_main_loop_mutex.Enter();
  g_main_loop->UpdatePlayList(pl);
  g_main_loop_mutex.Leave();

  ch[m_column].sort_dir = -ch[m_column].sort_dir;

  UpdatePlayListData();
  UpdatePlayListIndex();

  int pos = ListView_GetNextItem(m_list, -1, LVNI_FOCUSED);

  if (pos >= 0)
  {
    ListView_EnsureVisible(m_list, pos, FALSE);
  }


  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);
}

void RSE_PlayListWnd::SelectCurrent()
{
  SetFocus(m_hwnd);
  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  while (pos != -1)
  {
    ListView_SetItemState(m_list, pos, ~LVIS_SELECTED, LVIS_SELECTED);
    pos = ListView_GetNextItem(m_list, pos, LVNI_SELECTED);
  }

  g_main_loop_mutex.Enter();
  int curtrack = g_main_loop->GetCurrentTrack();
  g_main_loop_mutex.Leave();

  ListView_SetItemState(m_list, curtrack,
    LVNI_SELECTED | LVNI_FOCUSED, LVNI_SELECTED | LVNI_FOCUSED);
  ListView_EnsureVisible(m_list, curtrack, FALSE);
}

void RSE_PlayListWnd::AddToQueue()
{
  char text[2048];
  int subitem = 0;
  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  while (pos != -1)
  {
    for (int i = 0; i < m_column_header.GetSize(); i++)
    {
      ColumnHeader *ch = m_column_header.Get();

      if (ch[i].id == ID_PL_HEADER_FILEPATH)
      {
        subitem = i;
        break;
      }
    }

    ListView_GetItemText(m_list, pos, subitem, text, sizeof(text));

    g_main_loop_mutex.Enter();
    g_main_loop->AddToQueue(text);
    g_main_loop_mutex.Leave();

    pos = ListView_GetNextItem(m_list, pos, LVNI_SELECTED);
  }
}

void RSE_PlayListWnd::RemoveFromQueue()
{
  g_main_loop_mutex.Enter();
  g_main_loop->RemoveFromQueue();
  g_main_loop_mutex.Leave();
}

void RSE_PlayListWnd::ClearQueue()
{
  g_main_loop_mutex.Enter();
  g_main_loop->ClearQueue();
  g_main_loop_mutex.Leave();
}

void RSE_PlayListWnd::Sort(int column)
{
  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

  m_column = column;

  LVITEM lvi = { 0, };
  char text[2048];
  int sub_item = 0;
  WDL_String strbuf;

  ColumnHeader *ch = m_column_header.Get();

  if (ch[m_column].id == ID_PL_HEADER_INDEX)
  {
    ch[m_column].sort_dir = -1;
  }

  ListView_SortItems(m_list, ListViewCompareProc, (LPARAM)this);

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      sub_item = i;
      break;
    }
  }

  WDL_PtrList_DeleteOnDestroy<char> *pl = new WDL_PtrList_DeleteOnDestroy<char>(free);

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, sub_item, text, sizeof(text));

    pl->Add(strdup(text));
  }

  g_main_loop_mutex.Enter();
  g_main_loop->UpdatePlayList(pl);
  g_main_loop_mutex.Leave();

  ch[m_column].sort_dir = -ch[m_column].sort_dir;

  UpdatePlayListData();
  UpdatePlayListIndex();

  int pos = ListView_GetNextItem(m_list, -1, LVNI_FOCUSED);

  if (pos >= 0)
  {
    ListView_EnsureVisible(m_list, pos, FALSE);
  }


  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);
}

void RSE_PlayListWnd::RemoveSelected()
{
  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  while (pos != -1)
  {
    ListView_DeleteItem(m_list, pos);

    pos = ListView_GetNextItem(m_list, pos - 1, LVNI_SELECTED);
  }

  int sub_item = 0;
  char text[2048];
  ColumnHeader *ch = m_column_header.Get();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      sub_item = i;
      break;
    }
  }

  WDL_PtrList_DeleteOnDestroy<char> *pl = new WDL_PtrList_DeleteOnDestroy<char>(free);

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, sub_item, text, sizeof(text));

    pl->Add(strdup(text));
  }

  g_main_loop_mutex.Enter();
  g_main_loop->UpdatePlayList(pl);
  g_main_loop_mutex.Leave();

  UpdatePlayListData();
  UpdatePlayListIndex();

  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);
}

void RSE_PlayListWnd::CropSelected()
{
  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

  for (int i = ListView_GetItemCount(m_list) - 1; i >= 0; i--)
  {
    if (ListView_GetItemState(m_list, i, LVIS_SELECTED))
    {
      continue;
    }

    ListView_DeleteItem(m_list, i);
  }

  int sub_item = 0;
  char text[2048];
  ColumnHeader *ch = m_column_header.Get();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      sub_item = i;
      break;
    }
  }

  WDL_PtrList_DeleteOnDestroy<char> *pl = new WDL_PtrList_DeleteOnDestroy<char>(free);

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, sub_item, text, sizeof(text));

    pl->Add(strdup(text));
  }

  g_main_loop_mutex.Enter();
  g_main_loop->UpdatePlayList(pl);
  g_main_loop_mutex.Leave();

  UpdatePlayListData();
  UpdatePlayListIndex();
  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);
}

void RSE_PlayListWnd::RemoveMissingFiles()
{
  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

  char text[2048];
  int subitem = 0;

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    ColumnHeader *ch = m_column_header.Get();

    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      subitem = i;
      break;
    }
  }

  for (int i = ListView_GetItemCount(m_list) - 1; i >= 0; i--)
  {
    ListView_GetItemText(m_list, i, subitem, text, sizeof(text));

    WDL_FileRead *file = new WDL_FileRead(text);

    if (!file->IsOpen())
    {
      ListView_DeleteItem(m_list, i);
    }

    delete file;
  }

  int sub_item = 0;
  ColumnHeader *ch = m_column_header.Get();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      sub_item = i;
      break;
    }
  }

  WDL_PtrList_DeleteOnDestroy<char> *pl = new WDL_PtrList_DeleteOnDestroy<char>(free);

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, sub_item, text, sizeof(text));

    pl->Add(strdup(text));
  }

  g_main_loop_mutex.Enter();
  g_main_loop->UpdatePlayList(pl);
  g_main_loop_mutex.Leave();

  UpdatePlayListData();
  UpdatePlayListIndex();
  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);
}

void RSE_PlayListWnd::RemoveDuplicateEntries()
{
  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

  char text[2048], path[2048];
  int subitem = 0;

  WDL_TypedBuf<int> rem_item;

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    ColumnHeader *ch = m_column_header.Get();

    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      subitem = i;
      break;
    }
  }

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, subitem, text, sizeof(text));

    for (int j = ListView_GetItemCount(m_list) - 1; j > i; j--)
    {
      ListView_GetItemText(m_list, j, subitem, path, sizeof(path));

      if (!strcmp(text, path))
      {
        ListView_DeleteItem(m_list, j);
      }
    }
  }

  int sub_item = 0;
  ColumnHeader *ch = m_column_header.Get();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      sub_item = i;
      break;
    }
  }

  WDL_PtrList_DeleteOnDestroy<char> *pl = new WDL_PtrList_DeleteOnDestroy<char>(free);

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, sub_item, text, sizeof(text));

    pl->Add(strdup(text));
  }

  g_main_loop_mutex.Enter();
  g_main_loop->UpdatePlayList(pl);
  g_main_loop_mutex.Leave();

  UpdatePlayListData();
  UpdatePlayListIndex();

  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);
}

void RSE_PlayListWnd::MoveTracks(int new_pos)
{
  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

  LVITEM lvi = { 0, };
  char text[2048];
  int pos = -1;
  int selected = 0;
  int last_insert = 0;
  int newpos = new_pos;

  pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);
  if (newpos > pos) newpos++; // place item over hittest point
  while (pos != -1)
  {
    selected++;

    lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_STATE;
    lvi.iItem = pos;
    //lvi.lParam = pos;
    lvi.iSubItem = 0;
    lvi.pszText = text;
    lvi.cchTextMax= sizeof(text);
    lvi.state = ~LVNI_SELECTED;
    ListView_GetItem(m_list, &lvi);
    ListView_GetItemText(m_list, pos, 0, text, sizeof(text));
    lvi.iItem = newpos;
    //lvi.lParam = newpos;
    lvi.iSubItem = 0;
    lvi.pszText = text;
    lvi.cchTextMax = (int)strlen(text);
    int retpos = ListView_InsertItem(m_list, &lvi);
    if (retpos > pos)
    {
      last_insert = retpos - 1;
    }
    else
    {
      last_insert = retpos;
    }

    if (lvi.iItem < pos)
    {
      newpos++;
    }

    if (retpos <= pos)
    {
      pos++;
    }

    for (int i = 1; i < Header_GetItemCount(ListView_GetHeader(m_list)); i++)
    {
      ListView_GetItemText(m_list, pos, i, text, sizeof(text));
      ListView_SetItemText(m_list, retpos, i, text);
    }
    ListView_DeleteItem(m_list, pos);
    pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);
  }

  //WDL_FastString numstr;

  //for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  //{
  //  ListView_GetItemText(m_list, i, 0, text, sizeof(text));

  //  //LVITEM lvi = { 0, };
  //  lvi.mask = LVIF_TEXT /* | LVIF_PARAM */;
  //  lvi.iItem = i;
  //  lvi.iSubItem = 0;
  //  //lvi.lParam = i;
  //  lvi.pszText = text;
  //  lvi.cchTextMax = (int)strlen(text);

  //  ListView_SetItem(m_list, &lvi);

  //  numstr.SetFormatted(128, "%d", i + 1);

  //  lvi.mask = LVIF_TEXT;
  //  lvi.iItem = i;
  //  lvi.iSubItem = 1;
  //  lvi.pszText = (char *)numstr.Get();
  //  lvi.cchTextMax = numstr.GetLength();

  //  ListView_SetItem(m_list, &lvi);
  //}

  UpdatePlayListData();
  UpdatePlayListIndex();

  while (selected--)
  {
    ListView_SetItemState(m_list, last_insert--,
      LVIS_SELECTED | LVIS_FOCUSED,
      LVIS_SELECTED | LVIS_FOCUSED);
  }

  //for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  //{
  //  //LVITEM lvi = { 0, };
  //  lvi.mask = LVIF_TEXT | LVIF_PARAM;
  //  lvi.iItem = i;
  //  lvi.iSubItem = 0;

  //  ListView_GetItem(m_list, &lvi);
  //  ListView_GetItemText(m_list, i, 0, text, sizeof(text));
  //  lvi.pszText = text;
  //  lvi.lParam = i;
  //  ListView_SetItem(m_list, &lvi);
  //}

#if defined(_WIN32)
  ListView_SetItemState(m_list, -1, ~LVIS_DROPHILITED, LVIS_DROPHILITED);
#endif

  int sub_item = 0;
  ColumnHeader *ch = m_column_header.Get();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      sub_item = i;
      break;
    }
  }

  WDL_PtrList_DeleteOnDestroy<char> *pl = new WDL_PtrList_DeleteOnDestroy<char>(free);

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, sub_item, text, sizeof(text));

    pl->Add(strdup(text));
  }

  g_main_loop_mutex.Enter();
  g_main_loop->UpdatePlayList(pl);
  g_main_loop_mutex.Leave();

  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);
}

void RSE_PlayListWnd::MoveAfterCurrent()
{
  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  if (pos < 0) return;

  g_main_loop_mutex.Enter();
  int cur = g_main_loop->GetCurrentTrack();
  g_main_loop_mutex.Leave();

  int movpos = cur + 1;
  int totmov = 0;
  int below = 0;

  while (pos != -1)
  {
    totmov++;
    if (pos == cur) return;
    pos = ListView_GetNextItem(m_list, pos, LVNI_SELECTED);
  }

  int focus = ListView_GetNextItem(m_list, -1, LVNI_FOCUSED);

  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

  LVITEM lvi = { 0, };
  char text[2048];

  pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  while (pos != -1)
  {
    if (pos < cur)
    {
      for (int i = 0; i < Header_GetItemCount(ListView_GetHeader(m_list)); i++)
      {
        if (!i)
        {
          lvi.mask = LVIF_TEXT | LVIF_PARAM;
          lvi.iItem = pos;
          lvi.iSubItem = i;
          lvi.pszText = text;
          lvi.cchTextMax = sizeof(text);

          ListView_GetItem(m_list, &lvi);

          lvi.iItem = movpos;
          ListView_InsertItem(m_list, &lvi);
        }
        else
        {
          ListView_GetItemText(m_list, pos, i, text, sizeof(text));
          ListView_SetItemText(m_list, movpos, i, text);
        }
      }

      movpos++;
      pos = ListView_GetNextItem(m_list, pos, LVNI_SELECTED);
    }
    else
    {
      break;
    }
  }

  pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  while (pos != -1)
  {
    if (pos < cur)
    {
      below++;
      ListView_DeleteItem(m_list, pos);
      pos = ListView_GetNextItem(m_list, pos - 1, LVNI_SELECTED);
    }
    else
    {
      break;
    }
  }

  cur -= below;
  movpos = cur + 1 + below;

  pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  while (pos != -1)
  {
    if (pos > cur)
    {
      for (int i = 0; i < Header_GetItemCount(ListView_GetHeader(m_list)); i++)
      {
        if (!i)
        {
          lvi.mask = LVIF_TEXT | LVIF_PARAM;
          lvi.iItem = pos;
          lvi.iSubItem = i;
          lvi.pszText = text;
          lvi.cchTextMax = sizeof(text);

          ListView_GetItem(m_list, &lvi);

          lvi.iItem = movpos;
          ListView_InsertItem(m_list, &lvi);
          pos++;
        }
        else
        {
          ListView_GetItemText(m_list, pos, i, text, sizeof(text));
          ListView_SetItemText(m_list, movpos, i, text);
        }
      }

      movpos++;
      ListView_DeleteItem(m_list, pos);
      pos = ListView_GetNextItem(m_list, pos - 1, LVNI_SELECTED);
    }
    else
    {
      break;
    }
  }

  int start = cur + 1;
  int end = start + totmov;

  for (int i = start; i < end; i++)
  {
    ListView_SetItemState(m_list, i, LVIS_SELECTED | LVIS_FOCUSED,
      LVIS_SELECTED | LVIS_FOCUSED);
  }

  if (focus >= 0)
  {
    ListView_SetItemState(m_list, focus, LVIS_SELECTED | LVIS_FOCUSED,
      LVIS_SELECTED | LVIS_FOCUSED);
    ListView_EnsureVisible(m_list, focus, FALSE);
  }

  int sub_item = 0;
  ColumnHeader *ch = m_column_header.Get();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      sub_item = i;
      break;
    }
  }

  WDL_PtrList_DeleteOnDestroy<char> *pl = new WDL_PtrList_DeleteOnDestroy<char>(free);

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, sub_item, text, sizeof(text));

    pl->Add(strdup(text));
  }

  g_main_loop_mutex.Enter();
  g_main_loop->UpdatePlayList(pl);
  g_main_loop_mutex.Leave();

  UpdatePlayListData();
  UpdatePlayListIndex();

  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);
}

void RSE_PlayListWnd::MoveUp()
{
  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  if (pos < 1) return;

  pos--;

  MoveTracks(pos);

  ListView_EnsureVisible(m_list, pos, FALSE);
}

void RSE_PlayListWnd::MoveDown()
{
  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  if (pos == -1) return;

  int move_pos = 0;

  while (pos != -1)
  {
    if (pos) move_pos = pos;
    pos = ListView_GetNextItem(m_list, pos, LVNI_SELECTED);
  }

  if (move_pos >= ListView_GetItemCount(m_list)) return;

  move_pos++;

  MoveTracks(move_pos);

  ListView_EnsureVisible(m_list, move_pos, FALSE);
}

void RSE_PlayListWnd::CutTracks()
{
  WDL_HeapBuf *kr = CopySelectedTracks();

  if (kr)
  {
    g_main_loop_mutex.Enter();
    g_main_loop->SetKillRegion(kr);
    g_main_loop_mutex.Leave();

    WDL_FastString strbuf;
    strbuf.SetFormatted(128, "| Cut %d bytes", kr->GetSize());
    g_main_wnd->ShowKillRegionInfo(strbuf.Get());
  }

  RemoveSelected();
}

void RSE_PlayListWnd::CopyTracks()
{
  WDL_HeapBuf *kr = CopySelectedTracks();

  if (kr)
  {
    g_main_loop_mutex.Enter();
    g_main_loop->SetKillRegion(kr);
    g_main_loop_mutex.Leave();

    WDL_FastString strbuf;
    strbuf.SetFormatted(128, "| Copied %d bytes", kr->GetSize());
    g_main_wnd->ShowKillRegionInfo(strbuf.Get());
  }
}

void RSE_PlayListWnd::PasteTracks()
{
  WDL_HeapBuf *pastedata = NULL;

  g_main_loop_mutex.Enter();
  g_main_loop->GetKillRegion(&pastedata);
  g_main_loop_mutex.Leave();

  if (!pastedata)
  {
    return;
  }

  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED | LVNI_FOCUSED);

  if (pos < 0)
  {
    return;
  }

  SendMessage(m_list, WM_SETREDRAW, FALSE, 0);

  ProjectStateContext *psc = ProjectCreateMemCtx_Read(pastedata);
  if (!psc)
  {
    return;
  }

  LineParser lp;

  LVITEM lvi = { 0, };
  WDL_String strbuf;
  int item_count = 0;
  int inspt = pos;

  while (ProjectContext_GetNextLine(psc, &lp))
  {
    //if (lp.gettoken_str(0)[0] == '>') break;
    if (lp.gettoken_str(0)[0] == '<')
    {
      if (!stricmp(lp.gettoken_str(0), "<EUTERPE_PLAYLIST"))
      {
        if (lp.gettoken_float(1) < 0.3) break;
      }
      if (!stricmp(lp.gettoken_str(0), "<PLAYLIST_ITEM"))
      {
        item_count = pos++;
      }
    }
    else
    {
      if (!stricmp(lp.gettoken_str(0), "TYPE"))
      {
        lvi.mask = LVIF_TEXT | LVIF_PARAM;
        lvi.lParam = (LPARAM)item_count;
        lvi.iItem = item_count;
        lvi.iSubItem = 0;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_InsertItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "INDEX"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 1;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "TITLE"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 2;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "ARTIST"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 3;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "ALBUM"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 4;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "LENGTH"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 5;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "GENRE"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 6;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "TRACK"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 7;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "YEAR"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 8;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "BITRATE"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 9;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "CHANNELS"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 10;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "SAMPLERATE"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 11;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "FILENAME"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 12;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "FILEEXTENSION"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 13;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "FILESIZE"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 14;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "SHA"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 15;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      if (!stricmp(lp.gettoken_str(0), "FILEPATH"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 16;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);

        g_main_loop_mutex.Enter();
        g_main_loop->AddTrack(strbuf.Get());
        g_main_loop_mutex.Leave();
      }
      if (!stricmp(lp.gettoken_str(0), "COMMENT"))
      {
        lvi.mask = LVIF_TEXT;
        lvi.iItem = item_count;
        lvi.iSubItem = 17;

        strbuf.Set(lp.gettoken_str(1));
        lvi.pszText = strbuf.Get();
        lvi.cchTextMax = strbuf.GetLength();

        ListView_SetItem(m_list, &lvi);
      }
      WDL_UTF8_HookListView(m_list);
    }
  }

  delete psc;

  SelectNone();

  for (int i = inspt; i < pos; i++)
  {
    ListView_SetItemState(m_list, i, LVIS_SELECTED | LVIS_FOCUSED,
      LVIS_SELECTED | LVIS_FOCUSED);
  }

  //ListView_EnsureVisible(m_list, inspt + pos, FALSE);

  int sub_item = 0;
  char text[2048];
  ColumnHeader *ch = m_column_header.Get();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      sub_item = i;
      break;
    }
  }

  WDL_PtrList_DeleteOnDestroy<char> *pl = new WDL_PtrList_DeleteOnDestroy<char>(free);

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, sub_item, text, sizeof(text));

    pl->Add(strdup(text));
  }

  g_main_loop_mutex.Enter();
  g_main_loop->UpdatePlayList(pl);
  g_main_loop_mutex.Leave();

  UpdatePlayListData();
  UpdatePlayListIndex();

  SendMessage(m_list, WM_SETREDRAW, TRUE, 0);

  strbuf.SetFormatted(128, "| Pasted %d bytes", pastedata->GetSize());
  g_main_wnd->ShowKillRegionInfo(strbuf.Get());
}

void RSE_PlayListWnd::OpenFileLocation()
{
  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED | LVNI_FOCUSED);

  if (pos < 0) return;

  int sub_item = 0;
  char text[2048];
  ColumnHeader *ch = m_column_header.Get();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      sub_item = i;
      break;
    }
  }

  ListView_GetItemText(m_list, pos, sub_item, text, sizeof(text));

  WDL_FastString fn(text);
  fn.remove_filepart();

  ShellExecute(m_hwnd, "", "explorer.exe", fn.Get(), "", SW_SHOWNORMAL);
}

void RSE_PlayListWnd::ApplyListViewColors(int bgc, int txtc)
{
  m_prop_wnd.ApplyListViewColors(bgc, txtc);
}

void RSE_PlayListWnd::Play(int track_num)
{
  g_main_loop_mutex.Enter();
  g_main_loop->Play(track_num);
  g_main_loop_mutex.Leave();
}

WDL_HeapBuf *RSE_PlayListWnd::CopySelectedTracks()
{
  WDL_HeapBuf *copydata = new WDL_NEW WDL_HeapBuf;
  if (!copydata)
  {
    delete copydata;
    return NULL;
  }

  ProjectStateContext *psc = ProjectCreateMemCtx_Write(copydata);
  if (!psc)
  {
    delete copydata;
    return NULL;
  }

  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  if (pos < 0)
  {
    delete psc;
    delete copydata;
    return NULL;
  }

  char text[2048];
  WDL_FastString strbuf;

  pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  psc->AddLine("<EUTERPE_PLAYLIST 0.3");

  while (pos != -1)
  {
    psc->AddLine("<PLAYLIST_ITEM %d", pos);

    for (int j = 0; j < m_column_header.GetSize(); j++)
    {
      ColumnHeader *ch = m_column_header.Get();

      switch (ch[j].id)
      {
        case ID_PL_HEADER_TYPE:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "TYPE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_INDEX:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "INDEX", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_TITLE:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "TITLE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_ARTIST:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "ARTIST", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_ALBUM:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "ALBUM", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_LENGTH:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "LENGTH", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_GENRE:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "GENRE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_TRACK:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "TRACK", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_YEAR:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "YEAR", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_BITRATE:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "BITRATE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_CHANNELS:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "CHANNELS", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_SAMPLERATE:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "SAMPLERATE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_FILENAME:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "FILENAME", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_FILEEXTENSION:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "FILEEXTENSION", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_FILESIZE:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "FILESIZE", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_SHA:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "SHA", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_FILEPATH:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "FILEPATH", strbuf.Get());
          }
          break;
        case ID_PL_HEADER_COMMENT:
          {
            ListView_GetItemText(m_list, pos, j, text, sizeof(text));
            makeEscapedConfigString(text, &strbuf);
            psc->AddLine("%s %s", "COMMENT", strbuf.Get());
          }
          break;
      }
    }

    psc->AddLine(">");

    pos = ListView_GetNextItem(m_list, pos, LVNI_SELECTED);
  }

  psc->AddLine(">");
  delete psc;

  return copydata;
}

void RSE_PlayListWnd::UpdatePlayListData()
{
  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    LVITEM lvi = { 0, };
    lvi.mask = LVIF_PARAM;
    lvi.iItem = i;

    ListView_GetItem(m_list, &lvi);
    lvi.lParam = i;
    lvi.iSubItem = 0;

    ListView_SetItem(m_list, &lvi);
  }
}

void RSE_PlayListWnd::UpdatePlayListIndex()
{
  char text[32];
  WDL_String strbuf;
  int subitem = 0;

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    ColumnHeader *ch = m_column_header.Get();

    if (ch[i].id == ID_PL_HEADER_INDEX)
    {
      subitem = i;
      break;
    }
  }

  g_main_loop_mutex.Enter();
  int cur = g_main_loop->GetCurrentTrack();
  g_main_loop_mutex.Leave();

  strbuf.SetFormatted(32, "%d", cur + 1);

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    ListView_GetItemText(m_list, i, subitem, text, sizeof(text));

    if (!strcmp(strbuf.Get(), text))
    {
      g_main_loop_mutex.Enter();
      g_main_loop->SetCurrentTrack(i);
      g_main_loop_mutex.Leave();

      break;
    }
  }

  for (int i = 0; i < ListView_GetItemCount(m_list); i++)
  {
    strbuf.SetFormatted(64, "%d", i + 1);
    ListView_SetItemText(m_list, i, subitem, strbuf.Get());
  }
}

void RSE_PlayListWnd::ScanFiles(const char *path, bool empty_buffer)
{
  WDL_DirScan dir;
  WDL_FastString fn;

  m_lp.parse(g_media_ext.Get());

  if (empty_buffer) m_files.Empty(true);

  if (!dir.First(path))
  {
    do
    {
      if (dir.GetCurrentIsDirectory())
      {
        dir.GetCurrentFullFN(&fn);

        if (strcmp(fn.get_filepart(), ".") &&
            strcmp(fn.get_filepart(), ".."))
        {
          ScanFiles(fn.Get(), false);
        }
      }
      else
      {
        WDL_FastString *str = new WDL_FastString();

        if (str)
        {
          dir.GetCurrentFullFN(str);

          bool valid_ext = false;

          for (int i = 0; i < m_lp.getnumtokens(); i++)
          {
            if (wdl_filename_cmp(str->get_fileext(), m_lp.gettoken_str(i)) == 0)
            {
              valid_ext = true;
              break;
            }
          }

          if (valid_ext)
          {
            m_files.Add(str);
          }
          else
          {
            delete str;
          }
        }
        else
        {
          delete str;
        }
      }
    }
    while (!dir.Next());
  }

  qsort(m_files.GetList(), m_files.GetSize(), sizeof(WDL_FastString *),
    sort_by_file_path);
}

void RSE_PlayListWnd::Properties()
{
  char text[2048];
  int subitem = 0;

  WDL_PtrList<char> *fns = new WDL_PtrList<char>();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    ColumnHeader *ch = m_column_header.Get();

    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      subitem = i;
      break;
    }
  }

  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  while (pos != -1)
  {
    ListView_GetItemText(m_list, pos, subitem, text, sizeof(text));

    fns->Add(strdup(text));

    pos = ListView_GetNextItem(m_list, pos, LVNI_SELECTED);
  }

  if (fns->GetSize()) m_prop_wnd.ShowProperties(fns, m_hwnd);
}

void RSE_PlayListWnd::PreviewArtwork()
{
  char text[2048];
  int subitem = 0;

  WDL_PtrList<char> *fns = new WDL_PtrList<char>();
  WDL_TypedBuf<int> *trk = new WDL_TypedBuf<int>();

  for (int i = 0; i < m_column_header.GetSize(); i++)
  {
    ColumnHeader *ch = m_column_header.Get();

    if (ch[i].id == ID_PL_HEADER_FILEPATH)
    {
      subitem = i;
      break;
    }
  }

  int pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);

  while (pos != -1)
  {
    ListView_GetItemText(m_list, pos, subitem, text, sizeof(text));

    fns->Add(strdup(text));
    trk->Add(pos);

    pos = ListView_GetNextItem(m_list, pos, LVNI_SELECTED);
  }

  if (fns->GetSize()) m_prevart_wnd.PreviewArtwork(fns, trk, m_hwnd);
}

WDL_DLGRET RSE_PlayListWnd::NewListProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
  switch (msg)
  {
  case WM_MOUSEWHEEL:
  case WM_MOUSEHWHEEL:
    //MessageBox(NULL, "playlist mousewheel", "", MB_OK);
    if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
    {
      //SendMessage(s_list, LVM_SCROLL, -wparam, 0);
      return 0;
    }
    break;
  case WM_SYSKEYDOWN:
  case WM_KEYDOWN:
    {
      switch(wparam)
      {
      case 'A':
        {
          if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
          {
            for (int i = 0; i < ListView_GetItemCount(s_list); i++)
            {
              ListView_SetItemState(s_list, i, LVIS_SELECTED, LVIS_SELECTED);
            }

            SetFocus(s_list);
          }
        }
        break;
      }
    }
    break;
  }

  return CallWindowProc(PrevListProc, hwnd, msg, wparam, lparam);
}

WDL_DLGRET RSE_PlayListWnd::ST_PlayListWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
  RSE_PlayListWnd *self = (RSE_PlayListWnd *)GetWindowLongPtr(hwnd, GWLP_USERDATA);

  if (!self && msg == WM_INITDIALOG)
  {
    SetWindowLongPtr(hwnd, GWLP_USERDATA, lparam);
    self = (RSE_PlayListWnd *)lparam;
    self->m_hwnd = hwnd;
  }

  if (self)
  {
    return self->PlayListWndProc(msg, wparam, lparam);
  }
  else
  {
    return 0;
  }
}

WDL_DLGRET RSE_PlayListWnd::PlayListWndProc(UINT msg, WPARAM wparam, LPARAM lparam)
{
#ifdef _WIN32
  if (g_scroll_message && msg == g_scroll_message)
  {
    //MessageBox(NULL, "g_scroll_message", "", MB_OK);
    msg = WM_MOUSEWHEEL;
    wparam <<= 16;
  }
#endif

  switch (msg)
  {
  case WM_MOUSEWHEEL:
  case WM_MOUSEHWHEEL:
    {
      //MessageBox(NULL, "WM_MOUSEWHEEL", "", MB_OK);
    } break;
  case WM_INITDIALOG:
    {
      g_playlist_wnd = this;

      m_list = GetDlgItem(m_hwnd, IDC_LIST1);

      m_resize.init(m_hwnd);
      m_resize.init_itemhwnd(m_list, 0.0f, 0.0f, 1.0f, 1.0f);

#if (_WIN32)
      ListView_SetExtendedListViewStyleEx(m_list,
        LVS_EX_HEADERDRAGDROP | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER,
        LVS_EX_HEADERDRAGDROP | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
#else
      ListView_SetExtendedListViewStyleEx(m_list,
        LVS_EX_HEADERDRAGDROP | LVS_EX_FULLROWSELECT,
        LVS_EX_HEADERDRAGDROP | LVS_EX_FULLROWSELECT);
#endif

      //LONG_PTR style = GetWindowLongPtr(m_list, GWL_STYLE);
      //style |=  WS_HSCROLL | WS_VSCROLL;
      //SetWindowLongPtr(m_list, GWL_STYLE, style);

      ColumnHeader default_column_header[] =
      {
        // The alignment of the leftmost column is
        // always LVCFMT_LEFT; it cannot be changed.
        { ID_PL_HEADER_TYPE, "Type", 50, LVCFMT_LEFT, 1, 0 },
        { ID_PL_HEADER_INDEX, "#", 50, LVCFMT_RIGHT, 1, 1 },
        { ID_PL_HEADER_TITLE, "Title", 200, LVCFMT_LEFT, 1, 0 },
        { ID_PL_HEADER_ARTIST, "Artist", 200, LVCFMT_LEFT, 1, 0 },
        { ID_PL_HEADER_ALBUM, "Album", 200, LVCFMT_LEFT, 1, 0 },
        { ID_PL_HEADER_LENGTH, "Length", 50, LVCFMT_RIGHT, 1, 1 },
        { ID_PL_HEADER_GENRE, "Genre", 80, LVCFMT_LEFT, 1, 0 },
        { ID_PL_HEADER_TRACK, "Track", 50, LVCFMT_RIGHT, 1, 1 },
        { ID_PL_HEADER_YEAR, "Year", 50, LVCFMT_RIGHT, 1, 1 },
        { ID_PL_HEADER_BITRATE, "Bitrate", 80, LVCFMT_RIGHT, 1, 1 },
        { ID_PL_HEADER_CHANNELS, "Channels", 50, LVCFMT_RIGHT, 1, 1 },
        { ID_PL_HEADER_SAMPLERATE, "Sample rate", 80, LVCFMT_RIGHT, 1, 1 },
        { ID_PL_HEADER_FILENAME, "File name", 150, LVCFMT_LEFT, 1, 0 },
        { ID_PL_HEADER_FILEEXTENSION, "File extension", 80, LVCFMT_LEFT, 1, 0 },
        { ID_PL_HEADER_FILESIZE, "File size", 100, LVCFMT_RIGHT, 1, 1 },
        { ID_PL_HEADER_SHA, "SHA-1", 100, LVCFMT_LEFT, 1, 0 },
        { ID_PL_HEADER_FILEPATH, "File path", 150, LVCFMT_LEFT, 1 , 0 },
        { ID_PL_HEADER_COMMENT, "Comment", 100, LVCFMT_LEFT, 1, 0 }
#if 0
        { ID_PL_HEADER_PARENT, "Parent directory", 200, LVCFMT_LEFT, 1, 0 },
        { ID_PL_HEADER_PLAYING, "Playing", 50, LVCFMT_LEFT, 1, 0},
        { ID_PL_HEADER_QUEUE, "Queue", 50, LVCFMT_LEFT, 1, 0},
#endif
      };

      m_column_header.Resize(sizeof(default_column_header) /
        sizeof(default_column_header[0]));

      for (int i = 0; i < m_column_header.GetSize(); i++)
      {
        ColumnHeader *ch = m_column_header.Get();
        ch[i] = default_column_header[i];
      }

      LVCOLUMN lvc = { 0, };
      lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;

      int w = 0;
      WDL_FastString s;

      ColumnHeader *ch = m_column_header.Get();

      for (int i = 0; i < m_column_header.GetSize(); i++)
      {
        s.SetFormatted(128, "pl_column_width_%02d", i);
        w = g_ini_file->read_int(s.Get(), ch[i].width, EUTERPE_NAME);
        lvc.cx = w;
        lvc.fmt = ch[i].fmt;
        lvc.pszText = (char *)ch[i].text;
        ListView_InsertColumn(m_list, i, &lvc);
      }

      WDL_TypedBuf<int> order_array;
      order_array.Resize(Header_GetItemCount(ListView_GetHeader(m_list)));
      int *oa = order_array.Get();

      for (int i = 0; i < order_array.GetSize(); i++)
      {
        s.SetFormatted(128, "pl_column_order_%02d", i);
        oa[i] = g_ini_file->read_int(s.Get(), i, EUTERPE_NAME);
      }

      ListView_SetColumnOrderArray(m_list, order_array.GetSize(), order_array.Get());

      m_context_menu = (HMENU)GetSubMenu(LoadMenu(g_inst, MAKEINTRESOURCE(IDR_MAIN_CONTEXT)), 0);
      m_column_menu = (HMENU)GetSubMenu(LoadMenu(g_inst, MAKEINTRESOURCE(IDR_MAIN_PLAYLIST)), 0);
      m_sort_menu = (HMENU)GetSubMenu(LoadMenu(g_inst, MAKEINTRESOURCE(IDR_MAIN_PLAYLIST)), 1);

      PrevListProc = (WNDPROC)SetWindowLongPtr(GetDlgItem(m_hwnd, IDC_LIST1),
        GWLP_WNDPROC, (LONG_PTR)NewListProc);

      s_list = m_list;

      int bgc = (0 << 16) | (0 << 8) | (0 << 0);
      bgc = g_ini_file->read_int("pl_bg_color", bgc, "preferences");
      int txtc = (192 << 16) | (192 << 8) | (192 << 0);
      txtc = g_ini_file->read_int("pl_txt_color", txtc, "preferences");

      ListView_SetBkColor(m_list, RGB((bgc >> 16) & 0xFF,
        (bgc >> 8) & 0xFF, (bgc >> 0) & 0xFF));
      ListView_SetTextBkColor(m_list, RGB((bgc >> 16) & 0xFF,
        (bgc >> 8) & 0xFF, (bgc >> 0) & 0xFF));
      ListView_SetTextColor(m_list, RGB((txtc >> 16) & 0xFF,
        (txtc >> 8) & 0xFF, (txtc >> 0) & 0xFF));

      int defcol = g_ini_file->read_int("pl_defcol", 1, EUTERPE_NAME);

      if (defcol)
      {
        SendMessage(m_hwnd, WM_COMMAND, ID_PL_HEADER_DEFAULT, 0);
      }

      //SetTimer(m_hwnd, RSE_PL_WANT_NEXT_TRACK, RSE_PL_WANT_NEXT_TRACK_MS, NULL);

      WDL_FastString fn;
      fn.Set(g_settings_path.Get());
      fn.Append("euterpe.edb");

      WDL_FastString pl_path(g_settings_path);
      pl_path.Append("euterpe.epl");

      LoadPlayList(pl_path.Get());

      //BringWindowToTop(m_list);
    }

    break;

  case WM_TIMER:
    {

    }
    break;

  case WM_DESTROY:
    {
      WDL_FastString s;
      WDL_TypedBuf<int> order_array;

      order_array.Resize(Header_GetItemCount(ListView_GetHeader(m_list)));
      ListView_GetColumnOrderArray(m_list, order_array.GetSize(), order_array.Get());

      int *oa = order_array.Get();

      for (int i = 0; i < order_array.GetSize(); i++)
      {
        s.SetFormatted(128, "pl_column_order_%02d", i);
        g_ini_file->write_int(s.Get(), oa[i], EUTERPE_NAME);
      }

      for (int i = 0; i < Header_GetItemCount(ListView_GetHeader(m_list)); i++)
      {
        s.SetFormatted(128, "pl_column_width_%02d", i);
        g_ini_file->write_int(s.Get(), ListView_GetColumnWidth(m_list, i), EUTERPE_NAME);
      }

      if (m_context_menu)
      {
        DestroyMenu(m_context_menu);
        m_context_menu = NULL;
      }

      if (m_column_menu)
      {
        DestroyMenu(m_column_menu);
        m_column_menu = NULL;
      }

      if (m_sort_menu)
      {
        DestroyMenu(m_sort_menu);
        m_sort_menu = NULL;
      }

      WDL_FastString pl_path(g_settings_path);
      pl_path.Append("euterpe.epl");

      SavePlayList(pl_path.Get());

      m_hwnd = NULL;
    }
    break;

  case WM_DROPFILES:
    {
      HDROP drop = (HDROP)wparam;

      POINT pt;
      GetCursorPos(&pt);

      LVHITTESTINFO ht;
      ht.pt.x = pt.x;
      ht.pt.y = pt.y;
      ScreenToClient(m_list, &ht.pt);
      ListView_HitTest(m_list, &ht);

      //WDL_FastString ss;
      //ss.SetFormatted(111, "%d", ht.iItem);
      //MessageBox(NULL, ss.Get(), "", MB_OK);
      //return 0;

      // TODO: Drop items to certain position.
      // You will need to adjust AddItem...

      char text[2048];

      // Count list before insert, we will
      // substract later to find the actual
      // inserts.
      int nn = ListView_GetItemCount(m_list);

      const int n = DragQueryFile(drop, -1, NULL, 0);

      for (int i = 0; i < n; i++)
      {
        DragQueryFile(drop, i, text, sizeof(text));

        WDL_DirScan tmpdir;

        if (tmpdir.First(text) == 0)
        {
          ScanFiles(text, true);

          for (int j = 0; j < m_files.GetSize(); j++)
          {
            AddItem(m_files.Get(j)->Get());
          }

          m_files.Empty(true);
        }
        else
        {
          AddItem(text);
        }
      }

      DragFinish(drop);

      int total = ListView_GetItemCount(m_list);
      nn = total - nn; // actual inserts
      int last = total - 1;
      int insert_point = total - nn;

      for (int i = 0; i < total; i++)
      {
        ListView_SetItemState(m_list, i, ~LVIS_SELECTED, LVIS_SELECTED);
      }

      for (int i = total; i >= insert_point; i--)
      {
        ListView_SetItemState(m_list, i, LVIS_SELECTED, LVIS_SELECTED);
      }

      ListView_SetItemState(m_list, last, LVIS_FOCUSED, LVIS_FOCUSED);

      ListView_EnsureVisible(m_list, last, FALSE);
    }
    break;

  case WM_SIZE:
    {
      if (wparam != SIZE_MINIMIZED)
      {
        m_resize.onResize();

        int pos = ListView_GetNextItem(m_list, -1, LVIS_FOCUSED);
        if (pos >= 0)
        {
          ListView_EnsureVisible(m_list, pos, FALSE);
        }
      }
    }
    break;

  case WM_MOVE:
    {

    }
    break;

  case WM_ERASEBKGND:
    {
      //return TRUE;
      return 1;
    }
    break;

  case WM_PAINT:
    {
      //PAINTSTRUCT ps;
      //int cx = GetSystemMetrics(SM_CXSCREEN);
      //int cy = GetSystemMetrics(SM_CYSCREEN);

      //HDC dc = BeginPaint(m_hwnd, &ps);
      //BitBlt(dc, 0, 0, cx, cy, dc, 0, 0, SRCCOPY);
      //EndPaint(m_hwnd, &ps);
    }
    break;

  case WM_COMMAND:
    {
      switch (LOWORD(wparam))
      {
      case ID_PL_HEADER_TYPE:
      case ID_PL_HEADER_TITLE:
      case ID_PL_HEADER_ARTIST:
      case ID_PL_HEADER_ALBUM:
      case ID_PL_HEADER_LENGTH:
      case ID_PL_HEADER_GENRE:
      case ID_PL_HEADER_TRACK:
      case ID_PL_HEADER_YEAR:
      case ID_PL_HEADER_BITRATE:
      case ID_PL_HEADER_CHANNELS:
      case ID_PL_HEADER_SAMPLERATE:
      case ID_PL_HEADER_FILENAME:
      case ID_PL_HEADER_FILEPATH:
      case ID_PL_HEADER_FILEEXTENSION:
      case ID_PL_HEADER_FILESIZE:
      case ID_PL_HEADER_INDEX:
      case ID_PL_HEADER_SHA:
      case ID_PL_HEADER_COMMENT:
        for (int i = 0; i < m_column_header.GetSize(); i++)
        {
          ColumnHeader *ch = m_column_header.Get();

          if (ch[i].id == LOWORD(wparam))
          {
            MENUITEMINFO mii = { 0 };
            mii.cbSize = sizeof(mii);
            mii.fMask = MIIM_STATE;

            if (GetMenuItemInfo(m_column_menu, ch[i].id, FALSE, &mii))
            {
              if (!(mii.fState & MFS_CHECKED))
              {
                ListView_SetColumnWidth(m_list, i, ch[i].width);
              }
              else if (mii.fState & MFS_CHECKED)
              {
                ListView_SetColumnWidth(m_list, i, 0);
              }

              //mii.fState ^= MFS_CHECKED;
              //SetMenuItemInfo(m_column_menu, pl_column_info[i].id, FALSE, &mii);
              g_ini_file->write_int("pl_defcol", 0, EUTERPE_NAME);
            }
          }
        }
        break;
      case ID_PL_HEADER_DEFAULT:
        {
          int defzero[] =
          {
            ID_PL_HEADER_TYPE,
            ID_PL_HEADER_GENRE,
            ID_PL_HEADER_TRACK,
            ID_PL_HEADER_YEAR,
            ID_PL_HEADER_CHANNELS,
            ID_PL_HEADER_FILENAME,
            ID_PL_HEADER_FILEPATH,
            ID_PL_HEADER_FILEEXTENSION,
            ID_PL_HEADER_SHA,
            ID_PL_HEADER_COMMENT
          };

          int defstd[] =
          {
            ID_PL_HEADER_INDEX,
            ID_PL_HEADER_TITLE,
            ID_PL_HEADER_ARTIST,
            ID_PL_HEADER_ALBUM,
            ID_PL_HEADER_LENGTH,
            ID_PL_HEADER_BITRATE,
            ID_PL_HEADER_SAMPLERATE,
            ID_PL_HEADER_FILESIZE
          };

          for (int i = 0, it = 0; i < (sizeof(defzero) / sizeof(defzero[0])); i++)
          {
            for (int j = 0; j < m_column_header.GetSize(); j++)
            {
              if (m_column_header.Get()[j].id == defzero[i])
              {
                it = j;
                break;
              }
            }

            ListView_SetColumnWidth(m_list, it, 0);
          }

          for (int i = 0, it = 0, w = 0; i < (sizeof(defstd) / sizeof(defstd[0])); i++)
          {
            for (int j = 0; j < m_column_header.GetSize(); j++)
            {
              if (m_column_header.Get()[j].id == defstd[i])
              {
                it = j;
                w = m_column_header.Get()[j].width;
                break;
              }
            }

            ListView_SetColumnWidth(m_list, it, w);
          }
        }
        break;
      case ID_PLAYLIST_SORT_TYPE:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_TYPE) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_INDEX:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_INDEX) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_TITLE:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_TITLE) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_FILEPATH:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_FILEPATH) { Sort(i); break; }
          }
        }
        break;

      case ID_PLAYLIST_SORT_ARTIST:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_ARTIST) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_ALBUM:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_ALBUM) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_LENGTH:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_LENGTH) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_GENRE:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_GENRE) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_TRACK:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_TRACK) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_YEAR:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_YEAR) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_BITRATE:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_BITRATE) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_CHANNELS:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_CHANNELS) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_SAMPLERATE:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_SAMPLERATE) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_FILENAME:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_FILENAME) { Sort(i); break; }
          }
        }
        break;
     case ID_PLAYLIST_SORT_FILEEXTENSION:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_FILEEXTENSION) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_FILESIZE:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_FILESIZE) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_SHA:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_SHA) { Sort(i); break; }
          }
        }
        break;
      case ID_PLAYLIST_SORT_COMMENT:
        {
          ColumnHeader *ch = m_column_header.Get();
          for (int i = 0; i < m_column_header.GetSize(); i++)
          {
            if (ch[i].id == ID_PL_HEADER_COMMENT) { Sort(i); break; }
          }
        }
        break;
      case ID_PL_PLAY:
        {
          PlaySelected();
        }
        break;
      case ID_PL_OPENFILELOCATION:
        {
          OpenFileLocation();
        }
        break;
      case ID_PL_REMOVE:
        {
          RemoveSelected();
        }
        break;
      case ID_PL_CROP:
        {
          CropSelected();
        }
        break;
      case ID_PL_CUT:
        {
          SendMessage(g_main_wnd->Handle(), WM_COMMAND, ID_EDIT_CUT_TRACKS, 0);
        }
        break;
      case ID_PL_COPY:
        {
          SendMessage(g_main_wnd->Handle(), WM_COMMAND, ID_EDIT_COPY_TRACKS, 0);
        }
        break;
      case ID_PL_PASTE:
        {
          SendMessage(g_main_wnd->Handle(), WM_COMMAND, ID_EDIT_PASTE_TRACKS, 0);
        }
        break;
      case ID_PL_QUEUE:
        {
          AddToQueue();
        }
        break;
      case ID_PL_CLEARQUEUE:
        {
          ClearQueue();
        }
        break;
      case ID_PL_PROPERTIES:
        {
          Properties();
        }
        break;
      case ID_PL_PREVIEWARTWORK:
        {
          PreviewArtwork();
        }
        break;
      }
    }
    break;

  case WM_CONTEXTMENU:
    {
      POINT pt;
      pt.x = GET_X_LPARAM(lparam);
      pt.y = GET_Y_LPARAM(lparam);
      ScreenToClient(m_hwnd, (LPPOINT)&pt);

      HWND h = ListView_GetHeader(GetDlgItem(m_hwnd, IDC_LIST1));
      RECT r;
      GetWindowRect(h, &r);
      ScreenToClient(m_hwnd, (LPPOINT)&r);
      ScreenToClient(m_hwnd, ((LPPOINT)&r)+1);

      ColumnHeader *ch = m_column_header.Get();

      EnableMenuItem(m_column_menu, ID_PL_HEADER_INFO, MF_GRAYED);

#if defined(__APPLE__) || defined(__linux__)
      r.bottom = r.top + SWELL_GetListViewHeaderHeight(m_list);
#endif

      if (pt.x > r.left && pt.y > r.top && pt.x < r.right && pt.y < r.bottom)
      {
        for (int i = 0; i < m_column_header.GetSize(); i++)
        {
          if (ListView_GetColumnWidth(m_list, i))
          {
            CheckMenuItem(m_column_menu, ch[i].id, MF_CHECKED);
          }
          else
          {
            CheckMenuItem(m_column_menu, ch[i].id, MF_UNCHECKED);
          }
        }

        ClientToScreen(m_hwnd, (LPPOINT)&pt);
        TrackPopupMenu(m_column_menu, TPM_LEFTALIGN, pt.x, pt.y, 0, m_hwnd, NULL);
      }
      else
      {
        GetCursorPos(&pt);
        int pos = ListView_GetNextItem(m_list, -1, LVIS_SELECTED);

        if (pos >= 0)
        {
          g_main_loop_mutex.Enter();
          WDL_HeapBuf *kr = NULL;
          g_main_loop->GetKillRegion(&kr);
          g_main_loop_mutex.Leave();

          if (kr)
          {
            EnableMenuItem(m_context_menu, ID_PL_PASTE, MF_ENABLED);
          }
          else
          {
            //EnableMenuItem(m_context_menu, ID_PL_PASTE, MF_DISABLED);
            EnableMenuItem(m_context_menu, ID_PL_PASTE, MF_GRAYED);
          }

          TrackPopupMenu(m_context_menu, TPM_LEFTALIGN, pt.x, pt.y, 0, m_hwnd, NULL);
        }
      }
    }
    break;
  case WM_NOTIFY:
    switch (LOWORD(wparam))
    {
    case IDC_LIST1:
      if (((LPNMHDR)lparam)->code == LVN_BEGINDRAG)
      {
        m_drag_started = true;
        SetFocus(m_list);
        SetCapture(m_hwnd);
      }
      if (((LPNMHDR)lparam)->code == LVN_COLUMNCLICK)
      {
        LPNMLISTVIEW lv = (LPNMLISTVIEW)lparam;
        Sort(lv->iSubItem);
      }
      if (((LPNMHDR)lparam)->code == NM_DBLCLK)
      {
        int pos = ListView_GetNextItem(m_list, -1, LVIS_SELECTED | LVIS_FOCUSED);

        if (pos >= 0)
        {
          g_main_loop_mutex.Enter();
          g_main_loop->Stop();
          g_main_loop->Play(pos);
          g_main_loop_mutex.Leave();
        }
      }
    }
    break;
  case WM_MOUSEMOVE:
    {
      if (m_drag_started)
      {
        LVHITTESTINFO ht;
        ht.pt.x = LOWORD(lparam);
        ht.pt.y = HIWORD(lparam);
        ClientToScreen(m_hwnd, &ht.pt);
        ScreenToClient(m_list, &ht.pt);
        ListView_HitTest(m_list, &ht);

        WDL_FastString str;

        if (IsStableRelease())
        {
          str.SetFormatted(128, "%s ", EUTERPE_NAME_MARKETING);
        }
        else
        {
          str.SetFormatted(128, "%s ", EUTERPE_FULL_VERSION " (" EUTERPE_ARCH ")");
        }

        if (ht.iItem >= 0)
        {
          str.AppendFormatted(128, "| Move to %d", ht.iItem + 1);
        }

        SetWindowText(g_main_wnd->Handle(), str.Get());

        if (m_hwnd != GetCapture()) m_drag_started = false;
      }
    }
    break;
  case WM_LBUTTONUP:
    {
      LVITEM lvi = { 0, };
      char text[2048];
      int pos = -1;
      int selected = 0;
      int last_insert = 0;

      ReleaseCapture();
      m_drag_started = false;

      // when mouse move is complete set focus back to the selected item
      pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);
      ListView_SetItemState(m_list, pos, LVNI_SELECTED | LVNI_FOCUSED, LVNI_SELECTED | LVNI_FOCUSED);
      pos = -1;

      LVHITTESTINFO ht;
      ht.pt.x = LOWORD(lparam);
      ht.pt.y = HIWORD(lparam);
      ClientToScreen(m_hwnd, &ht.pt);
      ScreenToClient(m_list, &ht.pt);
      ListView_HitTest(m_list, &ht);

      if (ht.iItem == -1) // Out of list view
        return 0;

      if (((ht.flags & LVHT_ONITEMLABEL) == 0) && // Not in an item
        ((ht.flags & LVHT_ONITEMSTATEICON) == 0))
        return 0;

      // Dropped item is selected?
      //lvi.iItem = ht.iItem;
      //lvi.iSubItem = 0;
      //lvi.mask = LVIF_STATE;
      //lvi.stateMask = LVIS_SELECTED;
      //ListView_GetItem(m_list, &lvi);
      //if (lvi.state & LVIS_SELECTED)
      //  return;

      pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);
      if (ht.iItem > pos) ht.iItem++; // place item over hittest point
      while (pos != -1)
      {
        selected++;

        lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_STATE;
        lvi.iItem = pos;
        //lvi.lParam = pos;
        lvi.iSubItem = 0;
        lvi.pszText = text;
        lvi.cchTextMax= sizeof(text);
        lvi.state = ~LVNI_SELECTED;
        ListView_GetItem(m_list, &lvi);
        ListView_GetItemText(m_list, pos, 0, text, sizeof(text));
        lvi.iItem = ht.iItem;
        //lvi.lParam = ht.iItem;
        lvi.iSubItem = 0;
        lvi.pszText = text;
        lvi.cchTextMax = (int)strlen(text);
        int retpos = ListView_InsertItem(m_list, &lvi);
        if (retpos > pos)
        {
          last_insert = retpos - 1;
        }
        else
        {
          last_insert = retpos;
        }

        if (lvi.iItem < pos)
        {
          ht.iItem++;
        }

        if (retpos <= pos)
        {
          pos++;
        }

        for (int i = 1; i < Header_GetItemCount(ListView_GetHeader(m_list)); i++)
        {
          ListView_GetItemText(m_list, pos, i, text, sizeof(text));
          ListView_SetItemText(m_list, retpos, i, text);
        }
        ListView_DeleteItem(m_list, pos);
        pos = ListView_GetNextItem(m_list, -1, LVNI_SELECTED);
      }

      //WDL_FastString numstr;

      //for (int i = 0; i < ListView_GetItemCount(m_list); i++)
      //{
      //  ListView_GetItemText(m_list, i, 0, text, sizeof(text));

      //  //LVITEM lvi = { 0, };
      //  lvi.mask = LVIF_TEXT /* | LVIF_PARAM */;
      //  lvi.iItem = i;
      //  lvi.iSubItem = 0;
      //  //lvi.lParam = i;
      //  lvi.pszText = text;
      //  lvi.cchTextMax = (int)strlen(text);

      //  ListView_SetItem(m_list, &lvi);

      //  numstr.SetFormatted(128, "%d", i + 1);

      //  lvi.mask = LVIF_TEXT;
      //  lvi.iItem = i;
      //  lvi.iSubItem = 1;
      //  lvi.pszText = (char *)numstr.Get();
      //  lvi.cchTextMax = numstr.GetLength();

      //  ListView_SetItem(m_list, &lvi);
      //}

      UpdatePlayListData();
      UpdatePlayListIndex();

      while (selected--)
      {
        ListView_SetItemState(m_list, last_insert--,
          LVIS_SELECTED | LVIS_FOCUSED,
          LVIS_SELECTED | LVIS_FOCUSED);
      }

      //for (int i = 0; i < ListView_GetItemCount(m_list); i++)
      //{
      //  //LVITEM lvi = { 0, };
      //  lvi.mask = LVIF_TEXT | LVIF_PARAM;
      //  lvi.iItem = i;
      //  lvi.iSubItem = 0;

      //  ListView_GetItem(m_list, &lvi);
      //  ListView_GetItemText(m_list, i, 0, text, sizeof(text));
      //  lvi.pszText = text;
      //  lvi.lParam = i;
      //  ListView_SetItem(m_list, &lvi);
      //}

      int sub_item = 0;
      ColumnHeader *ch = m_column_header.Get();

      for (int i = 0; i < m_column_header.GetSize(); i++)
      {
        if (ch[i].id == ID_PL_HEADER_FILEPATH)
        {
          sub_item = i;
          break;
        }
      }

      WDL_PtrList_DeleteOnDestroy<char> *pl = new WDL_PtrList_DeleteOnDestroy<char>(free);

      for (int i = 0; i < ListView_GetItemCount(m_list); i++)
      {
        ListView_GetItemText(m_list, i, sub_item, text, sizeof(text));

        pl->Add(strdup(text));
      }

      g_main_loop_mutex.Enter();
      g_main_loop->UpdatePlayList(pl);
      g_main_loop_mutex.Leave();
    }
    break;
  }

  //return DefWindowProc(m_hwnd, msg, wparam, lparam);
  return 0;
}
