#include "Player.h"

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

#include "WimpLib:Choices.h"
#include "WimpLib:mem.h"
#include "WimpLib:DragDrop.h"
#include "WimpLib:Exception.h"
#include "WimpLib:File.h"
#include "WimpLib:Font.h"
#include "WimpLib:Hourglass.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:Message.h"
#include "WimpLib:Slider.h"
#include "WimpLib:Task.h"
#include "WimpLib:Template.h"
#include "WimpLib:Timer.h"
#include "WimpLib:Utils.h"
#include "WimpLib:Window.h"

#include "Cmds.h"
#include "DCDswi.h"
#include "DigitalCD.h"
#include "Options.h"
#include "PlayList.h"

#define	Panel_DockTop      0x00000001
#define	Panel_DockLeft     0x00000002
#define	Panel_DockBottom   0x00000004
#define	Panel_DockRight    0x00000008
#define	Panel_DockIconbar  0x00000010
#define	Panel_DockMask     0x0000001f
#define	Panel_ForceDocking ~0x7fffffff // Used on first open and mode changes

typedef struct
{
	const char* m_pTemplateName;
	const char* m_pPrefName;
	CTemplate*  m_pTemplate;
	HWind       m_w;
	int         m_docking;
	CPoint      m_Pos;
	HIcon       m_VolumeButton;
} Panel;

struct Player
{
	Panel               m_Ctrl;
	Panel               m_Mini;
	Panel*              m_CurrentPanel;

	int                 nr;
	const char*         m_pSuffix;

	Media_Status        Status;
	int                 m_PollTime;

	int                 m_AuthorMetaMode;
	int                 m_AuthorMetaTime;

	float               m_DragPos;
	int                 m_DragVolume;

	struct
	{
		int                 current;
		bool				revertdirection;
		void*               handle;
		int                 next[2];
		MediaPosition       pos;
		int                 shufflecount;
		bool				prepnext;
	}                   track;

	Media_LoopMode      LoopMode;
	unsigned int        ShuffleMode[2];
	unsigned int        m_Flags;
	bool                ListMode;
	int                 TimeMode;
	int                 Volume;

	struct PlayList*    pPlayList;
	WList               m_TrackList;

	FPlayer_ReloadSkin  m_pReloadSkin;
	const CmdHandler*   m_pCmdHandler;
	void*               m_pCmdHandle;

	// Plug-Ins
	int                 m_DCDUtilsHandle; // DCDUtils handle
	int                 m_SongsCount;     // Count number of tracks played
	int                 m_HardwareType;
	int                 m_PlugInMetaMode; // Title message
	int                 ErrorCount;
	bool                ErrorReversedDirection;
};

#define Icon_Play         1
#define Icon_Pause        2
#define Icon_Stop         3
#define Icon_Rewind       4
#define Icon_Forward      5
#define Icon_Previous     6
#define Icon_Next         7
#define Icon_TrackText    8
#define Icon_Track        9
#define Icon_Time        13
#define Icon_TimeMode    19
#define Icon_SetTimeMode 20
#define Icon_SetPos      21
#define Icon_SetPos2     22
#define Icon_Volume      23
#define Icon_LoopMode    24
#define Icon_ShuffleMode 25
#define Icon_IntroScan   26
#define Icon_ListMode    27
#define Icon_SetTrack    28
#define Icon_DiscName    29
#define Icon_TrackName   30
#define Icon_Back        31
#define Icon_Close       32
#define Icon_Pin         33
#define Icon_Mini        34

#define Volume_Max 6553600
#define MAX_TIMEMODE 5

static const char Var_Player[]		= "Player";
static const char Var_Open[]		= "Open";
static const char Var_RepeatMode[]	= "RepeatMode";
static const char Var_IntroScanMode[]	= "IntroScanMode";
static const char Var_ProgramMode[]	= "ProgramMode";
static const char Var_ShuffleMode[]	= "ShuffleMode";
static const char Var_ShuffleMode2[]	= "ShuffleMode2";
static const char Var_TimeMode[]	= "TimeMode";
static const char Var_Volume[]		= "Volume";
static const char Var_NewVolume[]	= "NewVolume";
static const char Var_NextTrack[]	= "NextTrack";
static const char Var_MiniMode[]	= "MiniMode";
static const char Var_Shortcuts[]	= "ShortCuts";
static const char Var_Author[]		= "Author";
static const char Var_Title[]		= "Title";
static const char Var_Track[]		= "Track";
static const char Var_FontName[]	= "FontName";
static const char Var_FontSizeX[]	= "FontSizeX";
static const char Var_FontSizeY[]	= "FontSizeY";
static const char Var_Vertical[]	= "Vertical";
static const char Var_MaxSprite[]	= "MaxSprite";
static const char Var_Button[]		= "Button";
static const char Var_PosBar[]		= "PosBar";

static List* Players = NULL;
static CSpriteArea*	pCtrlSprites = NULL;
static int			AuthorSizeX;
static int			AuthorSizeY;
static const char*	AuthorFontName = NULL;
static HFont		AuthorFont = 0;
static int			TitleSizeX;
static int			TitleSizeY;
static const char*	TitleFontName = NULL;
static HFont		TitleFont = 0;
static int			TrackSizeX;
static int			TrackSizeY;
static const char*	TrackFontName = NULL;
static HFont		TrackFont = 0;
static int			TimeSizeX;
static int			TimeSizeY;
static const char*	TimeFontName = NULL;
static HFont		TimeFont = 0;
static int			VolumeType = ESliderType_Vertical;
static int			VolumeSprites = 20;
static HIcon		VolumeButton = HIcon_None;
static int			PosBarType = 0;

static void Player_ReShuffle(Player*, bool next);
static void Player_SetTrackPos(Player*, float value);

static void Player_Init(Player*, bool identify, bool buildlist);
static void Player_ReadCurrentPos(Player*);
static void Player_StartTrack(Player* This, int index, bool reversedirection);
static void Player_PlayDirectedTrack(Player* This, int index, int offset);

static void Player_FixInfo(Player* This, unsigned int flags);
static EListenerAction Player_MsgHandler(void* handle, const Event* e);
static EListenerAction Player_EventHandler(void* handle, const Event* e);
static EListenerAction Player_NullEvent(void* handle, const Event* e);

static const CmdHandler Player_CmdHandler =
{ Player_CheckCommand
, Player_ExecCommand
};

/*-------------------------------------------------------------------------*
 *--- Accessors -----------------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Returns the player's controller number.
 *
 * This number is also used externally for remote control of DigitalCD
 * through the exchange of DigitalCD_Misc (0x5327E) messages.
 */

int Player_GetNr(Player* This)
{
	return This->nr;
}

/**
 * Returns the player handle returned when registering this controller in DCDUtils.
 */

int Player_GetDCDUtilsHandle(Player* This)
{
	return This->m_DCDUtilsHandle;
}

/**
 * Returns the Playlist associated to this controller or NULL.
 */

PlayList* Player_GetPlayList(Player* This)
{
	return This->pPlayList;
}

/**
 * Returns true if the controller will accept to gain input focus
 * in order to process keyboard shortcuts.
 */

bool Player_AllowsShortcuts(Player* This)
{
	return ((This->m_Flags & Player_Shortcuts) != 0);
}

/**
 * Returns a controller Wimp window handle.
 *
 * @param  type  -1 current window in use
 *                0 large panel window
 *                1 mini panel window
 */

HWind Player_GetWindow(Player* This, int type)
{
	switch(type)
	{
		case 0: return This->m_Ctrl.m_w;
		case 1: return This->m_Mini.m_w;
	}

	return This->m_CurrentPanel->m_w;
}

/**
 * Returns the media hardware type corresponding to the current track.
 */

int Player_GetHardwareType(Player* This)
{
	return This->m_HardwareType;
}

/**
 * Returns the player's current status.
 */

Media_Status Player_GetStatus(Player* This)
{
	return This->Status;
}

/**
 * Returns the player's shuffle mode.
 */

unsigned int Player_GetShuffleMode(Player* This)
{
	if (This->m_Flags & Player_SeparateShuffle)
		return This->ShuffleMode[This->ListMode];

	return This->ShuffleMode[0];
}

/**
 * Returns the player's shuffle mode, taking disable all into account.
 */

static unsigned int Player_GetActiveShuffleMode(Player* This)
{
	unsigned int shuffle = Player_GetShuffleMode(This);

	if (shuffle & 0x80000000)
		return 0;

	return shuffle;
}

/**
 * Returns true if there is a track currently active (playing or paused).
 */

bool Player_IsStatusActive(Player* This)
{
	if ((This->Status == status_play)
	||  (This->Status == status_busy)
	||  (This->Status == status_pause))
		return true;

	return false;
}

/**
 * Returns true if there is a track currently playing.
 */

bool Player_IsStatusPlaying(Player* This)
{
	if ((This->Status == status_play)
	||  (This->Status == status_busy))
		return true;

	return false;
}

/**
 * Returns a void pointer to the current track or NULL.
 *
 * Only the attached playlist knows the structure hiding behind that pointer.
 */

static void* Player_GetCurrentTrack(Player* This)
{
	if (This->track.current == -1) return NULL;

	return This->track.handle;
}

/**
 * Returns a void pointer to the current track if active or NULL.
 *
 * Only the attached playlist knows the structure hiding behind that pointer.
 */

static void* Player_GetActiveTrack(Player* This)
{
	if ((This->track.current == -1)
	||  !Player_IsStatusActive(This))
		return NULL;

	return This->track.handle;
}

/**
 * Returns a volume in range [0, Volume_Max].
 *
 * When the user is dragging the volume slider, the volume corresponding
 * to the current position of the slider is returned.
 *
 * In normal use, the volume returned is not the volume originally set
 * by the user but the value returned by the media behind the playlist
 * to reflect any limitation (non-linrearity, limied numer of steps, ...)
 * of the said media.
 */

static int Player_ReadVolume(Player* This)
{
	if (This->m_DragVolume >= 0)
		return This->m_DragVolume;

	This->Volume = PlayList_GetVolume(This->pPlayList);
	return This->Volume;
}

static bool Player_TrackChanged(Player* This)
{
	return (  (This->track.pos.driver.pos <  This->track.pos.driver.start)
	       || (This->track.pos.driver.pos >= This->track.pos.driver.end));
}

static bool Player_IsTrackPlayed(Player* This)
{
	return (PlayList_GetStatus(This->pPlayList) == status_stop);
}

/*-------------------------------------------------------------------------*
 *--- Display functions ---------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Shows the current display time mode.
 */

static void Player_ShowTimeMode(Player* This)
{
	static char token[] = "Time0";

	snprintf(token, sizeof(token), "Time%d", This->TimeMode);
	Icon_SetData(This->m_CurrentPanel->m_w, Icon_TimeMode, Msg_Lookup(token));
}

/**
 * Refreshes the state of the Play, Pause and Stop buttons
 * according to the current song status.
 */

static void Player_ShowStatus(Player* This)
{
	Icon_SetHighlight(This->m_CurrentPanel->m_w, Icon_Play, Player_IsStatusPlaying(This));
	Icon_SetHighlight(This->m_CurrentPanel->m_w, Icon_Pause, (This->Status == status_pause));
	Icon_SetHighlight(This->m_CurrentPanel->m_w, Icon_Stop, !Player_IsStatusActive(This));

	Menu_Refresh(This->m_CurrentPanel->m_w, NULL);
}

/**
 * Send title information to plug-in.
 */

static void Player_ShowTitleMessage(Player* This, const char* pmsg)
{
	DCD_Misc_Message msg = {{sizeof(DCD_Misc_Message), 0, 0, 0, EMsg_DCD_Misc}, 0};

	msg.reason = 6;
	msg.controller = This->m_DCDUtilsHandle;
	strncpy(msg.data.text, pmsg, sizeof(msg.data.text));
	msg.hdr.size = (msg.data.text - (char*) &msg) + strlen(msg.data.text) + 1;
	Task_SendMsg((Msg*) &msg, 0);

	msg.reason = 7;
	msg.controller = Player_GetNr(This);
	strncpy(msg.data.text, pmsg, sizeof(msg.data.text));
	msg.hdr.size = (msg.data.text - (char*) &msg) + strlen(msg.data.text) + 1;
	Task_SendMsg((Msg*) &msg, 0);
}

/**
 * Refreshes the contents of the secondary text line.
 * The contents changes every 5 seconds between track album, author and type.
 * If absent it uses in order: album, author and type of track or list.
 */

#define COUNT_AUTHORMETAMODE 7
static const EMetaId AuthorMetas[COUNT_AUTHORMETAMODE] =
{ EMetaId_StreamStation
, EMetaId_StreamAlbum
, EMetaId_StreamBox
, EMetaId_StreamDate
, EMetaId_StreamArtist
, EMetaId_StreamCollective
, EMetaId_StreamType
};

#define COUNT_PLUGINMETAMODE 8
static const EMetaId PlugInMetas[COUNT_PLUGINMETAMODE] =
{ EMetaId_StreamTitle
, EMetaId_StreamStation
, EMetaId_StreamAlbum
, EMetaId_StreamBox
, EMetaId_StreamDate
, EMetaId_StreamArtist
, EMetaId_StreamCollective
, EMetaId_StreamType
};

