#include "CDTreeView.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 "Cmds.h"
#include "CmdList.h"
#include "CDCmds.h"
#include "CDSearch.h"
#include "CDTreeNodes.h"
#include "DigitalCD.h"
#include "DocEvents.h"
#include "Options.h"
#include "Setup.h"
#include "UserEvents.h"

typedef struct
{
	const CDTrack*     pTrack;
	const OTreeNode*   pNode;
} CDTrackMap;

struct CDTreeView
{
	View         m_View;
	OTreeList*   m_pTreeList;
	Array*       m_pTrackSet;
	List*        m_pSelection;
	List*        m_pDescSelection;
	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;
	CDField_Info m_FieldList[ECDField_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;
		bool         bCD;
		StrCol*      pStrCol;
		WStringList* pWList;
		const char*  pOldValue;
	} m_Edit;
};

static const char Sec_CDs[] = "CDs";
static const char Sec_SearchCds[] = "SearchCds";
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 CDTreeView_Listener(void* handle, const Event* e);
static const MouseShape ptr_scrh = {"ptr_scrh", 10, 4};

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

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

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

void CDTreeViews_CDTreeViews(void)
{
	throw_CDTreeNodes_CDTreeNodes();

	if (NodeHeader_Strings[3] == NULL)
	{
		NodeHeader_Strings[0] = throw_mem_allocstring(Msg_Lookup("CDAlbum"));
		NodeHeader_Strings[1] = throw_mem_allocstring(Msg_Lookup("CDArtist"));
		NodeHeader_Strings[2] = throw_mem_allocstring(Msg_Lookup("CDTitle"));
		NodeHeader_Strings[3] = throw_mem_allocstring(Msg_Lookup("CDArtist"));
	}

	// 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 CDTreeViews_NotCDTreeViews(void)
{
	mem_free(NodeHeader_Strings[0]);
	mem_free(NodeHeader_Strings[1]);
	mem_free(NodeHeader_Strings[2]);
	mem_free(NodeHeader_Strings[3]);
	CDTreeNodes_NotCDTreeNodes();
}

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

	if ((pDoc && CDDescList_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 CDTreeView* This, const Mouse* pMouse, unsigned int flags)
{
	CWindCvt cvt = Window_GetPosInfo(This->m_HeaderWnd);
	Mouse m = *pMouse;
	int i;

	IGNORE(flags);

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

	m.pt.x -= CDHeaderNode_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 CDItemAction_ResizeColumn0 + i;
	}

	return EWItemArea_Outside;
}

/**
 * Redraws the headers.
 *
 * @param  r          Pointer to the window's redraw structure.
 */
static void WListHeader_Redraw(const CDTreeView* This, const CWindRedraw* r)
{
	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, ...
	box.x0 += CDHeaderNode_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)
{
	CDTreeView* 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)
{
	CDTreeView* 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 (CDTreeView_IsBusy(This)
			||  (m->but == EBut_Menu))
				return EListenerAction_StopEvent;

			action = WListHeader_GetActionFromScreenPt(This, m, 0);

			if ((action >= CDItemAction_ResizeColumn0)
			&&  (action < (CDItemAction_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 - CDItemAction_ResizeColumn0;

				rct.x1 = CDHeaderNode_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 >= CDItemAction_ResizeColumn0)
					&&  (action < (CDItemAction_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 CDTreeView_SetHeader(CDTreeView* 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 CDTrackMap* pa, const CDTrackMap* 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 CDTreeView* This, const CDTrackMap* 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_CDTreeView_AddTrackMap(CDTreeView* This, const CDTrack* pTrack, const OTreeNode* pNode)
{
	CDTrackMap 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 CDTreeView_RemoveTrackMap(CDTreeView* This, const CDTrack* pTrack, const OTreeNode* pNode)
{
	CDTrackMap 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* CDTreeView_FindNode(const CDTreeView* This, const CDTrack* pTrack)
{
	CDTrackMap Info = {pTrack, NULL};
	int pos;

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

	if (pos == -1) return NULL;

	return (OTreeNode*) ((CDTrackMap*) 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 CDTreeView_ListIndex(const CDTreeView* 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* CDTreeView_GetChoicesBaseSection(const CDDescList* pDoc)
{
	switch(CDDescList_GetType(pDoc))
	{
		case CDDescList_TypeSearch: return Sec_SearchCds; break;
	}

	return Sec_CDs;
}

static const char* CDTreeView_GetChoicesSection(const CDDescList* pDoc, bool bWrite)
{
	switch(CDDescList_GetType(pDoc))
	{
		case CDDescList_TypeSearch:
		{
			if (bWrite)
				return NULL;

			return Sec_SearchCds;
		}
		break;
	}

	return Sec_CDs;
}

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

	if (This->m_bReadChoices)
	{
		CWindOpen info;
		const char* section = CDTreeView_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))
		{
			// 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))
		{
			Window_Open(w);

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

	This->m_bReadChoices = false;
}

/*-------------------------------------------------------------------------*
 *--- CDTreeView CView callbacks ------------------------------------------*
 *-------------------------------------------------------------------------*/

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

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

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

				if (pe->pElement)
				{
					CDTrack* pTrack = CDDesc_GetTrack(pe->pElement, 0);
					if (pTrack)
					{
						OTreeNode* pRefNode = CDTreeView_FindNode(This, pTrack);

						if (pRefNode)
							CDTreeNodes_ShowNode(pRefNode, 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))
					return EListenerAction_ContinueEvent;

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

					if (pRefNode)
					{
						This->m_bUpdateAtNull = true;
						CDTreeView_Show(This, false);
						CDTreeNodes_ShowNode(pRefNode, true);
					}
				}
				else
				{
					This->m_bUpdateAtNull = true;
					CDTreeView_Show(This, false);
				}
			}
		}
		break;
		case EDocEvent_StartUpdate:
		{
			if (pe->pContainer == pDoc)
				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)
				OTreeList_AllowRefresh(This->m_pTreeList, true);

			if ((CDDescList_GetType(pDoc) == CDDescList_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_CDTreeNodes_InsertTrack(This->m_pTreeList, pe->pElement);
					throw_CDTreeView_AddTrackMap(This, pe->pElement, pNode);

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

				// multiple copies?
				while ((pRefNode = CDTreeView_FindNode(This, pe->pElement)) != NULL)
				{
					CDTreeView_RemoveTrackMap(This, pe->pElement, pRefNode);
					CDTreeNodes_Remove(This->m_pTreeList, pRefNode);

					This->m_bUpdateAtNull = true;
				}
			}
		}
		break;
		case EDocEvent_UpdateElement:
		{
			OTreeNode* pRefNode = CDTreeView_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_CDTreeNodes_InsertTrack(This->m_pTreeList, pe->pElement);
					if (pNode != pRefNode)
						throw_CDTreeView_AddTrackMap(This, pe->pElement, pNode);
				}
				catch
				{
					if (pNode && (pNode != pRefNode)) CDTreeNodes_Remove(This->m_pTreeList, pNode);
					throw_current();
				}
				catch_end

				// Differing nodes, remove the old one
				if (pNode != pRefNode)
				{
					CDTreeView_RemoveTrackMap(This, pe->pElement, pRefNode);
					CDTreeNodes_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_CDTreeView_NewRootNode(CDTreeView* This, CDDescList* pDoc)
{
	OTreeNode* pNode;

	switch(CDDescList_GetType(pDoc))
	{
		case CDDescList_TypeSearch:
		{
			This->m_treemode &= ~CDTreeMode_Flat;
			pNode = throw_CDTreeNodes_NewSearchNode(This->m_treemode);
		}
		break;
		default:
		{
			pNode = throw_CDTreeNodes_NewCDListNode(This->m_treemode);
		}
	}

	return pNode;
}

/**
 * Initialises the tree view with the content the CD database.
 */
static void CDTreeView_OnInitialUpdate(View* pView)
{
	CDTreeView* This = (CDTreeView*) pView;
	CDDescList* pDoc = (CDDescList*) View_GetDocument(pView);
	OTreeNode* volatile pNode = throw_CDTreeView_NewRootNode(This, pDoc);

	OTreeList_SetRootNode(This->m_pTreeList, pNode);

	CDTreeView_SetHeader(This, (This->m_treemode & CDTreeMode_Flat) != 0);

	Array_Clear(This->m_pTrackSet);

	try
	{
		for (int i = 0; i < CDSearchList_TrackCount(pDoc); i++)
		{
			CDTrack* pTrack = CDSearchList_GetTrack(pDoc, i);

			pNode = throw_CDTreeNodes_InsertTrack(This->m_pTreeList, pTrack);
			throw_CDTreeView_AddTrackMap(This, pTrack, pNode);
		}
	}
	catch
	{
		if (pNode) CDTreeNodes_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 CDTreeView_SetViewNumber(View* pView, int iViewNumber)
{
	CDTreeView* This = (CDTreeView*) pView;
//	CDDescList* pDoc = (CDDescList*) View_GetDocument(pView);
	HWind w = OTreeList_GetWindow(This->m_pTreeList);

	View_SetViewNumber(pView, iViewNumber);

	Window_SetTitle(w, "%s", View_GetTitle(pView));
}

static void CDTreeView_NotCDTreeView(View* pView)
{
	CDTreeView*   This = (CDTreeView*) pView;
	CDDescList* pDoc = (CDDescList*) View_GetDocument(pView);
	CWindState Info = Window_GetState(OTreeList_GetWindow(This->m_pTreeList));
	const char* section = CDTreeView_GetChoicesSection(pDoc, true);

	CDSearch_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 < ECDField_FieldCount; i++)
		{
			Choices_Write(section, SPrintf(Var_FieldWidth, This->m_FieldList[i].id), "%d", This->m_FieldList[i].width);
		}
	}

	CDTreeView_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, CDTreeView_OnDocEvent);
	Task_RemoveListener(EEvent_Null, CDTreeView_Listener, This);
	Task_RemoveListener(EEvent_OptionParentalLock, CDTreeView_Listener, This);
	Delete_Toolbar(This->m_pToolbar);
	This->m_pToolbar = NULL;

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

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

	View_NotView(pView);
}

static ViewVPtr CDTreeViewVPtr =
{
	  NULL
	, CDTreeView_SetViewNumber
	, CDTreeView_OnInitialUpdate
	, View_HasSelection
	, NULL
	, CDTreeView_NotCDTreeView
};

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

static unsigned int CDTreeView_GetTrackCount(const void* handle)
{
	const CDTreeView* This = handle;
	CDDescList* pDoc = (CDDescList*) View_GetDocument(&This->m_View);

	return CDSearchList_TrackCount(pDoc);
}

static CDTrack* CDTreeView_GetTrack(const void* handle, unsigned int index)
{
	const CDTreeView* This = handle;
	CDDescList* pDoc = (CDDescList*) View_GetDocument(&This->m_View);

	return CDSearchList_GetTrack(pDoc, index);
}

static unsigned int CDTreeView_GetState(const void* handle, const CDTrack* pTrack)
{
	const CDTreeView* This = handle;
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
	const OTreeNode* pNode = CDTreeView_FindNode(This, pTrack);
	int lindex = pNode ? CDTreeView_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 CDTreeView_SetState(void* handle, const CDTrack* pTrack, unsigned int state, unsigned int mask)
{
	CDTreeView* This = handle;
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);
	const OTreeNode* pRefNode = CDTreeView_FindNode(This, pTrack);

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

		if (lindex == -1)
		{
			if (!state) return;
			lindex = CDTreeNodes_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 CDTreeView_ShowTrack(void* handle, const CDTrack* pTrack)
{
	CDTreeView* This = handle;
	const OTreeNode* pRefNode = CDTreeView_FindNode(This, pTrack);

	if (pRefNode) CDTreeNodes_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 CDTreeView_EditMetadata(CDTreeView* 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 CDItemAction_EditCollective:
			{
				This->m_Edit.bCD = true;
				This->m_Edit.MetaId = EMetaId_StreamCollective;
				This->m_Edit.pStrCol = DigitalCD.pCollectivesCol;
			}
			break;
			case CDItemAction_EditAlbumAuthor:
			{
				This->m_Edit.bCD = true;
				This->m_Edit.MetaId = EMetaId_StreamArtist;
				This->m_Edit.pStrCol = DigitalCD.pArtistsCol;
			}
			break;
			case CDItemAction_EditBox:
			{
				This->m_Edit.bCD = true;
				This->m_Edit.MetaId = EMetaId_StreamBox;
				This->m_Edit.pStrCol = DigitalCD.pBoxesCol;
			}
			break;
			case CDItemAction_EditAlbum:
			{
				This->m_Edit.bCD = true;
				This->m_Edit.MetaId = EMetaId_StreamAlbum;
				This->m_Edit.pStrCol = DigitalCD.pAlbumsCol;
			}
			break;
			case CDItemAction_EditAuthor:
			{
				This->m_Edit.bCD = false;
				This->m_Edit.MetaId = EMetaId_StreamArtist;
				This->m_Edit.pStrCol = DigitalCD.pArtistsCol;
			}
			break;
			case CDItemAction_EditTitle:
			{
				This->m_Edit.bCD = false;
				This->m_Edit.MetaId = EMetaId_StreamTitle;
				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"
						, CDTreeNodes_GetField(pINode, This->m_Edit.MetaId, This->m_Edit.bCD)
						);
				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 void CDTreeView_StoreNodeMetadata(CDTreeView* This, OTreeNode* pNode, const char* text)
{
	if (CDTreeNodes_IsCDDesc(pNode) || CDTreeNodes_IsTrack(pNode))
	{
		if (This->m_Edit.bCD)
		{
			CDDesc* pDesc = CDTreeNodes_IsTrack(pNode)
			              ? CDTrack_GetCDDesc(throw_CDTreeNodes_GetTrack(pNode))
			              : throw_CDTreeNodes_GetCDDesc(pNode);
			bool bModified;

			bModified = throw_CDDesc_SetMetaString(pDesc, This->m_Edit.MetaId, text);
			if (bModified) CDDesc_RefreshViews(pDesc, This, bModified);
		}
		else
		{
			CDTrack* pTrack = throw_CDTreeNodes_GetTrack(pNode);
			bool bModified;

			bModified = throw_CDTrack_SetMetaString(pTrack, This->m_Edit.MetaId, text);
			if (bModified) CDTrack_RefreshViews(pTrack, This, bModified);
		}
	}
	else
	{
		// Neither CD nor Track, update childs
		pNode = OTreeNode_GetFirstChild(pNode);

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

/**
 * Stores the edited field, move to next field or track
 */
static void CDTreeView_StoreMetadata(CDTreeView* This)
{
	CDDescList* pDoc = (CDDescList*) 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);
	CDTrack* pTrack = NULL;
	WItemArea area;
	int item = -1;

	if (pNode)
	{
		if (CDTreeNodes_IsTrack(pNode))
			pTrack = throw_CDTreeNodes_GetTrack(pNode);

		if (strcmp(nvl(This->m_Edit.pOldValue), Icon_GetData(w, This->m_Edit.Icon)))
		{
			// Recursively store edited field in node and childs
			CDDescList_StartUpdate(pDoc);
			CDTreeView_StoreNodeMetadata(This, pNode, Icon_GetData(w, This->m_Edit.Icon));
			CDDescList_EndUpdate(pDoc);
		}

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

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

				if (i < (This->m_FieldCount - 1))
				{
					area.id = CDTreeView_GetFieldAction(pTrack, This->m_FieldList[i+1].id);
				}
				else
				{
					item++;
					if (item >= WListCore_Count(pWList))
						item = -1;
					else
					{
						OTreeNode* pNextNode = OTreeList_GetWListItem(This->m_pTreeList, item);
						CDTrack* pNextTrack = throw_CDTreeNodes_GetTrack(pNextNode);

						if (CDTrack_GetCDDesc(pTrack) != CDTrack_GetCDDesc(pNextTrack))
							item = -1;
						else
							area.id = CDItemAction_EditTitle;
					}
				}
			}
			else
			{
				if (This->m_Edit.AreaId == CDItemAction_EditTitle)
				{
					item++;
					if (item >= WListCore_Count(pWList))
						item = -1;
					else
					{
						OTreeNode* pNextNode = OTreeList_GetWListItem(This->m_pTreeList, item);
						if (CDTreeNodes_IsTrack(pNextNode))
						{
							CDTrack* pNextTrack = throw_CDTreeNodes_GetTrack(pNextNode);

							if (CDTrack_GetCDDesc(pTrack) != CDTrack_GetCDDesc(pNextTrack))
								item = -1;
							else
								area.id = CDItemAction_EditTitle;
						}
						else
							item = -1;
					}
				}
			}
		}
	}

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

	CDTreeView_EditMetadata(This, item, &area);
}

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

static void CDTreeView_Rebuild(CDTreeView* This, unsigned int mode)
{
	This->m_treemode = mode;
	CDTreeView_OnInitialUpdate(&This->m_View);
	This->m_bUpdateAtNull = true;
}

static void CDTreeView_GetSelectedTracks(CDTreeView* 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);
		List_Clear(This->m_pDescSelection);

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

			if (CDTreeNodes_IsTrack(pINode))
			{
				// If a parent node is selected, track is already in list
				CDTrack* pTrack = throw_CDTreeNodes_GetTrack(pINode);

				if (!bMix || (List_Find(This->m_pSelection, 0, pTrack) == -1))
					List_InsertBefore(This->m_pSelection, NULL, pTrack);
			}
			else if (CDTreeNodes_IsCDDesc(pINode))
			{
				// If a parent node is selected, desc is already in list
				CDDesc* pDesc = throw_CDTreeNodes_GetCDDesc(pINode);

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

				// If a parent node is selected, track/desc 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) CDTreeNodes_GetChildTracks(pINode, This->m_pSelection);
				if (!pNode) CDTreeNodes_GetChildDescs(pINode, This->m_pDescSelection);
				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 CDTreeView_CheckCommand(void* handle, uint32_t id)
{
	CDTreeView* This = handle;
	int state = 0;

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

	CDTreeView_GetSelectedTracks(This);

	switch(id)
	{
		case Cmd_CDList_GroupBy_Collective:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & CDTreeMode_GroupBy_Collective)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_CDList_GroupBy_Artist:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & CDTreeMode_GroupBy_Artist)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_CDList_GroupBy_Box:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & CDTreeMode_GroupBy_Box)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_CDList_GroupBy_Album:
		{
			state |= ECmdState_Tick;
		}
		break;
		case Cmd_CDList_GroupBy_CompilByArtist:
		{
			state |= ECmdState_Allow;
			if (This->m_treemode & CDTreeMode_GroupBy_CompByArtist)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_CDList_SortBy_Number:
		{
			state |= ECmdState_Allow;
            if ((This->m_treemode & CDTreeMode_Mask_OrderBy) == CDTreeMode_OrderBy_TrackNumber)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_CDList_SortBy_Name:
		{
			state |= ECmdState_Allow;
            if ((This->m_treemode & CDTreeMode_Mask_OrderBy) == CDTreeMode_OrderBy_Alphabetic)
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_CDList_SortAlbum_Name:
		{
			state |= ECmdState_Allow;
            if ((This->m_treemode & CDTreeMode_SortAlbum_Date) == 0)
            	state |= ECmdState_Tick;
		}
		break;
		case Cmd_CDList_SortAlbum_Date:
		{
			state |= ECmdState_Allow;
            if ((This->m_treemode & CDTreeMode_SortAlbum_Date) != 0)
            	state |= ECmdState_Tick;
		}
		break;
		case Cmd_CDList_Hierarchical:
		{
			state |= ECmdState_Allow;
            if (!(This->m_treemode & CDTreeMode_Flat))
				state |= ECmdState_Tick;
		}
		break;
		case Cmd_CDList_EditClear:
		case Cmd_CDList_Mark_Set:
		case Cmd_CDList_Mark_Remove:
		{
			if (List_Count(This->m_pSelection) != 0)
				state |= ECmdState_Allow;
		}
		break;
		case Cmd_CDList_Find:
		case Cmd_CDList_Mark_GoPrev:
		case Cmd_CDList_Mark_GoNext:
		case Cmd_CDList_Mark_Clear:
		{
//			if (CDDescList_TrackCount(pDoc) != 0)
				state |= ECmdState_Allow;
		}
		break;
		default:
			return CDView_CheckCommand(&This->m_View, id, This->m_pSelection, This->m_pDescSelection);
	}

	return state;
}

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

static CmdHandler CDTreeView_CmdHandler =
{ CDTreeView_CheckCommand
, CDTreeView_ExecCommand
};

static bool CDTreeView_ExecCommand(void* handle, uint32_t id)
{
	CDTreeView* This = handle;
	WListCore* pWList = OTreeList_GetWListCore(This->m_pTreeList);

	CDTreeView_GetSelectedTracks(This);

	switch(id)
	{
		case Cmd_CDList_EditClear:
		{
			if (WListCore_SetItemsState(pWList, 0, EWItem_Selected, EWItem_Selected, EWItem_Selected))
				This->m_bUpdateAtNull = true;
		}
		break;
		case Cmd_CDList_GroupBy_Collective:
		{
			CDTreeView_Rebuild(This, This->m_treemode ^ CDTreeMode_GroupBy_Collective);
		}
		break;
		case Cmd_CDList_GroupBy_Artist:
		{
			CDTreeView_Rebuild(This, This->m_treemode ^ CDTreeMode_GroupBy_Artist);
		}
		break;
		case Cmd_CDList_GroupBy_Box:
		{
			CDTreeView_Rebuild(This, This->m_treemode ^ CDTreeMode_GroupBy_Box);
		}
		break;
		case Cmd_CDList_GroupBy_CompilByArtist:
		{
			CDTreeView_Rebuild(This, This->m_treemode ^ CDTreeMode_GroupBy_CompByArtist);
		}
		break;
		case Cmd_CDList_SortBy_Number:
		{
			uint32_t mode = This->m_treemode & ~CDTreeMode_Mask_OrderBy;
			CDTreeView_Rebuild(This, mode | CDTreeMode_OrderBy_TrackNumber);
		}
		break;
		case Cmd_CDList_SortBy_Name:
		{
			uint32_t mode = This->m_treemode & ~CDTreeMode_Mask_OrderBy;
			CDTreeView_Rebuild(This, mode | CDTreeMode_OrderBy_Alphabetic);
		}
		break;
		case Cmd_CDList_SortAlbum_Name:
		{
			uint32_t mode = This->m_treemode & ~CDTreeMode_SortAlbum_Date;
			CDTreeView_Rebuild(This, mode);
		}
		break;
		case Cmd_CDList_SortAlbum_Date:
		{
			uint32_t mode = This->m_treemode | CDTreeMode_SortAlbum_Date;
			CDTreeView_Rebuild(This, mode);
		}
		break;
		case Cmd_CDList_Hierarchical:
		{
			CDTreeView_Rebuild(This, This->m_treemode ^ CDTreeMode_Flat);
		}
		break;
		case Cmd_CDList_Find:
		{
			CDSearch_Open(This
				, CDTreeView_GetTrackCount
				, CDTreeView_GetTrack
				, CDTreeView_GetState
				, CDTreeView_SetState
				, CDTreeView_ShowTrack);
		}
		break;
		case Cmd_CDList_Mark_Set:
		{
			ListNode* pNode = NULL;

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

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

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

				CDTreeView_SetState(This, pTrack, 0, EWItem_Marked);
			}
		}
		break;
		case Cmd_CDList_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_CDList_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_CDList_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;
			CDTrack* pTrack = NULL;
			CDDesc* pDesc = 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 && CDTreeNodes_IsTrack(pNode))
				pTrack = throw_CDTreeNodes_GetTrack(pNode);
			if (pNode && CDTreeNodes_IsCDDesc(pNode))
				pDesc = throw_CDTreeNodes_GetCDDesc(pNode);

			return CDView_ExecCommand(&This->m_View, id, This->m_pSelection, This->m_pDescSelection, pTrack, pDesc);
		}
	}

	return true;
}

static EListenerAction CDTreeView_Listener(void* handle, const Event* e)
{
	CDTreeView* This = handle;

	switch(e->Type)
	{
		case EEvent_Null:
		{
			// Disable anything if a background Job is active
			if (CDTreeView_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 >= CDItemAction_ResizeColumn0)
				&&  (area.id < (CDItemAction_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_OptionParentalLock:
		{
			if (Options()->Player.bParentalLock)
				Window_Close(OTreeList_GetWindow(This->m_pTreeList));
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

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

	switch(e->Type)
	{
		case EEvent_WindowClose:
		{
			CDTreeView_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 (CDTreeView_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 (CDTreeView_EditMetadata(This, item, &area))
				return EListenerAction_StopEvent;
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static EListenerAction List_EventHandler(void* handle, const Event* e)
{
	CDTreeView* This = handle;
	CDDescList* pDoc = (CDDescList*) View_GetDocument(&This->m_View);

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

			CDSearch_Close(This);

			// Close last view on search window, closes document
			if (CDDescList_GetType(pDoc) == CDDescList_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()
			&&  CDTreeView_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 (CDTreeView_IsBusy(This))
				return EListenerAction_StopEvent;

			if (m->but == EBut_Menu)
			{
				CmdHandler_OpenMenu(&CDTreeView_CmdHandler, This, This->m_pCmdGroup);

				return EListenerAction_StopEvent;
			}

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

			if (area.id == EWItemArea_Select)
			{
				switch(m->but)
				{
					case EBut_Select:
					{
						CmdHandler_ExecCommand(&CDTreeView_CmdHandler, This, Cmd_CDList_Properties);

						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
					{
						CDTreeView_EditMetadata(This, -1, NULL);
						return EListenerAction_StopEvent;
					}
					break;
					case 0x00d: // Return
					{
						CDTreeView_StoreMetadata(This);
						return EListenerAction_StopEvent;
					}
					break;
				}
			}
			else
			{
				const int code = key->code;

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

				if (CmdHandler_KeyPressed(&CDTreeView_CmdHandler, This, "CDList", code))
					return EListenerAction_StopEvent;
			}
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			switch(((Msg*) e->pData)->hdr.action)
			{
				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)
					{
						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;
}

CDTreeView* throw_New_CDTreeView(CDDescList* pDoc)
{
	CDTreeView* This = throw_mem_calloc(1, sizeof(CDTreeView));
	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(CDTrackMap), 4096);

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

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

		section = CDTreeView_GetChoicesSection(pDoc, false);

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

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

		switch (CDDescList_GetType(pDoc))
		{
			case CDDescList_TypeMain:
				This->m_pCmdGroup = "CDList_Main";
			break;
			default: This->m_pCmdGroup = "CDList_Search";
		}

		try
		{
			This->m_pToolbar = throw_New_Toolbar(&CDTreeView_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_CDTreeView_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, CDTreeView_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionParentalLock, CDTreeView_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, CDTreeView_OnDocEvent);

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

	return This;
}

int CDTreeView_GetFieldList(const CDTreeView* This, const CDField_Info** ppFieldList)
{
	*ppFieldList = This->m_FieldList;

	return This->m_FieldCount;
}

unsigned int CDTreeView_GetFieldAction(const CDTrack* pTrack, ECDField_Id id)
{
	IGNORE(pTrack);

	switch(id)
	{
		case ECDField_Album: return CDItemAction_EditAlbum; break;
		case ECDField_AlbumAuthor: return CDItemAction_EditAlbumAuthor; break;
		case ECDField_Title: return CDItemAction_EditTitle; break;
		case ECDField_Author: return CDItemAction_EditAuthor; break;
		default: return EWItemArea_Select;
	}
}
