#include "FLTreeNodes.h"

#include <string.h>

#include "WimpLib:DocView.h"
#include "WimpLib:Desktop.h"
#include "WimpLib:Display.h"
#include "WimpLib:Exception.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:mem.h"
#include "WimpLib:Message.h"
#include "WimpLib:OTreeList.h"
#include "WimpLib:Task.h"
#include "WimpLib:Utils.h"

#include "DigitalCD.h"
#include "FLTreeView.h"
#include "PListFiles.h"
#include "PListRadio.h"
#include "Options.h"

// Order of enum must follow sorting order
// bits 0-3 define object type
// bit 4    signal entities of tracks with active "Compilation" flag.
//          That is to say  with box/album, as opposed to those without
//          box/album which behave as if there was no "Compilation" flag
// bit 5-7  used to have seperate contruction modes for identical entities.
typedef enum
// - Top level -
{ Entity_FileList            = 0x01
, Entity_Type                = 0x02
// - Types -
, Entity_Radios              = 0x03
, Entity_Lists               = 0x04
, Entity_Files               = 0x05
// - Radios -
, Entity_MimeType            = 0x06
, Entity_Broadcaster         = 0x07
, Entity_Bandwidth           = 0x08
// - Files -
, Entity_Collective          = 0x09
, Entity_Artist              = 0x0A
, Entity_Box                 = 0x0B
, Entity_Album               = 0x0C
, Entity_Track               = 0x0D
} Entity;

typedef enum
{ Key_Unknown             = 0x00
, Key_FileList            = 0x00 | Entity_FileList
// - Types -
, Key_Radios              = 0x00 | Entity_Radios
, Key_Lists               = 0x00 | Entity_Lists
, Key_Files               = 0x00 | Entity_Files
// - Radios -
, Key_MimeType            = 0x00 | Entity_MimeType
, Key_Bandwidth           = 0x00 | Entity_Bandwidth
, Key_Broadcaster         = 0x00 | Entity_Broadcaster
// - Files -
, Key_NC                  = 0x10 // Used internally to bypass Compilations
, Key_Collective          = 0x00 | Entity_Collective
, Key_HComp_Collective    = 0x20 | Entity_Collective // Comp but no Box/Album!
, Key_Comp_Collective     = 0x30 | Entity_Collective
, Key_Artist              = 0x00 | Entity_Artist
, Key_HComp_Artist        = 0x20 | Entity_Artist // Comp but no Box/Album!
, Key_Comp_Artist         = 0x30 | Entity_Artist
, Key_Box                 = 0x00 | Entity_Box
, Key_Comp_Box            = 0x20 | Entity_Box
, Key_Album               = 0x00 | Entity_Album
, Key_Comp_Album          = 0x20 | Entity_Album
, Key_Track               = 0x90 | Entity_Track
, Key_FlatTrack           = 0xF0 | Entity_Track
, Key_Mask_Entity         = 0x0F
, Key_Mask_Sort           = 0x1F
} Key;

typedef struct ChildInfo ChildInfo;

static void Track_GetSubEntity(const FLTrack* pTrack, ChildInfo* pInfo);

typedef struct
{
	uint32_t        Key;
	const FileList* pDoc;
} KeyInfo;

typedef struct
{
	uint32_t        Key;
	const FileList* pDoc;
	uint32_t        val;
} IntInfo;

typedef struct
{
	uint32_t        Key;
	const FileList* pDoc;
	const void*     Ptr;
} PtrInfo;

typedef struct
{
	uint32_t        Key;
	const FileList* pDoc;
	const char*     Str;
} StrInfo;

typedef struct
{
	uint32_t        Key;
	const FileList* pDoc;
	const FLTrack*  pTrack;
} TrackInfo;

// AlbumInfo will be prefilled in Child info, updated has we follow the grouping chain
// and BoxInfo & ArtistInfo will be extracted from it so it must follow the same layout.
// !!! Attention:
// NULL and &nil have some different meaning, in these structure
// so beware that StrCol_Add(NULL) returns &nil.
typedef struct
{
	uint32_t        Key;
	const FileList* pDoc;
	const char*     Collective;
	const char*     Artist;
	const char*     Box;
	const char*     Date;
	const char*     Album;
} AlbumInfo;

typedef struct
{
	uint32_t        Key;
	const FileList* pDoc;
	const char*     Collective;
	const char*     Artist;
	const char*     Box;
	const char*     Date;
} BoxInfo;

typedef struct
{
	uint32_t        Key;
	const FileList* pDoc;
	const char*     Collective;
	const char*     Artist;
} ArtistInfo;

typedef struct
{
	uint32_t        Key;
	const FileList* pDoc;
	const char*     Collective;
} CollectiveInfo;

struct ChildInfo
{
	const OTreeNodeVPtr* pVPtr;
	uint32_t            size;
	uint32_t            flags;
	union
	{
		KeyInfo         key;
		IntInfo         val;
		StrInfo         str;
		PtrInfo         ptr;
		TrackInfo       track;
		AlbumInfo       album;
		BoxInfo         box;
		ArtistInfo      artist;
		CollectiveInfo  collective;
	} i;
};

/*-------------------------------------------------------------------------*
 *--- Global functions ----------------------------------------------------*
 *-------------------------------------------------------------------------*/

static char* NodeType_Strings[3] = {NULL, NULL, NULL};
static char* NodeBand_Strings[5] = {NULL, NULL, NULL, NULL, NULL};

static struct
{
	int   Tree_Indent;
	CRect Text;
	struct
	{
		CRect  o;
		CSize  size;
	} Group;
	struct
	{
		CRect  o;
	} Clue;
	struct
	{
		CRect  o;
		CSize  size;
	} Play;
	struct
	{
		CRect  o;
		CSize  size;
	} State;
	struct
	{
		CRect  o;
		CSize  size;
	} Object;
	struct
	{
		CRect  o;
		CSize  size;
	} VSlider;
} Sizes;

void throw_FLTreeNodes_FLTreeNodes(void)
{
	if (NodeType_Strings[2] == NULL)
	{
		NodeType_Strings[0] = throw_mem_allocstring(Msg_Lookup("DSRadios:Radios"));
		NodeType_Strings[1] = throw_mem_allocstring(Msg_Lookup("DSLists:Lists"));
		NodeType_Strings[2] = throw_mem_allocstring(Msg_Lookup("DSFiles:Files"));
	}

	if (NodeBand_Strings[4] == NULL)
	{
		NodeBand_Strings[0] = throw_mem_allocstring(Msg_Lookup("DSBand0:Unknown"));
		NodeBand_Strings[1] = throw_mem_allocstring(Msg_Lookup("DSBand1:Modem"));
		NodeBand_Strings[2] = throw_mem_allocstring(Msg_Lookup("DSBand2:ISDN"));
		NodeBand_Strings[3] = throw_mem_allocstring(Msg_Lookup("DSBand3:ADSL"));
		NodeBand_Strings[4] = throw_mem_allocstring(Msg_Lookup("DSBand4:WDSL"));
	}

	// Read the various object sizes
	Sizes.Tree_Indent = 40;
	Sizes.Text.y1 = 40;
	Sizes.Group.size   = Desktop_GetNamedSpriteSize("n_files");
	Sizes.Play.size    = Desktop_GetNamedSpriteSize("flplayon");
	Sizes.State.size   = Desktop_GetNamedSpriteSize("s_play");
	Sizes.Object.size  = Desktop_GetNamedSpriteSize("i_file");
	Sizes.VSlider.size = Desktop_GetNamedSpriteSize("vslider");

	if (Sizes.Text.y1 < Sizes.Group.size.cy + 4)
		Sizes.Text.y1 = Sizes.Group.size.cy + 4;
	if (Sizes.Text.y1 < Sizes.Play.size.cy + 4)
		Sizes.Text.y1 = Sizes.Play.size.cy + 4;
	if (Sizes.Text.y1 < Sizes.State.size.cy + 4)
		Sizes.Text.y1 = Sizes.State.size.cy + 4;
	if (Sizes.Text.y1 < Sizes.Object.size.cy + 4)
		Sizes.Text.y1 = Sizes.Object.size.cy + 4;
	if (Sizes.Text.y1 < Sizes.VSlider.size.cy + 4)
		Sizes.Text.y1 = Sizes.VSlider.size.cy + 4;
	if (Sizes.Text.y1 & 3)
	{
		Sizes.Text.y1 &= ~3;
		Sizes.Text.y1 += 4;
	}
	Sizes.Text.x0 = 8;
	Sizes.Text.x1 = 16;
	Sizes.Text.y0 = (Sizes.Text.y1 - 40) >> 1;

	Sizes.Clue.o.x0 = 8;
	Sizes.Clue.o.y0 = Sizes.Text.y0 + 4;
	Sizes.Clue.o.x1 = 40;
	Sizes.Clue.o.y1 = Sizes.Text.y1;

	Sizes.Group.o.x0 = 4;
	Sizes.Group.o.x1 = 8 + Sizes.Group.size.cx;
	Sizes.Group.o.y0 = ((Sizes.Text.y1 - Sizes.Group.size.cy + 2) >> 1) & ~1;
	Sizes.Group.o.y1 = Sizes.Text.y1;
	Sizes.Play.o.x0 = 4;
	Sizes.Play.o.x1 = 8 + Sizes.Play.size.cx;
	Sizes.Play.o.y0 = ((Sizes.Text.y1 - Sizes.Play.size.cy + 2) >> 1) & ~1;
	Sizes.Play.o.y1 = Sizes.Text.y1;
	Sizes.State.o.x0 = 4;
	Sizes.State.o.x1 = 8 + Sizes.State.size.cx;
	Sizes.State.o.y0 = ((Sizes.Text.y1 - Sizes.State.size.cy + 2) >> 1) & ~1;
	Sizes.State.o.y1 = Sizes.Text.y1;
	Sizes.Object.o.x0 = 4;
	Sizes.Object.o.x1 = 8 + Sizes.Object.size.cx;
	Sizes.Object.o.y0 = ((Sizes.Text.y1 - Sizes.Object.size.cy + 2) >> 1) & ~1;
	Sizes.Object.o.y1 = Sizes.Text.y1;
	Sizes.VSlider.o.x0 = 0;
	Sizes.VSlider.o.x1 = Sizes.VSlider.size.cx;
	Sizes.VSlider.o.y0 = ((Sizes.Text.y1 - Sizes.VSlider.size.cy + 2) >> 1) & ~1;
	Sizes.VSlider.o.y1 = Sizes.Text.y1;
}

void FLTreeNodes_NotFLTreeNodes(void)
{
	mem_free(NodeType_Strings[0]);
	mem_free(NodeType_Strings[1]);
	mem_free(NodeType_Strings[2]);
	mem_free(NodeBand_Strings[0]);
	mem_free(NodeBand_Strings[1]);
	mem_free(NodeBand_Strings[2]);
	mem_free(NodeBand_Strings[3]);
	mem_free(NodeBand_Strings[4]);
}