static void Player_ShowListName(Player* This)
{
	int newtime = Timer_GetTime();
	CIcon icon;
	int newlen = 0;
	int mode;
	const char* text = &nil;
	char* string;
	void* pTrack = Player_GetActiveTrack(This);

	if ((newtime - This->m_AuthorMetaTime) >= 0)
	{
		This->m_AuthorMetaTime = newtime + 500;
		This->m_AuthorMetaMode += 1;
		if (This->m_AuthorMetaMode >= COUNT_AUTHORMETAMODE) This->m_AuthorMetaMode = 0;
		This->m_PlugInMetaMode += 1;
		if (This->m_PlugInMetaMode >= COUNT_PLUGINMETAMODE) This->m_PlugInMetaMode = 0;
	}

	// Loop till some text to show
	mode = This->m_AuthorMetaMode;
	do
	{
		text = PlayList_GetText(This->pPlayList, pTrack, AuthorMetas[This->m_AuthorMetaMode]);
		if (!text[0])
			text = PlayList_GetText(This->pPlayList, NULL, AuthorMetas[This->m_AuthorMetaMode]);
		if (!text[0])
		{
			This->m_AuthorMetaMode += 1;
			if (This->m_AuthorMetaMode >= COUNT_AUTHORMETAMODE) This->m_AuthorMetaMode = 0;
		}
	}
	while (!text[0] && (mode != This->m_AuthorMetaMode));

	Icon_GetInfo(This->m_CurrentPanel->m_w, Icon_DiscName, &icon);

	// take writable copy
	string = SPrintf("%s", text);

	// avoid icon text aligment problems
	newlen = Font_GetSplitPosition(icon.flags >> 24, string, icon.box.x1 - icon.box.x0 - 8);
	string[newlen] = 0;

	Icon_SetData(This->m_CurrentPanel->m_w, Icon_DiscName, string);

	// Loop till some text to show
	mode = This->m_PlugInMetaMode;
	do
	{
		text = PlayList_GetText(This->pPlayList, pTrack, PlugInMetas[This->m_PlugInMetaMode]);
		if (!text[0] && !This->m_PlugInMetaMode)
			text = PlayList_GetText(This->pPlayList, pTrack, EMetaId_StreamName);
		if (!text[0])
			text = PlayList_GetText(This->pPlayList, NULL, PlugInMetas[This->m_PlugInMetaMode]);
		if (!text[0])
		{
			This->m_PlugInMetaMode += 1;
			if (This->m_PlugInMetaMode >= COUNT_PLUGINMETAMODE) This->m_PlugInMetaMode = 0;
		}
	}
	while (!text[0] && (mode != This->m_PlugInMetaMode));

	Player_ShowTitleMessage(This, text);
}

/**
 * Converts a duration into a string.
 *
 * @param
 *   time  duration given in centiseconds
 *
 * @returns
 *   string in the format "HH:MM:SS.D"
 */

static const char* Player_TimeToString(unsigned int time)
{
	static char	string[12];
	char*		s = string;
	int		t;

	time += 5; // for rounding
	t = time/360000; if (t > 0) {s += snprintf(s, 5, "%d:", t);}
	t = time/60000; if (t > 0) {t = t % 6; s += snprintf(s, 2, "%1d", t);}
	t = time/6000; if (t > 0) {t = t % 10; s += snprintf(s, 3, "%1d:", t);}
	t = time/1000; if (t > 0) {t = t % 6; s += snprintf(s, 2, "%1d", t);}
	t = time/100; t = t % 10; s += snprintf(s, 3, "%1d.", t);
	t = time/10; t = t % 10; snprintf(s, 2, "%1d", t);

	return string;
}

/**
 * Returns the appropriate title. Assumes a track is playing.
 */

static const char* Player_GetTitle(Player* This)
{
	const char* ptitle = PlayList_GetText(This->pPlayList, This->track.handle, EMetaId_StreamTitle);
	if (!*ptitle) ptitle = PlayList_GetText(This->pPlayList, This->track.handle, EMetaId_StreamName);

	return ptitle;
}

/**
 * Refreshes the track title and track number fields.
 *
 * The track number if either provided by the playlist, generated automatically
 * or is 0 if no track is active.
 *
 * The track title is either provided by the playlist, generated automatically
 * from the track number or is empty if no track is active. The duration
 * of the track is appended to the end of the title when available.
 */

static void Player_ShowTrack(Player* This)
{
	char string[1024];
	unsigned int time;
	void* ptrack = Player_GetActiveTrack(This);
	int	track;

	if (ptrack)
	{
		// Ask number from playlist
		track = PlayList_GetTrackNummer(This->pPlayList, ptrack);
		// If playlist can't provide one, use our own one
		if (track == 0)
			track = This->track.current + 1;
	}
	else track = 0;


	snprintf(string, 5, "%04d", (track > 9999) ? 0 : track);

	Icon_SetData(This->m_CurrentPanel->m_w, Icon_Track + 3, string + 3);
	string[3] = 0;
	Icon_SetData(This->m_CurrentPanel->m_w, Icon_Track + 2, string + 2);
	string[2] = 0;
	Icon_SetData(This->m_CurrentPanel->m_w, Icon_Track + 1, string + 1);
	string[1] = 0;
	Icon_SetData(This->m_CurrentPanel->m_w, Icon_Track, string);

	if (ptrack)
	{
		CIcon icon;
		int newlen, len = 0;
		Icon_GetInfo(This->m_CurrentPanel->m_w, Icon_TrackName, &icon);
		const char* ptitle = Player_GetTitle(This);

		if (*ptitle)
			snprintf(string, sizeof(string), "%s", ptitle);
		else
			snprintf(string, sizeof(string), Msg_Lookup("UnkTrack: Track %d"), track);
		len = strlen(string);

		if (This->pPlayList->pcmds->pGetConfig(This->pPlayList, (Media_Config) PlayList_CfgSeekable))
		{
			// Add song length if enough space in icon
			time = This->track.pos.section.len;
			snprintf(string + len, sizeof(string) - len, " (%s)", Player_TimeToString(time));
		}

		// Compute visible part
		newlen = Font_GetSplitPosition(icon.flags >> 24, string, icon.box.x1 - icon.box.x0 - 8);
		if (newlen < len)
			len = newlen;
		else if (newlen >= strlen(string))
			len = strlen(string);
		// Set new lentgh
		string[len] = 0;
	}
	else string[0] = 0;

	Icon_SetData(This->m_CurrentPanel->m_w, Icon_TrackName, string);
}

/**
 * Refreshes the time field and the position bar.
 *
 * When the user is dragging the position slider, the time field shows
 * the elapsed time corresponding to the current slider position.
 *
 * In normal use, the position bar reflects the position within the current track
 * and the time field contains depending on the display time mode
 * the elapsed/remaining/total section/global time.
 */

static void Player_ShowPosition(Player* This)
{
	unsigned int time, time2, t;
	float ftime;

	if (Player_IsStatusActive(This))
	{
		// Dragging ?
		if (Slider_IsDragging(This->m_CurrentPanel->m_w, Icon_SetPos, Icon_SetPos2))
			time = (unsigned int)(This->m_DragPos - This->track.pos.driver.start);
		else
			time = (unsigned int)(This->track.pos.driver.pos - This->track.pos.driver.start);
		time2 = (unsigned int)(This->track.pos.driver.end - This->track.pos.driver.start);
		if (time > time2) time = time2;
		if (time2 == 0) time2 = 1;
		ftime = (float) time / (float) time2;

		Slider_Set(This->m_CurrentPanel->m_w, Icon_SetPos, Icon_SetPos2, PosBarType, ftime);

		// Track active
		switch(This->TimeMode)
		{
			case 1: time = This->track.pos.section.len - This->track.pos.section.pos; break;
			case 2: time = This->track.pos.volume.pos; break;
			case 3: time = This->track.pos.volume.len - This->track.pos.volume.pos; break;
			case 4: time = This->track.pos.volume.len; break;
			case 5: time = This->track.pos.section.len; break;
			default: time = This->track.pos.section.pos; break;
		}
	}
	else
	{
		// No track active
		ftime = 0;
		Slider_Set(This->m_CurrentPanel->m_w, Icon_SetPos, Icon_SetPos2, PosBarType, ftime);

		time = 0;
	}

	// Round time to .1 second
	time += 5;

	t = time/360000; t = t % 10;
	Icon_Printf(This->m_CurrentPanel->m_w, Icon_Time + 0, "%1d", t);
	t = time/60000; t = t % 6;
	Icon_Printf(This->m_CurrentPanel->m_w, Icon_Time + 1, "%1d", t);
	t = time/6000; t = t % 10;
	Icon_Printf(This->m_CurrentPanel->m_w, Icon_Time + 2, "%1d", t);
	t = time/1000; t = t % 6;
	Icon_Printf(This->m_CurrentPanel->m_w, Icon_Time + 3, "%1d", t);
	t = time/100; t = t % 10;
	Icon_Printf(This->m_CurrentPanel->m_w, Icon_Time + 4, "%1d", t);
	t = time/10; t = t % 10;
	Icon_Printf(This->m_CurrentPanel->m_w, Icon_Time + 5, "%1d", t);
}

/**
 * Combines ShowTrack and ShowPosition.
 */

static void Player_ShowTrackAndPosition(Player* This)
{
	Player_ShowTrack(This);
	Player_ShowPosition(This);
}

/**
 * Reads the current volume and refreshes the volume bar.
 */

static void Player_ShowVolume(Player* This)
{
	int sprite;
	float volume;

	volume = (float) Player_ReadVolume(This) / (float) Volume_Max;

	if (volume > .025) volume += (float) .025;
	if (volume < 0) volume = 0;
	if (volume > 1) volume = 1;

	sprite = (int) (volume * VolumeSprites);

	if (VolumeType & ESliderType_Button)
	{
		Icon_PrintfValidation(This->m_CurrentPanel->m_w, Icon_Volume, "Svolume%d;YS", sprite);
		Slider_Set(This->m_CurrentPanel->m_w, Icon_Volume, This->m_CurrentPanel->m_VolumeButton, VolumeType, volume);
	}
	else
	{
		Icon_PrintfValidation(This->m_CurrentPanel->m_w, Icon_Volume, "Svolume%d;Pptr_music;YS", sprite);
	}
}

/**
 * Refreshes the state of the Loop Mode toggle.
 */

static void Player_ShowLoopMode(Player* This)
{
	Icon_PrintfValidation(This->m_CurrentPanel->m_w, Icon_LoopMode, "Sloop%d;Pptr_music", This->LoopMode);
}

/**
 * Refreshes the state of the Introscan toggle.
 */

static void Player_ShowIntroScan(Player* This)
{
	Icon_PrintfValidation(This->m_CurrentPanel->m_w, Icon_IntroScan, "Sintro%d;Pptr_music", (This->m_Flags & Player_IntroScan) != 0);
}

/**
 * Refreshes the state of the List Mode toggle.
 */

static void Player_ShowListMode(Player* This)
{
	Icon_PrintfValidation(This->m_CurrentPanel->m_w, Icon_ListMode, "Slist%d;Pptr_music", This->ListMode);
}

/**
 * Refreshes the state of the Shuffle Mode toggle.
 */

static void Player_ShowShuffleMode(Player* This)
{
	if (This->m_Flags & Player_ShuffleMenu)
		Icon_PrintfValidation(This->m_CurrentPanel->m_w, Icon_ShuffleMode, "Sshuffle%d;Pptr_menu", (Player_GetActiveShuffleMode(This) != 0));
	else
		Icon_PrintfValidation(This->m_CurrentPanel->m_w, Icon_ShuffleMode, "Sshuffle%d;Pptr_music", (Player_GetActiveShuffleMode(This) != 0));
}

/**
 * Shows a short message in the secondary text line
 * and tells the player to keep it for 2 seconds.
 */

void Player_ShowMessage(Player* This, const char* pmsg)
{
	DCD_Misc_Message msg = {{sizeof(DCD_Misc_Message), 0, 0, 0, EMsg_DCD_Misc}, 0};

	This->m_AuthorMetaTime = Timer_GetTime() + 500;

	Icon_SetData(This->m_CurrentPanel->m_w, Icon_DiscName, pmsg);

	msg.reason = 5;
	msg.controller = Player_GetNr(This);
	strncpy(msg.data.text, pmsg, sizeof(msg.data.text));
	msg.hdr.size = (msg.data.text - (char*) &msg) + strlen(msg.data.text) + 1;
	Task_SendMsg((Msg*) &msg, 0);
}

/**
 * Shows the control panel window and refreshes its content.
 */

void Player_ShowControls(Player* This, HWind behind)
{
	if (This->Status == status_undef)
		Player_Refresh(This, player_refresh_tracklist);

	Player_ShowTimeMode(This);
	Player_ShowStatus(This);
	Player_ShowListName(This);
	Player_ShowTrackAndPosition(This);
	Player_ShowVolume(This);
	Player_ShowLoopMode(This);
	Player_ShowIntroScan(This);
	Player_ShowListMode(This);
	Player_ShowShuffleMode(This);

	Window_OpenBehind(This->m_CurrentPanel->m_w, behind);
}

