#include "FLTreeView.h"

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

#include "WimpLib:Array.h"
#include "WimpLib:Choices.h"
#include "WimpLib:Clipboard.h"
#include "WimpLib:Desktop.h"
#include "WimpLib:Display.h"
#include "WimpLib:DragDrop.h"
#include "WimpLib:DragSend.h"
#include "WimpLib:Exception.h"
#include "WimpLib:File.h"
#include "WimpLib:Hourglass.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:mem.h"
#include "WimpLib:Menu.h"
#include "WimpLib:Message.h"
#include "WimpLib:OTreeList.h"
#include "WimpLib:Task.h"
#include "WimpLib:Template.h"
#include "WimpLib:Utils.h"
#include "WimpLib:Window.h"
#include "WimpLib:WStringList.h"
#include "WimpLib:XFer.h"

#include "Cmds.h"
#include "CmdList.h"
#include "DigitalCD.h"
#include "DocEvents.h"
#include "FileTypes.h"
#include "FLCmds.h"
#include "FLSearch.h"
#include "FLTreeNodes.h"
#include "Options.h"
#include "PListFiles.h"
#include "PListRadio.h"
#include "Setup.h"
#include "UserEvents.h"

typedef struct
{
	const FLTrack*     pTrack;
	const OTreeNode*   pNode;
} TrackMap;

struct FLTreeView
{
	View         m_View;
	OTreeList*   m_pTreeList;
	Array*       m_pTrackSet;
	List*        m_pSelection;
	List*        m_pDragSelection;
	Toolbar*     m_pToolbar;
	const char*  m_pCmdGroup;
	unsigned int m_treemode;
	bool         m_bUpdateAtNull;
	bool         m_bExplodeAtNull;
	bool         m_bShowsHourglass;
	bool         m_bReadChoices;
	unsigned int m_FieldCount;
	FLField_Info m_FieldList[EFLField_FieldCount];
	struct
	{
		unsigned int field;
		unsigned int minx;
	} m_Resize;
	bool         m_bHeader;
	HWind        m_HeaderWnd;
	const MouseShape* m_pShape;
	struct
	{
		HIcon        Icon;
		int          Item;
		unsigned int AreaId;
		EMetaId      MetaId;
		StrCol*      pStrCol;
		WStringList* pWList;
		const char*  pOldValue;
	} m_Edit;
};

static const char Sec_PlayLists[] = "PlayLists";
static const char Sec_SearchLists[] = "SearchLists";
static const char Sec_FileQueue[] = "FileQueue";
static const char Var_Open[] = "Open";
static const char Var_PosX[] = "PosX";
static const char Var_PosY[] = "PosY";
static const char Var_Width[] = "Width";
static const char Var_Height[] = "Height";
static const char Var_ScrollX[] = "ScrollX";
static const char Var_ScrollY[] = "ScrollY";
static const char Var_TreeMode[] = "TreeMode";
static const char Var_FieldWidth[] = "Field%d_Width";

static EListenerAction FLTreeView_Listener(void* handle, const Event* e);
static const MouseShape ptr_nodrop = {"ptr_nodrop", 0, 0};
static const MouseShape ptr_move = {"ptr_move", 0, 0};
static const MouseShape ptr_copy = {"ptr_copy", 0, 0};
static const MouseShape ptr_link = {"ptr_link", 0, 0};
static const MouseShape ptr_scrh = {"ptr_scrh", 10, 4};

static char* NodeHeader_Strings[EFLField_FieldCount] = {NULL, NULL, NULL};

static struct
{
	CRect Text;
	struct
	{
		CRect  o;
		CSize  size;
	} VSlider;
} Sizes;

/*-------------------------------------------------------------------------*
 *--- Static init ---------------------------------------------------------*
 *-------------------------------------------------------------------------*/

void FLTreeViews_FLTreeViews(void)
{
	throw_FLTreeNodes_FLTreeNodes();

	if (NodeHeader_Strings[2] == NULL)
	{
		NodeHeader_Strings[0] = throw_mem_allocstring(Msg_Lookup("DSTitle"));
		NodeHeader_Strings[1] = throw_mem_allocstring(Msg_Lookup("DSAlbum"));
		NodeHeader_Strings[2] = throw_mem_allocstring(Msg_Lookup("DSArtist"));
	}

	// Read the various object sizes
	Sizes.Text.y1 = 40;
	Sizes.VSlider.size = Desktop_GetNamedSpriteSize("vslider");
	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.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 FLTreeViews_NotFLTreeViews(void)
{
	mem_free(NodeHeader_Strings[0]);
	mem_free(NodeHeader_Strings[1]);
	mem_free(NodeHeader_Strings[2]);
	FLTreeNodes_NotFLTreeNodes();
}

/**
 * Checks if some background job affecting this window is running.
 */
static bool FLTreeView_IsBusy(const FLTreeView* This)
{
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);

	if ((pDoc && FileList_IsBusy(pDoc))
	||  !OTreeList_IsRefreshAllowed(This->m_pTreeList))
		return true;

	return false;
}

/*-------------------------------------------------------------------------*
 *--- Header pane ---------------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Checks if mouse position is means column resizing.
 *
 * @param  pMouse     Pointer to mouse info.
 * @param  flags      Check mode.
 */
static int WListHeader_GetActionFromScreenPt(const FLTreeView* This, const Mouse* pMouse, unsigned int flags)
{
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	CWindCvt cvt = Window_GetPosInfo(This->m_HeaderWnd);
	Mouse m = *pMouse;
	int i;

	IGNORE(flags);

	m.pt = PointToWindow(&pMouse->pt, &cvt);

	if (FileList_GetType(pDoc) == FileList_TypeQueue)
		m.pt.x -= FLQueueNode_GetFirstColumnPos();
	else
		m.pt.x -= FLHeaderNode_GetFirstColumnPos();

	if (m.pt.x < 0)
		return EWItemArea_Outside;

	for (i = 0; i < This->m_FieldCount; i++)
	{
		m.pt.x -= This->m_FieldList[i].width;
		// slider
		if (m.pt.x < - Sizes.VSlider.o.x1)
			return EWItemArea_Outside;

		if (m.pt.x < 0)
			return FLItemAction_ResizeColumn0 + i;
	}

	return EWItemArea_Outside;
}

/**
 * Redraws the headers.
 *
 * @param  r          Pointer to the window's redraw structure.
 */
static void WListHeader_Redraw(const FLTreeView* This, const CWindRedraw* r)
{
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	CRect box = RectToWindow(&r->bounds, &r->cvt);
	CRect refbox;
	CPoint pt;
	int bcol = 4;
	int fcol = 0;
	const char* pString;
	int i;
	int width;

	box.x0 = 0;
	box.y0 = 0;
	box.y1 = Sizes.Text.y1;
	box = RectToScreen(&box, &r->cvt);
	RoundRectForPlot(&box);

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

	// Marks, ...
	if (FileList_GetType(pDoc) == FileList_TypeQueue)
		box.x0 += FLQueueNode_GetFirstColumnPos();
	else
		box.x0 += FLHeaderNode_GetFirstColumnPos();

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

	for (i = 0; i < This->m_FieldCount; i++)
	{
		// Plot text
		width = This->m_FieldList[i].width;
		pString = NodeHeader_Strings[This->m_FieldList[i].id];
		box.x1 = refbox.x0 + width - Sizes.Text.x0;
		box.x0 = refbox.x0 + Sizes.Text.x0;
		Desktop_PlotText(pString, &box);
		// Plot slider
		pt.x = refbox.x0 + width - Sizes.VSlider.o.x1;
		pt.y = box.y0 + Sizes.VSlider.o.y0;
		Desktop_PlotNamedSprite("vslider", pt, NULL);
		// Move to next position
		refbox.x0 += width;
	}
}

/**
 * Handles mouse movements while resizing an header.
 *
 * @param  m          Pointer to mouse info.
 */
static void FieldSlider_NullEvent(void* handle, const Mouse* m)
{
	FLTreeView* This = handle;
	int size = m->pt.x - This->m_Resize.minx;

	if (This->m_FieldList[This->m_Resize.field].width != size)
	{
		CWindCvt cvt = Window_GetPosInfo(This->m_HeaderWnd);
		WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
		CRect box = RectToWindow(&cvt.box, &cvt);

		This->m_FieldList[This->m_Resize.field].width = size;
		WListCore_RecalcSize(pWList);
		Window_Invalidate(This->m_HeaderWnd, &box);
	}
}

/**
 * Handles events for the header's pane window.
 *
 * @param  e          Event.
 */