int FLHeaderNode_GetFirstColumnPos(void)
{
	return Sizes.Clue.o.x1
		 + Sizes.Play.o.x1
		 + Sizes.State.o.x1
		 + Sizes.Object.o.x1;
}

int FLQueueNode_GetFirstColumnPos(void)
{
	return Sizes.Clue.o.x1
		 + Sizes.State.o.x1
		 + Sizes.Object.o.x1;
}

static unsigned int GetBand(unsigned int rate)
{
	int i;

	for (i = 0; i < 4; i++)
	{
		if (rate < Options()->Internet.BandRates[i])
			break;
	}

	return i;
}

static Entity Track_NodeType(const FLTrack* pTrack)
{
	int type = FLTrack_GetObjectType(pTrack);

	switch(type)
	{
		case FLTrack_Type_Url:
		{
			type = Entity_Radios;
		}
		break;
		case FLTrack_Type_FileList:
		case FLTrack_Type_Directory:
		case FLTrack_Type_YellowPage:
		{
			type = Entity_Lists;
		}
		break;
		default:
		{
			type = Entity_Files;
		}
	}

	return (Entity) type;
}

/*-------------------------------------------------------------------------*
 *--- Compare functions to be used by a tree node when inserting childs. --*
 *-------------------------------------------------------------------------*/

static int Compare_Strings(const char* pa, const char* pb)
{
	if (pa)
	{
		if (pb)
		{
			if (*pa)
			{
				if (*pb)
				{
					// First compare without taking case and accents into account
					int val = String_Collate(3, pa, pb, -1);

					// If equal, distinguish case and accents
					if (!val) val = String_Collate(0, pa, pb, -1);

					return val;
				}

				return 1;
			}
			else if (*pb)
				return -1;

			return 0;
		}

		return 1;
	}
	else if (pb)
		return -1;

	return 0;
}

static int Compare_RStrings(const char* pa, const char* pb)
{
	if (pa)
	{
		if (pb)
		{
			if (*pa)
			{
				if (*pb)
				{
					// First compare without taking case and accents into account
					int val = String_Collate(3, pa, pb, -1);

					// If equal, distinguish case and accents
					if (!val) val = String_Collate(0, pa, pb, -1);

					return val;
				}

				return -1;
			}
			else if (*pb)
				return 1;

			return 0;
		}

		return 1;
	}
	else if (pb)
		return -1;

	return 0;
}

static int Compare_Tracks(const TrackInfo* pa, const TrackInfo* pb)
{
	int val = 0;

	switch(pa->Key & FLTreeMode_Mask_OrderBy)
	{
		case FLTreeMode_OrderBy_TrackNumber:
		{
			val = FLTrack_GetOrder(pa->pTrack) - FLTrack_GetOrder(pb->pTrack);
		}
		// no break;
		case FLTreeMode_OrderBy_Alphabetic:
		{
			if (!val) val = Compare_Strings(FLTrack_GetTrackName(pa->pTrack), FLTrack_GetTrackName(pb->pTrack));
			if (!val) val = FLTrack_GetSection(pa->pTrack) - FLTrack_GetSection(pb->pTrack);
		}
		break;
		case FLTreeMode_OrderBy_Raw:
		{
			val = ((char*) pa->pDoc) - ((char*) pb->pDoc);
			if (!val && pa->pDoc)
				val = FileList_FindTrack(pa->pDoc, pa->pTrack) - FileList_FindTrack(pa->pDoc, pb->pTrack);
		}
		break;
	}

	// No childs possible, must somehow distinguish them
	if (!val)
		val = ((char*) pa->pTrack) - ((char*) pb->pTrack);

	return val;
}

static int Compare_SameChilds(const void* pa, const void* pb)
{
	const KeyInfo* sa = pa;
	int val = 0;

    // Compare objects of same type
	switch(sa->Key & Key_Mask_Entity)
	{
		case Entity_Type:
		case Entity_Bandwidth:
		// Handle PtrInfo as IntInfo
		case Entity_FileList:
		{
			const IntInfo* sa = pa;
			const IntInfo* sb = pb;

			val = sa->val - sb->val;
		}
		break;
		case Entity_MimeType:
		case Entity_Broadcaster:
		{
			const StrInfo* sa = pa;
			const StrInfo* sb = pb;

			val = Compare_RStrings(sa->Str, sb->Str);
		}
		break;
		case Entity_Collective:
		{
			const CollectiveInfo* sa = pa;
			const CollectiveInfo* sb = pb;

			val = Compare_RStrings(sa->Collective, sb->Collective);
		}
		break;
		case Entity_Artist:
		{
			const ArtistInfo* sa = pa;
			const ArtistInfo* sb = pb;

			val = Compare_RStrings(sa->Artist, sb->Artist);
			if (!val) val = Compare_RStrings(sa->Collective, sb->Collective);
		}
		break;
		case Entity_Box:
		{
			const BoxInfo* sa = pa;
			const BoxInfo* sb = pb;

			if ((sa->Key & FLTreeMode_GroupBy_Album)
			&&  (sa->Key & FLTreeMode_SortAlbum_Date))
				val = Compare_RStrings(sa->Date, sb->Date);

			if (!val) val = Compare_RStrings(sa->Box, sb->Box);
			if (!val) val = Compare_RStrings(sa->Collective, sb->Collective);
			if (!val) val = Compare_RStrings(sa->Artist, sb->Artist);
			if (!val) val = Compare_RStrings(sa->Date, sb->Date);
		}
		break;
		case Entity_Album:
		{
			const AlbumInfo* sa = pa;
			const AlbumInfo* sb = pb;

			if (sa->Key & FLTreeMode_SortAlbum_Date)
				val = Compare_RStrings(sa->Date, sb->Date);

			if (!val) val = Compare_RStrings(sa->Album, sb->Album);
			if (!val) val = Compare_RStrings(sa->Collective, sb->Collective);
			if (!val) val = Compare_RStrings(sa->Artist, sb->Artist);
			if (!val) val = Compare_RStrings(sa->Box, sb->Box);
			if (!val) val = Compare_RStrings(sa->Date, sb->Date);
		}
		break;
		case Entity_Track:
		{
			val = Compare_Tracks(pa, pb);
		}
		break;
	}

	return val;
}

static int Compare_Flat(const TrackInfo* pa, const TrackInfo* pb)
{
	ChildInfo InfoA;
	ChildInfo InfoB;
	int val;

	memset(&InfoA, 0, sizeof(InfoA));
	memset(&InfoB, 0, sizeof(InfoB));
	InfoA.i.key.Key = InfoB.i.key.Key = Key_Unknown | (pa->Key & ~FLTreeMode_Mask_Keys);
	InfoA.i.key.pDoc = pa->pDoc;
	InfoB.i.key.pDoc = pb->pDoc;

	while(1)
	{
		Track_GetSubEntity(pa->pTrack, &InfoA);
		Track_GetSubEntity(pb->pTrack, &InfoB);

		// entity compare
		val = (InfoA.i.key.Key & Key_Mask_Sort) - (InfoB.i.key.Key & Key_Mask_Sort);
		if (val) return val;

		// They belong to the same kind of entity
		val = Compare_SameChilds(&InfoA.i, &InfoB.i);
		if (val) return val;

		if ((InfoA.i.key.Key & FLTreeMode_Mask_Keys) >= Key_Track)
			break;
	}

	// No childs possible, must somehow distinguish them
	val = ((char*) pa->pTrack) - ((char*) pb->pTrack);

	return val;
}

static int Compare_Childs(const void* pa, const void* pb)
{
	const KeyInfo* sa = pa;
	const KeyInfo* sb = pb;

	// Compare by object types
	int val = (sa->Key & Key_Mask_Sort) - (sb->Key & Key_Mask_Sort);
	if (val) return val;

	if ((sa->Key & FLTreeMode_Mask_Keys) == Key_FlatTrack)
		return Compare_Flat(pa, pb);

	return Compare_SameChilds(pa, pb);
}

//--------------------------------------------------------------------------

static void Node_GetTextColors(const WPlotItem* pItem, int* bcol, int* fcol)
{
	switch (pItem->m_flags & (EWItem_Active | EWItem_Selected))
	{
		case EWItem_Active:
		{
			*bcol = 1;
			*fcol = 4;
		}
		break;
		case EWItem_Selected:
		{
			*bcol = 7;
			*fcol = 0;
		}
		break;
		case (EWItem_Active + EWItem_Selected):
		{
			*bcol = 5;
			*fcol = 0;
		}
		break;
		default:
		{
			*bcol = 0;
			*fcol = 7;
		}
	}
}

static int GetWidth_NodeSpriteString(const OTreeNode* pNode, const char* pString)
{
	return OTreeNode_DefWidth(pNode)
	       + Sizes.Group.o.x1 + Sizes.Text.x1
	       + Desktop_GetTextWidth(pString);
}

static int GetWidth_FLNodeString(const OTreeNode* pNode, const char* pString)
{
	return OTreeNode_DefWidth(pNode)
	       + Sizes.Play.o.x1 + Sizes.State.o.x1 + Sizes.Object.o.x1
	       + Sizes.Text.x1 + Desktop_GetTextWidth(pString);
}

static void Plot_Grid(const OTreeNode* pNode, const WPlotItem* pItem)
{
	const OTreeNode* pTmp;
	const OTreeNode* pBrother;
	CRect  box;
	int    i, x, y;
	bool bHasBox = OTreeNode_AllowExpand(pNode);
	int level = OTreeNode_GetLevel(pNode);
	WListCore* pList = OTreeList_GetWListCore(OTreeNode_GetTreeList(pNode));

	box = RectToScreen(&pItem->m_box, &pItem->m_cvtinfo);
	RoundRectForPlot(&box);

	x = box.x0 + (level * Sizes.Tree_Indent) - Sizes.Tree_Indent/2;
	y = (box.y1 + box.y0)/2;

	// Set dash-line pattern
	Display_SetDashPattern(8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa);

	if (level > 1)
	{
		Display_Plot(4, x, box.y1);
		Display_Plot(29, x, y + (bHasBox ? 8 : 0));
	}

	pBrother = OTreeNode_GetSuccessor(pNode);
	if (pBrother && (level > 1))
	{
		Display_Plot(4, x, y - (bHasBox ? 8 : 0));
		Display_Plot(29, x, box.y0);
	}

	if ((level > 1) || bHasBox)
	{
		Display_Plot(4, x + (bHasBox ? 8 : 0), y);
		Display_Plot(29, box.x0 - 1 + level * Sizes.Tree_Indent, y);
	}

	if (bHasBox)
	{
		Display_Plot(4, x - 8, y - 8);
		Display_Plot(5, x - 8, y + 8);
		Display_Plot(5, x + 8, y + 8);
		Display_Plot(5, x + 8, y - 8);
		Display_Plot(5, x - 8, y - 8);
		if ((pItem->m_index + 1) < WListCore_Count(pList))
		{
			pTmp = WListCore_Get(pList, pItem->m_index + 1);
			pTmp = OTreeNode_GetParent(pTmp);
		}
		else
			pTmp = NULL;

		if (pTmp != pNode)
		{
			Display_Plot(4, x, y - 4);
			Display_Plot(5, x, y + 4);
		}
		Display_Plot(4, x - 4, y);
		Display_Plot(5, x + 4, y);
	}

	pTmp = OTreeNode_GetParent(pNode);

	for (i = level - 2; i >= 1; i--)
	{
		x -= Sizes.Tree_Indent;
		pBrother = OTreeNode_GetSuccessor(pTmp);
		if (pBrother)
		{
			Display_Plot(4, x, box.y1);
			Display_Plot(29, x, box.y0);
		}
		pTmp = OTreeNode_GetParent(pTmp);
	}
}