/*-------------------------------------------------------------------------*
 *--- Accessors functions -------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Sets the current playing position in the player's active list,
 * and updates the mark in that list.
 * Note: does not update the control panel window.
 *
 * @param  current position [0, track list size[ or -1 if not playing.
 */

static void Player_SetCurrentTrack(Player* This, int current)
{
	int count = WList_Count(&This->m_TrackList);

	WList_SetItemsState(&This->m_TrackList, 0, EWItem_Selected, EWItem_Selected, EWItem_Selected);
	if ((This->track.current >= 0)
	&&  (This->track.current < count))
		WList_SetItemState(&This->m_TrackList, This->track.current, 0, EWItem_Marked);
	if ((current >= 0) && (current < count))
	{
		This->track.current = current;
		This->track.handle = WList_Get(&This->m_TrackList, current);
		WList_SetItemState(&This->m_TrackList, current, EWItem_Marked, EWItem_Marked);
		WList_ShowItem(&This->m_TrackList, current);
	}
	else
	{
		This->track.current = -1;
		This->track.handle = NULL;
		This->m_HardwareType = -1;
	}
}

/**
 * Sets Introscan Mode and refreshes its state in the control panel window and menu.
 *
 * @param  on  set introscan to on (true) or off (false).
 */

void Player_SetIntroScan(Player* This, bool on)
{
	if (on)
		This->m_Flags |= Player_IntroScan;
	else
		This->m_Flags &= ~Player_IntroScan;

	Player_ShowIntroScan(This);

	Menu_Refresh(This->m_CurrentPanel->m_w, NULL);

	Player_ShowMessage(This, Msg_Lookup(SPrintf("CmdIntro%d", (This->m_Flags & Player_IntroScan) != 0)));
}

/**
 * Sets List Mode and refreshes its state in the control panel window and menu.
 *
 * @param  on  set list mode to on (true) or off (false).
 */

void Player_SetListMode(Player* This, bool on)
{
	if (This->ListMode == on)
		return;

	This->ListMode = on;

	Player_ShowListMode(This);
	Player_ShowShuffleMode(This);

	Player_ReShuffle(This, true);
	if ((This->Status == status_undef)
	||  (This->Status == status_empty)
	||  (This->Status == status_stop))
		Player_SetStatus(This, PlayList_GetStatus(This->pPlayList));

	Menu_Refresh(This->m_CurrentPanel->m_w, NULL);
}

/**
 * Sets Loop Mode and refreshes its state in the control panel window and menu.
 *
 * @param  mode  loop mode to switch to:
 *               normal play, loop on list, loop on track, play single track.
 */

void Player_SetLoopMode(Player* This, Media_LoopMode mode)
{
	This->LoopMode = mode;

	Player_ShowLoopMode(This);
	PlayList_SetLoopMode(This->pPlayList, mode);

	Menu_Refresh(This->m_CurrentPanel->m_w, NULL);

	Player_ShowMessage(This, Msg_Lookup(SPrintf("CmdLoop%d", This->LoopMode)));
}

/**
 * Sets Shuffle Mode and refreshes its state in the control panel window and menu.
 *
 * @param  shuffle  set shuffle mode.
 */

void Player_SetShuffleMode(Player* This, unsigned int shuffle)
{
	if (This->m_Flags & Player_SeparateShuffle)
		This->ShuffleMode[This->ListMode]  = shuffle;
	else
		This->ShuffleMode[0] = shuffle;

	Player_ShowShuffleMode(This);

	Menu_Refresh(This->m_CurrentPanel->m_w, NULL);

	if (shuffle & 0x80000000) shuffle = 0;
	Player_ShowMessage(This, Msg_Lookup(SPrintf("CmdShuffle%d", (shuffle != 0))));

	Player_ReShuffle(This, false);
}

/**
 * Sets player and track volume and refreshes the control panel window.
 *
 * @param  volume  volume in range [0, Volume_Max].
 */

static void Player_SetVolume(Player* This, int volume)
{
	if (volume < 0) volume = 0;
	if (volume > Volume_Max) volume = Volume_Max;

	if (This->m_DragVolume >= 0)
		volume = This->m_DragVolume;
	else	This->Volume = volume;
	PlayList_SetVolume(This->pPlayList, volume);

	Player_ShowVolume(This);

	Player_ShowMessage(This, SPrintf(Msg_Lookup("CmdVolume:"), volume/65536.));
}

/**
 * Toggles acceptance of input focus by the control panel.
 * When true, the control panel will accept to gain the input focus
 * when clicked on and therefore can be controlled by keyboard shortcuts.
 * Note that plug-ins can forward keyboard shortcuts to the control panel
 * even when acceptance of input focus is turned off.
 */

void Player_ToggleShortcuts(Player* This)
{
	This->m_Flags ^= Player_Shortcuts;
}

/**
 * Set media hardware type for use by DCDUtils and plug-ins.
 *
 * @param  type  hardware type.
 */

void Player_SetHardwareType(Player* This, int type)
{
	This->m_HardwareType = type;
}

/*-------------------------------------------------------------------------*
 *--- Skins and Panels ----------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Initializes a panel's info from the user preferences.
 */

static void throw_Panel_Panel(Panel* Ctrl, const char* section, const char* name, const char* suffix)
{
	memset(Ctrl, 0, sizeof(*Ctrl));
	Ctrl->m_w = HWind_None;

	Ctrl->m_pTemplateName = throw_mem_allocstring(SPrintf("%s%s", name, suffix));
	if (!strcmp(name, "Control"))
		Ctrl->m_pPrefName = &nil;
	else
		Ctrl->m_pPrefName = throw_mem_allocstring(name);

	Ctrl->m_Pos.x = Choices_ReadInt(section, SPrintf("Pos%sX", Ctrl->m_pPrefName), 200);
	Ctrl->m_Pos.y = Choices_ReadInt(section, SPrintf("Pos%sY", Ctrl->m_pPrefName), 200);
	Ctrl->m_docking = Choices_ReadInt(section, SPrintf("Docking%s", Ctrl->m_pPrefName), 0);
	Ctrl->m_docking &= Panel_DockMask;
}

/**
 * Stores a panel info in the user preferences.
 */

static void Panel_WriteProfile(Panel* Ctrl, const char* section)
{
	if (Ctrl->m_w != HWind_None)
	{
		CWindState Info = Window_GetState(Ctrl->m_w);

		// Remember position
	 	Ctrl->m_Pos.x = Info.o.cvt.box.x0;
 		Ctrl->m_Pos.y = Info.o.cvt.box.y0;
	}

	Choices_Write(section, SPrintf("Pos%sX", Ctrl->m_pPrefName), "%d", Ctrl->m_Pos.x);
	Choices_Write(section, SPrintf("Pos%sY", Ctrl->m_pPrefName), "%d", Ctrl->m_Pos.y);
	Choices_Write(section, SPrintf("Docking%s", Ctrl->m_pPrefName), "%d", Ctrl->m_docking & Panel_DockMask);
}

/**
 * Creates a new panel window for the current skin.
 */

static void throw_force_validation(CIcon* pi, const char* padd)
{
	if (pi->flags & EIcon_Deleted)
		return;

	if (!(pi->flags & EIcon_Indirect))
	{
		pi->flags |= EIcon_Indirect | EIcon_Text;
		pi->data.it.buffer = (char*) &nil;
		pi->data.it.validation = throw_mem_allocstring(padd);
		pi->data.it.buf_size = 1;
	}
	else
	{
		char* pval = throw_mem_alloc(128);
		char* pv = pval;
		// Copy old validation string (Ctrl terminated!!!) but remove P command
		if (pi->data.it.validation != NULL)
		{
			bool skip = false;
			char* pc = pi->data.it.validation;

			while(*pc >= ' ')
			{
				skip= (toupper(*pc++) == 'P');
				if (!skip) *pv++= pc[-1];

				while(*pc >= ' ')
				{
					if (!skip)
						*pv++= *pc;

					if (*pc++ == ';')
						break; // next command
				}
			}
		}
		if ((pv > pval) && (pv[-1] != ';'))
			*pv++ = ';';
		strcpy(pv, padd);
		pi->data.it.validation = pval;
	}
}

static void throw_Panel_CreateWindow(Panel* Ctrl, Player* This)
{
	static const char valsh[] = "Pptr_scrh,11,4;YS";
	static const char valsv[] = "Pptr_scrv,4,11;YS";
	static const char valm[]  = "Pptr_music;YS";
	CWind* pw;
	CIcon* pi;
	CRect box;

	try
	{
		Ctrl->m_pTemplate = throw_Templates_Copy(Ctrl->m_pTemplateName);

		pw = Template_GetWindow(Ctrl->m_pTemplate);
		box = pw->o.o.cvt.box;
		pw->o.o.cvt.box.x0 = Ctrl->m_Pos.x;
		pw->o.o.cvt.box.y0 = Ctrl->m_Pos.y;
		pw->o.o.cvt.box.x1 = pw->o.o.cvt.box.x0 + box.x1 - box.x0;
		pw->o.o.cvt.box.y1 = pw->o.o.cvt.box.y0 + box.y1 - box.y0;
		pw->extra_flags |= EWindX_ExtendScrollRequests;

		if (pw->nicons > Icon_Volume)
		{
			pi = ((CIcon*) (pw+1)) + Icon_Volume;
			throw_force_validation(pi, "                              ");
		}

		if ((VolumeType & ESliderType_Button) && (0 <= VolumeButton) && (pw->nicons > VolumeButton))
		{
			pi = ((CIcon*) (pw+1)) + VolumeButton;
			throw_force_validation(pi, (VolumeType & ESliderType_Vertical) ? valsv : valsh);
		}

		if (PosBarType & ESliderType_Button)
		{
			pi = ((CIcon*) (pw+1)) + Icon_SetPos;
			throw_force_validation(pi, valm);
			pi = ((CIcon*) (pw+1)) + Icon_SetPos2;
			throw_force_validation(pi, (PosBarType & ESliderType_Vertical) ? valsv : valsh);
		}
		else
		{
			pi = ((CIcon*) (pw+1)) + Icon_SetPos;
			throw_force_validation(pi, valm);
			pi = ((CIcon*) (pw+1)) + Icon_SetPos2;
			throw_force_validation(pi, valm);
		}

		Ctrl->m_w = throw_Window_CreateCustom(Ctrl->m_pTemplate, pCtrlSprites, NULL);
		throw_Window_RegisterEventHandler(Ctrl->m_w, Player_EventHandler, This, false);

		Ctrl->m_VolumeButton = Icon_GetInfo(Ctrl->m_w, VolumeButton, NULL) ? VolumeButton : Icon_Volume;
	}
	catch
	{
		Templates_Remove(Ctrl->m_pTemplate);
		Window_Delete(Ctrl->m_w);
		Ctrl->m_pTemplate = NULL;
		Ctrl->m_w = HWind_None;
		throw_current();
	}
	catch_end
}

/**
 * Removes old panel window and creates a new one for the current skin.
 */

static void Panel_DeleteWindow(Panel* Ctrl, Player* This)
{
	if (Ctrl->m_pTemplate != NULL)
	{
		Templates_Remove(Ctrl->m_pTemplate);
		Ctrl->m_pTemplate = NULL;
	}

	if (Ctrl->m_w != HWind_None)
	{
		CWindState Info = Window_GetState(Ctrl->m_w);

		// Remember old position
	 	Ctrl->m_Pos.x = Info.o.cvt.box.x0;
 		Ctrl->m_Pos.y = Info.o.cvt.box.y0;

		Window_DeRegisterEventHandler(Ctrl->m_w, Player_EventHandler, This);
		Window_Delete(Ctrl->m_w);
		Ctrl->m_w = HWind_None;
	}
}

/**
 * Removes old panel window and creates a new one for the current skin.
 */

static void Panel_NotPanel(Panel* Ctrl, Player* This)
{
	Templates_Remove(Ctrl->m_pTemplate);
	Ctrl->m_pTemplate = NULL;
	Window_DeRegisterEventHandler(Ctrl->m_w, Player_EventHandler, This);
	Window_Delete(Ctrl->m_w);
	Ctrl->m_w = HWind_None;
	mem_free(Ctrl->m_pTemplateName);
	Ctrl->m_pTemplateName = NULL;
	mem_free(Ctrl->m_pPrefName);
	Ctrl->m_pPrefName = NULL;
}

/**
 * Updates a panel's mode specific information.
 */

static void Panel_ModeChange(Panel* Ctrl)
{
	Ctrl->m_docking |= Panel_ForceDocking;

	if (Ctrl->m_w != HWind_None)
	{
		Icon_SetState(Ctrl->m_w, Icon_DiscName, AuthorFont << 24, 0xff000000);
		Icon_SetState(Ctrl->m_w, Icon_TrackName, TitleFont << 24, 0xff000000);
		Icon_SetState(Ctrl->m_w, Icon_TrackText, TrackFont << 24, 0xff000000);
		Icon_SetState(Ctrl->m_w, Icon_TimeMode, TimeFont << 24, 0xff000000);
	}
}

/**
 * Selects the current panel and notify DCDUtils of the change.
 */

static void Player_SelectPanel(Player* This, Panel* Ctrl)
{
	This->m_CurrentPanel = Ctrl;
	Panel_ModeChange(Ctrl);

	DCDUtils_SetIntInfo(This->m_DCDUtilsHandle, DCD_Info_Window, This->m_CurrentPanel->m_w);
}