static EListenerAction WListHeader_EventHandler(void* handle, const Event* e)
{
	FLTreeView* This = handle;

	switch(e->Type)
	{
		case EEvent_WindowOpen:
		{
			// Force position according to the parent window.
			CWindOpen*	po = (CWindOpen*) e->pData;
			CWindOpen	o = Window_GetOwnerInfo(po->w);
			CRect		rect;

			po->cvt.s.cx = o.cvt.s.cx;

			rect.x0 = o.cvt.s.cx;
			rect.y0 = o.cvt.s.cy - Sizes.Text.y1;
			rect.x1 = o.cvt.box.x1 - o.cvt.box.x0 + o.cvt.s.cx;
			rect.y1 = o.cvt.s.cy;

			Window_OpenPane(po, &o, &rect, 0);

			return EListenerAction_ContinueEvent;
		}
		break;
		case EEvent_WindowRedraw:
		{
			Window_Redraw(This->m_HeaderWnd
				, (Window_Drawer) WListHeader_Redraw
				, This);

			return EListenerAction_StopEvent;
		}
		break;
		case EEvent_Mouse:
		{
			const Mouse* m = e->pData;
			int action;

			// Skip if a background Job is active
			// Column resizing is not performed with menu button.
			if (FLTreeView_IsBusy(This)
			||  (m->but == EBut_Menu))
				return EListenerAction_StopEvent;

			action = WListHeader_GetActionFromScreenPt(This, m, 0);

			if ((action >= FLItemAction_ResizeColumn0)
			&&  (action < (FLItemAction_ResizeColumn0 + This->m_FieldCount))
			&&  ((m->but == EBut_DragSelect) || m->but == EBut_DragAdjust))
			{
				// Resize column n
				CWindCvt cvt = Window_GetPosInfo(This->m_HeaderWnd);
				CRect rct;

				This->m_Resize.field = action - FLItemAction_ResizeColumn0;

				rct.x1 = FLHeaderNode_GetFirstColumnPos();
				for (int i = 0; i <= This->m_Resize.field; i++)
				{
					rct.x0 = rct.x1;
					rct.x1 += This->m_FieldList[i].width;
				}
				rct = RectToScreen(&rct, &cvt);
				This->m_Resize.minx = rct.x0 - (rct.x1 - m->pt.x);
				rct.x0 = This->m_Resize.minx + 16;
				rct.x1 = Task_GetModeInfo()->box.x1;
				rct.y0 = rct.y1 = m->pt.y;
				rct.y1 += 4; // Exclusive
				Drag_DragPoint(This, FieldSlider_NullEvent, &rct, &ptr_scrh);
			}
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			switch(((Msg*) e->pData)->hdr.action)
			{
				case EMsg_HelpRequest:
				{
					const Msg_HelpRequest* msg = e->pData;
					int action;

					action = WListHeader_GetActionFromScreenPt(This, &msg->m, 0);

					if ((action >= FLItemAction_ResizeColumn0)
					&&  (action < (FLItemAction_ResizeColumn0 + This->m_FieldCount)))
					{
						Task_HelpReply(msg, Msg_Lookup("DSLiDCH"));

						return EListenerAction_StopEvent;
					}
				}
				break;
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

/**
 * Defines the presence/absence of an columns header that won't scroll with the window.
 * Implemented as a pane window.
 *
 * @param  b          True if header must be present.
 */
static void FLTreeView_SetHeader(FLTreeView* This, bool b)
{
	CSize s;
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
	HWind w = WListCore_GetWindow(pWList);

	if (b == This->m_bHeader)
		return;

	This->m_bHeader = b;
	if (b)
	{
		CTemplate* t = throw_Templates_Blank(0, EWind_Moveable | EWind_Pane, 0, 0);

		t->window->title_fg = 0xff;
		t->window->ex.x0 = 0;
		t->window->ex.x1 = 1<<15;
		t->window->ex.y0 = 0;
		t->window->ex.y1 = Sizes.Text.y1;
		This->m_HeaderWnd = throw_Window_Create(t, NULL);
		throw_Window_RegisterEventHandler(This->m_HeaderWnd, WListHeader_EventHandler, This, false);
		throw_Window_AddPane(w, This->m_HeaderWnd);

		// Move treelist to take account of its presence
		s.cx = 0;
		s.cy = Sizes.Text.y1;
		WListCore_SetOffset(pWList, s);
	}
	else
	{
		Window_Close(This->m_HeaderWnd);
		Window_DeRegisterEventHandler(This->m_HeaderWnd, WListHeader_EventHandler, This);
		Window_Delete(This->m_HeaderWnd);
		This->m_HeaderWnd = HWind_None;

		// Move treelist to take account of its absence
		s.cx = 0;
		s.cy = 0;
		WListCore_SetOffset(pWList, s);
	}
}

/*-------------------------------------------------------------------------*
 *--- Mapping of tracks vs tree nodes -------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Compares two trackmaps for insertion/location in the array of mappings.
 *
 * @param  pa  First trackmap.
 * @param  pb  Second trackmap.
 *
 * @returns < 0 if a < b, 0 if a = b, > 0 if a > b.
 */
static int TrackSet_Compare(const TrackMap* pa, const TrackMap* pb)
{
	int val;

	val = (char*) pa->pTrack - (char*) pb->pTrack;
	if (!val && pb->pNode) val = (char*) pa->pNode - (char*) pb->pNode;

	return val;
}

/*
 * Returns where the trackmap should be located in the list of mappings
 * if it is/should be present, that is in range [0, count].
 *
 * @param  pi      trackmap to locate.
 * @param  bExact  if true return -1 if not present.
 *
 * @returns  Index in Trackmaps array or -1.
 */
static int TrackSet_Find(const FLTreeView* This, const TrackMap* pi, bool bExact)
{
	int i = 0;
	int k = Array_Count(This->m_pTrackSet) - 1;
	int j = (i + k) / 2;

	if (k >= 0)
	{
		int val = TrackSet_Compare(Array_Get(This->m_pTrackSet, j), pi);

		while (i < j)
		{
			if (val >= 0) k = j; // Allow for duplicates
			else i = j;

			j = (i + k) / 2;
			val = TrackSet_Compare(Array_Get(This->m_pTrackSet, j), pi);
		}

		if (bExact)
		{
			if (!val) return j;

			val = TrackSet_Compare(Array_Get(This->m_pTrackSet, k), pi);
			if (!val) return k;

			return -1;
		}
		else
		{
			if (val >= 0) return j;

			val = TrackSet_Compare(Array_Get(This->m_pTrackSet, k), pi);
			if (val >= 0) return k;

			return k + 1;
		}
	}

	return bExact ? -1 : 0;
}

/**
 * Adds a trackmap to the list of mappings.
 *
 * @param  pTrack      Pointer to the track.
 * @param  pNode       Pointer to the node.
 */
static void throw_FLTreeView_AddTrackMap(FLTreeView* This, const FLTrack* pTrack, const OTreeNode* pNode)
{
	TrackMap Info = {pTrack, pNode};
	int pos;

	pos = TrackSet_Find(This, &Info, false);

	Array_Insert(This->m_pTrackSet, pos, &Info);
}

/**
 * Removes a trackmap from the list of mappings.
 *
 * @param  pTrack      Pointer to the track.
 * @param  pNode       Pointer to the node.
 */
static bool FLTreeView_RemoveTrackMap(FLTreeView* This, const FLTrack* pTrack, const OTreeNode* pNode)
{
	TrackMap Info = {pTrack, pNode};
	int pos;

	pos = TrackSet_Find(This, &Info, true);

	if (pos >= 0)
	{
		Array_Remove(This->m_pTrackSet, pos);
		return true;
	}

	return false;
}

/**
 * Finds the node associated to a track.
 *
 * @param  pTrack      Pointer to the track.
 */
static OTreeNode* FLTreeView_FindNode(const FLTreeView* This, const FLTrack* pTrack)
{
	TrackMap Info = {pTrack, NULL};
	int pos;

	pos = TrackSet_Find(This, &Info, true);

	if (pos == -1) return NULL;

	return (OTreeNode*) ((TrackMap*) Array_Get(This->m_pTrackSet, pos))->pNode;
}

/*-------------------------------------------------------------------------*
 *--- Viewer Misc handling ------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Finds the list item associated to a node.
 *
 * @param  pNode       Pointer to the node.
 */
static int FLTreeView_ListIndex(const FLTreeView* This, const OTreeNode* pNode)
{
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);

	// must not show hidden nodes
	if (!OTreeNode_AllowExpand(OTreeNode_GetParent(pNode)))
		pNode = OTreeNode_GetParent(pNode);

	return WListCore_Find(pWList, 0, pNode);
}

static const char* FLTreeView_GetChoicesBaseSection(FileList* pDoc)
{
	switch(FileList_GetType(pDoc))
	{
		case FileList_TypeSearch: return Sec_SearchLists; break;
		case FileList_TypeQueue: return Sec_FileQueue; break;
	}

	return Sec_PlayLists;
}

static const char* FLTreeView_GetChoicesSection(FileList* pDoc, bool bWrite)
{
	switch(FileList_GetType(pDoc))
	{
		case FileList_TypeNormal:
		case FileList_TypeMain:
		{
			if (bWrite 	&& !FileList_HasValidPath(pDoc))
				return NULL;
		}
		break;
		case FileList_TypeSearch:
		{
			if (bWrite)
				return NULL;

			return Sec_SearchLists;
		}
		break;
		case FileList_TypeQueue:
			return Sec_FileQueue;
		break;
	}

	return FileList_GetPathName(pDoc);
}

/**
 * Shows the tree view if parental lock allows it.
 *
 * @param  b          True if if nust be shown in last saved position.
 */
static void FLTreeView_Show(FLTreeView* This, bool bFromChoices)
{
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	HWind w = OTreeList_GetWindow(This->m_pTreeList);

	if (This->m_bReadChoices)
	{
		CWindOpen info;
		const char* section = FLTreeView_GetChoicesSection(pDoc, false);

		// set size & position
		info.w = w;
		info.cvt.box.x0 = Choices_ReadInt(section, Var_PosX, 500);
		info.cvt.box.y0 = Choices_ReadInt(section, Var_PosY, 500);
		info.cvt.box.x1 = info.cvt.box.x0 + Choices_ReadInt(section, Var_Width, 300);
		info.cvt.box.y1 = info.cvt.box.y0 + Choices_ReadInt(section, Var_Height, 400);
		info.cvt.s.cx = Choices_ReadInt(section, Var_ScrollX, 0);
		info.cvt.s.cy = Choices_ReadInt(section, Var_ScrollY, 0);
		info.behind = -3;
		throw_OTreeList_SetPane(This->m_pTreeList, HWind_None, &info.cvt.box);
		OTreeList_SetExtent(This->m_pTreeList);
		Window_FitInExtent(w, &info);
		Window_WimpOpen(&info);
		Window_WimpClose(w);

		if (!(Options()->Player.bParentalLock)
		||  (FileList_GetType(pDoc) == FileList_TypeQueue))
		{
			// open for real ?
			if (Choices_ReadBool(section, Var_Open, false) || !bFromChoices)
			{
				info.behind = -1;
				Window_OpenAt(w, &info);
			}

			if (!bFromChoices)
				Caret_SetInvisible(w);
		}
	}
	else
	{
		if (!(Options()->Player.bParentalLock)
		||  (FileList_GetType(pDoc) == FileList_TypeQueue))
		{
			Window_Open(w);

			if (!bFromChoices)
		 		Caret_SetInvisible(w);
		}
	}

	This->m_bReadChoices = false;
}

/*-------------------------------------------------------------------------*
 *--- Viewer File/Selection Drag and Drop handling ------------------------*
 *-------------------------------------------------------------------------*/

// Procedure for removing the current selection

static void FLTreeView_Selection_Remove(FLTreeView* This)
{
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);

	FileList_Remove(pDoc, This->m_pSelection, false);
}

static void FLTreeView_Selection_RemoveAll(FLTreeView* This)
{
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);

	FileList_Remove(pDoc, This->m_pSelection, true);
}

// Callback for transfer of current selection

static bool FLTreeView_Selection_SendProc(void* handle, file_type type, sXFer_Chunk* pchunk)
{
	FLTreeView*   This = handle;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	uint32_t count = List_Count(This->m_pSelection);

	if (!pchunk->ref)
	{
		// Very first call, send header, set next ref to -1
		FileList_SendHeader(pDoc, type, pchunk, count);
		pchunk->ref = -1;
		return true;
	}
	else if (pchunk->ref == -1)
	{
		// first track to save, reset ref to 0, will be incremented in turn by the code below
		pchunk->ref = 0;
	}
	else if (pchunk->ref == -2)
	{
		// Very last call, trailer was saved, return false
		return false;
	}

	if (pchunk->ref >= count)
	{
		// all tracks send, send trailer, set next ref to -2
		FileList_SendTrailer(pDoc, type, pchunk, count);
		pchunk->ref = -2;
		return true;
	}

	// find next selected track
	FileList_SendTrack(pDoc, type, pchunk, List_Get(This->m_pSelection, pchunk->ref), true);
	pchunk->ref++;

	return true;
}

static void FLTreeView_DragSelection_Remove(FLTreeView* This)
{
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);

	FileList_Remove(pDoc, This->m_pDragSelection, false);
}

static void FLTreeView_DragSelection_RemoveAll(FLTreeView* This)
{
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);

	FileList_Remove(pDoc, This->m_pDragSelection, true);
}

// Callback for transfer of current selection