static void Plot_Base(const OTreeNode* pNode, const WPlotItem* pItem, CRect* pbox, const FLTrack* pTrack)
{
	int width = OTreeNode_DefWidth(pNode);
	*pbox = RectToScreen(&pItem->m_box, &pItem->m_cvtinfo);

	RoundRectForPlot(pbox);

	// Plot Grid
	Desktop_SetStdColour(7);
	Plot_Grid(pNode, pItem);

	if (pTrack)
	{
		const KeyInfo* info = OTreeNode_GetData(pNode);
		CPoint pt;

		// Plot marks
		pt.x = pbox->x0 + Sizes.Clue.o.x0;
		pt.y = pbox->y0 + Sizes.Clue.o.y0;
		if (pItem->m_flags & EWItem_Marked)
			Desktop_PlotNamedSprite("m_found", pt, NULL);
		else if (info->Key & FLTreeMode_IsLink)
			Desktop_PlotNamedSprite("m_link", pt, NULL);
		else if (FLTrack_GetClue(pTrack))
			Desktop_PlotSprite(FLTrack_GetClue(pTrack), pt, NULL);
	}

	pbox->x0 += width;
}

static void Plot_FLPlayableInfo(const WPlotItem* pItem, CRect* pbox, const FLTrack* pTrack)
{
	int         FLflags = FLTrack_GetFlags(pTrack);
	CPoint      pt;

	IGNORE(pItem);

	// Plot play selection
	pt.x = pbox->x0 + Sizes.Play.o.x0;
	pt.y = pbox->y0 + Sizes.Play.o.y0;
	if (!(FLflags & FLTrack_NeverPlay))
	{
		if (FLflags & FLTrack_MustNotPlay)
			Desktop_PlotNamedSprite("flplayoff", pt, NULL);
		else
			Desktop_PlotNamedSprite("flplayon", pt, NULL);
	}

	pbox->x0 += Sizes.Play.o.x1;
}

static void Area_FLPlayableInfo(const Mouse* pMouse, const FLTrack* pTrack, WItemArea* pArea)
{
	int FLflags = FLTrack_GetFlags(pTrack);

	int x1 = pArea->rect.x0 + Sizes.Play.o.x1;

	if (pMouse == NULL)
	{
		pArea->rect.x0 = x1;
		return;
	}

	if (pMouse->pt.x < x1)
	{
		if (!(FLflags & FLTrack_NeverPlay))
			pArea->id = FLItemAction_TogglePlay;
		else
			pArea->id = EWItemArea_Inactive;

		pArea->rect.x1 = x1;
		return;
	}

	pArea->rect.x0 = x1;
}

static void Plot_FLStateObjectInfo(const WPlotItem* pItem, CRect* pbox, const FLTrack* pTrack)
{
	int         FLflags = FLTrack_GetFlags(pTrack);
	const char* pSpriteName = NULL;
	CPoint      pt;

	IGNORE(pItem);

	// Plot state
	if (FLflags & FLTrack_Invalidated)
		pSpriteName = "s_invalid";
	else if ((PListFiles_GetPlayedTrack(PListFiles_Get()) == pTrack)
	     ||  (PListRadio_GetPlayedTrack(PListRadio_Get()) == pTrack))
		pSpriteName = "s_play";
	else if (FLflags & FLTrack_FailedToPlay)
		pSpriteName = "s_failed";
	else if (FLflags & FLTrack_PlayedToday)
		pSpriteName = "s_playedt";
	else if (FLflags & FLTrack_Played)
		pSpriteName = "s_played";

	if (pSpriteName)
	{
		pt.x = pbox->x0 + Sizes.State.o.x0;
		pt.y = pbox->y0 + Sizes.State.o.y0;
		Desktop_PlotNamedSprite(pSpriteName, pt, NULL);
	}

	pbox->x0 += Sizes.State.o.x1;

	if (FLflags & FLTrack_IsPlaylist)
	{
		const FileList* pList = FLTrack_GetSubList(pTrack);
		if (pList)
		{
			switch(FileList_GetType(pList))
			{
				case FileList_TypeDir:    pSpriteName = "i_pdir";   break;
				case FileList_TypeExtern: pSpriteName = "i_pext"; break;
				case FileList_TypeYP:     pSpriteName = "i_pnet";   break;
				default: pSpriteName = "i_plist";
			}
		}
		else if (FLflags & FLTrack_IsUrl)
			pSpriteName = "o_pnet";
		else if (FLflags & FLTrack_IsDir)
			pSpriteName = "o_pdir";
		else
			pSpriteName = "o_plist";
	}
	else if (FLflags & FLTrack_IsUrl)
		pSpriteName = "i_url";
	else
		pSpriteName = "i_file";

	if (pSpriteName)
	{
		pt.x = pbox->x0 + Sizes.Object.o.x0;
		pt.y = pbox->y0 + Sizes.Object.o.y0;
		Desktop_PlotNamedSprite(pSpriteName, pt, NULL);
	}

	pbox->x0 += Sizes.Object.o.x1;
}

static void Area_FLStateObjectInfo(const Mouse* pMouse, const FLTrack* pTrack, WItemArea* pArea)
{
	IGNORE(pTrack);
	int x1 = pArea->rect.x0 + Sizes.State.o.x1;

	if (pMouse == NULL)
	{
		pArea->rect.x0 = x1 + Sizes.Object.o.x1;
		return;
	}

	if (pMouse->pt.x < x1)
	{
		pArea->id = EWItemArea_Inactive;
		pArea->rect.x1 = x1;
		return;
	}

	pArea->rect.x0 = x1;
	x1 += Sizes.Object.o.x1;

	if (pMouse->pt.x < x1)
	{
		pArea->id = FLItemAction_DragNode;
		pArea->rect.x1 = x1;
		return;
	}

	pArea->rect.x0 = x1;
}

static void Plot_TextBack(const WPlotItem* pItem, const CRect* pbox, int type, int bcol, int fcol)
{
	int ptype = -1;

	if (bcol != 0)
	{
		Desktop_SetStdColour(bcol);
		Display_Plot(4, pbox->x0, pbox->y0);
		Display_Plot(101, pbox->x1, pbox->y1);
	}

	Desktop_SetStdColour(fcol);

	// Determine what to plot
	switch(type)
	{
		case 0:
		{
			if (pItem->m_flags & EWItem_HasFocus)
				ptype = 0;
		}
		break;
		case 1:
		{
			if (pItem->m_flags & EWItem_HasFocus)
				ptype = 0;
			else if (!(pItem->m_flags & (EWItem_Active | EWItem_Selected)))
				ptype = 1;
		}
		break;
		case 2:
		{
			if (pItem->m_flags & EWItem_HasFocus)
				ptype = 1;
		}
		break;
	}

	// Plot
	switch(ptype)
	{
		case 0:
		{
			// Set dash-line pattern
			Display_SetDashPattern(24, 0xdb, 0x6d, 0xb6, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc);
			// plot dashed rectangle
			Display_Plot(4, pbox->x0, pbox->y0);
			Display_Plot(21, pbox->x0, pbox->y1);
			Display_Plot(53, pbox->x1, pbox->y1);
			Display_Plot(53, pbox->x1, pbox->y0);
			Display_Plot(53, pbox->x0, pbox->y0);
		}
		break;
		case 1:
		{
			// Plot filled rectangle as underline
			Display_Plot(4, pbox->x0 + 8, pbox->y0 + 4);
			Display_Plot(101, pbox->x1 - 8, pbox->y0 + 7);
		}
		break;
	}
}

static void Plot_Text(const WPlotItem* pItem, CRect* pbox, const char* pString, int type)
{
	CRect box = *pbox;
	int bcol, fcol;
	int width;

	Node_GetTextColors(pItem, &bcol, &fcol);

	width = Desktop_GetTextWidth(pString) + Sizes.Text.x1;

	// Plot filled rectangle
	box.x1 = box.x0 + width - Task_GetModeInfo()->dx;

	Plot_TextBack(pItem, &box, type, bcol, fcol);

	// Plot text
	Desktop_SetStdColour(fcol);
	Desktop_SetStdColour(0x80 + bcol);
	box.x1 = box.x0 + width - Sizes.Text.x0;
	box.x0 += Sizes.Text.x0;
	Desktop_PlotText(pString, &box);

	pbox->x0 += width;
}

static void Plot_SpriteString(const OTreeNode* pNode, const WPlotItem* pItem, const char* pSprite, const char* pString)
{
	CRect box;
	CPoint pt;
	int bcol, fcol;

	Node_GetTextColors(pItem, &bcol, &fcol);

	Plot_Base(pNode, pItem, &box, NULL);

	// Plot Sprite
	pt.x = box.x0 + Sizes.Group.o.x0;
	pt.y = box.y0 + Sizes.Group.o.y0;
	Desktop_PlotNamedSprite(pSprite, pt, NULL);
	box.x0 += Sizes.Group.o.x1;

	// Plot Text
	Plot_Text(pItem, &box, pString, 1);
}

static CSize Node_GetBox(const void* pObject, const void* pOwner, const int* pSizes, const int* pMaxSizes)
{
	CSize size = {pMaxSizes[0], Sizes.Text.y1};

	IGNORE(pObject);
	IGNORE(pOwner);
	IGNORE(pSizes);

	return size;
}

static void Node_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	const OTreeNode* pNode = pObject;

	IGNORE(pOwner);

	OTreeNode_DefArea(pNode, pItem, pMouse, pArea);
	if (pMouse == NULL)
	{
		pArea->rect.x1 = pItem->m_box.x0 + pItem->m_psizes[0];
	}
	else
	{
		if (pArea->id != EWItemArea_Select)
			return;

		if (pMouse->pt.x > (pItem->m_box.x0 + pItem->m_psizes[0]))
			pArea->id = EWItemArea_Outside;
	}
}

static void Node_GetSpriteStringArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	const OTreeNode* pNode = pObject;
	int x1;

	IGNORE(pOwner);

	OTreeNode_DefArea(pNode, pItem, pMouse, pArea);

	if (pMouse == NULL)
	{
		pArea->rect.x0 = pArea->rect.x0 + Sizes.Group.o.x1;
		pArea->rect.x1 = pItem->m_box.x0 + pItem->m_psizes[0];
	}
	else
	{
		if (pArea->id != EWItemArea_Select)
			return;

		if (pMouse->pt.x > (pItem->m_box.x0 + pItem->m_psizes[0]))
		{
			pArea->id = EWItemArea_Outside;
			return;
		}

		x1 = pArea->rect.x0 + Sizes.Group.o.x1;
		if (pMouse->pt.x < x1)
		{
			pArea->id = FLItemAction_DragNode;
			pArea->rect.x1 = x1;
			return;
		}

		pArea->rect.x0 = x1;
	}
}