/**
 * Deletes the player's old panels and recreate them using the current skin.
 */

void throw_Player_ReloadSkin(Player* This)
{
	CWindState Info = Window_GetState(This->m_CurrentPanel->m_w);

	Panel_DeleteWindow(&This->m_Ctrl, This);
	Panel_DeleteWindow(&This->m_Mini, This);
	throw_Panel_CreateWindow(&This->m_Ctrl, This);
	throw_Panel_CreateWindow(&This->m_Mini, This);

	Player_SelectPanel(This, This->m_CurrentPanel);

	if (Info.flags & EWind_IsOpen)
		Player_ShowControls(This, Info.o.behind);
}

/*-------------------------------------------------------------------------*
 *--- Preferences Loading/Saving ------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Reads the user's saved preferences for this player or provide default values.
 * This doesn't include panel informations, since they must be created prior to this call.
 */

static void Player_ReadProfile(Player* This)
{
	char section[] = "Player xx";

	snprintf(section, sizeof(section), "Player %d", This->nr);

	Player_SetLoopMode(This, (Media_LoopMode) FitInRange(Choices_ReadInt(section, Var_RepeatMode, loop_volume_play), loop_volume_play, loop_section_play));
	Player_SetIntroScan(This, Choices_ReadBool(section, Var_IntroScanMode, false));
	Player_SetListMode(This, Choices_ReadBool(section, Var_ProgramMode, This->ListMode));
	This->ShuffleMode[0] = Choices_ReadInt(section, Var_ShuffleMode, 0);
	This->ShuffleMode[1] = Choices_ReadInt(section, Var_ShuffleMode2, 0);
	Player_SetShuffleMode(This, Player_GetShuffleMode(This));
	if (Choices_ReadBool(section, Var_Shortcuts, true))
		This->m_Flags |= Player_Shortcuts;
	else
		This->m_Flags &= ~Player_Shortcuts;
	This->TimeMode = FitInRange(Choices_ReadInt(section, Var_TimeMode, 0), 0, MAX_TIMEMODE);
	if ((This->m_Flags & Player_RememberPosition) && Options()->Player.bRememberPosition)
		This->track.next[0] = Choices_ReadInt(section, Var_NextTrack, 0);
}

/**
 * Reads the user's saved preferences for this player or provide default values.
 * This is the part that can't be done before player and playlist are fully linked.
 */

static void Player_ReadListProfile(Player* This)
{
	char section[] = "Player xx";
	int tmp;

	snprintf(section, sizeof(section), "Player %d", This->nr);

	tmp = FitInRange(Choices_ReadInt(section, Var_Volume, 256), 0, 256);
	Player_SetVolume(This, FitInRange(Choices_ReadInt(section, Var_NewVolume, tmp*25600), 0, Volume_Max));
}

/**
 * Stores the user's preferences for this player.
 */

void Player_WriteProfile(Player* This)
{
	char section[] = "Player xx";

	if ((This->m_Ctrl.m_w == HWind_None) || (This->m_Mini.m_w == HWind_None))
		return;

	if (Options()->Player.bSinglePanelMode)
	{
		snprintf(section, sizeof(section), "Player x");
		Choices_Write(section, Var_Player, "%d", This->nr);
	}
	else
		snprintf(section, sizeof(section), "Player %d", This->nr);

	Choices_Write(section, Var_Open, "%d", Window_IsOpen(This->m_CurrentPanel->m_w));

	Panel_WriteProfile(&This->m_Ctrl, section);
	Panel_WriteProfile(&This->m_Mini, section);

	Choices_Write(section, Var_MiniMode, "%d", (This->m_CurrentPanel == &This->m_Mini));

	snprintf(section, sizeof(section), "Player %d", This->nr);
	Choices_Write(section, Var_RepeatMode, "%d", This->LoopMode);
	Choices_Write(section, Var_IntroScanMode, "%d", (This->m_Flags & Player_IntroScan) != 0);
	Choices_Write(section, Var_ProgramMode, "%d", This->ListMode);
	Choices_Write(section, Var_ShuffleMode, "%d", This->ShuffleMode[0]);
	Choices_Write(section, Var_ShuffleMode2, "%d", This->ShuffleMode[1]);
	Choices_Write(section, Var_Shortcuts, "%d", (This->m_Flags & Player_Shortcuts) != 0);
	Choices_Write(section, Var_TimeMode, "%d", This->TimeMode);
	Choices_Write(section, Var_NewVolume, "%d", This->Volume);
	if ((This->m_Flags & Player_RememberPosition) && Options()->Player.bRememberPosition)
		Choices_Write(section, Var_NextTrack, "%d", This->track.next[0]);
}

/*----------------------------------------------------------------------
 *- Constructor & Destructor -------------------------------------------
 *----------------------------------------------------------------------*/

static const char* Player_GetItemText(void* pHandle, const void* pItem)
{
	Player* pPlayer = (Player*) pHandle;

	return PlayList_GetText(pPlayer->pPlayList, (void*) pItem, EMetaId_StreamName);
}


Player* throw_New_Player(PlayList* pPlayList, const char* pSuffix, FPlayer_ReloadSkin pReloadSkin, int flags)
{
	Player* This = throw_mem_calloc(1, sizeof(*This));
	char section[] = "Player xx";
	CRect box = {0, 0, 320, 400};

	try
	{
		This->m_DCDUtilsHandle = -1;
		This->m_pSuffix = pSuffix;
		This->m_HardwareType = -1;
		This->m_DragPos = -1;
		This->m_DragVolume = -1;
		This->m_pReloadSkin = pReloadSkin;
		This->m_Ctrl.m_w = HWind_None;
		This->m_Mini.m_w = HWind_None;
		This->m_TrackList.m_Core.m_wnd = HWind_None;
		This->m_Flags = flags & Player_CreateMask;
		This->m_pCmdHandler = &Player_CmdHandler;
		This->m_pCmdHandle = This;

		This->nr = PlayLists_Find(pPlayList);

		if (This->m_Flags & Player_SeparateShuffle)
			This->ListMode = 1;

		throw_os(DCDUtils_RegisterPlayer(&This->m_DCDUtilsHandle));

		if (Options()->Player.bSinglePanelMode)
			snprintf(section, sizeof(section), "Player x");
		else
			snprintf(section, sizeof(section), "Player %d", This->nr);

		throw_Panel_Panel(&This->m_Ctrl, section, "Control", This->m_pSuffix);
		throw_Panel_Panel(&This->m_Mini, section, "Mini", This->m_pSuffix);

		throw_Panel_CreateWindow(&This->m_Ctrl, This);
		throw_Panel_CreateWindow(&This->m_Mini, This);

		Player_SelectPanel(This, Choices_ReadBool(section, Var_MiniMode, false) ? &This->m_Mini : &This->m_Ctrl);

		throw_Task_AddListener(EEvent_Null, Player_NullEvent, This, false);
		throw_Task_AddMsgListener(EMsg_ModeChange, Player_MsgHandler, This, false);

		DCDUtils_SetInfo(This->m_DCDUtilsHandle, 0, -1);
		DCDUtils_SetIntInfo(This->m_DCDUtilsHandle, DCD_Info_Task, Task_GetId());

		throw_Players_Add(This);
		This->Status = status_undef;
		This->track.current = -1;
		This->track.handle = NULL;
		This->track.next[0] = 0;
		This->track.next[1] = 0;
		This->pPlayList = pPlayList;

		CTemplate* t = throw_Templates_Blank(256, EWind_NormalWindow - EWind_BackIcon - EWind_CloseIcon, box.x1, box.y1);

		throw_WList_WList(&This->m_TrackList, EWList_SelModePopup | EWList_ShowMarks, t, This, Player_GetItemText);
		WList_SetParent(&This->m_TrackList, This->m_CurrentPanel->m_w);

		HWind w = WList_GetWindow(&This->m_TrackList);

		Window_SetTitle(w, Msg_Lookup("Tracks"));

		This->ShuffleMode[0] = 0; // cf SetListMode
		This->ShuffleMode[1] = 0; // cf SetListMode
		Player_ReadProfile(This);

		if (Choices_ReadBool(section, Var_Open, false))
			Player_ShowControls(This, -1);
		This->m_PollTime = Timer_GetTime() + 500;
		This->m_AuthorMetaTime = 0;
	}
	catch
	{
		Delete_Player(This);
		throw_current();
	}
	catch_end

	return This;
}

void Delete_Player(Player* This)
{
	if (This)
	{
		if (Player_IsStatusActive(This))
			Player_SetStatus(This, status_stop);

		Player_WriteProfile(This);
		DCDUtils_DeregisterPlayer(This->m_DCDUtilsHandle);
		Players_Del(This);
		WList_NotWList(&This->m_TrackList);
		Panel_NotPanel(&This->m_Mini, This);
		Panel_NotPanel(&This->m_Ctrl, This);
		Task_RemoveMsgListener(EMsg_ModeChange, Player_MsgHandler, This);
		Task_RemoveListener(EEvent_Null, Player_NullEvent, This);

		mem_free(This);
	}
}

/**
 * Rebuilds and shuffles the list of tracks.
 * This will also update track.current and track.next.
 *
 * @param  bModeSwitch
 */
static void Player_ReShuffle(Player* This, bool bModeSwitch)
{
	int i;
	int nr_tracks;
	const void* pcurrent = NULL;
	const void* pnext = NULL;
	const void** pptracks = NULL;

	bool off = !WLib_Hourglass_IsOn();
	if (off) WLib_Hourglass_On();

	This->track.shufflecount = 0;

	// Find handles of current and next track so that we can update
	// their indexes for the updated/reshuffled list.
	// Don't do it if This->ListMode was changed.
	if (!bModeSwitch)
	{
		pcurrent = Player_GetCurrentTrack(This);
		if ((This->track.next[This->ListMode] >= 0)
		&&  (This->track.next[This->ListMode] < WList_Count(&This->m_TrackList)))
			pnext = WList_Get(&This->m_TrackList, This->track.next[This->ListMode]);
	}

	// Get new shuffled playing list
	nr_tracks = PlayList_GetTrackList(This->pPlayList, &pptracks, This->ListMode, Player_GetActiveShuffleMode(This));

	WList_AllowRefresh(&This->m_TrackList, false);
	WList_Clear(&This->m_TrackList);

	try
	{
		for (i = 0; i < nr_tracks; i++)
		{
			throw_WList_Insert(&This->m_TrackList, -1, (void*) pptracks[i]);
		}
	}
	catch
	{
		App_ReportException();
	}
	catch_end

	nr_tracks = WList_Count(&This->m_TrackList);
	WList_AllowRefresh(&This->m_TrackList, true);

	// If This->ListMode changed and we were playing, play next track from new list
	if (Player_IsStatusPlaying(This))
	{
		int track;

		if (bModeSwitch)
		{
			track = This->track.next[This->ListMode];

			if ((track < 0) || (track >= nr_tracks))
				track = 0;

			if (track < nr_tracks)
				Player_PlayTrack(This, track);
			else
				Player_SetStatus(This, status_stop);

			// Don't update track.next
			pnext = NULL;
		}
		else
		{
			// Do we still find the current track in the list?
			if ((track = WList_Find(&This->m_TrackList, 0, pcurrent)) != -1)
			{
				Player_SetCurrentTrack(This, track);
				Player_ShowTrack(This);

				// Next track is current track
				This->track.next[This->ListMode] = track;
				pnext = NULL;
			}
			else
			{
				// Attempt to play the track currently using the same index
				// on the assumption that we just deleted the current track
				if ((This->LoopMode == loop_volume_loop)
				||  (This->LoopMode == loop_section_loop))
				{
					if (This->track.current >= nr_tracks)
						This->track.current = 0;
				}

				if (This->track.current >= nr_tracks)
				{
					Player_SetStatus(This, status_stop);

					// Next track is end of list as forcing 0 with always restart the list
					// from the beginning even if tracks were to be added to the end of the list
					This->track.next[This->ListMode] = nr_tracks;
					pnext = NULL;
				}
				else
				{
					Player_PlayTrack(This, This->track.current);
					// next is updated by playtrack
					pnext = NULL;
				}
			}
		}
	}

	// Update track.next if still necessary
	if (pnext) This->track.next[This->ListMode] = WList_Find(&This->m_TrackList, 0, pnext);

	mem_free(pptracks);

	if (off) WLib_Hourglass_Off();
}

static void Player_CheckReShuffle(Player* This)
{
	if (!Player_GetActiveShuffleMode(This)) return;

	This->track.shufflecount++;

	if ((This->track.shufflecount >= WList_Count(&This->m_TrackList))
	&& (Options()->Player.bReShuffleOnLoop))
		Player_ReShuffle(This, false);
}

/*----------------------------------------------------------------------
 *- Commands -----------------------------------------------------------
 *----------------------------------------------------------------------*/

/*
 * Command: Move back one position in the track
 *          when rewind button is clicked
 */