static bool FLTreeView_DragSelection_SendProc(void* handle, file_type type, sXFer_Chunk* pchunk)
{
	FLTreeView*   This = handle;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	uint32_t count = List_Count(This->m_pDragSelection);

	if (!pchunk->ref)
	{
		// Very first call, send header, set next ref to -1
		FileList_SendHeader(pDoc, type, pchunk, count);
		pchunk->ref = -1;
		return true;
	}
	else if (pchunk->ref == -1)
	{
		// first track to save, reset ref to 0, will be incremented in turn by the code below
		pchunk->ref = 0;
	}
	else if (pchunk->ref == -2)
	{
		// Very last call, trailer was saved, return false
		return false;
	}

	if (pchunk->ref >= count)
	{
		// all tracks send, send trailer, set next ref to -2
		FileList_SendTrailer(pDoc, type, pchunk, count);
		pchunk->ref = -2;
		return true;
	}

	// find next selected track
	FileList_SendTrack(pDoc, type, pchunk, List_Get(This->m_pDragSelection, pchunk->ref), true);
	pchunk->ref++;

	return true;
}

// Check that view allows reorganizing tracks
static bool FLTreeView_AllowsReorg(const FLTreeView* This)
{
	if (!(This->m_treemode & FLTreeMode_Flat)
	||  (This->m_treemode & FLTreeMode_Mask_GroupBy)
	||  ((This->m_treemode & FLTreeMode_Mask_OrderBy) != FLTreeMode_OrderBy_Raw))
		return false;

	return true;
}

// Callback for transfer of current selection

static bool FLTreeView_TransferData(void* handle, file_type type, const void* data, const ScreenPos* pInfo)
{
	FLTreeView* This = handle;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	const List* pList = data;
	ListNode* pNode = NULL;
	int pos = FileList_TrackCount(pDoc);
	bool bMod = false;

	IGNORE(type);

	if (FLTreeView_AllowsReorg(This)
	&&  pInfo
	&&  (pInfo->w == OTreeList_GetWindow(This->m_pTreeList)))
	{
		WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);

		if (pInfo->i < 0)
			pos = WListCore_ItemFromScreenPt(pWList, &pInfo->pt, EWListPtrMode_Insert);
		else
			pos = pInfo->i;

		if ((pos < 0) || (pos >= FileList_TrackCount(pDoc)))
			pos = FileList_TrackCount(pDoc);
	}

	FileList_StartUpdate(pDoc);

	try
	{
		switch(FileList_GetType(pDoc))
		{
			case FileList_TypeNormal:
			case FileList_TypeExtern:
			case FileList_TypeMain:
			{
			  	// Insert copy of authorised types
				while ((pNode = List_GetSuccessor(pList, pNode)) != NULL)
				{
	  				const FLTrack* pTrack = List_GetNodeData(pList, pNode);

					if (FileList_AllowTrackType(pDoc, FLTrack_GetFlags(pTrack)))
					{
						pTrack = throw_New_FLTrack_Copy(pDoc, pTrack);
						throw_FileList_InsertTrackAt(pDoc, pos++, pTrack);
						bMod = true;
					}
				}
			}
			break;
			case FileList_TypeQueue:
			{
			  	// Insert copy of link to authorised types
				while ((pNode = List_GetSuccessor(pList, pNode)) != NULL)
				{
	  				const FLTrack* pTrack = List_GetNodeData(pList, pNode);

					if (FileList_AllowTrackType(pDoc, FLTrack_GetFlags(pTrack)))
					{
						if ((FLTrack_ResolveLink(pTrack) == pTrack)
						&&  (pDoc == FLTrack_GetOwner(pTrack)))
							pTrack = throw_New_FLTrack_Copy(pDoc, pTrack);
						else
							pTrack = throw_New_FLTrack_Link(pDoc, pTrack);
						throw_FileList_InsertTrackAt(pDoc, pos++, pTrack);
						bMod = true;
					}
				}
			}
			break;
			default:
				// Don't allow insertion of tracks
				;
		}
	}
	catch
	{
	  	App_ReportException();

		return false;
	}
	catch_end

	FileList_EndUpdate(pDoc, false, bMod);

	return true;
}

static bool FLTreeView_TransferMoveData(void* handle, file_type type, const void* data, const ScreenPos* pInfo)
{
	FLTreeView* This = handle;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	const List* pList = data;
	ListNode* pNode = NULL;
	int pos = FileList_TrackCount(pDoc);
	bool bMod = false;

	IGNORE(type);

	if (FileList_GetType(pDoc) != FileList_TypeMain)
		return FLTreeView_TransferData(handle, type, data, pInfo);

	if (FLTreeView_AllowsReorg(This)
	&&  pInfo
	&&  (pInfo->w == OTreeList_GetWindow(This->m_pTreeList)))
	{
		WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);

		if (pInfo->i < 0)
			pos = WListCore_ItemFromScreenPt(pWList, &pInfo->pt, EWListPtrMode_Insert);
		else
			pos = pInfo->i;

		if ((pos < 0) || (pos >= FileList_TrackCount(pDoc)))
			pos = FileList_TrackCount(pDoc);
	}

	FileList_StartUpdate(pDoc);

	try
	{
		// Insert copy any type of track
		while ((pNode = List_GetSuccessor(pList, pNode)) != NULL)
		{
			FLTrack* pOldTrack = List_GetNodeData(pList, pNode);

			if (FileList_AllowTrackType(pDoc, FLTrack_GetFlags(pOldTrack)))
			{
				FLTrack* pNewTrack;

				pNewTrack = throw_New_FLTrack_Copy(pDoc, pOldTrack);
				throw_FileList_InsertTrackAt(pDoc, pos++, pNewTrack);
				FLTrack_AttachSubList(pNewTrack, FLTrack_GetSubList(pOldTrack));
				FLTrack_AttachSubList(pOldTrack, NULL);
				bMod = true;
			}
		}
	}
	catch
	{
	  	App_ReportException();

		return false;
	}
	catch_end

	FileList_EndUpdate(pDoc, false, bMod);

	return true;
}

// Callback for checking if the current drag is accepted

static bool FLTreeView_OnDragging(void* handle, const Msg_Dragging* rcv)
{
	FLTreeView* This = handle;
	OTreeList*  pList = This->m_pTreeList;
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	int      flags;
	bool     bMove;

	// If we are not within work area of our own window
	// we must hide the ghost caret we could be showing
	// and return that the message is not for us.

	if ((rcv->pos.w != OTreeList_GetWindow(pList) && (rcv->pos.w != This->m_HeaderWnd))
	||  (rcv->pos.i != HIcon_None)
	||  (rcv->flags & DragFlags_DoNotClaim)
	||  (DragDrop_GetFileType(rcv->types, FileList_Load_FileTypes) == -1))
	{
		WListCore_HideGhostCaret(pWList);
		return false;
	}

	if (rcv->pos.w != This->m_HeaderWnd)
	{
		DragDrop_FTransfer proc = FLTreeView_TransferData;

		// Move within own window EOR shift is pressed
		if (DragDrop_GetUserHandle() == This)
		{
			if (FLTreeView_AllowsReorg(This))
				bMove = !Keyboard_PollShift();
			else
				bMove = false;

			// Special case for internal move (cf. sublists)
			if (bMove) proc = FLTreeView_TransferMoveData;
		}
		else bMove = Keyboard_PollShift();

		switch(FileList_GetType(pDoc))
		{
			case FileList_TypeNormal:
			case FileList_TypeExtern:
			case FileList_TypeMain:
			{
				if (bMove)
					Desktop_SetPointer(&ptr_move);
				else
					Desktop_SetPointer(&ptr_copy);
			}
			break;
			case FileList_TypeQueue:
			{
				if (DragDrop_GetUserHandle() != This)
				{
					Desktop_SetPointer(&ptr_link);
					bMove = false;
				}
				else
				{
					Desktop_SetPointer(&ptr_move);
					bMove = true;
				}
			}
			break;
			default:
				// Don't accept insertion
				return false;
		}

		if (FLTreeView_AllowsReorg(This))
		{
			// Show ghost caret
			WListCore_ShowGhostCaret(pWList, &rcv->pos.pt);

			Window_ScrollIfMouseOnBorder(OTreeList_GetWindow(pList), 0, 40);
		}

		// Reply to message
		flags = ClaimFlags_PointerChange | ClaimFlags_RemoveDragBox;
		if (bMove) flags |= ClaimFlags_DeleteSource;

		DragDrop_ReplyToDragging(rcv, flags, FileList_Load_FileTypes, proc, This);

		return true;
	}
	else
	{
		Desktop_SetPointer(&ptr_nodrop);

		WListCore_HideGhostCaret(pWList);

		if (FLTreeView_AllowsReorg(This))
			Window_ScrollIfMouseOnBorder(OTreeList_GetWindow(pList), 0, 40);

		// Reply to message
		flags = ClaimFlags_PointerChange | ClaimFlags_RemoveDragBox;

		DragDrop_ReplyToDragging(rcv, flags, NULL, NULL, This);

		return true;
	}
}

/*-------------------------------------------------------------------------*
 *--- FLTreeView CView callbacks ------------------------------------------*
 *-------------------------------------------------------------------------*/