static void NodeTrack_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	const OTreeNode* pNode = pObject;
	const TrackInfo* info = OTreeNode_GetData(pNode);
	const FLTrack* pTrack = info->pTrack;

	IGNORE(pOwner);

	OTreeNode_DefArea(pNode, pItem, pMouse, pArea);

	if (pMouse == NULL)
	{
		Area_FLPlayableInfo(pMouse, pTrack, pArea);
		Area_FLStateObjectInfo(pMouse, pTrack, pArea);
		pArea->rect.x1 = pItem->m_box.x0 + pItem->m_psizes[0];
	}
	else
	{
		if (pArea->id != EWItemArea_Select)
			return;

		if (pMouse->pt.x > (pItem->m_box.x0 + pItem->m_psizes[0]))
		{
			pArea->id = EWItemArea_Outside;
			return;
		}

		Area_FLPlayableInfo(pMouse, pTrack, pArea);
		if (pArea->id != EWItemArea_Select)
			return;

		Area_FLStateObjectInfo(pMouse, pTrack, pArea);
		if (pArea->id != EWItemArea_Select)
			return;

		if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
		{
			switch(Track_NodeType(pTrack))
			{
				case Entity_Radios:
					pArea->id = FLItemAction_EditStation;
				break;
				default:
					pArea->id = FLItemAction_EditTitle;
			}
			pArea->rect.x1 = pItem->m_box.x1;
		}
	}
}

//--------------------------------------------------------------------------

static const char* NodeType_Sprites[] =
{
	  "n_urls"
	, "n_lists"
	, "n_files"
};

static void NodeType_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const IntInfo* info = OTreeNode_GetData(pNode);
	const char* pString = NodeType_Strings[info->val];

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeType_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const IntInfo* info = OTreeNode_GetData(pNode);
	const char* pSprite = NodeType_Sprites[info->val];
	const char* pString = NodeType_Strings[info->val];
	int bcol = 4;
	int fcol = 0;
	CRect box;
	CPoint pt;

	IGNORE(pOwner);

	Plot_Base(pNode, pItem, &box, NULL);

	Desktop_SetStdColour(bcol);
	// Plot filled rectangle
	Display_Plot(4, box.x0, box.y0);
	Display_Plot(101, box.x1, box.y1);

	// Plot Sprite
	pt.x = box.x0 + Sizes.Group.o.x0;
	pt.y = box.y0 + Sizes.Group.o.y0;
	Desktop_PlotNamedSprite(pSprite, pt, NULL);
	box.x0 += Sizes.Group.o.x1;

	// Plot text
	Desktop_SetStdColour(fcol);
	Desktop_SetStdColour(0x80 + bcol);
	box.x0 += Sizes.Text.x0;
	box.x1 = box.x0 + Desktop_GetTextWidth(pString);
	Desktop_PlotText(pString, &box);

	if (pItem->m_flags & EWItem_HasFocus)
	{
		Desktop_SetStdColour(fcol);
		// Plot filled rectangle
		Display_Plot(4, box.x0, box.y0 + 4);
		Display_Plot(101, box.x1, box.y0 + 7);
	}
}

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const char* NodeTrack_String(const TrackInfo* pInfo)
{
	const FLTrack* pTrack = pInfo->pTrack;
	unsigned int order = FLTrack_GetOrder(pTrack);
	unsigned int section = FLTrack_GetSection(pTrack);
	const char* pName = FLTrack_GetTrackName(pTrack);

	if (((pInfo->Key & FLTreeMode_Mask_OrderBy) != FLTreeMode_OrderBy_Alphabetic)
	&&  (order > 0))
	{
		if (section > 0)
			return SPrintf("%02d - %s [%02d]", order, pName, section);
		return SPrintf("%02d - %s", order, pName);
	}
	if (section > 0)
		return SPrintf("%s [%02d]", pName, section);

	return pName;
}

static const char* NodeFlatTrack_String(const TrackInfo* pInfo, int index)
{
	const FLTrack* pTrack = pInfo->pTrack;
	const char* pString;
	const char* pString2;
	const char* pString3;

	switch(index)
	{
		case EFLField_Title:
		{
			pString = NodeTrack_String(pInfo);
		}
		break;
		case EFLField_Album:
		{
			switch(Track_NodeType(pTrack))
			{
				case Entity_Radios:
					pString = FLTrack_GetMetaString(pTrack, EMetaId_StreamMimeType);
				break;
				default:
				{
					pString = FLTrack_GetMetaString(pTrack, EMetaId_StreamAlbum);
					pString2 = FLTrack_GetMetaString(pTrack, EMetaId_StreamBox);
					pString3 = FLTrack_GetMetaString(pTrack, EMetaId_StreamDate);

					if (*pString3)
					{
						if (pInfo->Key & FLTreeMode_SortAlbum_Date)
						{
							if (*pString2)
							{
								if (*pString)
									pString = SPrintf("[%s] %s - %s", pString3, pString2, pString);
								else
									pString = SPrintf("[%s] %s", pString3, pString2);
							}
							else if (*pString)
								pString = SPrintf("[%s] %s", pString3, pString);
							else
								pString = SPrintf("[%s]", pString3);
						}
						else
						{
							if (*pString2)
							{
								if (*pString)
									pString = SPrintf("%s - %s [%s]", pString2, pString, pString3);
								else
									pString = SPrintf("%s [%s]", pString2, pString3);
							}
							else if (*pString)
								pString = SPrintf("%s [%s]", pString, pString3);
							else
								pString = SPrintf("[%s]", pString3);
						}
					}
					else
					{
						if (*pString2)
						{
							if (*pString)
								pString = SPrintf("%s - %s", pString2, pString);
							else
								pString = pString2;
						}
					}
				}
			}
		}
		break;
		case EFLField_Author:
		{
			switch(Track_NodeType(pTrack))
			{
				case Entity_Radios:
					pString = FLTrack_GetMetaString(pTrack, EMetaId_StreamBroadcaster);
				break;
				default:
				{
					pString = FLTrack_GetMetaString(pTrack, EMetaId_StreamArtist);
					pString2 = FLTrack_GetMetaString(pTrack, EMetaId_StreamCollective);
					if (*pString2)
					{
						if (*pString)
							pString = SPrintf("%s - %s", pString2, pString);
						else
							pString = pString2;
					}
				}
			}
		}
		break;
		default:
			pString = &nil;
	}

	return pString;
}

static void NodeFlatTrack_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const FLTreeView* pView = pOwner;
	const FLField_Info* pFieldList;
	const OTreeNode* pNode = pObject;
	const TrackInfo* info = OTreeNode_GetData(pNode);
	const FLTrack* pTrack = info->pTrack;
	const char* pString;
	CRect box, refbox;
	CPoint pt;
	int bcol, fcol;
	int count, i;
	int width;

	box = RectToScreen(&pItem->m_box, &pItem->m_cvtinfo);
	RoundRectForPlot(&box);

	Desktop_SetStdColour(7);

	// Plot marks
	pt.x = box.x0 + Sizes.Clue.o.x0;
	pt.y = box.y0 + Sizes.Clue.o.y0;
	if (pItem->m_flags & EWItem_Marked)
		Desktop_PlotNamedSprite("m_found", pt, NULL);
	else if (info->Key & FLTreeMode_IsLink)
		Desktop_PlotNamedSprite("m_link", pt, NULL);
	else if (FLTrack_GetClue(pTrack))
		Desktop_PlotSprite(FLTrack_GetClue(pTrack), pt, NULL);

	box.x0 += Sizes.Clue.o.x1;

	Plot_FLPlayableInfo(pItem, &box, pTrack);
	Plot_FLStateObjectInfo(pItem, &box, pTrack);

	Node_GetTextColors(pItem, &bcol, &fcol);

	// Plot filled rectangle
	box.x1 -= Task_GetModeInfo()->dx;
	Plot_TextBack(pItem, &box, 0, bcol, fcol);

	// Plot texts
	Desktop_SetStdColour(fcol);
	Desktop_SetStdColour(0x80 + bcol);
    refbox = box;

	count = FLTreeView_GetFieldList(pView, &pFieldList);

	for (i = 0; i < count; i++)
	{
		// Plot text
		width = pFieldList[i].width;
		pString = NodeFlatTrack_String(info, pFieldList[i].id);
		box.x1 = refbox.x0 + width - Sizes.Text.x0;
		box.x0 = refbox.x0 + Sizes.Text.x0;
		Desktop_PlotText(pString, &box);
		// Move to next position
		refbox.x0 += width;
	}
}

static void NodeFlatTrack_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	IGNORE(pObject);
	IGNORE(pOwner);
	IGNORE(psizes);
}

static CSize NodeFlatTrack_GetBox(const void* pObject, const void* pOwner, const int* pSizes, const int* pMaxSizes)
{
	const FLTreeView* pView = pOwner;
	const FLField_Info* pFieldList;
	int count, i;
	CSize size = { Sizes.Clue.o.x1
	             + Sizes.Play.o.x1
	             + Sizes.State.o.x1
	             + Sizes.Object.o.x1
	             , Sizes.Text.y1
	             };

	IGNORE(pObject);
	IGNORE(pSizes);
	IGNORE(pMaxSizes);

	count = FLTreeView_GetFieldList(pView, &pFieldList);

	for (i = 0; i < count; i++)
		size.cx += pFieldList[i].width;

	return size;
}