static void Player_Rewind(Player* This)
{
	if (This->Status != status_play)
		return;

	PlayList_Seek(This->pPlayList, EMedia_Seek_PreviousPos);
	Player_ReadCurrentPos(This);

	if (Player_TrackChanged(This))
	{
		int cur_track = This->track.current;

		This->ErrorCount = 0;
		Player_StartTrack(This, cur_track - 1, true);

		if (Player_IsStatusPlaying(This))
		{
			if (cur_track == This->track.current)
				Player_SetTrackPos(This, 0);
			else
			{
				Player_SetTrackPos(This, .99);
				Player_Rewind(This);
			}
		}
	}
	else
	{
		Player_ShowPosition(This);
	}
}

/*
 * Command: Move forward one position in the track
 *          when forward button is clicked
 */

static void Player_Forward(Player* This)
{
	if (This->Status != status_play)
		return;

	PlayList_Seek(This->pPlayList, EMedia_Seek_NextPos);
	Player_ReadCurrentPos(This);

	if (Player_TrackChanged(This))
	{
		int cur_track;

		Player_CheckReShuffle(This);

		cur_track = This->track.current;

		This->ErrorCount = 0;
		Player_StartTrack(This, cur_track + 1, false);
		if (cur_track == This->track.current)
			Player_SetStatus(This, status_stop);
		else
			Player_ReadCurrentPos(This);
	}

	Player_ShowPosition(This);
}

static void Player_InvertIntroScan(Player* This)
{
	Player_SetIntroScan(This, (This->m_Flags & Player_IntroScan) == 0);
}

static void Player_InvertListMode(Player* This)
{
	Player_SetListMode(This, !This->ListMode);
}

/*
 * Command: Switch between the existing time display modes
 *          when clicking on the time or time mode icons
 */

static void Player_ToggleTimeMode(Player* This, bool next)
{
	if (next)
	{
		This->TimeMode++;
		if (This->TimeMode > MAX_TIMEMODE) This->TimeMode = 0;
	}
	else
	{
		This->TimeMode--;
		if (This->TimeMode < 0) This->TimeMode = MAX_TIMEMODE;
	}

	Player_ShowTimeMode(This);
	Player_ShowPosition(This);
}

/*
 * Command: Set the position within the current track
 *          when clicking on the position bar
 */

static void Player_UpdatePos(void* pData, int value, int maxval, ESlider_UpdateMode mode)
{
	Player* This = pData;

	if (mode != ESlider_UpdateMode_Cancel)
	{
		Player_SetTrackPos(This, (float) value / (float) maxval);
		Player_ReadCurrentPos(This);
		This->m_DragPos = This->track.pos.driver.pos;
	}

	if (mode != ESlider_UpdateMode_Dragging) This->m_DragPos = -1;
}

/*
 * Command: Set the volume level
 *          when clicking or dragging the volume icon
 */

static void Player_UpdateVolume(void* pData, int value, int maxvalue, ESlider_UpdateMode mode)
{
	Player* This = pData;

	if (mode != ESlider_UpdateMode_Cancel)
	{
		float fval = (float) value / (float) maxvalue;
		value = (int) (fval * Volume_Max);
	}
	else	value = This->Volume;

	if (mode == ESlider_UpdateMode_Dragging)
		This->m_DragVolume = value;
	else	This->m_DragVolume = -1;

	Player_SetVolume(This, value);
}

/*
 * Command: Switch between the loop modes
 *          when loop icon is clicked
 */

static void Player_ToggleLoopMode(Player* This, bool next)
{
	int mode = This->LoopMode;

	if (next)
	{
		mode++;
		if (mode > loop_section_play) mode = loop_volume_play;
	}
	else
	{
		mode--;
		if (mode < loop_volume_play) mode = loop_section_play;
	}

	Player_SetLoopMode(This, (Media_LoopMode) mode);
}

static void Player_SetTrackPos(Player* This, float value)
{
	uint64_t pos, len;

	if (This->Status != status_play) return;
	if ((value < 0) || (value > 1)) return;

	pos = This->track.pos.driver.start;
	len = This->track.pos.driver.end - This->track.pos.driver.start;
	PlayList_Play(This->pPlayList, pos + (int64_t) (float)(value * (float) len));

	Player_ReadCurrentPos(This);
	Player_ShowPosition(This);
}

static void Player_ListTracks(Player* This, bool bSubMenu)
{
	int items = WList_Count(&This->m_TrackList);
	int width;
	// Force menu window at maximal width if not too large
	CWind Info = Window_GetInfo(WList_GetWindow(&This->m_TrackList));
	width = Info.ex.x1 - Info.ex.x0;
	if (width > 640) width = 640;
	Info.o.o.cvt.box.x1 = Info.o.o.cvt.box.x0 + width;
	if (items < 10) items = 10;
	if (items > Options()->Player.MaxVisibleTracks) items = Options()->Player.MaxVisibleTracks;
	Info.o.o.cvt.box.y1 = Info.o.o.cvt.box.y0 + items*40;
	Info.o.o.behind = -3;
	Window_WimpOpen(&Info.o.o);
	Window_WimpClose(Info.o.o.w);
	// Open menu
	WList_SetParent(&This->m_TrackList, This->m_CurrentPanel->m_w);
	if (bSubMenu)
		WList_SubMenu(&This->m_TrackList);
	else
		WList_Popup(&This->m_TrackList, HWind_None, 0);
	if (This->track.current != -1)
		WList_ShowItem(&This->m_TrackList, This->track.current);
	else if ((This->track.next[This->ListMode] >= 0)
	     &&  (This->track.next[This->ListMode] < WList_Count(&This->m_TrackList)))
		WList_ShowItem(&This->m_TrackList, This->track.next[This->ListMode]);
}

void Player_SetCmdHandler(Player* This, const CmdHandler* pHandler, void* handle)
{
	This->m_pCmdHandler = pHandler;
	This->m_pCmdHandle = handle;
}