static EListenerAction FLTreeView_OnDocEvent(void* pListener, const Event* pEvent)
{
	FLTreeView* This = pListener;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	const DocEvent* pe = pEvent->pData;

	switch(pe->event)
	{
		case EDocEvent_ShowView:
		{
			if (pe->pContainer == pDoc)
			{
				if ((Options()->Player.bParentalLock)
				&&  (FileList_GetType(pDoc) != FileList_TypeQueue))
					return EListenerAction_ContinueEvent;

				FLTreeView_Show(This, true);
				This->m_bUpdateAtNull = true;

				// Stop broadcasting of the update to the other views
				return EListenerAction_StopEvent;
			}
		}
		break;
		case EDocEvent_ShowElement:
		{
			if (pe->pContainer == pDoc)
			{
				if ((Options()->Player.bParentalLock)
				&&  (FileList_GetType(pDoc) != FileList_TypeQueue))
					return EListenerAction_ContinueEvent;

				if (pe->pElement)
				{
					OTreeNode* pRefNode = FLTreeView_FindNode(This, pe->pElement);

					if (pRefNode)
					{
						This->m_bUpdateAtNull = true;
						FLTreeView_Show(This, false);
						FLTreeNodes_ShowNode(pRefNode, true);
					}
				}
				else
				{
					This->m_bUpdateAtNull = true;
					FLTreeView_Show(This, false);
				}
			}
		}
		break;
		case EDocEvent_StartUpdate:
		{
			if ((pe->pContainer == pDoc)
			||  (FileList_GetType(pDoc) > FileList_TypeMain))
				OTreeList_AllowRefresh(This->m_pTreeList, false);
		}
		break;
		case EDocEvent_EndUpdate:
		{
			// Ensure at least first level is visible
			OTreeList_ExpandParents(This->m_pTreeList, NULL);

			if ((pe->pContainer == pDoc)
			||  (FileList_GetType(pDoc) > FileList_TypeMain))
				OTreeList_AllowRefresh(This->m_pTreeList, true);

			if ((FileList_GetType(pDoc) == FileList_TypeSearch)
			&& This->m_bExplodeAtNull)
			{
				This->m_bExplodeAtNull = false;
				OTreeList_Expand(This->m_pTreeList, -1, 2, true);
			}
		}
		break;
		case EDocEvent_InsertElement:
		{
			if (pe->pContainer == pDoc)
			{
				OTreeNode* volatile pNode = NULL;

				This->m_bUpdateAtNull = true;
				This->m_bExplodeAtNull = true;

				try
				{
					pNode = throw_FLTreeNodes_Insert(This->m_pTreeList, pDoc, pe->pElement);
					throw_FLTreeView_AddTrackMap(This, pe->pElement, pNode);

					if (pEvent->pSender == This)
						FLTreeNodes_ShowNode(pNode, true);
				}
				catch
				{
					if (pNode) FLTreeNodes_Remove(This->m_pTreeList, pNode);
					throw_current();
				}
				catch_end
			}
		}
		break;
		case EDocEvent_RemoveElement:
		{
			if (pe->pContainer == pDoc)
			{
				OTreeNode* pRefNode;

				// multiple copies?
				while ((pRefNode = FLTreeView_FindNode(This, pe->pElement)) != NULL)
				{
					FLTreeView_RemoveTrackMap(This, pe->pElement, pRefNode);
					FLTreeNodes_Remove(This->m_pTreeList, pRefNode);

					This->m_bUpdateAtNull = true;
				}
			}
		}
		break;
		case EDocEvent_UpdateElement:
		{
			OTreeNode* pRefNode = FLTreeView_FindNode(This, pe->pElement);

			if (pRefNode)
			{
				This->m_bUpdateAtNull = true;

				WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
				int pos = WListCore_Find(pWList, 0, pRefNode);
				unsigned int state = 0;

				if (pos != -1)
					state = WListCore_GetItemState(pWList, pos);

				// Reorder node within parent
				OTreeList_Update(This->m_pTreeList, pRefNode);

				// Try to insert the track again, if it still follows the same hierarchy
				// it will return the same node
				OTreeNode* volatile pNode = NULL;

				try
				{
					pNode = throw_FLTreeNodes_Insert(This->m_pTreeList, pDoc, pe->pElement);
					if (pNode != pRefNode)
						throw_FLTreeView_AddTrackMap(This, pe->pElement, pNode);
				}
				catch
				{
					if (pNode && (pNode != pRefNode)) FLTreeNodes_Remove(This->m_pTreeList, pNode);
					throw_current();
				}
				catch_end

				// Differing nodes, remove the old one
				if (pNode != pRefNode)
				{
					FLTreeView_RemoveTrackMap(This, pe->pElement, pRefNode);
					FLTreeNodes_Remove(This->m_pTreeList, pRefNode);
				}

				// If item was visible, show it again
				if (pos != -1)
				{
					pos = OTreeList_ExpandParents(This->m_pTreeList, pNode);
					WListCore_SetItemState(pWList, pos, state, EWItem_Selected | EWItem_Marked);
					WListCore_Refresh(pWList, pos, pos);
				}
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

/**
 *  Note: Called from construction when pDoc not attached to view yet.
 */
static OTreeNode* throw_FLTreeView_NewRootNode(FLTreeView* This, FileList* pDoc)
{
	OTreeNode* pNode;

	switch(FileList_GetType(pDoc))
	{
		case FileList_TypeQueue:
		{
			pNode = throw_FLTreeNodes_NewQueueNode(This->m_treemode);
		}
		break;
		case FileList_TypeSearch:
		{
			pNode = throw_FLTreeNodes_NewSearchNode(This->m_treemode);
		}
		break;
		default:
		{
			pNode = throw_FLTreeNodes_NewFileListNode(This->m_treemode);
		}
	}

	return pNode;
}

/**
 * Initialises the tree view with the content of the document.
 */
static void FLTreeView_OnInitialUpdate(View* pView)
{
	FLTreeView* This = (FLTreeView*) pView;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	OTreeNode* volatile pNode = throw_FLTreeView_NewRootNode(This, pDoc);

	OTreeList_SetRootNode(This->m_pTreeList, pNode);

	FLTreeView_SetHeader(This, (This->m_treemode & FLTreeMode_Flat) != 0);

	Array_Clear(This->m_pTrackSet);

	try
	{
		for (int i = 0; i < FileList_TrackCount(pDoc); i++)
		{
			FLTrack* pTrack = FileList_GetTrack(pDoc, i);

			pNode = throw_FLTreeNodes_Insert(This->m_pTreeList, pDoc, pTrack);
			throw_FLTreeView_AddTrackMap(This, pTrack, pNode);
		}
	}
	catch
	{
		if (pNode) FLTreeNodes_Remove(This->m_pTreeList, pNode);
		App_ReportException();
	}
	catch_end

	OTreeList_ExpandParents(This->m_pTreeList, NULL);
}

/**
 *  Sets the window's title.
 *
 * @param  iViewNumber View number.
 */
static void FLTreeView_SetViewNumber(View* pView, int iViewNumber)
{
	FLTreeView* This = (FLTreeView*) pView;
	FileList* pDoc = (FileList*) View_GetDocument(pView);
	FLTrack* pTrack = FileList_GetOwner(pDoc);
	HWind w = OTreeList_GetWindow(This->m_pTreeList);

	View_SetViewNumber(pView, iViewNumber);

	if (FileList_GetType(pDoc) == FileList_TypeMain)
		Window_SetTitle(w, "%s", Msg_Lookup("FLMain"));
	else
		Window_SetTitle(w, "%s", View_GetTitle(pView));

	if (pTrack
	&&  !FileList_IsModified(pDoc))
	{
		if (FLTrack_GetObjectType(pTrack) == FLTrack_Type_FileList)
			FLTrack_SetFilename(pTrack, FileList_GetPathName(pDoc), true);
		FLTrack_RefreshViews(pTrack, This, false, true);
	}
}

/**
 *  Checks if the tree view presents selected items.
 */
static bool FLTreeView_HasSelection(const View* pView)
{
	FLTreeView*   This = (FLTreeView*) pView;

	return (List_Count(This->m_pSelection) > 0);
}

static void FLTreeView_NotFLTreeView(View* pView)
{
	FLTreeView*   This = (FLTreeView*) pView;
	FileList* pDoc = (FileList*) View_GetDocument(pView);
	CWindState Info = Window_GetState(OTreeList_GetWindow(This->m_pTreeList));
	const char* section = FLTreeView_GetChoicesSection(pDoc, true);

	FLSearch_Close(This);

	if (section)
	{
        Choices_Write(section, Var_Open, "%d", (Info.flags & EWind_IsOpen) ? 1 : 0);
        Choices_Write(section, Var_PosX, "%d", Info.o.cvt.box.x0);
        Choices_Write(section, Var_PosY, "%d", Info.o.cvt.box.y0);
        Choices_Write(section, Var_Width, "%d", Info.o.cvt.box.x1 - Info.o.cvt.box.x0);
		Choices_Write(section, Var_Height, "%d", Info.o.cvt.box.y1 - Info.o.cvt.box.y0);
        Choices_Write(section, Var_ScrollX, "%d", Info.o.cvt.s.cx);
		Choices_Write(section, Var_ScrollY, "%d", Info.o.cvt.s.cy);
		Choices_Write(section, Var_TreeMode, "0x%x", This->m_treemode);

		for (int i = 0; i < EFLField_FieldCount; i++)
		{
			Choices_Write(section, SPrintf(Var_FieldWidth, This->m_FieldList[i].id), "%d", This->m_FieldList[i].width);
		}
	}

	FLTreeView_SetHeader(This, false);

	Delete_Array(This->m_pTrackSet);
	This->m_pTrackSet = NULL;
	Delete_OTreeList(This->m_pTreeList);
	This->m_pTreeList = NULL;

	DocEvents_RemoveListener(NULL, This, FLTreeView_OnDocEvent);
	Task_RemoveListener(EEvent_Null, FLTreeView_Listener, This);
	Task_RemoveListener(EEvent_OptionShowFileAsTitle, FLTreeView_Listener, This);
	Task_RemoveListener(EEvent_OptionParentalLock, FLTreeView_Listener, This);
	Delete_Toolbar(This->m_pToolbar);
	This->m_pToolbar = NULL;

	Delete_List(This->m_pSelection);
	This->m_pSelection = NULL;
	Delete_List(This->m_pDragSelection);
	This->m_pDragSelection = NULL;

	if (This->m_bShowsHourglass)
	{
		WLib_Hourglass_Off();
		This->m_bShowsHourglass = false;
	}

	View_NotView(pView);
}

static ViewVPtr FLTreeViewVPtr =
{
	  NULL
	, FLTreeView_SetViewNumber
	, FLTreeView_OnInitialUpdate
	, FLTreeView_HasSelection
	, (View_FSendSelection) FLTreeView_Selection_SendProc
	, FLTreeView_NotFLTreeView
};

/*-------------------------------------------------------------------------*
 *--- Viewer Search callbacks ---------------------------------------------*
 *-------------------------------------------------------------------------*/

static unsigned int FLTreeView_GetTrackCount(const void* handle)
{
	const FLTreeView* This = handle;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);

	return FileList_TrackCount(pDoc);
}

static FLTrack* FLTreeView_GetTrack(const void* handle, unsigned int index)
{
	const FLTreeView* This = handle;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);

	return FileList_GetTrack(pDoc, index);
}

static unsigned int FLTreeView_GetState(const void* handle, const FLTrack* pTrack)
{
	const FLTreeView* This = handle;
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
	const OTreeNode* pNode = FLTreeView_FindNode(This, pTrack);
	int lindex = pNode ? FLTreeView_ListIndex(This, pNode) : -1;

	return (lindex != -1) ? WListCore_GetItemState(pWList, lindex) : EWItem_Deleted;
}

/**
 * Updates the state of the list item associated to a track.
 * If the track is hidden and state is non-0, the track is shown.
 *
 * @param  pTrack      Pointer to the track.
 * @param  state       New state flags.
 * @param  mask        Flags to update.
 */
static void FLTreeView_SetState(void* handle, const FLTrack* pTrack, unsigned int state, unsigned int mask)
{
	FLTreeView* This = handle;
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
	const OTreeNode* pRefNode = FLTreeView_FindNode(This, pTrack);

	if (pRefNode)
	{
		int lindex = FLTreeView_ListIndex(This, pRefNode);

		if (lindex == -1)
		{
			if (!state) return;
			lindex = FLTreeNodes_ShowNode(pRefNode, false);
		}

		if (lindex != -1)
		{
			if (WListCore_SetItemState(pWList, lindex, state, mask))
			{
				if (mask & EWItem_Selected)
					This->m_bUpdateAtNull = true;
			}
		}
	}
}

/**
 * Ensures that a track is made visible.
 *
 * @param  pTrack      Pointer to the track.
 */
static void FLTreeView_ShowTrack(void* handle, const FLTrack* pTrack)
{
	FLTreeView* This = handle;
	const OTreeNode* pRefNode = FLTreeView_FindNode(This, pTrack);

	if (pRefNode) FLTreeNodes_ShowNode(pRefNode, true);
}

/*-------------------------------------------------------------------------*
 *--- CDTreeView Field edition --------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Edit text from a field if we Alt-clicked on such a field or aborts an existing edition.
 *
 * @param  item        List item ckicked on.
 * @param  pArea       Descriptor of the area clicked on.
 *
 * @returns True if new edition is started.
 */
static bool FLTreeView_EditMetadata(FLTreeView* This, int item, WItemArea* pArea)
{
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
	HWind      w = WListCore_GetWindow(pWList);

	if (This->m_Edit.Icon != HIcon_None)
	{
		// Remove any pending entry box
		CCaret caret;

		if ((This->m_Edit.Item == item) && (This->m_Edit.AreaId == pArea->id)) return true;

		Caret_Get(&caret);
		if ((caret.pos.w == w)
		&&  (caret.pos.i == This->m_Edit.Icon))
			Caret_SetInvisible(w);

		if (This->m_Edit.pWList != NULL)
		{
			Delete_WStringList(This->m_Edit.pWList);
			This->m_Edit.pWList = NULL;
		}
		if (This->m_Edit.pOldValue != NULL)
		{
			mem_free(This->m_Edit.pOldValue);
			This->m_Edit.pOldValue = NULL;
		}
		Icon_ForceRedraw(w, This->m_Edit.Icon);
		mem_free(Icon_GetData(w, This->m_Edit.Icon));
		Icon_Delete(w, This->m_Edit.Icon);
		This->m_Edit.Icon = HIcon_None;
	}

	if (item >= 0)
	{
		// Create entry box at the bottom of the list item
		CIcon   icon;
		OTreeNode* pINode = OTreeList_GetWListItem(This->m_pTreeList, item);

		icon.data.it.buffer = NULL;

		// Select metadate to edit
		This->m_Edit.MetaId = EMetaId_None;
		switch(pArea->id)
		{
			case FLItemAction_EditCollective:
			{
				This->m_Edit.MetaId = EMetaId_StreamCollective;
				This->m_Edit.pStrCol = DigitalCD.pCollectivesCol;
			}
			break;
			case FLItemAction_EditArtist:
			{
				This->m_Edit.MetaId = EMetaId_StreamArtist;
				This->m_Edit.pStrCol = DigitalCD.pArtistsCol;
			}
			break;
			case FLItemAction_EditBox:
			{
				This->m_Edit.MetaId = EMetaId_StreamBox;
				This->m_Edit.pStrCol = DigitalCD.pBoxesCol;
			}
			break;
			case FLItemAction_EditAlbum:
			{
				This->m_Edit.MetaId = EMetaId_StreamAlbum;
				This->m_Edit.pStrCol = DigitalCD.pAlbumsCol;
			}
			break;
			case FLItemAction_EditTitle:
			{
				This->m_Edit.MetaId = EMetaId_StreamTitle;
				This->m_Edit.pStrCol = NULL;
			}
			break;
			case FLItemAction_EditMimeType:
			{
				This->m_Edit.MetaId = EMetaId_StreamMimeType;
				This->m_Edit.pStrCol = DigitalCD.pMimeTypesCol;
			}
			break;
			case FLItemAction_EditBroadcaster:
			{
				This->m_Edit.MetaId = EMetaId_StreamBroadcaster;
				This->m_Edit.pStrCol = DigitalCD.pBroadcastersCol;
			}
			break;
			case FLItemAction_EditStation:
			{
				This->m_Edit.MetaId = EMetaId_StreamStation;
				This->m_Edit.pStrCol = NULL;
			}
			break;
		}

		WListCore_SetFocus(pWList, item, false);
		if (This->m_Edit.MetaId != EMetaId_None)
		{
			try
			{
				icon.box = pArea->rect;
				icon.flags = 0x0700f135;
				icon.data.it.buffer = throw_mem_alloc(256);
				snprintf( icon.data.it.buffer, 255, "%s"
						, FLTreeNodes_GetField(pINode, This->m_Edit.MetaId)
						);
				icon.data.it.validation = "Pptr_write";
				icon.data.it.buf_size = 256;
				This->m_Edit.Icon = throw_Icon_Create(w, &icon);
				if (This->m_Edit.pStrCol != NULL)
			 		This->m_Edit.pWList = throw_New_WStringList(This->m_Edit.pStrCol, w, This->m_Edit.Icon);
				Icon_ForceRedraw(w, This->m_Edit.Icon);
				Icon_SetFocus(w, This->m_Edit.Icon, -1);
				DragBox(NULL);

				This->m_Edit.Item = item;
				This->m_Edit.AreaId = pArea->id;
				This->m_Edit.pOldValue = mem_allocstring(icon.data.it.buffer);

				CWindState state = Window_GetState(w);
				if (Window_ScrollToRect(&state.o, &icon.box))
					Window_OpenAt(w, &state.o);
			}
			catch
			{
				Icon_Delete(WListCore_GetWindow(pWList), This->m_Edit.Icon);
				This->m_Edit.Icon = HIcon_None;
				mem_free(icon.data.it.buffer);
				mem_free(This->m_Edit.pOldValue);
				This->m_Edit.pOldValue = NULL;
			}
			catch_end
		}
		else This->m_Edit.Icon = HIcon_None;
	}
	else This->m_Edit.Icon = HIcon_None;

	return (This->m_Edit.Icon != HIcon_None);
}

static bool FLTreeView_StoreNodeMetadata(FLTreeView* This, OTreeNode* pNode, const char* text)
{
	bool bMod = false;
	if (FLTreeNodes_IsTrack(pNode))
	{
		FLTrack* pTrack = throw_FLTreeNodes_GetTrack(pNode);

		bMod = FLTrack_SetMetaText(pTrack, This->m_Edit.MetaId, EMetaOrigin_User, text);
		FLTrack_RefreshViews(pTrack, This, false, bMod);
	}
	else
	{
		// non terminal node
		pNode = OTreeNode_GetFirstChild(pNode);

		// Attention we modify the tree while looping
		while (pNode != NULL)
		{
			OTreeNode* pNext = OTreeNode_GetSuccessor(pNode);
			bMod |= FLTreeView_StoreNodeMetadata(This, pNode, text);
			pNode = pNext;
		}
	}

	return bMod;
}

/**
 * Stores the edited field
 */
static void FLTreeView_StoreMetadata(FLTreeView* This)
{
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
	HWind      w = WListCore_GetWindow(pWList);
	OTreeNode* pNode = OTreeList_GetWListItem(This->m_pTreeList, This->m_Edit.Item);
	FLTrack* pTrack = NULL;
	WItemArea area;
	int item = -1;

	if (pNode)
	{
		if (FLTreeNodes_IsTrack(pNode))
			pTrack = throw_FLTreeNodes_GetTrack(pNode);

		if (strcmp(nvl(This->m_Edit.pOldValue), Icon_GetData(w, This->m_Edit.Icon)))
		{
			// Recursively store edited field in node and childs
			FileList_StartUpdate(pDoc);
			bool bMod = FLTreeView_StoreNodeMetadata(This, pNode, Icon_GetData(w, This->m_Edit.Icon));
			FileList_EndUpdate(pDoc, false, bMod);
		}

		// Try to move to next field or next track
		// Beware that the list may have been reordered
		if (pTrack != NULL)
		{
			pNode = FLTreeView_FindNode(This, pTrack);
			item = WListCore_Find(pWList, 0, pNode);

			if (This->m_treemode & FLTreeMode_Flat)
			{
				int i;
				// Find next field
				for (i = 0; i < This->m_FieldCount; i++)
				{
					if (FLTreeView_GetFieldAction(pTrack, This->m_FieldList[i].id) == This->m_Edit.AreaId)
						break;
				}

				if (i < (This->m_FieldCount - 1))
					area.id = FLTreeView_GetFieldAction(pTrack, This->m_FieldList[i+1].id);
				else
					item = -1;
			}
			else
			{
				item = -1;
			}
		}
	}

	if (item != -1)
	{
		WListCore_ItemArea(pWList, item, &area);
	}

	FLTreeView_EditMetadata(This, item, &area);
}

/*-------------------------------------------------------------------------*
 *--- Viewer Event Handler ------------------------------------------------*
 *-------------------------------------------------------------------------*/

static void FLTreeView_Rebuild(FLTreeView* This, unsigned int mode)
{
	This->m_treemode = mode;
	FLTreeView_OnInitialUpdate(&This->m_View);
	This->m_bUpdateAtNull = true;
}

static void FLTreeView_GetSelectedTracks(FLTreeView* This)
{
	if (This->m_bUpdateAtNull == true)
	{
		WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
		int index = WListCore_FindItemState(pWList, 0, EWItem_Selected, EWItem_Selected);
		bool bMix = false;

		List_Clear(This->m_pSelection);

		while (index != -1)
		{
			OTreeNode* pINode = OTreeList_GetWListItem(This->m_pTreeList, index);

			if (FLTreeNodes_IsTrack(pINode))
			{
				// If a parent node is selected, track is already in list
				FLTrack* pTrack = throw_FLTreeNodes_GetTrack(pINode);

				if (!bMix || (List_Find(This->m_pSelection, 0, pTrack) == -1))
					List_InsertBefore(This->m_pSelection, NULL, pTrack);
			}
			else
			{
				OTreeNode* pNode = pINode;

				// If a parent node is selected, track is already in list
				while ((pNode = OTreeNode_GetParent(pNode)) != NULL)
				{
					int pos = WListCore_Find(pWList, 0, pNode);
					if ((pos != -1)
					&&  (WListCore_GetItemState(pWList, pos) & EWItem_Selected))
						break;
				}

				if (!pNode) FLTreeNodes_GetChildTracks(pINode, This->m_pSelection);
				bMix = true;
			}

			index = WListCore_FindItemState(pWList, index + 1, EWItem_Selected, EWItem_Selected);
		}

//		if (bMix) List_Clear(This->m_pSelection);

		This->m_bUpdateAtNull = false;
	}
}

static int FLTreeView_CheckCommand(void* handle, uint32_t id)
{
	FLTreeView* This = handle;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	int state = 0;

	// Disable any command if a background Job is acive
	if (FLTreeView_IsBusy(This))
		return state;

	FLTreeView_GetSelectedTracks(This);

	switch(id)
	{
		case Cmd_PList_EditSelectAll:
		{
			if ((This->m_treemode & FLTreeMode_Flat)
			&&  (FileList_TrackCount(pDoc) != 0))
				state |= ECmdState_Allow;
		}
		break;
		case Cmd_PList_GroupBy_MimeType:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & FLTreeMode_GroupBy_MimeType)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_GroupBy_Broadcaster:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & FLTreeMode_GroupBy_Broadcaster)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_GroupBy_Rate:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & FLTreeMode_GroupBy_Rate)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_GroupBy_ObjectType:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & FLTreeMode_GroupBy_ObjectType)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_GroupBy_Collective:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & FLTreeMode_GroupBy_Collective)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_GroupBy_Artist:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & FLTreeMode_GroupBy_Artist)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_GroupBy_Box:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & FLTreeMode_GroupBy_Box)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_GroupBy_Album:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & FLTreeMode_GroupBy_Album)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_GroupBy_Compil:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & FLTreeMode_GroupBy_Compilation)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_GroupBy_CompilByArtist:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & FLTreeMode_GroupBy_CompByArtist)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_SortBy_Number:
		{
			state |= ECmdState_Allow;
            if ((This->m_treemode & FLTreeMode_Mask_OrderBy) == FLTreeMode_OrderBy_TrackNumber)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_SortBy_Name:
		{
			state |= ECmdState_Allow;
            if ((This->m_treemode & FLTreeMode_Mask_OrderBy) == FLTreeMode_OrderBy_Alphabetic)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_SortBy_None:
		{
			state |= ECmdState_Allow;
            if ((This->m_treemode & FLTreeMode_Mask_OrderBy) == FLTreeMode_OrderBy_Raw)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_SortAlbum_Name:
		{
			state |= ECmdState_Allow;
            if ((This->m_treemode & FLTreeMode_SortAlbum_Date) == 0)
            	state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_SortAlbum_Date:
		{
			state |= ECmdState_Allow;
            if ((This->m_treemode & FLTreeMode_SortAlbum_Date) != 0)
            	state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_Hierarchical:
		{
			state |= ECmdState_Allow;
            if (!(This->m_treemode & FLTreeMode_Flat))
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_PList_UseDefault:
		case Cmd_PList_SetAsDefault:
		{
			state |= ECmdState_Allow;
		}
		break;
		case Cmd_PList_ObjectMenu:
		{
			const CmdContext* ctx = CmdHandler_GetContext(NULL);
			int type = 0;
			const char* title;

			// Check that there is at least one selected item
			if (List_Count(This->m_pSelection))
			{
				// Check that all objects are of the same type
				ListNode* pNode = List_GetSuccessor(This->m_pSelection, NULL);
				const FLTrack* pTrack = List_GetNodeData(This->m_pSelection, pNode);
				type = FLTrack_GetObjectType(pTrack);

				state |= ECmdState_Allow;
				while ((pNode = List_GetSuccessor(This->m_pSelection, pNode)) != NULL)
				{
	  				pTrack = List_GetNodeData(This->m_pSelection, pNode);

					if (type != FLTrack_GetObjectType(pTrack))
					{
						state = 0;
						type = 0;
						break;
					}
				}
			}

			if (ctx->menu != NULL)
			{
				try
				{
					title = Msg_Lookup(SPrintf("Lst05%1x0", type));
					throw_Menu_ItemSetText(ctx->menu, ctx->item, title);
				}
				catch
				{
					App_ReportException();
				}
				catch_end
			}
		}
		break;
		case Cmd_PList_Play:
		case Cmd_PList_List_Play:
		case Cmd_PList_DirList_Play:
		case Cmd_PList_File_Play:
		case Cmd_PList_Radio_Play:
		{
			// Empty terminal nodes selection?
			if (List_Count(This->m_pSelection) == 0)
			{
				int index = WListCore_GetFocus(OTreeList_GetWListCore(This->m_pTreeList));
				OTreeNode* pNode = NULL;
				FLTrack* pTrack = NULL;

				if ((index >= 0) && (index < WListCore_Count(OTreeList_GetWListCore(This->m_pTreeList))))
					pNode = OTreeList_GetWListItem(This->m_pTreeList, index);

				// trying to play non terminal node?
				if (pNode)
				{
					List* list = New_List();

					// Only terminal nodes may be selected
					while(OTreeNode_AllowExpand(pNode))
						pNode = OTreeNode_GetFirstChild(pNode);

					if (FLTreeNodes_IsTrack(pNode))
					{
						int state;
						pTrack = throw_FLTreeNodes_GetTrack(pNode);

						List_InsertBefore(list, NULL, pTrack);
						state = FileView_CheckCommand(&This->m_View, id, list);
						Delete_List(list);

						return state;
					}

					Delete_List(list);
				}
			}

			return FileView_CheckCommand(&This->m_View, id, This->m_pSelection);
		}
		break;
		case Cmd_PList_EditClear:
		case Cmd_PList_Mark_Set:
		case Cmd_PList_Mark_Remove:
		{
			if (List_Count(This->m_pSelection) != 0)
				state |= ECmdState_Allow;
		}
		break;
		case Cmd_PList_Find:
		case Cmd_PList_Mark_GoPrev:
		case Cmd_PList_Mark_GoNext:
		case Cmd_PList_Mark_Clear:
		{
			if (FileList_TrackCount(pDoc) != 0)
				state |= ECmdState_Allow;
		}
		break;
		case Cmd_PList_EditPaste:
		{
			switch(FileList_GetType(pDoc))
			{
				case FileList_TypeNormal:
				case FileList_TypeExtern:
				case FileList_TypeMain:
				case FileList_TypeQueue:
				{
					if (Clipboard_AllowPaste(FileList_Load_FileTypes, false))
					state |= ECmdState_Allow;
				}
				break;
				default:
					// Don't allow insertion of tracks
					;
			}
		}
		break;
		default:
			return FileView_CheckCommand(&This->m_View, id, This->m_pSelection);
	}

	return state;
}

static bool FLTreeView_ExecCommand(void* handle, uint32_t id);

static CmdHandler FLTreeView_CmdHandler =
{ FLTreeView_CheckCommand
, FLTreeView_ExecCommand
};

static bool FLTreeView_ExecCommand(void* handle, uint32_t id)
{
	FLTreeView* This = handle;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);

	FLTreeView_GetSelectedTracks(This);

	switch(id)
	{
		case Cmd_PList_EditPaste:
		{
			Clipboard_Paste(WListCore_GetWindow(pWList), WListCore_GetFocus(pWList), 0, 0, FileList_Load_FileTypes);
		}
		break;
		case Cmd_PList_EditSelectAll:
		{
			if (WListCore_SetItemsState(pWList, EWItem_Selected, EWItem_Selected, 0, 0))
				This->m_bUpdateAtNull = true;
		}
		break;
		case Cmd_PList_EditClear:
		{
			if (WListCore_SetItemsState(pWList, 0, EWItem_Selected, EWItem_Selected, EWItem_Selected))
				This->m_bUpdateAtNull = true;
		}
		break;
		case Cmd_PList_GroupBy_MimeType:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_GroupBy_MimeType);
		}
		break;
		case Cmd_PList_GroupBy_Broadcaster:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_GroupBy_Broadcaster);
		}
		break;
		case Cmd_PList_GroupBy_Rate:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_GroupBy_Rate);
		}
		break;
		case Cmd_PList_GroupBy_ObjectType:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_GroupBy_ObjectType);
		}
		break;
		case Cmd_PList_GroupBy_Collective:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_GroupBy_Collective);
		}
		break;
		case Cmd_PList_GroupBy_Artist:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_GroupBy_Artist);
		}
		break;
		case Cmd_PList_GroupBy_Box:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_GroupBy_Box);
		}
		break;
		case Cmd_PList_GroupBy_Album:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_GroupBy_Album);
		}
		break;
		case Cmd_PList_GroupBy_Compil:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_GroupBy_Compilation);
		}
		break;
		case Cmd_PList_GroupBy_CompilByArtist:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_GroupBy_CompByArtist);
		}
		break;
		case Cmd_PList_SortBy_Number:
		{
			uint32_t mode = This->m_treemode & ~FLTreeMode_Mask_OrderBy;
			FLTreeView_Rebuild(This, mode | FLTreeMode_OrderBy_TrackNumber);
		}
		break;
		case Cmd_PList_SortBy_Name:
		{
			uint32_t mode = This->m_treemode & ~FLTreeMode_Mask_OrderBy;
			FLTreeView_Rebuild(This, mode | FLTreeMode_OrderBy_Alphabetic);
		}
		break;
		case Cmd_PList_SortBy_None:
		{
			uint32_t mode = This->m_treemode & ~FLTreeMode_Mask_OrderBy;
			FLTreeView_Rebuild(This, mode | FLTreeMode_OrderBy_Raw);
		}
		break;
		case Cmd_PList_SortAlbum_Name:
		{
			uint32_t mode = This->m_treemode & ~FLTreeMode_SortAlbum_Date;
			FLTreeView_Rebuild(This, mode);
		}
		break;
		case Cmd_PList_SortAlbum_Date:
		{
			uint32_t mode = This->m_treemode | FLTreeMode_SortAlbum_Date;
			FLTreeView_Rebuild(This, mode);
		}
		break;
		case Cmd_PList_Hierarchical:
		{
			FLTreeView_Rebuild(This, This->m_treemode ^ FLTreeMode_Flat);
		}
		break;
		case Cmd_PList_UseDefault:
		{
			FLTreeView_Rebuild
				( This
				, Choices_ReadInt
					( FLTreeView_GetChoicesBaseSection(pDoc)
					, Var_TreeMode
					, 0
					)
				);
		}
		break;
		case Cmd_PList_SetAsDefault:
		{
			Choices_Write
				( FLTreeView_GetChoicesBaseSection(pDoc)
				, Var_TreeMode
				, "0x%x"
				, This->m_treemode
				);
		}
		break;
		case Cmd_PList_ObjectMenu:
		{
			const FLTrack* pTrack = List_Get(This->m_pSelection, 0);
			const char* pMenu = NULL;

			switch(FLTrack_GetObjectType(pTrack))
			{
				case FLTrack_Type_File:
				{
					pMenu = "Lst_Files";
				}
				break;
				case FLTrack_Type_Url:
				{
					pMenu = "Lst_Radios";
				}
				break;
				case FLTrack_Type_FileList:
				{
					pMenu = "Lst_Lists";
				}
				break;
				case FLTrack_Type_Directory:
				{
					pMenu = "Lst_DirLists";
				}
				break;
				case FLTrack_Type_YellowPage:
				{
					pMenu = "Lst_YPLists";
				}
				break;
				default: ;
			}

			if (pMenu)
			{
				CmdHandler_OpenMenu(&FLTreeView_CmdHandler, This, pMenu);

				return EListenerAction_StopEvent;
			}
		}
		break;
		case Cmd_PList_Play:
		case Cmd_PList_List_Play:
		case Cmd_PList_DirList_Play:
		case Cmd_PList_File_Play:
		case Cmd_PList_Radio_Play:
		{
			// Empty terminal nodes selection?
			if (List_Count(This->m_pSelection) == 0)
			{
				int index = WListCore_GetFocus(OTreeList_GetWListCore(This->m_pTreeList));
				OTreeNode* pNode = NULL;
				FLTrack* pTrack = NULL;

				if ((index >= 0) && (index < WListCore_Count(OTreeList_GetWListCore(This->m_pTreeList))))
					pNode = OTreeList_GetWListItem(This->m_pTreeList, index);

				// trying to play non terminal node?
				if (pNode)
				{
					List* list = New_List();

					// Only terminal nodes may be selected
					while(OTreeNode_AllowExpand(pNode))
						pNode = OTreeNode_GetFirstChild(pNode);

					if (FLTreeNodes_IsTrack(pNode))
					{
						pTrack = throw_FLTreeNodes_GetTrack(pNode);

						List_InsertBefore(list, NULL, pTrack);
						FileView_ExecCommand(&This->m_View, id, list, NULL);
						Delete_List(list);

						return true;
					}

					Delete_List(list);
				}
			}

			return FileView_ExecCommand(&This->m_View, id, This->m_pSelection, NULL);
		}
		break;
		case Cmd_PList_Find:
		{
			FLSearch_Open(This
				, FLTreeView_GetTrackCount
				, FLTreeView_GetTrack
				, FLTreeView_GetState
				, FLTreeView_SetState
				, FLTreeView_ShowTrack);
		}
		break;
		case Cmd_PList_Mark_Set:
		{
			ListNode* pNode = NULL;

			while ((pNode = List_GetSuccessor(This->m_pSelection, pNode)) != NULL)
			{
  				const FLTrack* pTrack = List_GetNodeData(This->m_pSelection, pNode);

				FLTreeView_SetState(This, pTrack, EWItem_Marked, EWItem_Marked);
			}
		}
		break;
		case Cmd_PList_Mark_Remove:
		{
			ListNode* pNode = NULL;

			while ((pNode = List_GetSuccessor(This->m_pSelection, pNode)) != NULL)
			{
  				const FLTrack* pTrack = List_GetNodeData(This->m_pSelection, pNode);

				FLTreeView_SetState(This, pTrack, 0, EWItem_Marked);
			}
		}
		break;
		case Cmd_PList_Mark_GoPrev:
		{
			int cur = WListCore_GetFocus(pWList);
			int prev;
			int i = -1;

			do
			{
				prev = i;
				i = WListCore_FindItemState(pWList, prev + 1, EWItem_Marked, EWItem_Marked);
			} while ((i != -1) && (i < cur));

			if (prev != -1)
			{
				WListCore_SetFocus(pWList, prev, true);
			}
		}
		break;
		case Cmd_PList_Mark_GoNext:
		{
			int i = WListCore_FindItemState(pWList, WListCore_GetFocus(pWList) + 1, EWItem_Marked, EWItem_Marked);

			if (i != -1)
			{
				WListCore_SetFocus(pWList, i, true);
			}
		}
		break;
		case Cmd_PList_Mark_Clear:
		{
			WListCore_SetItemsState(pWList, 0, EWItem_Marked, EWItem_Marked, EWItem_Marked);
		}
		break;
		default:
		{
			int index = WListCore_GetFocus(OTreeList_GetWListCore(This->m_pTreeList));
			OTreeNode* pNode = NULL;
			FLTrack* pTrack = NULL;

			if ((index >= 0) && (index < WListCore_Count(OTreeList_GetWListCore(This->m_pTreeList))))
				pNode = OTreeList_GetWListItem(This->m_pTreeList, index);

			// Only terminal nodes may be selected
			if (pNode && FLTreeNodes_IsTrack(pNode))
				pTrack = throw_FLTreeNodes_GetTrack(pNode);

			return FileView_ExecCommand(&This->m_View, id, This->m_pSelection, pTrack);
		}
	}

	return true;
}