static void NodeFlatTrack_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	const FLTreeView* pView = pOwner;
	const OTreeNode* pNode = pObject;
	const TrackInfo* info = OTreeNode_GetData(pNode);
	const FLTrack* pTrack = info->pTrack;
	int selx0, x1;

	if (pMouse == NULL)
	{
		pArea->rect = pItem->m_box;
		pArea->rect.x0 = pArea->rect.x0 + Sizes.Clue.o.x1;
		Area_FLPlayableInfo(pMouse, pTrack, pArea);
		Area_FLStateObjectInfo(pMouse, pTrack, pArea);
		selx0 = pArea->rect.x0;

		const FLField_Info* pFieldList;
		int count = FLTreeView_GetFieldList(pView, &pFieldList);

		for (int i = 0; i < count; i++)
		{
			x1 = pArea->rect.x0 + pFieldList[i].width;
			if (pArea->id == FLTreeView_GetFieldAction(pTrack, pFieldList[i].id))
			{
				if (pArea->id == EWItemArea_Select)
					pArea->rect.x0 = selx0;
				else
					pArea->rect.x1 = x1;
				return;
			}
			pArea->rect.x0 = x1;
		}

		pArea->rect.x0 = selx0;
	}
	else
	{
		pArea->rect = pItem->m_box;
		pArea->id = EWItemArea_Select;

		x1 = pArea->rect.x0 + Sizes.Clue.o.x1;
		if (pMouse->pt.x < x1)
		{
			pArea->id = EWItemArea_Outside;
			return;
		}
		pArea->rect.x0 = x1;

		Area_FLPlayableInfo(pMouse, pTrack, pArea);
		if (pArea->id != EWItemArea_Select)
			return;

		Area_FLStateObjectInfo(pMouse, pTrack, pArea);
		if (pArea->id != EWItemArea_Select)
			return;

		if (!Keyboard_PollAlt())
			return;

		selx0 = pArea->rect.x0;

		const FLField_Info* pFieldList;
		int count = FLTreeView_GetFieldList(pView, &pFieldList);

		for (int i = 0; i < count; i++)
		{
			x1 = pArea->rect.x0 + pFieldList[i].width;
			if (pMouse->pt.x < x1)
			{
				pArea->id = FLTreeView_GetFieldAction(pTrack, pFieldList[i].id);

				if (pArea->id == EWItemArea_Select)
					pArea->rect.x0 = selx0;
				else
					pArea->rect.x1 = x1;
				return;
			}
			pArea->rect.x0 = x1;
		}

		pArea->rect.x0 = selx0;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeFlatTrack_VPtr =
{ { NodeFlatTrack_Plot
  , NodeFlatTrack_GetSize
  , NodeFlatTrack_GetBox
  , NodeFlatTrack_GetArea
  }
, NULL
, NULL
, NULL
, NULL
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void NodeQueueTrack_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const FLTreeView* pView = pOwner;
	const FLField_Info* pFieldList;
	const OTreeNode* pNode = pObject;
	const TrackInfo* info = OTreeNode_GetData(pNode);
	const FLTrack* pTrack = info->pTrack;
	const char* pString;
	CRect box, refbox;
	CPoint pt;
	int bcol, fcol;
	int count, i;
	int width;

	box = RectToScreen(&pItem->m_box, &pItem->m_cvtinfo);
	RoundRectForPlot(&box);

	Desktop_SetStdColour(7);

	// Plot marks
	pt.x = box.x0 + Sizes.Clue.o.x0;
	pt.y = box.y0 + Sizes.Clue.o.y0;
	if (pItem->m_flags & EWItem_Marked)
		Desktop_PlotNamedSprite("m_found", pt, NULL);
	else if (info->Key & FLTreeMode_IsLink)
		Desktop_PlotNamedSprite("m_link", pt, NULL);
	else if (FLTrack_GetClue(pTrack))
		Desktop_PlotSprite(FLTrack_GetClue(pTrack), pt, NULL);

	box.x0 += Sizes.Clue.o.x1;

	Plot_FLStateObjectInfo(pItem, &box, pTrack);

	Node_GetTextColors(pItem, &bcol, &fcol);

	// Plot filled rectangle
	box.x1 -= Task_GetModeInfo()->dx;
	Plot_TextBack(pItem, &box, 0, bcol, fcol);

	// Plot texts
	Desktop_SetStdColour(fcol);
	Desktop_SetStdColour(0x80 + bcol);
    refbox = box;

	count = FLTreeView_GetFieldList(pView, &pFieldList);

	for (i = 0; i < count; i++)
	{
		// Plot text
		width = pFieldList[i].width;
		pString = NodeFlatTrack_String(info, pFieldList[i].id);
		box.x1 = refbox.x0 + width - Sizes.Text.x0;
		box.x0 = refbox.x0 + Sizes.Text.x0;
		Desktop_PlotText(pString, &box);
		// Move to next position
		refbox.x0 += width;
	}
}

static void NodeQueueTrack_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	IGNORE(pObject);
	IGNORE(pOwner);
	IGNORE(psizes);
}

static CSize NodeQueueTrack_GetBox(const void* pObject, const void* pOwner, const int* pSizes, const int* pMaxSizes)
{
	const FLTreeView* pView = pOwner;
	const FLField_Info* pFieldList;
	int count, i;
	CSize size = { Sizes.Clue.o.x1
	             + Sizes.State.o.x1
	             + Sizes.Object.o.x1
	             , Sizes.Text.y1
	             };

	IGNORE(pObject);
	IGNORE(pSizes);
	IGNORE(pMaxSizes);

	count = FLTreeView_GetFieldList(pView, &pFieldList);

	for (i = 0; i < count; i++)
		size.cx += pFieldList[i].width;

	return size;
}

static void NodeQueueTrack_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	const OTreeNode* pNode = pObject;
	const TrackInfo* info = OTreeNode_GetData(pNode);
	const FLTrack* pTrack = info->pTrack;
	int x1;

	IGNORE(pOwner);

	OTreeNode_DefArea(pNode, pItem, pMouse, pArea);

	if (pMouse == NULL)
	{
		pArea->rect.x0 = pArea->rect.x0 + Sizes.Clue.o.x1;
		Area_FLStateObjectInfo(pMouse, pTrack, pArea);
		pArea->rect.x1 = pItem->m_box.x1;
	}
	else
	{
		if (pArea->id != EWItemArea_Select)
			return;

		x1 = pArea->rect.x0 + Sizes.Clue.o.x1;
		if (pMouse->pt.x < x1)
		{
			pArea->id = EWItemArea_Outside;
			return;
		}

		pArea->rect.x0 = x1;
		Area_FLStateObjectInfo(pMouse, pTrack, pArea);
		if (pArea->id != EWItemArea_Select)
			return;

		if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
		{
			pArea->id = FLItemAction_EditTitle;
			pArea->rect.x1 = pItem->m_box.x1;
		}
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeQueueTrack_VPtr =
{ { NodeQueueTrack_Plot
  , NodeQueueTrack_GetSize
  , NodeQueueTrack_GetBox
  , NodeQueueTrack_GetArea
  }
, NULL
, NULL
, NULL
, NULL
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const char* NodeRadioBandwidth_String(int band)
{
	if (band <= 0)
		return NodeBand_Strings[0];
	else if (band >= 4)
		return SPrintf("%s ( >= %d kbps)", NodeBand_Strings[band], Options()->Internet.BandRates[3]);

	return SPrintf("%s ( < %d kbps)", NodeBand_Strings[band], Options()->Internet.BandRates[band]);
}

static void NodeRadioBandwidth_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const IntInfo* info = OTreeNode_GetData(pNode);

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, NodeRadioBandwidth_String(info->val));
}

static void NodeRadioBandwidth_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const IntInfo* info = OTreeNode_GetData(pNode);

	IGNORE(pOwner);

	Plot_SpriteString(pNode, pItem, "n_bandwidth", NodeRadioBandwidth_String(info->val));
//	Plot_Void(pItem, &box);
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeRadioRate_VPtr =
{ { NodeRadioBandwidth_Plot
  , NodeRadioBandwidth_GetSize
  , Node_GetBox
  , Node_GetSpriteStringArea
  }
, NULL
, NULL
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const char* NodeRadioTrack_String(const FLTrack* pTrack)
{
	int rate = FLTrack_GetRate(pTrack);

	if (rate > 0)
		return SPrintf("%s [%d kbps]", FLTrack_GetTrackName(pTrack), rate);

	return FLTrack_GetTrackName(pTrack);
}

static void NodeRadioTrack_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const FLTrack* pTrack = ((const TrackInfo*) OTreeNode_GetData(pNode))->pTrack;

	IGNORE(pOwner);

	psizes[0] = GetWidth_FLNodeString(pNode, NodeRadioTrack_String(pTrack));
}

static void NodeRadioTrack_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const FLTrack* pTrack = ((const TrackInfo*) OTreeNode_GetData(pNode))->pTrack;
	CRect box;

	IGNORE(pOwner);

	Plot_Base(pNode, pItem, &box, pTrack);
	Plot_FLPlayableInfo(pItem, &box, pTrack);
	Plot_FLStateObjectInfo(pItem, &box, pTrack);
	Plot_Text(pItem, &box, NodeRadioTrack_String(pTrack), 0);
//	Plot_Void(pItem, &box);
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeRadioTrack_VPtr =
{ { NodeRadioTrack_Plot
  , NodeRadioTrack_GetSize
  , Node_GetBox
  , NodeTrack_GetArea
  }
, NULL
, NULL
, NULL
, NULL
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void throw_NodeBroadcaster_AllocData(OTreeNode* This, void* pData, const void* pRefData)
{
	StrInfo* info = pData;
	const StrInfo* refinfo = pRefData;

	IGNORE(This);

	info->Key = refinfo->Key;
	info->Str = throw_StrCol_Add(DigitalCD.pBroadcastersCol, refinfo->Str);
}

static void NodeBroadcaster_ClearData(OTreeNode* This, void* pData)
{
	StrInfo* info = pData;

	IGNORE(This);

	StrCol_Remove(DigitalCD.pBroadcastersCol, info->Str);
}

static void NodeBroadcaster_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const char* pString = ((const StrInfo*) OTreeNode_GetData(pNode))->Str;

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeBroadcaster_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const char* pString = ((const StrInfo*) OTreeNode_GetData(pNode))->Str;

	IGNORE(pOwner);

	Plot_SpriteString(pNode, pItem, "n_bcaster", pString);
}

static void NodeBroadcaster_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_GetSpriteStringArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = FLItemAction_EditBroadcaster;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeBroadcaster_VPtr =
{ { NodeBroadcaster_Plot
  , NodeBroadcaster_GetSize
  , Node_GetBox
  , NodeBroadcaster_GetArea
  }
, throw_NodeBroadcaster_AllocData
, NodeBroadcaster_ClearData
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void throw_NodeMimeType_AllocData(OTreeNode* This, void* pData, const void* pRefData)
{
	StrInfo* info = pData;
	const StrInfo* refinfo = pRefData;

	IGNORE(This);

	info->Key = refinfo->Key;
	info->Str = throw_StrCol_Add(DigitalCD.pMimeTypesCol, refinfo->Str);
}

static void NodeMimeType_ClearData(OTreeNode* This, void* pData)
{
	StrInfo* info = pData;

	IGNORE(This);

	StrCol_Remove(DigitalCD.pMimeTypesCol, info->Str);
}

static void NodeMimeType_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const char* pString = ((const StrInfo*) OTreeNode_GetData(pNode))->Str;

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeMimeType_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const char* pString = ((const StrInfo*) OTreeNode_GetData(pNode))->Str;

	IGNORE(pOwner);

	Plot_SpriteString(pNode, pItem, "n_mimetype", pString);
}