int Player_CheckCommand(void* handle, CmdID id)
{
	Player* This = handle;
	int state = 0;

	switch(id)
	{
		case Cmd_Ctrl_PlayPause:
		case Cmd_Ctrl_Play:
		{
			if (WList_Count(&This->m_TrackList) > 0)
				state = ECmdState_Allow;
		}
		break;
		case Cmd_Ctrl_Pause:
		{
			if (Player_IsStatusPlaying(This))
				state = ECmdState_Allow;
		}
		break;
		case Cmd_Ctrl_Stop:
		{
			if (Player_IsStatusActive(This))
				state = ECmdState_Allow;
		}
		break;
		case Cmd_Ctrl_RestartSection:
		case Cmd_Ctrl_RestartTrack:
		case Cmd_Ctrl_Rewind:
		case Cmd_Ctrl_Forward:
		case Cmd_Ctrl_PrevSection:
		case Cmd_Ctrl_NextSection:
		{
			if (This->Status == status_play)
				state = ECmdState_Allow;
		}
		break;
		case Cmd_Ctrl_PrevTrack:
		{
			if (Player_IsStatusActive(This))
			{
				int nr_tracks = WList_Count(&This->m_TrackList);
				int track = This->track.current - 1;

//				if (!Player_IsStatusPlaying(This))
//					break;

				if ((This->LoopMode == loop_volume_loop)
				||  (This->LoopMode == loop_section_loop))
				{
					if (track < 0)
						track = nr_tracks - 1;
				}

				if ((track >= 0)
				&&  (track < nr_tracks)
				&&  (track != This->track.current))
					state = ECmdState_Allow;
			}
			else
				state = Player_CheckCommand(This, Cmd_Ctrl_Play);
		}
		break;
		case Cmd_Ctrl_NextTrack:
		{
			if (Player_IsStatusActive(This))
			{
				int nr_tracks = WList_Count(&This->m_TrackList);
				int track = This->track.current + 1;

//				if (!Player_IsStatusPlaying(This))
//					break;

				if ((This->LoopMode == loop_volume_loop)
				||  (This->LoopMode == loop_section_loop))
				{
					if (track >= nr_tracks)
						track = 0;
				}

				if ((track >= 0)
				&&  (track < nr_tracks)
				&&  (track != This->track.current))
					state = ECmdState_Allow;
			}
			else
				state = Player_CheckCommand(This, Cmd_Ctrl_Play);
		}
		break;
		case Cmd_Ctrl_ListTracks:
		{
			if (DCDUtils_GetFullScreenPlugIn() == -1)
				state = ECmdState_Allow;
		}
		break;
		case Cmd_Ctrl_PrevLoop:
		case Cmd_Ctrl_NextLoop:
		{
			state = ECmdState_Allow;
		}
		break;
		case Cmd_Ctrl_Loop_NormalPlay:
		{
			state = ECmdState_Allow;
			if (This->LoopMode == loop_volume_play)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_Ctrl_Loop_OnList:
		{
			state = ECmdState_Allow;
			if (This->LoopMode == loop_volume_loop)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_Ctrl_Loop_OnTrack:
		{
			state = ECmdState_Allow;
			if (This->LoopMode == loop_section_loop)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_Ctrl_Loop_EndTrack:
		{
			state = ECmdState_Allow;
			if (This->LoopMode == loop_section_play)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_Ctrl_ToggleShuffle:
		{
			state = ECmdState_Allow;
			if (Player_GetShuffleMode(This) != 0)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_Ctrl_ToggleProgram:
		case Cmd_Ctrl_ToggleCDProgram:
		{
			state = ECmdState_Allow;
			if (This->ListMode)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_Ctrl_ToggleIntroscan:
		{
			state = ECmdState_Allow;
			if (This->m_Flags & Player_IntroScan)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_Ctrl_VolMain_FineUp:
		case Cmd_Ctrl_VolMain_FineDown:
		case Cmd_Ctrl_VolMain_Up:
		case Cmd_Ctrl_VolMain_Down:
		case Cmd_Ctrl_PrevTimer:
		case Cmd_Ctrl_NextTimer:
		{
			state = ECmdState_Allow;
		}
		break;
		case Cmd_Ctrl_PlugIns_Start:
		{
			if (Player_IsStatusActive(This))
			{
				if ((DCDUtils_GetFullScreenPlugIn() == -1)
				&&  DCDUtils_HasValidDriver(Player_GetHardwareType(This)))
					state = ECmdState_Allow;
			}
			else return Player_CheckCommand(handle, Cmd_Ctrl_Play);
		}
		break;
		case Cmd_Ctrl_PlugIns_Stop:
		{
			if (DCDUtils_PlayerHasPlugIn(Player_GetDCDUtilsHandle(This)))
				state = ECmdState_Allow;
		}
		break;
		case Cmd_Ctrl_Shortcuts:
		{
			state = ECmdState_Allow;
			if (Player_AllowsShortcuts(This))
				state |= ECmdState_Tick;
		}
		break;
		default:
			return App_CheckCommand(NULL, id);
	}

	return state;
}

bool Player_ExecCommand(void* handle, CmdID id)
{
	Player* This = handle;
	bool done = true;

	switch(id)
	{
		case Cmd_Ctrl_PlayPause:
		{
			if (Player_IsStatusPlaying(This))
			{
				Player_SetStatus(This, status_pause);
				return true;
			}
		}
		// no break;
		case Cmd_Ctrl_Play:
		{
			switch (This->Status)
			{
				case status_busy:
				break;
				case status_play:
					Player_ExecCommand(This, Cmd_Ctrl_RestartTrack);
				break;
				case status_pause:
					Player_SetStatus(This, status_pause);
				break;
				default:
				{
					This->ErrorCount = 0;
					Player_PlayDirectedTrack(This, This->track.next[This->ListMode], 0);
				}
			}
		}
		break;
		case Cmd_Ctrl_Pause:
		{
			Player_SetStatus(This, status_pause);
		}
		break;
		case Cmd_Ctrl_Stop:
		{
			Player_SetStatus(This, status_stop);
		}
		break;
		case Cmd_Ctrl_RestartSection:
		{
			PlayList_Seek(This->pPlayList, EMedia_Seek_SectionStart);
		}
		break;
		case Cmd_Ctrl_RestartTrack:
		{
			Player_SetTrackPos(This, 0);
		}
		break;
		case Cmd_Ctrl_Rewind:
		{
			Player_Rewind(This);
		}
		break;
		case Cmd_Ctrl_Forward:
		{
			Player_Forward(This);
		}
		break;
		case Cmd_Ctrl_PrevSection:
		{
			PlayList_Seek(This->pPlayList, EMedia_Seek_PreviousSection);
		}
		break;
		case Cmd_Ctrl_NextSection:
		{
			PlayList_Seek(This->pPlayList, EMedia_Seek_NextSection);
		}
		break;
		case Cmd_Ctrl_PrevTrack:
		{
			Player_PlayDirectedTrack(This, This->track.next[This->ListMode], -1);
		}
		break;
		case Cmd_Ctrl_NextTrack:
		{
			Player_CheckReShuffle(This);

			Player_PlayDirectedTrack(This, This->track.next[This->ListMode], +1);
		}
		break;
		case Cmd_Ctrl_ListTracks:
		{
			const CmdContext* ctx = CmdHandler_GetContext(&Player_CmdHandler);

			Player_ListTracks(This, (ctx->menu != NULL));

			return true;
		}
		break;
		case Cmd_Ctrl_PrevLoop:
		{
			Player_ToggleLoopMode(This, false);
		}
		break;
		case Cmd_Ctrl_NextLoop:
		{
			Player_ToggleLoopMode(This, true);
		}
		break;
		case Cmd_Ctrl_Loop_NormalPlay:
		{
			Player_SetLoopMode(This, loop_volume_play);
		}
		break;
		case Cmd_Ctrl_Loop_OnList:
		{
			Player_SetLoopMode(This, loop_volume_loop);
		}
		break;
		case Cmd_Ctrl_Loop_OnTrack:
		{
			Player_SetLoopMode(This, loop_section_loop);
		}
		break;
		case Cmd_Ctrl_Loop_EndTrack:
		{
			Player_SetLoopMode(This, loop_section_play);
		}
		break;
		case Cmd_Ctrl_ToggleShuffle:
		{
			Player_SetShuffleMode(This, Player_GetShuffleMode(This) ^ 1);
		}
		break;
		case Cmd_Ctrl_ToggleProgram:
		case Cmd_Ctrl_ToggleCDProgram:
		{
			Player_InvertListMode(This);
		}
		break;
		case Cmd_Ctrl_ToggleIntroscan:
		{
			Player_InvertIntroScan(This);
		}
		break;
		case Cmd_Ctrl_VolMain_FineUp:
		{
			int volume = Player_ReadVolume(This) + 65536;
			Player_SetVolume(This, volume);
		}
		break;
		case Cmd_Ctrl_VolMain_FineDown:
		{
			int volume = Player_ReadVolume(This) - 65536;
			Player_SetVolume(This, volume);
		}
		break;
		case Cmd_Ctrl_VolMain_Down:
		{
			int volume = Player_ReadVolume(This) - 5*65536;
			Player_SetVolume(This, volume);
		}
		break;
		case Cmd_Ctrl_VolMain_Up:
		{
			int volume = Player_ReadVolume(This) + 5*65536;
			Player_SetVolume(This, volume);
		}
		break;
		case Cmd_Ctrl_PrevTimer:
		{
			Player_ToggleTimeMode(This, false);
		}
		break;
		case Cmd_Ctrl_NextTimer:
		{
			Player_ToggleTimeMode(This, true);
		}
		break;
		case Cmd_Ctrl_PlugIns_Start:
		{
			Menu_Close();
			if (!Player_IsStatusActive(This))
				Player_ExecCommand(This, Cmd_Ctrl_Play);
			Player_StartPlugIn(This);
		}
		break;
		case Cmd_Ctrl_PlugIns_Stop:
		{
			Player_StopPlugIns(This);
		}
		break;
		case Cmd_Ctrl_Shortcuts:
		{
			Player_ToggleShortcuts(This);
		}
		break;
		default:
			done = App_ExecCommand(NULL, id);
	}

	return done;
}

/*
 * Command: Move back one track
 *          when previous button is clicked
 */

static bool Player_Previous(Player* This, bool bPrevInError)
{
	if (bPrevInError)
		This->ErrorCount++;
	else
		This->ErrorCount = 0;

	bool done = CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_PrevTrack);
	Icon_PushControl(This->m_CurrentPanel->m_w, Icon_Previous, 10);
	return done;
}

/*
 * Command: Move forward one track
 *          when next button is clicked
 */

static bool Player_Next(Player* This, bool bPrevInError)
{
	if (bPrevInError)
		This->ErrorCount++;
	else
		This->ErrorCount = 0;

	bool done = CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_NextTrack);
	Icon_PushControl(This->m_CurrentPanel->m_w, Icon_Next, 10);
	return done;
}

void Player_SetStatus(Player* This, Media_Status status)
{
	switch(status)
	{
		case status_play: // set by playlist, player only sets status to busy
		{
			This->ErrorCount = 0;
		}
		break;
		case status_pause:
		{
			if (This->Status == status_undef)
			{
				PlayList_SetStop(This->pPlayList, false);
				Player_ShowListName(This);
				Player_SetStatus(This, status_stop);
				return;
			}
			if ((This->Status != status_play)
			&&  (This->Status != status_busy)
			&&  (This->Status != status_pause)
			&&  (This->Status != status_undef))
			{
				Icon_PushControl(This->m_CurrentPanel->m_w, Icon_Pause, 10);
				return;
			}
		}
		break;
		case status_error:
		{
			bool done;

			PlayList_ReportError(This->pPlayList);

			if (This->ErrorReversedDirection)
				done = Player_Previous(This, true);
			else
				done = Player_Next(This, true);

			if (done && Player_IsStatusPlaying(This))
				return;
		}
		// nobreak;
		case status_stop:
		{
			This->ErrorCount = 0;

			switch (This->Status)
			{
				case status_play:
				case status_busy:
				case status_pause:
				case status_error:
				{
					PlayList_SetStop(This->pPlayList, (status == status_error));
				}
				break;
				case status_stop:
				{
				}
				break;
				default:
				{
					Player_Init(This, true, true);
				}
			}

			if (This->Status != status_stop)
			{
				bool bAutoStart = ((This->Status == status_empty) || (This->Status == status_undef));

				if (This->Status != status_empty)
				{
					This->Status = status_stop;
					Player_ShowStatus(This);
					Player_ShowListName(This);
				}
				else
				{
					This->Status = status_stop;
					Menu_Refresh(This->m_CurrentPanel->m_w, NULL);
				}

				Player_SetCurrentTrack(This, -1);
				Player_ShowTrackAndPosition(This);
				DCDUtils_SetInfo(This->m_DCDUtilsHandle, 0, -1);

				if (bAutoStart
				&&  This->pPlayList->pcmds->pGetConfig(This->pPlayList, (Media_Config) PlayList_CfgAutoStart))
				{
					CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_Play);
				}
			}
			return;
		}
		break;
		case status_empty:
		{
			This->ErrorCount = 0;

			if ((This->Status == status_play)
			||  (This->Status == status_busy)
			||  (This->Status == status_pause)
			||  (This->Status == status_error))
			{
				PlayList_SetStop(This->pPlayList, false);
				Player_ShowListName(This);
				Player_ShowTrack(This);
			}

			if (This->Status != status_empty)
			{
				if (This->Status == status_stop)
					This->Status = status;
				else
				{
					This->Status = status;
					Player_ShowStatus(This);
				}

				Player_SetCurrentTrack(This, -1);
				Player_Init(This, false, true);
				Player_ShowTrackAndPosition(This);
				DCDUtils_SetInfo(This->m_DCDUtilsHandle, 0, -1);

				Menu_Refresh(This->m_CurrentPanel->m_w, NULL);
			}
			return;
		}
		break;
		default: ;
	}

	switch (This->Status)
	{
		case status_undef:
		{
			Player_Init(This, false, true);
			This->Status = status;
			Player_ShowStatus(This);
		}
		break;
		case status_busy:
		{
			if (status == status_play)
			{
				This->Status = status_play;
				Player_FixInfo(This, Media_Updated_All);
			}
		}
		// nobreak
		case status_play:
		{
			if (status == status_pause)
			{
				PlayList_SetPause(This->pPlayList, true);
				This->Status = status_pause;
				Player_ShowStatus(This);
			}
			else if (status == status_busy)
				This->Status = status_busy;
		}
		break;
		case status_pause:
		{
			PlayList_SetPause(This->pPlayList, false);
			PlayList_SetVolume(This->pPlayList, This->Volume);
			This->Status = status_busy;
			Player_ShowStatus(This);
		}
		break;
		case status_stop:
		{
			if ((status == status_play)
			||  (status == status_busy))
				This->Status = status;
		}
		break;
		case status_empty:
		{
			Icon_PushControl(This->m_CurrentPanel->m_w, Icon_Play, 10);
		}
		break;
		default: ;
	}
}

void Player_RemoteControl(Player* This, ECmd_Type_Player type, uint32_t value)
{
	switch(type)
	{
		case ECmd_Type_Player_Ctrl:
		{
			CmdHandler_ExecCommand(This->m_pCmdHandler, This->m_pCmdHandle, Cmd_Ctrl_Play + value);
		}
		break;
		case ECmd_Type_Player_Volume:
			Player_SetVolume(This, value << 8);
		break;
		case ECmd_Type_Player_Position:
		{
			unsigned int val = value;
			unsigned int maxval = This->track.pos.section.len;
			if (maxval)
				Player_SetTrackPos(This, (float) val / (float) maxval);
		}
		break;
	}
}

static void Player_Init(Player* This, bool identify, bool buildlist)
{
	if (PlayList_Init(This->pPlayList, identify))
		buildlist = true;

	Player_ShowListName(This);

	if (buildlist)
		Player_ReShuffle(This, false);
}

static void Player_ReadCurrentPos(Player* This)
{
	if (This->track.current == -1)
	{
		This->track.pos.driver.pos = 0;
		This->track.pos.volume.pos = 0;
		This->track.pos.section.pos = 0;
	}
	else
	{
		PlayList_GetPosition(This->pPlayList, This->track.handle, &This->track.pos);
	}
	DCDUtils_SetIntInfo(This->m_DCDUtilsHandle, DCD_Info_Pos, This->track.pos.section.pos);
}

/**
 * Won't do anything if player status is not set to playing.
 * Plays a given track. If playing fails, moves in list according to track.reversedirection
 * and retry (3 attempts max), at least if option to stop on error is not set.
 */

static void Player_StartTrack(Player* This, int index, bool reversedirection)
{
	if (!Player_IsStatusPlaying(This))
		return;

	int attempts = (Options()->Player.bStopPlayingOnError ? 1 : 3) - This->ErrorCount;
	int nr_tracks = WList_Count(&This->m_TrackList);

	Player_SetStatus(This, status_busy);

	int track = (reversedirection) ? index + 1 : index - 1;

	This->ErrorReversedDirection = reversedirection;

	while (attempts > 0)
	{
		attempts--;

		if (reversedirection)
		{
			track--;

			if ((This->LoopMode == loop_volume_loop)
			||  (This->LoopMode == loop_section_loop))
			{
				if (track < 0)
					track = nr_tracks - 1;
			}
		}
		else
		{
			track++;

			if ((This->LoopMode == loop_volume_loop)
			||  (This->LoopMode == loop_section_loop))
			{
				if (track >= nr_tracks)
					track = 0;
			}
		}

		if ((track < 0) || (track >= nr_tracks))
			break;

		Player_SetCurrentTrack(This, track);
		This->track.next[This->ListMode] = track;

		PlayList_SetVolume(This->pPlayList, This->Volume);
		// Note that this calls stops any previously playing track
		if (!PlayList_PlayTrack(This->pPlayList, This->track.handle, (This->ErrorCount > 0)))
		{
			This->ErrorCount++;
			continue;
		}
		This->track.prepnext = true;

		PlayList_SetVolume(This->pPlayList, This->Volume);

		Player_ReadCurrentPos(This);

		Player_FixInfo(This, Media_Updated_All | Media_New_Song);

		// Delay polling of CDs
		if (This->m_Flags & Player_SlowPolling)
			This->m_PollTime = Timer_GetTime() + 50;

		// If could not play next track, remove it
		if (!Player_IsTrackPlayed(This))
			return;

		// Ensure track menu is not open, ????
		Menu_Close();
	}

	// Could not be played
	PlayList_SetStop(This->pPlayList, true);
	Player_SetStatus(This, status_stop);
}

/**
 * Prepares the next track down in the list for playing (anticipated loading).
 */

static void Player_PrepNextTrack(Player* This)
{
	// Ensure only called once per playing track
	if (This->track.prepnext == false) return;
	This->track.prepnext = false;

	int nr_tracks = WList_Count(&This->m_TrackList);
	int track = This->track.current + 1;

	if ((This->LoopMode == loop_volume_loop)
	||  (This->LoopMode == loop_section_loop))
	{
		if (track >= nr_tracks)
			track = 0;
	}

	if ((track < 0) || (track >= nr_tracks)) return;

	PlayList_PrepTrack(This->pPlayList, WList_Get(&This->m_TrackList, track));
}

static void Player_FixInfo(Player* This, unsigned int flags)
{
	if (!flags) return;

	if (flags & Media_New_Song)
	{
		This->m_AuthorMetaTime = Timer_GetTime() + 500;
		This->m_AuthorMetaMode = 0;
		This->m_PlugInMetaMode = 0;

		This->m_SongsCount++;
		DCDUtils_SetInfo(This->m_DCDUtilsHandle, This->m_SongsCount, This->m_HardwareType);
	}

	if (flags & Media_Updated_Title)
	{
		const char* ptitle = Player_GetTitle(This);

		// Emulate new song
		DCDUtils_SetIntInfo(This->m_DCDUtilsHandle, DCD_Info_Title, (int) ptitle);
		DCDUtils_SetIntInfo(This->m_DCDUtilsHandle, DCD_Info_ModuleName
			, (int) PlayList_GetText(This->pPlayList, This->track.handle, EMetaId_ModuleName));
		DCDUtils_SetIntInfo(This->m_DCDUtilsHandle, DCD_Info_ModuleHandle
			, PlayList_GetModuleHandle(This->pPlayList, This->track.handle));
	}

	if (flags & Media_Updated_Length)
	{
		PlayList_GetPosition(This->pPlayList, This->track.handle, &This->track.pos);
		DCDUtils_SetIntInfo(This->m_DCDUtilsHandle, DCD_Info_StartPos, 0);
		DCDUtils_SetIntInfo(This->m_DCDUtilsHandle, DCD_Info_EndPos, This->track.pos.section.len);
	}


	if (flags & ( Media_Updated_Type | Media_Updated_Meta))
		Player_ShowListName(This);

	if (flags & ( Media_Updated_Length | Media_Updated_Meta))
		Player_ShowTrackAndPosition(This);
}

void Player_StartPlugIn(Player* This)
{
	if (Player_GetCurrentTrack(This)
	&&  Window_IsOpen(This->m_CurrentPanel->m_w))
	{
		DCDUtils_StartPlugIn(This->m_DCDUtilsHandle);
	}
}

void Player_StopPlugIns(Player* This)
{
	DCDUtils_StopPlugIns(This->m_DCDUtilsHandle);
}

int Player_FindTrack(Player* This, const void* ptrack)
{
	return WList_Find(&This->m_TrackList, 0, ptrack);
}

void Player_RemoveTrack(Player* This, const void* ptrack)
{
	int index = WList_Find(&This->m_TrackList, 0, ptrack);

	if (index >= 0)
	{
		const void* pcurrent = NULL;
		int nextindex = -1;
		int nr_tracks;

		// Find handles of current and next track so that we can update their indexes.
		pcurrent = Player_GetCurrentTrack(This);
		if ((This->track.next[This->ListMode] >= 0)
		&&  (This->track.next[This->ListMode] < WList_Count(&This->m_TrackList)))
			nextindex = This->track.next[This->ListMode];

		WList_Del(&This->m_TrackList, index);
		nr_tracks = WList_Count(&This->m_TrackList);

		if (nextindex >= nr_tracks)
			nextindex = -1;
		if (nextindex > index)
			nextindex--;

		if (This->track.current > index)
		{
			This->track.current -= 1;
			This->track.next[This->ListMode] = nextindex;
		}
		else if (This->track.current == index)
		{
			if (Player_IsStatusPlaying(This))
			{
				// Attempt to play the track currently using the same index
				if ((This->LoopMode == loop_volume_loop)
				||  (This->LoopMode == loop_section_loop))
				{
					if (This->track.current >= nr_tracks)
						This->track.current = 0;
				}

				if (This->track.current >= nr_tracks)
				{
					Player_SetStatus(This, status_stop);

					// Next track is end of list as forcing 0 with always restart the list
					// from the beginning even if tracks were to be added to the end of the list
					This->track.next[This->ListMode] = nr_tracks;
				}
				else
				{
					Player_PlayTrack(This, This->track.current);
					// next is updated by playtrack
				}
			}
			else
			{
				This->track.next[This->ListMode] = nextindex;
			}
		}
		else
		{
			This->track.next[This->ListMode] = nextindex;
		}
	}
}

/**
 * Plays a given track. If playing fails, moves in list according to track.reversedirection
 * and retry (3 attempts max), at least if option to stop on error is not set.
 *
 */

static void Player_PlayDirectedTrack(Player* This, int index, int offset)
{
	bool startplug = !Player_IsStatusActive(This);

	// If already playing/busy, simply move to track
	if (!Player_IsStatusPlaying(This))
	{
		int count = This->ErrorCount;
		// Not playing yet, force status to stop (in case we were paused, ...)
		Player_SetStatus(This, status_stop);
		This->ErrorCount = count;
		// Change status to busy which init some stuffs and calls us back again
		Player_Init(This, true, false);

		int nr_tracks = WList_Count(&This->m_TrackList);
		if (nr_tracks == 0)
		{
			Icon_PushControl(This->m_CurrentPanel->m_w, Icon_Play, 10);
			return;
		}

		Player_SetStatus(This, status_busy);
		Player_ShowStatus(This);

		if ((index < 0) || (index >= nr_tracks))
			index = 0;
	}

	Player_StartTrack(This, index + offset, (offset < 0));

	// Plug-In may may need to be started
	if (startplug
	&&  (Options()->PlugIns.bAutoStart)
	&&  !DCDUtils_PlayerHasPlugIn(This->m_DCDUtilsHandle))
		Player_StartPlugIn(This);
}

void Player_PlayTrack(Player* This, int index)
{
	This->ErrorCount = 0;
	Player_PlayDirectedTrack(This, index, 0);
}

/*----------------------------------------------------------------------
 *- Window handler functions -------------------------------------------
 *----------------------------------------------------------------------*/

static EListenerAction Player_NullEvent(void* handle, const Event* e)
{
	Player* This = (Player*) handle;
	PlayList * plist = This->pPlayList;
	Media_Status status;
	int newtime = Timer_GetTime();

	IGNORE(e);

	if (!Window_IsOpen(This->m_CurrentPanel->m_w)
//	&&  (This->Status != status_undef)
	&&  !Player_IsStatusPlaying(This))
		return EListenerAction_ContinueEvent;

	if ((This->m_Flags & Player_SlowPolling) && (This->Status != status_undef))
	{
		// Reduce polling for CDs
		if ((This->m_PollTime - newtime) >= 0)
				return EListenerAction_ContinueEvent;

		if (Player_IsStatusPlaying(This)
		&&  (  Window_IsOpen(This->m_CurrentPanel->m_w)
		    || (This->track.pos.section.pos > This->track.pos.section.len - 300)))
			This->m_PollTime = newtime + Options()->Drivers.CDFS.PollingDelay;
		else
			This->m_PollTime = newtime + 100;
	}

	status = PlayList_GetStatus(plist);

	if (status == status_empty)
	{
		Player_SetStatus(This, status_empty);
	}
	else if (Player_IsStatusPlaying(This))
	{
		bool next = (status == status_stop);
		bool prepnext = false;

		Player_ReadCurrentPos(This);
		Player_FixInfo(This, This->pPlayList->pcmds->pGetUpdates(This->pPlayList));

		if (!next && (This->m_Flags & Player_IntroScan))
		{
			if (This->track.pos.section.pos > Options()->Player.IntroScanTime)
				next = true;
		}

		if (This->m_DragPos < 0)
		{
			if (next)
			{
				Player_CheckReShuffle(This);

				switch(This->LoopMode)
				{
					case loop_volume_play:
					{
						if (This->track.current < WList_Count(&This->m_TrackList) - 1)
						{
							This->ErrorCount = 0;
							Player_PlayDirectedTrack(This, This->track.current, +1);
						}
						else
						{
							// Next track is end of list as forcing 0 with alway restart the list
							// from the beginning even if tracks wew added to the end of the list
							This->track.next[This->ListMode] = WList_Count(&This->m_TrackList);
							Player_SetStatus(This, status_stop);
						}
					}
					break;
					case loop_volume_loop:
					{
						This->ErrorCount = 0;
						Player_PlayDirectedTrack(This, This->track.current, +1);
					}
					break;
					case loop_section_loop:
					{
						This->ErrorCount = 0;
						Player_PlayDirectedTrack(This, This->track.current, 0);
					}
					break;
					default:
					{
						This->track.next[This->ListMode] = This->track.current + 1;
						Player_SetStatus(This, status_stop);
					}
				}
			}
			else
			{
				if (This->Status != status)
					Player_SetStatus(This, status);

				if ((This->track.pos.section.len - This->track.pos.section.pos) < 1000) // 10 seconds
				{
					switch(This->LoopMode)
					{
						case loop_volume_play:
						{
							if (This->track.current < WList_Count(&This->m_TrackList) - 1)
								prepnext = true;
						}
						break;
						case loop_volume_loop:
						{
							prepnext = true;
						}
						break;
					}
				}

				if (prepnext && Options()->Player.bGaplessPlayback)
				{
					Player_CheckReShuffle(This);
					This->track.shufflecount--; // Reset counter

					Player_PrepNextTrack(This);
				}
			}
		}
		Player_ShowPosition(This);
	}
	else if (This->Status != status_pause)
	{
		Player_SetStatus(This, status_stop);
	}


	if ((newtime - This->m_AuthorMetaTime) >= 0)
		Player_ShowListName(This);

	return EListenerAction_ContinueEvent;
}

static void Player_ClickHandler(Player* This, const Mouse* m)
{
	Panel* panel = This->m_CurrentPanel;

	switch(m->i)
	{
		case Icon_Back:
		{
			Window_OpenBehind(panel->m_w, -2);
		}
		break;
		case Icon_Close:
		{
			// Normal close or pin ?
			if (!((m->but & (EBut_ClickSelect|EBut_DragSelect|EBut_Select))
			      && (Keyboard_PollShift())))
			{
				DCDUtils_StopPlugIns(This->m_DCDUtilsHandle);
				Window_Close(panel->m_w);
				return;
			}
		}
		// no break
		case Icon_Pin:
		{
			Msg_Iconize msg;
			msg.hdr.size = 48;
			msg.hdr.ref = 0;
			msg.hdr.action = EMsg_Iconize;
			msg.w = panel->m_w;
			msg.t = Task_GetId();
			snprintf(msg.title, sizeof(msg.title), "%s", Task_GetName());
			Task_SendMsg((Msg*) &msg, 0);
			return;
		}
		break;
		case Icon_Play:
		{
			CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_Play);
		}
		break;
		case Icon_Pause:
		{
			if (This->Status == status_pause)
				CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_Play);
			else
				CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_Pause);
		}
		break;
		case Icon_Stop:
		{
			CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_Stop);
		}
		break;
		case Icon_Rewind:
		{
			if (Keyboard_PollShift())
			{
				if (m->but & EBut_Select)
					CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_PrevSection);
				else if (m->but & EBut_Adjust)
					CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_NextSection);
			}
			else
			{
				if (m->but & EBut_Select)
					Player_Rewind(This);
				else if (m->but & EBut_Adjust)
					Player_Forward(This);
			}
		}
		break;
		case Icon_Forward:
		{
			if (Keyboard_PollShift())
			{
				if (m->but & EBut_Select)
					CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_NextSection);
				else if (m->but & EBut_Adjust)
					CmdHandler_ExecCommand(&Player_CmdHandler, This, Cmd_Ctrl_PrevSection);
			}
			else
			{
				if (m->but & EBut_Select)
					Player_Forward(This);
				else if (m->but & EBut_Adjust)
					Player_Rewind(This);
			}
		}
		break;
		case Icon_Previous:
		{
			if (m->but & EBut_Select)
				Player_Previous(This, false);
			else if (m->but & EBut_Adjust)
				Player_Next(This, false);
		}
		break;
		case Icon_Next:
		{
			if (m->but & EBut_Select)
				Player_Next(This, false);
			else if (m->but & EBut_Adjust)
				Player_Previous(This, false);
		}
		break;
		case Icon_SetTrack:
		{
			Player_ListTracks(This, false);
		}
		break;
		case Icon_SetTimeMode:
		{
			Player_ToggleTimeMode(This, (m->but == EBut_Select));
		}
		break;
		case Icon_SetPos:
		case Icon_SetPos2:
		{
			if ((This->Status == status_play)
			&&  This->pPlayList->pcmds->pGetConfig(This->pPlayList, (Media_Config) PlayList_CfgSeekable))
				Slider_Update(panel->m_w, Icon_SetPos, Icon_SetPos2, PosBarType, m, Player_UpdatePos, This);
		}
		break;
		case Icon_Volume:
		{
			Slider_Update(panel->m_w, Icon_Volume, panel->m_VolumeButton, VolumeType, m, Player_UpdateVolume, This);
		}
		break;
		case Icon_LoopMode:
		{
			Player_ToggleLoopMode(This, (m->but == EBut_Select));
		}
		break;
		case Icon_ListMode:
		{
			Player_InvertListMode(This);
		}
		break;
		case Icon_IntroScan:
		{
			Player_InvertIntroScan(This);
		}
		break;
		case Icon_Mini:
		{
			Window_Close(panel->m_w);
			Player_SelectPanel(This, (panel != &This->m_Mini) ? &This->m_Mini : &This->m_Ctrl);
			Player_ShowControls(This, -1);
			panel = This->m_CurrentPanel;
		}
		break;
		default:
		{
			if ((VolumeType & ESliderType_Button) && (m->i == VolumeButton))
			{
				Slider_Update(panel->m_w, Icon_Volume, panel->m_VolumeButton, VolumeType, m, Player_UpdateVolume, This);
			}
			else if (m->but & (EBut_DragSelect | EBut_DragAdjust))
			{
				// Move window
				CDragBox drag;
				drag.type = EDragType_WindowMove;
				drag.w = panel->m_w;
				DragBox(&drag);
			}
			else if (m->but & EBut_ClickSelect)
			{
				// Bring window to top
				Window_Open(panel->m_w);
			}
		}
	}

	if (This->m_Flags & Player_Shortcuts)
		Caret_SetInvisible(panel->m_w);
}