static EListenerAction FLTreeView_Listener(void* handle, const Event* e)
{
	FLTreeView* This = handle;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);

	switch(e->Type)
	{
		case EEvent_Null:
		{
			// Disable anything if a background Job is active
			if (FLTreeView_IsBusy(This))
				return EListenerAction_ContinueEvent;

			// Do not force minimum size anymore
			{
				CRect r = {0, 0, 1, 1};
				throw_OTreeList_SetPane(This->m_pTreeList, HWind_None, &r);
			}

			// Not busy, hide hourglass
			if (This->m_bShowsHourglass)
			{
				WLib_Hourglass_Off();
				This->m_bShowsHourglass = false;
				Menu_Refresh(OTreeList_GetWindow(This->m_pTreeList), NULL);
			}

			{
				const Mouse* m = Mouse_Get();
				WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
				WItemArea area;
				area.id = -1;

				if (m->w != HWind_None)
				{
					if (OTreeList_GetWindow(This->m_pTreeList) == m->w)
						WListCore_ItemAreaFromScreenPt(pWList, m, 0, &area);
					else if (This->m_HeaderWnd == m->w)
						area.id = WListHeader_GetActionFromScreenPt(This, m, 0);
				}

				if ((area.id >= FLItemAction_ResizeColumn0)
				&&  (area.id < (FLItemAction_ResizeColumn0 + This->m_FieldCount)))
				{
					if (Desktop_GetPointer() == NULL)
					{
						Desktop_SetPointer(&ptr_scrh);
						This->m_pShape = &ptr_scrh;
					}
				}
				else
				{
					if (This->m_pShape == &ptr_scrh)
					{
						Desktop_ResetPointer();
						This->m_pShape = NULL;
					}
				}
			}
		}
		break;
		case EEvent_OptionShowFileAsTitle:
		{
			WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
			WListCore_RecalcSize(pWList);
		}
		break;
		case EEvent_OptionParentalLock:
		{
			if ((Options()->Player.bParentalLock)
			&&  (FileList_GetType(pDoc) != FileList_TypeQueue))
				Window_Close(OTreeList_GetWindow(This->m_pTreeList));
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static const _kernel_oserror* FLTreeView_LoadDocument(View* pView, XFer* xfer, file_type type, const ScreenPos* pInfo)
{
	FLTreeView* This = (FLTreeView*) pView;

	if (FLTreeView_AllowsReorg(This)
	&&  pInfo
	&&  (pInfo->w == OTreeList_GetWindow(This->m_pTreeList)))
	{
		WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);

		int* pi = (int*) &pInfo->i;
		if (pInfo->i < 0)
			*pi = WListCore_ItemFromScreenPt(pWList, &pInfo->pt, EWListPtrMode_Insert);
	}

	return FileList_LoadDocument(View_GetDocument(pView), xfer, type, pInfo);
}

/*
 * Handle inline edition events before normal treelist events.
 */
static EListenerAction List_PreEventHandler(void* handle, const Event* e)
{
	FLTreeView* This = handle;
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);

	switch(e->Type)
	{
		case EEvent_WindowClose:
		{
			FLTreeView_EditMetadata(This, -1, NULL);
		}
		break;
		case EEvent_Mouse:
		{
			const Mouse* m = e->pData;
			WItemArea area;
			int item;

			// Skip it if a background Job is active
			if (FLTreeView_IsBusy(This))
				return EListenerAction_ContinueEvent;

			// We are already editing?
			if (m->i != HIcon_None)
				return EListenerAction_ContinueEvent;

			item = WListCore_ItemAreaFromScreenPt(pWList, m, 0, &area);
			if (FLTreeView_EditMetadata(This, item, &area))
				return EListenerAction_StopEvent;
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static EListenerAction List_EventHandler(void* handle, const Event* e)
{
	FLTreeView* This = handle;
	FileList* pDoc = (FileList*) View_GetDocument(&This->m_View);
	FLTrack* pOwnerTrack = FileList_GetOwner(pDoc);

	switch(e->Type)
	{
		case EEvent_WindowClose:
		{
			const Mouse* m = Mouse_Get();

			if ((m->w == OTreeList_GetWindow(This->m_pTreeList))
			&&  (m->i == -3)
			&&  (m->but & EBut_Adjust))
			{
				if (pOwnerTrack)
				{
					FileList* pParent = FLTrack_GetOwner(pOwnerTrack);
					FileList_Show(pParent, FileList_FindTrack(pParent, pOwnerTrack));
				}
				if (Keyboard_PollShift()) return EListenerAction_StopEvent;
			}

			FLSearch_Close(This);

			// Close last view on search window, closes document
			if (FileList_GetType(pDoc) == FileList_TypeSearch)
			{
				View_OnCloseView(&This->m_View);
				return EListenerAction_StopEvent;
			}

			return EListenerAction_ContinueEvent;
		}
		break;
		case EEvent_WindowPtrLeave:
		{
			if (This->m_bShowsHourglass)
			{
				WLib_Hourglass_Off();
				This->m_bShowsHourglass = false;
				Menu_Refresh(OTreeList_GetWindow(This->m_pTreeList), NULL);

				return EListenerAction_StopEvent;
			}
		}
		break;
		case EEvent_WindowPtrEnter:
		{
			if (!WLib_Hourglass_IsOn()
			&&  FLTreeView_IsBusy(This))
			{
				This->m_bShowsHourglass = true;
				WLib_Hourglass_On();

				return EListenerAction_StopEvent;
			}
		}
		break;
		case EEvent_Mouse:
		{
			const Mouse* m = e->pData;
			WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
			WItemArea area;
			int item;

			// Disable any command if a background Job is active
			if (FLTreeView_IsBusy(This))
				return EListenerAction_StopEvent;

			if (m->but == EBut_Menu)
			{
				/*
				 * Calling Wimp_Poll after right button click on menu deletes
				 * the menu if wimp_create_menu is not called before.
				 * As Clipboard_AllowPaste normally calls Wimp_Poll it can only
				 * be checked before opening the menu, then on right button
				 * clicks we can only check if we have claimed the clipboard
				 */
				Clipboard_AllowPaste(FileList_Load_FileTypes, true);

				CmdHandler_OpenMenu(&FLTreeView_CmdHandler, This, This->m_pCmdGroup);

				return EListenerAction_StopEvent;
			}

			item = WListCore_ItemAreaFromScreenPt(pWList, m, 0, &area);

			switch (area.id)
			{
				case FLItemAction_TogglePlay:
				{
					// Change play option
					FLTrack* pTrack = throw_FLTreeNodes_GetTrack(OTreeList_GetWListItem(This->m_pTreeList, item));
					int       flags = FLTrack_MustNotPlay;
					bool      bMod = false;

					if (FLTrack_GetFlags(pTrack) & FLTrack_NeverPlay)
						return EListenerAction_StopEvent;

					if (FLTrack_GetFlags(pTrack) & FLTrack_MustNotPlay)
						flags = 0;

					FileList_StartUpdate(pDoc);

					if (List_Find(This->m_pSelection, 0, pTrack) != -1)
					{
						ListNode* pNode = NULL;

						// Set choosen option for the whole selection
						while ((pNode = List_GetSuccessor(This->m_pSelection, pNode)) != NULL)
						{
			  				FLTrack* pTrack = List_GetNodeData(This->m_pSelection, pNode);

							if (FLTrack_SetFlags(pTrack, flags, FLTrack_MustNotPlay))
							{
								FLTrack_RefreshViews(pTrack, This, false, false);
								bMod = true;
							}
						}
					}
					else
					{
						if (FLTrack_SetFlags(pTrack, flags, FLTrack_MustNotPlay))
						{
							FLTrack_RefreshViews(pTrack, This, false, false);
							bMod = true;
						}
					}

					FileList_EndUpdate(pDoc, bMod, false);

					if (bMod)
					{
						PListFiles_RefreshTrackList(PListFiles_Get(), pDoc);
						PListRadio_RefreshTrackList(PListRadio_Get());
					}

					return EListenerAction_StopEvent;
				}
				break;
				case FLItemAction_DragNode:
				{
					if (m->but == EBut_DragSelect)
					{
						List_Clear(This->m_pDragSelection);
						FLTreeNodes_GetChildTracks(OTreeList_GetWListItem(This->m_pTreeList, item), This->m_pDragSelection);

						if (List_Count(This->m_pDragSelection) > 0)
						{
							DragDrop_Info Info = DragDrop_DragFile(This, 0, FileList_Save_FileTypes);

							if (Info.FTransfer == FLTreeView_TransferMoveData)
							{
								DragDrop_Transfer(&Info
										, (XFer_RemoveProc) FLTreeView_DragSelection_RemoveAll
										, This->m_pDragSelection
										, This);
							}
							else if (Info.FTransfer)
							{
								DragDrop_Transfer(&Info
										, (XFer_RemoveProc) FLTreeView_DragSelection_Remove
										, This->m_pDragSelection
										, This);
							}
							else
							{
								static const char* leafname = "Selection";

								DragDrop_Send(&Info
										, leafname
										, 0
										, FLTreeView_DragSelection_SendProc
										, (XFer_RemoveProc) FLTreeView_DragSelection_Remove
										, NULL
										, NULL
										, App_ReportOSError
										, false
										, This);
							}
						}

						List_Clear(This->m_pDragSelection);
					}

					return EListenerAction_StopEvent;
				}
				break;
			}

			if (area.id == EWItemArea_Select)
			{
				switch(m->but)
				{
					case EBut_Select:
					{
						if (Keyboard_PollShift())
							CmdHandler_ExecCommand(&FLTreeView_CmdHandler, This, Cmd_PList_Properties);
						else
							CmdHandler_ExecCommand(&FLTreeView_CmdHandler, This, Cmd_PList_OpenOrProperties);

						return EListenerAction_StopEvent;
					}
					break;
					case EBut_DragSelect:
					{
						int item = WListCore_ItemFromScreenPt(pWList, &m->pt, EWListPtrMode_Hover);
						const OTreeNode* pNode = OTreeList_GetWListItem(This->m_pTreeList, item);

						if (WListCore_GetItemState(pWList, item) & EWItem_Selected)
						{
							if (FLTreeNodes_IsTrack(pNode))
							{
								FLTrack* pTrack = throw_FLTreeNodes_GetTrack(pNode);

								FLTreeView_GetSelectedTracks(This);

								if (List_Find(This->m_pSelection, 0, pTrack) != -1)
								{
									DragDrop_Info Info = DragDrop_DragFile(This, 0, FileList_Save_FileTypes);

									if (Info.FTransfer == FLTreeView_TransferMoveData)
									{
										DragDrop_Transfer(&Info
												, (XFer_RemoveProc) FLTreeView_Selection_RemoveAll
												, This->m_pSelection
												, This);
									}
									else if (Info.FTransfer)
									{
										DragDrop_Transfer(&Info
												, (XFer_RemoveProc) FLTreeView_Selection_Remove
												, This->m_pSelection
												, This);
									}
									else
									{
										static const char* leafname = "Selection";

										DragDrop_Send(&Info
												, leafname
												, 0
												, FLTreeView_Selection_SendProc
												, (XFer_RemoveProc) FLTreeView_Selection_Remove
												, NULL
												, NULL
												, App_ReportOSError
												, false
												, This);
									}
								}
							}
							return EListenerAction_StopEvent;
						}
					}
					break;
				}
			}
		}
		break;
		case EEvent_Key:
		{
			const Event_Key* key = e->pData;

			if (key->caret.pos.i != HIcon_None)
			{
				// Edit Name Icon
				switch(key->code)
				{
					case 0x01b: // Escape
					{
						FLTreeView_EditMetadata(This, -1, NULL);
						return EListenerAction_StopEvent;
					}
					break;
					case 0x00d: // Return
					{
						FLTreeView_StoreMetadata(This);
						return EListenerAction_StopEvent;
					}
					break;
				}
			}
			else
			{
				const int code = key->code;

				if (code == 0x181) // F1
				{
					App_StrongHelp("Playlist_Playlist");
					return EListenerAction_StopEvent;
				}

				if (CmdHandler_KeyPressed(&FLTreeView_CmdHandler, This, "PList", code))
					return EListenerAction_StopEvent;
			}
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			switch(((Msg*) e->pData)->hdr.action)
			{
				case EMsg_Dragging: // Drag & drop Message_Dragging
				{
					/* If we accept the message, it adds a PreEventHandler
					 * to ensure that only our OnDragging handles the
					 * following Message_Dragging messages.
					 */
					if (DragDrop_SetOnDraggingCallback(e->pData, FLTreeView_OnDragging, This))
						return EListenerAction_StopEvent;
				}
				break;
				case EMsg_DataLoad:
				case EMsg_DataSave:
				{
					const Msg_FileData* rcv = e->pData;

					switch(FileList_GetType(pDoc))
					{
						case FileList_TypeNormal:
						case FileList_TypeExtern:
						case FileList_TypeMain:
						case FileList_TypeQueue:
						break;
						default:
							// Don't allow insertion of tracks
							return EListenerAction_StopEvent;
					}

					if (FileList_AcceptSourceFileType(rcv->type))
						XFer_Receive((Msg*) rcv, (XFer_ReceiveProc) FLTreeView_LoadDocument, This);
					else if (rcv->hdr.action == EMsg_DataLoad)
					{
						FileList_AddFile(pDoc, -1, rcv->name, 0);
					}

					return EListenerAction_StopEvent;
				}
				break;
				case EMsg_HelpRequest:
				{
					const Msg_HelpRequest* msg = e->pData;
					WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
					WItemArea area;
					int item;

					item = WListCore_ItemAreaFromScreenPt(pWList, &msg->m, 0, &area);

					switch (area.id)
					{
						case FLItemAction_TogglePlay:
						{
							Task_HelpReply(msg, Msg_Lookup("DSLiPlH"));

							return EListenerAction_StopEvent;
						}
						break;
						case FLItemAction_DragNode:
						{
							Task_HelpReply(msg, Msg_Lookup("DSLiDNH"));

							return EListenerAction_StopEvent;
						}
						break;
						default:
						{
							if ((area.id >= EWOTreeItemArea_User)
							||  (area.id < EWItemArea_User))
							{
								Task_HelpReply(msg, Msg_Lookup("DSLiH"));

								return EListenerAction_StopEvent;
							}
						}
					}
				}
				break;
			}
		}
		break;
		case EEvent_SelectionChange:
		{
			This->m_bUpdateAtNull = true;

			return EListenerAction_StopEvent;
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

FLTreeView* throw_New_FLTreeView(FileList* pDoc)
{
	FLTreeView* This = throw_mem_calloc(1, sizeof(FLTreeView));
	CTemplate* volatile t = NULL;
	OTreeNode* volatile pNode = NULL;
	int height = 0;

	This->m_HeaderWnd = HWind_None;
	This->m_Edit.Icon = HIcon_None;
	This->m_bReadChoices = true;

	try
	{
		const char* section;
		int i;

		This->m_pTrackSet = New_Array(sizeof(TrackMap), 4096);

		This->m_pSelection = New_List();
		This->m_pDragSelection = New_List();

		This->m_treemode = Choices_ReadInt(FLTreeView_GetChoicesBaseSection(pDoc), Var_TreeMode, 0);

		section = FLTreeView_GetChoicesSection(pDoc, false);

		This->m_treemode = Choices_ReadInt(section, Var_TreeMode, This->m_treemode);
		This->m_treemode &= FLTreeMode_Mask_Read;

		This->m_FieldCount = EFLField_FieldCount;
		for (i = 0; i < EFLField_FieldCount; i++)
		{
			This->m_FieldList[i].id = (EFLField_Id) i;
			This->m_FieldList[i].width = FitInRange(Choices_ReadInt(section
			                                           , SPrintf(Var_FieldWidth, This->m_FieldList[i].id)
			                                           , 400)
			                                       , 8, 2000);
		}

		switch (FileList_GetType(pDoc))
		{
			case FileList_TypeMain: This->m_pCmdGroup = "PList_Main"; break;
			case FileList_TypeNormal: This->m_pCmdGroup = "PList_Stored"; break;
			case FileList_TypeExtern: This->m_pCmdGroup = "PList_Extern"; break;
			case FileList_TypeQueue: This->m_pCmdGroup = "PList_Queue"; break;
			case FileList_TypeDir: This->m_pCmdGroup = "PList_Dir"; break;
			case FileList_TypeYP: This->m_pCmdGroup = "PList_YP"; break;
			default: This->m_pCmdGroup = "PList_Search";
		}

		try
		{
			This->m_pToolbar = throw_New_Toolbar(&FLTreeView_CmdHandler, This, This->m_pCmdGroup);
		}
		catch
		{
			// may not exists
			This->m_pToolbar = NULL;
		}
		catch_end

		if (This->m_pToolbar)
		{
			CWind info = Window_GetInfo(Toolbar_GetWindow(This->m_pToolbar));
			height = info.ex.y1 - info.ex.y0;
		}
		if (height < 400) height = 400;

		t = throw_Templates_Blank(256, EWind_NormalWindow | EWind_Scroll_1, 500, height);

		pNode = throw_FLTreeView_NewRootNode(This, pDoc);

		This->m_pTreeList = throw_New_OTreeList
								( EWList_Transparent | EWList_SelModeMulti | EWList_MultiMark
								, t, pNode, This, 3
								);
		t = NULL;
		pNode = NULL;

		throw_Window_RegisterEventHandler(OTreeList_GetWindow(This->m_pTreeList), List_PreEventHandler, This, true);
		throw_Window_RegisterEventHandler(OTreeList_GetWindow(This->m_pTreeList), List_EventHandler, This, false);
		throw_Task_AddListener(EEvent_Null, FLTreeView_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionShowFileAsTitle, FLTreeView_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionParentalLock, FLTreeView_Listener, This, false);

		if (This->m_pToolbar)
			throw_Window_AddPane(OTreeList_GetWindow(This->m_pTreeList), Toolbar_GetWindow(This->m_pToolbar));

		OTreeList_SetParent(This->m_pTreeList, OTreeList_GetWindow(This->m_pTreeList));
		throw_DocEvents_AddListener(NULL, This, FLTreeView_OnDocEvent);

		throw_View_View(&This->m_View, &FLTreeViewVPtr, (Document*) pDoc);
	}
	catch
	{
		Delete_Toolbar(This->m_pToolbar);
		Delete_OTreeList(This->m_pTreeList);
// Deleted by TreeList
//		Delete_OTreeNode(pNode);
		Templates_Remove(t);
		Delete_List(This->m_pSelection);
		Delete_List(This->m_pDragSelection);
		mem_free(This);
		throw_current();
	}
	catch_end

	return This;
}

int FLTreeView_GetFieldList(const FLTreeView* This, const FLField_Info** ppFieldList)
{
	*ppFieldList = This->m_FieldList;

	return This->m_FieldCount;
}

unsigned int FLTreeView_GetFieldAction(const FLTrack* pTrack, EFLField_Id id)
{
	switch(id)
	{
		case EFLField_Title:
		{
			switch(FLTrack_GetObjectType(pTrack))
			{
				case FLTrack_Type_Url:
					return FLItemAction_EditStation;
				break;
				default:
					return FLItemAction_EditTitle;
			}
		}
		break;
		case EFLField_Album:
		{
			switch(FLTrack_GetObjectType(pTrack))
			{
				case FLTrack_Type_Url:
					return FLItemAction_EditMimeType;
				break;
				default:
					return FLItemAction_EditAlbum;
			}
		}
		break;
		case EFLField_Author:
		{
			switch(FLTrack_GetObjectType(pTrack))
			{
				case FLTrack_Type_Url:
					return FLItemAction_EditBroadcaster;
				break;
				default:
					return FLItemAction_EditArtist;
			}
		}
		break;
		default: return EWItemArea_Select;
	}
}