static void NodeMimeType_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_GetSpriteStringArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = FLItemAction_EditMimeType;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeRadioMimeType_VPtr =
{ { NodeMimeType_Plot
  , NodeMimeType_GetSize
  , Node_GetBox
  , NodeMimeType_GetArea
  }
, throw_NodeMimeType_AllocData
, NodeMimeType_ClearData
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeRadios_VPtr =
{ { NodeType_Plot
  , NodeType_GetSize
  , Node_GetBox
  , Node_GetSpriteStringArea
  }
, NULL
, NULL
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const char* NodeListTrack_String(const FLTrack* pTrack)
{
	return FLTrack_GetTrackName(pTrack);
}

static void NodeListTrack_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const FLTrack* pTrack = ((const TrackInfo*) OTreeNode_GetData(pNode))->pTrack;

	IGNORE(pOwner);

	psizes[0] = GetWidth_FLNodeString(pNode, NodeListTrack_String(pTrack));
}

static void NodeListTrack_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const FLTrack* pTrack = ((const TrackInfo*) OTreeNode_GetData(pNode))->pTrack;
	CRect box;

	IGNORE(pOwner);

	Plot_Base(pNode, pItem, &box, pTrack);
	Plot_FLPlayableInfo(pItem, &box, pTrack);
	Plot_FLStateObjectInfo(pItem, &box, pTrack);
	Plot_Text(pItem, &box, NodeListTrack_String(pTrack), 0);
//	Plot_Void(pItem, &box);
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeListTrack_VPtr =
{ { NodeListTrack_Plot
  , NodeListTrack_GetSize
  , Node_GetBox
  , NodeTrack_GetArea
  }
, NULL
, NULL
, NULL
, NULL
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeLists_VPtr =
{ { NodeType_Plot
  , NodeType_GetSize
  , Node_GetBox
  , Node_GetArea
  }
, NULL
, NULL
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void NodeTrack_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const TrackInfo* pInfo = OTreeNode_GetData(pNode);

	IGNORE(pOwner);

	psizes[0] = GetWidth_FLNodeString(pNode, NodeTrack_String(pInfo));
}

static void NodeTrack_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const TrackInfo* pInfo = OTreeNode_GetData(pNode);
	const FLTrack* pTrack = pInfo->pTrack;
	CRect box;

	IGNORE(pOwner);

	Plot_Base(pNode, pItem, &box, pTrack);
	Plot_FLPlayableInfo(pItem, &box, pTrack);
	Plot_FLStateObjectInfo(pItem, &box, pTrack);
	Plot_Text(pItem, &box, NodeTrack_String(pInfo), 0);
//	Plot_Void(pItem, &box);
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeTrack_VPtr =
{ { NodeTrack_Plot
  , NodeTrack_GetSize
  , Node_GetBox
  , NodeTrack_GetArea
  }
, NULL
, NULL
, NULL
, NULL
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void throw_NodeAlbum_AllocData(OTreeNode* This, void* pData, const void* pRefData)
{
	AlbumInfo* info = pData;
	const AlbumInfo* refinfo = pRefData;

	IGNORE(This);

	memset(info, 0, sizeof(*info));
	info->Key = refinfo->Key;
	info->Album = throw_StrCol_Add(DigitalCD.pAlbumsCol, refinfo->Album);
	if (refinfo->Box) info->Box = throw_StrCol_Add(DigitalCD.pBoxesCol, refinfo->Box);
	if (refinfo->Date) info->Date = throw_StrCol_Add(DigitalCD.pDatesCol, refinfo->Date);
	if (refinfo->Artist) info->Artist = throw_StrCol_Add(DigitalCD.pArtistsCol, refinfo->Artist);
	if (refinfo->Collective) info->Collective = throw_StrCol_Add(DigitalCD.pCollectivesCol, refinfo->Collective);
}

static void NodeAlbum_ClearData(OTreeNode* This, void* pData)
{
	AlbumInfo* info = pData;

	IGNORE(This);

	StrCol_Remove(DigitalCD.pAlbumsCol, info->Album);
	StrCol_Remove(DigitalCD.pBoxesCol, info->Box);
	StrCol_Remove(DigitalCD.pDatesCol, info->Date);
	StrCol_Remove(DigitalCD.pArtistsCol, info->Artist);
	StrCol_Remove(DigitalCD.pCollectivesCol, info->Collective);
}

static const char* NodeAlbum_String(const OTreeNode* pNode)
{
	const AlbumInfo* info = OTreeNode_GetData(pNode);

	if (info->Key & FLTreeMode_SortAlbum_Date)
	{
		if (info->Artist && *info->Artist)
		{
			if (info->Date && *info->Date)
				return SPrintf("[%s] %s (%s)", info->Date, info->Album, info->Artist);
			else
				return SPrintf("%s (%s)", info->Album, info->Artist);
		}
		else if (info->Date && *info->Date)
			return SPrintf("[%s] %s", info->Date, info->Album);
		else
			return info->Album;
	}
	else
	{
		if (info->Artist && *info->Artist)
		{
			if (info->Date && *info->Date)
				return SPrintf("%s [%s] (%s)", info->Album, info->Date, info->Artist);
			else
				return SPrintf("%s (%s)", info->Album, info->Artist);
		}
		else if (info->Date && *info->Date)
			return SPrintf("%s [%s]", info->Album, info->Date);
		else
			return info->Album;
	}
}

static void NodeAlbum_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const char* pString = NodeAlbum_String(pNode);

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeAlbum_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const AlbumInfo* info = OTreeNode_GetData(pNode);
	const char* pString = NodeAlbum_String(pNode);

	IGNORE(pOwner);

	if (info->Collective == NULL)
		Plot_SpriteString(pNode, pItem, "n_compil", pString);
	else
		Plot_SpriteString(pNode, pItem, "n_album", pString);
}

static void NodeAlbum_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_GetSpriteStringArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = FLItemAction_EditAlbum;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeAlbum_VPtr =
{ { NodeAlbum_Plot
  , NodeAlbum_GetSize
  , Node_GetBox
  , NodeAlbum_GetArea
  }
, throw_NodeAlbum_AllocData
, NodeAlbum_ClearData
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void throw_NodeBox_AllocData(OTreeNode* This, void* pData, const void* pRefData)
{
	BoxInfo* info = pData;
	const BoxInfo* refinfo = pRefData;

	IGNORE(This);

	memset(info, 0, sizeof(*info));
	info->Key = refinfo->Key;
	info->Box = throw_StrCol_Add(DigitalCD.pBoxesCol, refinfo->Box);
	if (refinfo->Date) info->Date = throw_StrCol_Add(DigitalCD.pDatesCol, refinfo->Date);
	if (refinfo->Artist) info->Artist = throw_StrCol_Add(DigitalCD.pArtistsCol, refinfo->Artist);
	if (refinfo->Collective) info->Collective = throw_StrCol_Add(DigitalCD.pCollectivesCol, refinfo->Collective);
}

static void NodeBox_ClearData(OTreeNode* This, void* pData)
{
	BoxInfo* info = pData;

	IGNORE(This);

	StrCol_Remove(DigitalCD.pBoxesCol, info->Box);
	StrCol_Remove(DigitalCD.pDatesCol, info->Date);
	StrCol_Remove(DigitalCD.pArtistsCol, info->Artist);
	StrCol_Remove(DigitalCD.pCollectivesCol, info->Collective);
}

static const char* NodeBox_String(const OTreeNode* pNode)
{
	const BoxInfo* info = OTreeNode_GetData(pNode);

	if (info->Artist && *info->Artist)
		return SPrintf("%s (%s)", info->Box, info->Artist);

	return info->Box;
}

static void NodeBox_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const char* pString = NodeBox_String(pNode);

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeBox_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const BoxInfo* info = OTreeNode_GetData(pNode);
	const char* pString = NodeBox_String(pNode);

	IGNORE(pOwner);

	if (info->Collective == NULL)
		Plot_SpriteString(pNode, pItem, "n_compset", pString);
	else
		Plot_SpriteString(pNode, pItem, "n_set", pString);
}

static void NodeBox_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_GetSpriteStringArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = FLItemAction_EditBox;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeBox_VPtr =
{ { NodeBox_Plot
  , NodeBox_GetSize
  , Node_GetBox
  , NodeBox_GetArea
  }
, throw_NodeBox_AllocData
, NodeBox_ClearData
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void throw_NodeArtist_AllocData(OTreeNode* This, void* pData, const void* pRefData)
{
	ArtistInfo* info = pData;
	const ArtistInfo* refinfo = pRefData;

	IGNORE(This);

	info->Key = refinfo->Key;
	info->Artist = throw_StrCol_Add(DigitalCD.pArtistsCol, refinfo->Artist);
	if (refinfo->Collective) info->Collective = throw_StrCol_Add(DigitalCD.pCollectivesCol, refinfo->Collective);
}

static void NodeArtist_ClearData(OTreeNode* This, void* pData)
{
	ArtistInfo* info = pData;

	IGNORE(This);

	StrCol_Remove(DigitalCD.pArtistsCol, info->Artist);
	StrCol_Remove(DigitalCD.pCollectivesCol, info->Collective);
}

static const char* NodeArtist_String(const OTreeNode* pNode)
{
	const ArtistInfo* info = OTreeNode_GetData(pNode);

	if (info->Collective && *info->Collective)
		return SPrintf("%s (%s)", info->Artist, info->Collective);

	return info->Artist;
}

static void NodeArtist_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const char* pString = NodeArtist_String(pNode);

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeArtist_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const char* pString = NodeArtist_String(pNode);

	IGNORE(pOwner);

	Plot_SpriteString(pNode, pItem, "n_artist", pString);
}

static void NodeArtist_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_GetSpriteStringArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = FLItemAction_EditArtist;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeArtist_VPtr =
{ { NodeArtist_Plot
  , NodeArtist_GetSize
  , Node_GetBox
  , NodeArtist_GetArea
  }
, throw_NodeArtist_AllocData
, NodeArtist_ClearData
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void throw_NodeCollective_AllocData(OTreeNode* This, void* pData, const void* pRefData)
{
	CollectiveInfo* info = pData;
	const CollectiveInfo* refinfo = pRefData;

	IGNORE(This);

	info->Key = refinfo->Key;
	info->Collective = throw_StrCol_Add(DigitalCD.pCollectivesCol, refinfo->Collective);
}

static void NodeCollective_ClearData(OTreeNode* This, void* pData)
{
	CollectiveInfo* info = pData;

	IGNORE(This);

	StrCol_Remove(DigitalCD.pCollectivesCol, info->Collective);
}

static void NodeCollective_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const char* pString = ((const CollectiveInfo*) OTreeNode_GetData(pNode))->Collective;

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeCollective_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const char* pString = ((const CollectiveInfo*) OTreeNode_GetData(pNode))->Collective;

	IGNORE(pOwner);

	Plot_SpriteString(pNode, pItem, "n_collective", pString);
}

static void NodeCollective_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_GetSpriteStringArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = FLItemAction_EditCollective;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeCollective_VPtr =
{ { NodeCollective_Plot
  , NodeCollective_GetSize
  , Node_GetBox
  , NodeCollective_GetArea
  }
, throw_NodeCollective_AllocData
, NodeCollective_ClearData
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void NodeFileList_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const PtrInfo* pInfo = OTreeNode_GetData(pNode);
	const Document* pDoc = pInfo->Ptr;

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, Document_GetPathName(pDoc));
}

static void NodeFileList_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const PtrInfo* pInfo = OTreeNode_GetData(pNode);
	const Document* pDoc = pInfo->Ptr;
	const char* pString = Document_GetPathName(pDoc);
	int bcol = 15;
	int fcol = 0;
	CRect box;
	CPoint pt;

	IGNORE(pOwner);

	Plot_Base(pNode, pItem, &box, NULL);

	Desktop_SetStdColour(bcol);
	// Plot filled rectangle
	Display_Plot(4, box.x0, box.y0);
	Display_Plot(101, box.x1, box.y1);

	// Plot Sprite
	pt.x = box.x0 + Sizes.Group.o.x0;
	pt.y = box.y0 + Sizes.Group.o.y0;
	Desktop_PlotNamedSprite("n_lists", pt, NULL);
	box.x0 += Sizes.Group.o.x1;

	// Plot text
	Desktop_SetStdColour(fcol);
	Desktop_SetStdColour(0x80 + bcol);
	box.x0 += Sizes.Text.x0;
	box.x1 = box.x0 + Desktop_GetTextWidth(pString);
	Desktop_PlotText(pString, &box);

	if (pItem->m_flags & EWItem_HasFocus)
	{
		Desktop_SetStdColour(fcol);
		// Plot filled rectangle
		Display_Plot(4, box.x0, box.y0 + 4);
		Display_Plot(101, box.x1, box.y0 + 7);
	}
}