static EListenerAction Player_MsgHandler(void* handle, const Event* e)
{
	Player* This = (Player*) handle;

	IGNORE(e);

	Panel_ModeChange(This->m_CurrentPanel);

	return EListenerAction_ContinueEvent;
}

static EListenerAction Player_EventHandler(void* handle, const Event* e)
{
	Player* This = (Player*) handle;
	Panel* pskin = This->m_CurrentPanel;

	switch(e->Type)
	{
		case EEvent_WindowOpen:
		{
			const Mode_Info* pinfo = Task_GetModeInfo();
			CWindState ps = Window_GetState(HWind_IconBar);
			CWindOpen* po = (CWindOpen*) e->pData;
			CRect      box;

			// Forbid iconised window to be opened if not same mode
			if (po->w != This->m_CurrentPanel->m_w) return EListenerAction_StopEvent;

			box = Window_GetExpectedOutline(po);

			if (!(pskin->m_docking & Panel_ForceDocking))
				pskin->m_docking = 0;
			else
				pskin->m_docking &= ~Panel_ForceDocking;

			if ((pskin->m_docking & Panel_DockLeft)
			||  (abs(box.x0) < 16))
			{
				po->cvt.box.x1 -= box.x0;
				po->cvt.box.x0 -= box.x0;

				pskin->m_docking |= Panel_DockLeft;
			}
			else
				pskin->m_docking &= ~Panel_DockLeft;

			if ((pskin->m_docking & Panel_DockRight)
			||  (abs(box.x1 - pinfo->box.x1) < 16))
			{
				po->cvt.box.x0 -= box.x1 - pinfo->box.x1;
				po->cvt.box.x1 -= box.x1 - pinfo->box.x1;
				pskin->m_docking |= Panel_DockRight;
			}
			else
				pskin->m_docking &= ~Panel_DockRight;

			if ((pskin->m_docking & Panel_DockBottom)
			||  (abs(box.y0) < 16))
			{
				po->cvt.box.y1 -= box.y0;
				po->cvt.box.y0 -= box.y0;
				pskin->m_docking |= Panel_DockBottom;
			}
			else
				pskin->m_docking &= ~Panel_DockBottom;

			if ((pskin->m_docking & Panel_DockIconbar)
			||  (abs(box.y0 - ps.o.cvt.box.y1) < 16))
			{
				po->cvt.box.y1 -= box.y0 - ps.o.cvt.box.y1;
				po->cvt.box.y0 -= box.y0 - ps.o.cvt.box.y1;
				pskin->m_docking |= Panel_DockIconbar;
			}
			else
				pskin->m_docking &= ~Panel_DockIconbar;

			if ((pskin->m_docking & Panel_DockTop)
			||  (abs(box.y1 - pinfo->box.y1) < 16))
			{
				po->cvt.box.y0 -= box.y1 - pinfo->box.y1;
				po->cvt.box.y1 -= box.y1 - pinfo->box.y1;
				pskin->m_docking |= Panel_DockTop;
			}
			else
				pskin->m_docking &= ~Panel_DockTop;

			DCDUtils_UpdatePlugInsPosition(This->m_DCDUtilsHandle);
		}
		break;
		case EEvent_SelectionChange:
		{
			const Event_SelectionChange* ev = e->pData;
			const Mouse* m = Mouse_Get();

			This->ErrorCount = 0;
			Player_PlayDirectedTrack(This, ev->Index, 0);
			if (m->but == EBut_Select)
				Menu_Close();
			return EListenerAction_StopEvent;
		}
		break;
		case EEvent_Mouse:
		{
			Player_ClickHandler(This, e->pData);
			return EListenerAction_StopEvent;
		}
		break;
		case EEvent_Key:
		{
			const Event_Key* key = e->pData;

			// Ignore key presses if shortcuts are disabled
			// unless a full screen plug-in forwards them to use
			if (((This->m_Flags & Player_Shortcuts) == 0)
			&&  (DCDUtils_GetFullScreenPlugIn() == -1))
				return EListenerAction_ContinueEvent;

			// Block shutdown and company in full screen mode
			switch(key->code)
			{
				case 0x181: // F1
				{
					App_StrongHelp("CPanel_CPanels");
					return EListenerAction_StopEvent;
				}
				break;
				case 0x1cc: // F12
				case 0x1dc: // Shift + F12
				case 0x1ec: // Ctrl + F12
				{
					if (DCDUtils_GetFullScreenPlugIn() != -1)
						return EListenerAction_StopEvent;
				}
				break;
			}

			return EListenerAction_ContinueEvent;
		}
		break;
		case EEvent_WindowScroll:
		{
			const Event_WindowScroll* sc = e->pData;
			int val = (sc->dx & 3) ? 0: (sc->dx >> 2);
			val += (sc->dy & 3) ? 0: (sc->dy >> 2);
			if (val)
			{
				if ((sc->icon == Icon_Volume) || (sc->icon == This->m_CurrentPanel->m_VolumeButton))
				{
					int volume = Player_ReadVolume(This) + val*65536;
					Player_SetVolume(This, volume);
				}
				else if ((sc->icon == Icon_SetPos) || (sc->icon == Icon_SetPos2))
				{
					if (val > 0)
						Player_Forward(This);
					else
						Player_Rewind(This);
				}
				else
				{
					CmdHandler_ExecCommand
						( This->m_pCmdHandler
						, This->m_pCmdHandle
						, (val > 0) ? Cmd_Ctrl_VolRel_FineUp : Cmd_Ctrl_VolRel_FineDown
						);
				}
			}
			return EListenerAction_StopEvent;
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			const Msg_HelpRequest* msg = e->pData;

			switch(msg->hdr.action)
			{
				case EMsg_HelpRequest:
				{
					char token[9];
					const char* str;

					if ((msg->m.i == Icon_TrackName)
					&&  Player_IsStatusActive(This))
					{
						unsigned int time = This->track.pos.section.len;
						const char* ptitle = Player_GetTitle(This);

						str = SPrintf( Msg_Lookup("CtrlH030b")
						             , ptitle
						             , Player_TimeToString(time)
						             );
					}
					else
					{
						snprintf(token, sizeof(token), "CtrlH%03d", msg->m.i);
						str = Msg_Lookup(token);
						if (!strcmp(str, token))
							str = Msg_Lookup("CtrlH");
					}
					Task_HelpReply(msg, str);

					return EListenerAction_StopEvent;
				}
				break;
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

void Player_Refresh(Player* This, unsigned int flags)
{
	if (flags & player_refresh_initlist)
	{
		Player_ReadListProfile(This);
	}
	if (flags & player_refresh_tracklist)
	{
		Player_ReShuffle(This, false);
		if ((This->Status == status_undef)
		||  (This->Status == status_empty)
		||  (This->Status == status_stop))
			Player_SetStatus(This, PlayList_GetStatus(This->pPlayList));
		flags |= player_refresh_track;
	}

	// don't move that lines above again !!!!
	if (flags & player_refresh_track)
	{
		Player_FixInfo(This, Media_Updated_All);
		if ((This->track.current >= 0)
		&&  (This->track.current < WList_Count(&This->m_TrackList)))
			WList_Set(&This->m_TrackList, This->track.current, This->track.handle);
	}

	if (flags & player_refresh_listtitle)
		Player_ShowListName(This);

	if (flags & player_refresh_position)
		Player_ShowPosition(This);

	if (flags & player_refresh_volume)
		PlayList_SetVolume(This->pPlayList, This->Volume);

	if (flags & player_refresh_loopmode)
		PlayList_SetLoopMode(This->pPlayList, This->LoopMode);

	if (flags & player_refresh_params)
		PlayList_RefreshParams(This->pPlayList);
}

/*----------------------------------------------------------------------
 *- List ---------------------------------------------------------------
 *----------------------------------------------------------------------*/

void throw_Players_Players(void)
{
	Players = New_List();
}

void throw_Players_LoadSkins(void)
{
	// Load skin sprites and templates
	const char* pDefSkin = "Default";
	const char* pSkinName = Options()->Player.pSkinName;
	const char* pPathName = SPrintf("DigitalCDRes:Skins.%s", pSkinName);
	CTemplate* t;

	// Remove old skins templates
	// Normally the first ones with the correct names are not used
	if ((t = Templates_Find("Control")) != NULL) Templates_Remove(t);
	if ((t = Templates_Find("ControlCD")) != NULL) Templates_Remove(t);
	if ((t = Templates_Find("Mini")) != NULL) Templates_Remove(t);
	if ((t = Templates_Find("MiniCD")) != NULL) Templates_Remove(t);

	mem_free(pCtrlSprites);
	pCtrlSprites = NULL;

	if (File_GetFileType(pPathName) < 0x1000)
		pSkinName = pDefSkin;
	pCtrlSprites = throw_Sprites_LoadFile(SPrintf("DigitalCDRes:Skins.%s.Sprites", pSkinName));
	if (Templates_Load(SPrintf("DigitalCDRes:Skins.%s.Templates", pSkinName)) != NULL)
		throw_string("SkErr0");

	pPathName = SPrintf("DigitalCDRes:Skins.%s.Settings", pSkinName);
	AuthorSizeX = Choices_ReadIntFrom(pPathName, Var_Author, Var_FontSizeX, 192);
	AuthorSizeY = Choices_ReadIntFrom(pPathName, Var_Author, Var_FontSizeY, 192);
	throw_mem_setstring(&AuthorFontName, Choices_ReadFrom(pPathName, Var_Author, Var_FontName, "Trinity.Medium"));
	TitleSizeX = Choices_ReadIntFrom(pPathName, Var_Title, Var_FontSizeX, 192);
	TitleSizeY = Choices_ReadIntFrom(pPathName, Var_Title, Var_FontSizeY, 192);
	throw_mem_setstring(&TitleFontName, Choices_ReadFrom(pPathName, Var_Title, Var_FontName, "Trinity.Medium.Italic"));
	TrackSizeX = Choices_ReadIntFrom(pPathName, Var_Track, Var_FontSizeX, 112);
	TrackSizeY = Choices_ReadIntFrom(pPathName, Var_Track, Var_FontSizeY, 160);
	throw_mem_setstring(&TrackFontName, Choices_ReadFrom(pPathName, Var_Track, Var_FontName, "Homerton.Bold"));
	TimeSizeX = Choices_ReadIntFrom(pPathName, Var_TimeMode, Var_FontSizeX, 112);
	TimeSizeY = Choices_ReadIntFrom(pPathName, Var_TimeMode, Var_FontSizeY, 160);
	throw_mem_setstring(&TimeFontName, Choices_ReadFrom(pPathName, Var_TimeMode, Var_FontName, "Homerton.Bold"));
	VolumeType = 0;
	if (Choices_ReadBoolFrom(pPathName, Var_Volume, Var_Vertical, true) == true)
		VolumeType |= ESliderType_Vertical;
	VolumeSprites = Choices_ReadIntFrom(pPathName, Var_Volume, Var_MaxSprite, 20);
	VolumeButton = Choices_ReadIntFrom(pPathName, Var_Volume, Var_Button, (int) HWind_None);
	if (VolumeButton == HIcon_None)
		VolumeButton = Icon_Volume;
	else
		VolumeType |= ESliderType_Button;
	PosBarType = 0;
	if (Choices_ReadBoolFrom(pPathName, Var_PosBar, Var_Vertical, false) == true)
		PosBarType |= ESliderType_Vertical;
	if (Choices_ReadIntFrom(pPathName, Var_PosBar, Var_Button, (int) HWind_None) != HWind_None)
		PosBarType |= ESliderType_Button;

	Players_ModeChange();
}

void Players_NotPlayers(void)
{
	Delete_List(Players);
	Players = NULL;

	mem_free(TimeFontName);
	TimeFontName = NULL;
	mem_free(TrackFontName);
	TrackFontName = NULL;
	mem_free(TitleFontName);
	TitleFontName = NULL;
	mem_free(AuthorFontName);
	AuthorFontName = NULL;
	mem_free(pCtrlSprites);
	pCtrlSprites = NULL;

	if (AuthorFont)
	{
		Font_Lose(AuthorFont);
		AuthorFont = 0;
	}
	if (TitleFont)
	{
		Font_Lose(TitleFont);
		TitleFont = 0;
	}
	if (TrackFont)
	{
		Font_Lose(TrackFont);
		TrackFont = 0;
	}
	if (TimeFont)
	{
		Font_Lose(TimeFont);
		TimeFont = 0;
	}
}

void Players_ModeChange(void)
{
	const Mode_Info* pMode = Task_GetModeInfo();

	if (AuthorFont) Font_Lose(AuthorFont);
	if (TitleFont) Font_Lose(TitleFont);
	if (TrackFont) Font_Lose(TrackFont);
	if (TimeFont) Font_Lose(TimeFont);

	Font_Find(AuthorFontName, AuthorSizeX, AuthorSizeY, pMode->resx, pMode->resy, &AuthorFont);
	Font_Find(TitleFontName, TitleSizeX, TitleSizeY, pMode->resx, pMode->resy, &TitleFont);
	Font_Find(TrackFontName, TrackSizeX, TrackSizeY, pMode->resx, pMode->resy, &TrackFont);
	Font_Find(TimeFontName, TimeSizeX, TimeSizeY, pMode->resx, pMode->resy, &TimeFont);
}

int Players_Count(void)
{
	return List_Count(Players);
}

void throw_Players_Add(Player* pObj)
{
	List_InsertBefore(Players, NULL, pObj);
}

void Players_Del(Player* pObj)
{
	int pos = List_Find(Players, 0, pObj);
	List_Remove(Players, pos);
}

Player* Players_Get(int i) throws(index)
{
	return (Player*) List_Get(Players, i);
}

void throw_Players_ReloadSkins(void)
{
	int i;

	throw_Players_LoadSkins();

	for (i = 0; i < List_Count(Players); i++)
	{
		Player* pPlayer = Players_Get(i);
		pPlayer->m_pReloadSkin(pPlayer);
	}
}