static CSize NodeFileList_GetBox(const void* pObject, const void* pOwner, const int* pSizes, const int* pMaxSizes)
{
	const OTreeNode* pNode = pObject;
	const PtrInfo* pInfo = OTreeNode_GetData(pNode);

	if (pInfo->Key & FLTreeMode_Flat)
		return NodeFlatTrack_GetBox(pObject, pOwner, pSizes, pMaxSizes);

	return Node_GetBox(pObject, pOwner, pSizes, pMaxSizes);
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeFiles_VPtr =
{ { NodeType_Plot
  , NodeType_GetSize
  , Node_GetBox
  , Node_GetSpriteStringArea
  }
, NULL
, NULL
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeFileList_VPtr =
{ { NodeFileList_Plot
  , NodeFileList_GetSize
  , NodeFileList_GetBox
  , Node_GetArea
  }
, NULL
, NULL
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeQueue_VPtr =
{ { NodeFileList_Plot
  , NodeFileList_GetSize
  , NodeQueueTrack_GetBox
  , Node_GetArea
  }
, NULL
, NULL
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeSearch_VPtr =
{ { NULL
  , NULL
  , NodeFileList_GetBox
  , NULL
  }
, NULL
, NULL
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

/**
 * This function is called in a loop to get the next child/grouping level
 * using the same ChildInfo block. This means that if they use the same layout,
 * attributes set at a higher level can be reused in the next levels.
 *
 * On a same level objects may use several attributes for display
 * and to differenciate/sort them. Let's take the album level: if two tracks
 * use the same album name, but have a different box, artist or collective,
 * then they belong to a different album.
 *
 * We combine all or part of the attributes to show distinct visuals
 * but if I group at several levels, info from the upper level becomes
 * redundant for the levels below and must be emptied in the ChildInfo block.
 * At display level I pay attention to check both that the attribute
 * is not NULL and does not point to an empty string before combining
 * it with other attributes but in fact querying attributes from the track
 * always replaces NULL by an empty string (&nil).
 *
 * Thus, I decided to mark compilation with a NULL in the "collective"
 * attribute. This means that when I empty an attribute used in an upper level
 * I use NULL in the "compilation" leveling path and a &nil in the other paths.
 * Visually it make no difference at text level, but still differentiate
 * with a different sprite and more importantly I can differentiate
 * between NULL and an empty string when sorting and so make a distinction
 * between comp/non-comp objects.
 *
 * Note:
 * We won't make a distinction between comp/non-comp collectives and artists.
 * This is implicit at collective level (since I cannot empty the text)
 * but at artist level we must be sure to use &nil instead of NULL.
 */
static void Track_GetSubEntity(const FLTrack* pTrack, ChildInfo* pInfo)
{
	uint32_t key = pInfo->i.album.Key & FLTreeMode_Mask_Keys;
	uint32_t mode = pInfo->i.album.Key & ~FLTreeMode_Mask_Keys;

	switch (key)
	{
		case Key_Unknown:
		case Key_FileList:
		{
			switch(Track_NodeType(pTrack))
			{
				case Entity_Radios:
				{
					pInfo->pVPtr = &NodeRadios_VPtr;
					pInfo->size = sizeof(pInfo->i.val);
					pInfo->i.val.Key = Key_Radios | mode;
					pInfo->i.val.val = 0;
				}
				break;
				case Entity_Lists:
				{
					pInfo->pVPtr = &NodeLists_VPtr;
					pInfo->size = sizeof(pInfo->i.val);
					pInfo->i.val.Key = Key_Lists | mode;
					pInfo->i.val.val = 1;
				}
				break;
				default:
				{
					pInfo->pVPtr = &NodeFiles_VPtr;
					pInfo->size = sizeof(pInfo->i.val);
					pInfo->i.val.Key = Key_Files | mode;
					pInfo->i.val.val = 2;
				}
				break;
			}

			if (mode & FLTreeMode_GroupBy_ObjectType)
				return;

			Track_GetSubEntity(pTrack, pInfo);
			return;
		}
		break;
	// - Radios -
		case Key_Radios:
		{
			const char* pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamMimeType);
			if ((mode & FLTreeMode_GroupBy_MimeType) && *pData)
			{
				pInfo->pVPtr = &NodeRadioMimeType_VPtr;
				pInfo->size = sizeof(pInfo->i.str);
				pInfo->i.str.Key = Key_MimeType | mode;
				pInfo->i.str.Str = pData;
				return;
			}
		}
		// no break;
		case Key_MimeType:
		{
			if (mode & FLTreeMode_GroupBy_Rate)
			{
				pInfo->pVPtr = &NodeRadioRate_VPtr;
				pInfo->size = sizeof(pInfo->i.val);
				pInfo->i.val.Key = Key_Bandwidth | mode;
				pInfo->i.val.val = GetBand(FLTrack_GetRate(pTrack));
				return;
			}
		}
		// no break;
		case Key_Bandwidth:
		{
			const char* pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamBroadcaster);
			if ((mode & FLTreeMode_GroupBy_Broadcaster) && *pData)
			{
				pInfo->pVPtr = &NodeBroadcaster_VPtr;
				pInfo->size = sizeof(pInfo->i.str);
				pInfo->i.str.Key = Key_Broadcaster | mode;
				pInfo->i.str.Str = pData;
				return;
			}
		}
		// no break;
		case Key_Broadcaster:
		{
			pInfo->pVPtr = &NodeRadioTrack_VPtr;
			pInfo->size = sizeof(pInfo->i.track);
			pInfo->i.track.Key = Key_Track | mode | pInfo->flags;
			pInfo->i.track.pTrack = pTrack;
			return;
		}
		break;
	// - Lists -
		case Key_Lists:
		{
			pInfo->pVPtr = &NodeListTrack_VPtr;
			pInfo->size = sizeof(pInfo->i.track);
			pInfo->i.track.Key = Key_Track | mode | pInfo->flags;
			pInfo->i.track.pTrack = pTrack;
			return;
		}
		break;
	// - Files -
		case Key_Files:
		{
			// Prefill with all useful info
			pInfo->i.album.Album = FLTrack_GetMetaString(pTrack, EMetaId_StreamAlbum);
			pInfo->i.album.Date = FLTrack_GetMetaString(pTrack, EMetaId_StreamDate);
			pInfo->i.album.Box = FLTrack_GetMetaString(pTrack, EMetaId_StreamBox);
			pInfo->i.album.Artist = FLTrack_GetMetaString(pTrack, EMetaId_StreamArtist);
			pInfo->i.album.Collective = FLTrack_GetMetaString(pTrack, EMetaId_StreamCollective);

			bool bCompil;

			bCompil = ((mode & FLTreeMode_GroupBy_Compilation)
			        && (FLTrack_GetFlags(pTrack) & FLTrack_Compilation)
			        && (*pInfo->i.album.Album
			            || ((mode & FLTreeMode_GroupBy_Box) && *pInfo->i.album.Box)));

			if (!bCompil)
			{
				pInfo->i.album.Key = Key_NC | mode;
				Track_GetSubEntity(pTrack, pInfo);
				return;
			}

	// Entities belonging to a groupment by compilation
			// Discard artist & collective info
			pInfo->i.album.Artist = NULL;
			pInfo->i.album.Collective = NULL;

			if ((mode & FLTreeMode_GroupBy_Box)
			&&  *pInfo->i.album.Box)
			{
				pInfo->pVPtr = &NodeBox_VPtr;
				pInfo->size = sizeof(pInfo->i.box);
				pInfo->i.box.Key = Key_Comp_Box | mode;
				return;
			}
		}
		// no break;
		case Key_Comp_Box:
		{
			if ((mode & FLTreeMode_GroupBy_Album)
			&&  *pInfo->i.album.Album)
			{
				pInfo->pVPtr = &NodeAlbum_VPtr;
				pInfo->size = sizeof(pInfo->i.album);
				pInfo->i.album.Key = Key_Comp_Album | mode;
				// Discard box info
				if (mode & FLTreeMode_GroupBy_Box)
					pInfo->i.album.Box = NULL;
				return;
			}
		}
		// no break;
		case Key_Comp_Album:
		{
			// put back artist & collective info
			pInfo->i.album.Artist = FLTrack_GetMetaString(pTrack, EMetaId_StreamArtist);
			pInfo->i.album.Collective = FLTrack_GetMetaString(pTrack, EMetaId_StreamCollective);

			if ((mode & FLTreeMode_GroupBy_CompByArtist)
			&&  (mode & FLTreeMode_GroupBy_Collective)
			&&  *pInfo->i.album.Collective)
			{
				pInfo->pVPtr = &NodeCollective_VPtr;
				pInfo->size = sizeof(pInfo->i.collective);
				if (key > Key_Files)
					pInfo->i.collective.Key = Key_Comp_Collective | mode;
				else // order after album
					pInfo->i.collective.Key = Key_HComp_Collective | mode;
				return;
			}
		}
		// no break;
		case Key_HComp_Collective:
		case Key_Comp_Collective:
		{
			if ((mode & FLTreeMode_GroupBy_CompByArtist)
			&&  *pInfo->i.album.Artist)
			{
				pInfo->pVPtr = &NodeArtist_VPtr;
				pInfo->size = sizeof(pInfo->i.artist);
				if (key > Key_Files)
					pInfo->i.artist.Key = Key_Comp_Artist | mode;
				else // order after album
					pInfo->i.artist.Key = Key_HComp_Artist | mode;
				if ((mode & FLTreeMode_GroupBy_CompByArtist)
				&&  (mode & FLTreeMode_GroupBy_Collective))
					pInfo->i.artist.Collective = &nil; // Special case, not NULL
				return;
			}
		}
		// no break;
		case Key_HComp_Artist:
		case Key_Comp_Artist:
		{
			pInfo->pVPtr = &NodeTrack_VPtr;
			pInfo->size = sizeof(pInfo->i.track);
			pInfo->i.track.Key = Key_Track | mode | pInfo->flags;
			pInfo->i.track.pTrack = pTrack;
			return;
		}
		break;
	// Entities not part of a groupment by compilation
		case Key_NC:
		{
			if ((mode & FLTreeMode_GroupBy_Collective)
			&&  *pInfo->i.album.Collective)
			{
				pInfo->pVPtr = &NodeCollective_VPtr;
				pInfo->size = sizeof(pInfo->i.collective);
				pInfo->i.collective.Key = Key_Collective | mode;
				return;
			}
		}
        // no break;
        case Key_Collective:
		{
			if ((mode & FLTreeMode_GroupBy_Artist)
			&&  *pInfo->i.album.Artist)
			{
				pInfo->pVPtr = &NodeArtist_VPtr;
				pInfo->size = sizeof(pInfo->i.artist);
				pInfo->i.artist.Key = Key_Artist | mode;
				if (mode & FLTreeMode_GroupBy_Collective)
					pInfo->i.artist.Collective = &nil;
				return;
			}
		}
		// no break;
        case Key_Artist:
		{
			if ((mode & FLTreeMode_GroupBy_Box)
			&&  *pInfo->i.album.Box)
			{
				pInfo->pVPtr = &NodeBox_VPtr;
				pInfo->size = sizeof(pInfo->i.box);
				pInfo->i.box.Key = Key_Box | mode;
				if (mode & FLTreeMode_GroupBy_Artist)
					pInfo->i.box.Artist = &nil;
				if (mode & FLTreeMode_GroupBy_Collective)
					pInfo->i.artist.Collective = &nil;
				return;
			}
		}
		// no break;
        case Key_Box:
		{
			if ((mode & FLTreeMode_GroupBy_Album)
			&&  *pInfo->i.album.Album)
			{
				pInfo->pVPtr = &NodeAlbum_VPtr;
				pInfo->size = sizeof(pInfo->i.album);
				pInfo->i.album.Key = Key_Album | mode;
				if (mode & FLTreeMode_GroupBy_Box)
					pInfo->i.album.Box = &nil;
				if (mode & FLTreeMode_GroupBy_Artist)
					pInfo->i.box.Artist = &nil;
				if (mode & FLTreeMode_GroupBy_Collective)
					pInfo->i.artist.Collective = &nil;
				return;
			}
		}
		// no break;
        case Key_Album:
		{
			pInfo->pVPtr = &NodeTrack_VPtr;
			pInfo->size = sizeof(pInfo->i.track);
			pInfo->i.track.Key = Key_Track | mode | pInfo->flags;
			pInfo->i.track.pTrack = pTrack;

			return;
		}
		break;
	}

	// Should not happen, but just in case
	pInfo->pVPtr = &NodeTrack_VPtr;
	pInfo->size = sizeof(pInfo->i.track);
	pInfo->i.track.Key = Key_Track | mode | pInfo->flags;
	pInfo->i.track.pTrack = pTrack;
}

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static uint32_t Mode_SortAlpha(uint32_t mode)
{
	if ((mode & FLTreeMode_Mask_OrderBy) == FLTreeMode_OrderBy_TrackNumber)
	{
		mode &= ~FLTreeMode_Mask_OrderBy;
		mode |= FLTreeMode_OrderBy_Alphabetic;
	}

	return mode;
}

OTreeNode* throw_FLTreeNodes_Insert(OTreeList* pOList, const FileList* pDoc, const FLTrack* pTrack)
{
	OTreeNode* volatile pNode = OTreeList_GetRootNode(pOList);
	ChildInfo Info;
	uint32_t mode = ((const KeyInfo*) OTreeNode_GetData(pNode))->Key & ~FLTreeMode_Mask_Keys;

	Info.flags = 0;
  	if (FLTrack_ResolveLink(pTrack) != pTrack)
  		Info.flags |= FLTreeMode_IsLink;
	Info.i.ptr.pDoc = pDoc;

	// When the track is not a file or there is no album group
	// track sorting order must be turned into alphabetic one.
	// (Will also lead to display of unnumbered tracks).
	// This change must be done here because of flat tracks mode.
	if (Track_NodeType(pTrack) != Entity_Files)
		mode = Mode_SortAlpha(mode);
	else if (  ((mode & FLTreeMode_GroupBy_Album) == 0)
	        || !*FLTrack_GetMetaString(pTrack, EMetaId_StreamAlbum))
		mode = Mode_SortAlpha(mode);

	try
	{
		if (FileList_GetType(pDoc) == FileList_TypeQueue)
		{
			Info.size = sizeof(Info.i.track);
			Info.i.track.Key = Key_FlatTrack | mode | Info.flags;
			Info.i.track.pTrack = pTrack;
			Info.pVPtr = &NodeQueueTrack_VPtr;
			pNode = throw_OTreeNode_InsertChild(pNode, Info.pVPtr, Info.size, &Info.i);
		}
		else if (mode & FLTreeMode_Flat)
		{
			Info.size = sizeof(Info.i.track);
			Info.i.track.Key = Key_FlatTrack | mode | Info.flags;
			Info.i.track.pTrack = pTrack;
			Info.pVPtr = &NodeFlatTrack_VPtr;
			pNode = throw_OTreeNode_InsertChild(pNode, Info.pVPtr, Info.size, &Info.i);
		}
		else
		{
			if (FileList_GetType(pDoc) == FileList_TypeSearch)
			{
				// Extra mandatory FileList level
				Info.size = sizeof(Info.i.ptr);
				Info.i.ptr.Key = Key_FileList | mode;
				Info.i.ptr.Ptr = FLTrack_GetOwner(pTrack);
				Info.pVPtr = &NodeFileList_VPtr;
				pNode = throw_OTreeNode_InsertChild(pNode, Info.pVPtr, Info.size, &Info.i);
			}
			else
				Info.i.key.Key = Key_Unknown | mode;

			while ((Info.i.key.Key & FLTreeMode_Mask_Keys) < Key_Track)
			{
				Track_GetSubEntity(pTrack, &Info);

				pNode = throw_OTreeNode_InsertChild(pNode, Info.pVPtr, Info.size, &Info.i);
			}
		}
	}
	catch
	{
		FLTreeNodes_Remove(pOList, pNode);
		throw_current();
	}
	catch_end

	return pNode;
}

void FLTreeNodes_Remove(OTreeList* pOList, OTreeNode* pRefNode)
{
	const OTreeNode* pRootNode = OTreeList_GetRootNode(pOList);

	while (pRefNode != pRootNode)
	{
		OTreeNode* pNode = OTreeNode_GetParent(pRefNode);
		OTreeList_Remove(pOList, pRefNode);

		if (OTreeNode_CountChilds(pNode) != 0)
			break;

		pRefNode = pNode;
	}
}

OTreeNode* throw_FLTreeNodes_NewFileListNode(uint32_t proposedtype)
{
	uint32_t treetype = proposedtype;
	OTreeNode* pRootNode;

	if ((treetype & (FLTreeMode_GroupBy_Artist|FLTreeMode_GroupBy_Album)) == FLTreeMode_GroupBy_Album)
		treetype |= FLTreeMode_GroupBy_Compilation;

	pRootNode = throw_New_OTreeNode(&NodeFiles_VPtr, sizeof(treetype), (void*) &treetype);

	return pRootNode;
}

OTreeNode* throw_FLTreeNodes_NewQueueNode(uint32_t proposedtype)
{
	uint32_t treetype = proposedtype;

	treetype &= ~(FLTreeMode_Mask_GroupBy | FLTreeMode_Mask_OrderBy);
	treetype |= FLTreeMode_Flat | FLTreeMode_OrderBy_Raw;

	return throw_New_OTreeNode(&NodeQueue_VPtr, sizeof(treetype), (void*) &treetype);
}

OTreeNode* throw_FLTreeNodes_NewSearchNode(uint32_t proposedtype)
{
	uint32_t treetype = proposedtype;

	treetype &= ~FLTreeMode_Flat;

	if ((treetype & (FLTreeMode_GroupBy_Artist|FLTreeMode_GroupBy_Album)) == FLTreeMode_GroupBy_Album)
		treetype |= FLTreeMode_GroupBy_Compilation;

	return throw_New_OTreeNode(&NodeSearch_VPtr, sizeof(treetype), (void*) &treetype);
}

bool FLTreeNodes_IsTrack(const OTreeNode* pNode)
{
	const KeyInfo* info = OTreeNode_GetData(pNode);

	if ((info->Key & Key_Mask_Entity) != Entity_Track)
		return false;

	return true;
}

FLTrack* throw_FLTreeNodes_GetTrack(const OTreeNode* pNode)
{
	const TrackInfo* info = OTreeNode_GetData(pNode);

	if ((info->Key & Key_Mask_Entity) != Entity_Track)
		throw_string("Tree Node is not a track");

	return (FLTrack*) info->pTrack;
}

const char* FLTreeNodes_GetField(const OTreeNode* pNode, EMetaId id)
{
	const KeyInfo* pa = OTreeNode_GetData(pNode);

	switch(pa->Key & Key_Mask_Entity)
	{
		case Entity_MimeType:
		case Entity_Broadcaster:
		{
			const StrInfo* sa = (void*) pa;

			return sa->Str;
		}
		break;
		case Entity_Collective:
		{
			const CollectiveInfo* sa = (void*) pa;

			return sa->Collective;
		}
		break;
		case Entity_Artist:
		{
			const ArtistInfo* sa = (void*) pa;

			return sa->Artist;
		}
		break;
		case Entity_Box:
		{
			const BoxInfo* sa = (void*) pa;

			return sa->Box;
		}
		break;
		case Entity_Album:
		{
			const AlbumInfo* sa = (void*) pa;

			return sa->Album;
		}
		break;
		case Entity_Track:
		{
			const TrackInfo* sa = (void*) pa;

			return FLTrack_GetMetaString(sa->pTrack, id);
		}
		break;
	}

	return &nil;
}

int FLTreeNodes_ShowNode(const OTreeNode* pNode, bool bForce)
{
	int Index = -1;

	if (pNode)
	{
		OTreeList* pOList = OTreeNode_GetTreeList(pNode);
		WListCore* pWList = OTreeList_GetWListCore(pOList);

		Index = OTreeList_ExpandParents(pOList, pNode);

		WListCore_Refresh(pWList, Index, Index);

		if (bForce)
			WListCore_ShowItem(pWList, Index);
	}

	return Index;
}

void FLTreeNodes_GetChildTracks(const OTreeNode* pNode, List* pList)
{
	if (!pNode) return;

	const TrackInfo* info = OTreeNode_GetData(pNode);

	if ((info->Key & Key_Mask_Entity) == Entity_Track)
		List_InsertBefore(pList, NULL, info->pTrack);
	else
	{
		for ( pNode = OTreeNode_GetFirstChild(pNode)
		    ; pNode != NULL
		    ; pNode = OTreeNode_GetSuccessor(pNode)
		    )
		{
			FLTreeNodes_GetChildTracks(pNode, pList);
		}
	}
}
