#include <string.h>
#include "WimpLib:Log.h"

#include "WimpLib:OTreeList.h"

#include "WimpLib:Desktop.h"
#include "WimpLib:Display.h"
#include "WimpLib:Exception.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:mem.h"
#include "WimpLib:Message.h"
#include "WimpLib:OrderedSet.h"
#include "WimpLib:Task.h"
#include "WimpLib:Window.h"

struct OTreeNode
{
	OTreeNode*           pParent;
	const OTreeNodeVPtr* pVPtr;
	union
	{
		OrderedNode*     pSortNode;
		OTreeList*       pTreeList;
	} s;
	OrderedSet*          pChilds;
	bool                 bExpanded;
	bool                 bToExpand;
	uint32_t             Data[];
};

struct OTreeList
{
	WListCore	m_Core;
	OTreeNode*	m_pRootNode;
	void*       m_pHandle;
};

#define Cmd_ExpandOrCollapse  0
#define Cmd_ExplodeOrCollapse 1
#define Cmd_ExplodeAllOrCollapse 2

/*-------------------------------------------------------------------------*
 *--- OTreeList Redraw handling -------------------------------------------*
 *-------------------------------------------------------------------------*/

#define Tree_Indent 40
#define Tree_Height 44

static void OTreeList_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeList* This = pOwner;
	const OTreeNode* pNode = pObject;

	pNode->pVPtr->item.FPlot(pNode, This->m_pHandle, pItem);
}

static void OTreeList_GetSizes(const void* pObject, const void* pOwner, int* pSizes)
{
	const OTreeList* This = pOwner;
	const OTreeNode* pNode = pObject;

	if (pNode->pVPtr->item.FGetSizes)
		pNode->pVPtr->item.FGetSizes(pNode, This->m_pHandle, pSizes);
}

static CSize OTreeList_GetBox(const void* pObject, const void* pOwner, const int* pSizes, const int* pMaxSizes)
{
	const OTreeList* This = pOwner;
	const OTreeNode* pNode = pObject;
	CSize s;

	if (!pNode) pNode = This->m_pRootNode;

	if (pNode)
		return pNode->pVPtr->item.FGetBox(pNode, This->m_pHandle, pSizes, pMaxSizes);
	else
	{
		s.cx = 4;
		s.cy = 4;
		return s;
	}
}

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

	pNode->pVPtr->item.FGetArea(pNode, This->m_pHandle, pItem, pMouse, pArea);
}

int OTreeNode_DefWidth(const OTreeNode* This)
{
	return OTreeNode_GetLevel(This) * Tree_Indent;
}

void OTreeNode_DefArea(const OTreeNode* This, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	int x, y;
	int level = OTreeNode_GetLevel(This);
	pArea->rect = pItem->m_box;

	if (pMouse == NULL)
	{
		pArea->rect.x0 += level * Tree_Indent;
		return;
	}

	CPoint offset = {pMouse->pt.x - pItem->m_box.x0, pMouse->pt.y - pItem->m_box.y0};

	if (offset.x >= level * Tree_Indent)
	{
		pArea->rect.x0 += level * Tree_Indent;
		pArea->id = EWItemArea_Select;
		return;
	}

	x = level * Tree_Indent - Tree_Indent/2;
	y = (pItem->m_box.y1 - pItem->m_box.y0)/2;

	if (OTreeNode_AllowExpand(This)
	&&  (offset.x >= x - 10)
	&&  (offset.x <= x + 10)
	&&  (offset.y >= y - 10)
	&&  (offset.y <= y + 10))
	{
		pArea->rect.x0 += x - 10;
		pArea->rect.y0 += y - 10;
		pArea->rect.x1 = pArea->rect.x0 + 20;
		pArea->rect.y1 = pArea->rect.y0 + 20;

		if (pMouse->but & (EBut_Menu|EBut_DragSelect|EBut_DragAdjust))
		{
			pArea->id = EWItemArea_Inactive;
			return;
		}

		if ((pMouse->but == EBut_Select)
		||  (pMouse->but == EBut_ClickSelect))
		{
			if (Keyboard_PollCtrl())
				pArea->id = EWOTreeItemArea_ExplodeAllOrCollapse;
			else if (Keyboard_PollShift())
				pArea->id = EWOTreeItemArea_ExplodeOrCollapse;
			else
				pArea->id = EWOTreeItemArea_ExpandOrCollapse;

			return;
		}

		pArea->id = EWOTreeItemArea_ExplodeOrCollapse;
		return;
	}

	pArea->id = EWItemArea_Outside;
}

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

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

	Desktop_SetStdColour(7);

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

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

	pBrother = OTreeNode_GetPredecessor(pNode);
	if (pBrother || OTreeNode_GetParent(OTreeNode_GetParent(pNode)))
	{
		Display_Plot(4, x, box.y1);
		Display_Plot(29, x, y + (bHasBox ? 8 : 0));
	}

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

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

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

	pTmp = OTreeNode_GetParent(pNode);

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

/*-------------------------------------------------------------------------*
 *--- OTreeNode Routines --------------------------------------------------*
 *-------------------------------------------------------------------------*/

int OTreeNode_GetLevel(const OTreeNode* pNode)
{
	int level = 0;

	while (pNode->pParent)
	{
		level++;
		pNode = pNode->pParent;
	}

	return level;
}

OTreeList* OTreeNode_GetTreeList(const OTreeNode* pNode)
{
	while (pNode->pParent)
		pNode = pNode->pParent;

	return pNode->s.pTreeList;
}

void OTreeNode_SetTreeList(OTreeNode* pNode, OTreeList* pTreeList)
{
	pNode->s.pTreeList = pTreeList;
	pNode->pParent = NULL;
}

/*-------------------------------------------------------------------------*
 *--- OTreeNode management ------------------------------------------------*
 *-------------------------------------------------------------------------*/

static void OTreeNode_SetDelete(void* pHandle, void* pData)
{
	IGNORE(pHandle);
	Delete_OTreeNode(pData);
}

/**
 * Compares two nodes' data according to parent node's criterias.
 *
 * @param   pa  Pointer to external data
 * @param   pb  Pointer to ordered tree node
 *
 * @returns     Signed value: < 0 if pa is lower than pb, 0 if pa is identical to pb, > 0 otherwise
 */

static int OTreeNode_SetCompare(const void* pa, const void* pb)
{
	const OTreeNode* pNodeA = pa;
	const OTreeNode* pNodeB = pb;

	return pNodeB->pParent->pVPtr->FCompare(pNodeA->Data, pNodeB->Data);
}

static const OrderedSetVPtr OSetVPtr =
{
	  NULL
	, OTreeNode_SetDelete
	, OTreeNode_SetCompare
};

/**
 * Creates a new ordered tree node.
 *
 * @param   pVPtr    Pointer to the node's functions set (VTable).
 * @param   size     size of the nodes data.
 * @param   pData    Pointer to the node's data.
 *
 * @returns          Pointer to a new OTreeNode.
 */

OTreeNode* throw_New_OTreeNode(const OTreeNodeVPtr* pVPtr, uint32_t size, const void* pData)
{
	uint32_t memsize = sizeof(OTreeNode) + size;
	OTreeNode* This = throw_mem_calloc(1, memsize);

	try
	{
		This->pVPtr = pVPtr;
		if (pVPtr->throw_FAllocData)
			pVPtr->throw_FAllocData(This, This->Data, pData);
		else
			memcpy(This->Data, pData, size);

		This->bExpanded = false;

		// Creates an ordered set which will take ordered tree nodes as parameters.
		// When the set is deleted all the nodes must be deleted as well.
		This->pChilds = New_OrderedSet(This, &OSetVPtr);
	}
	catch
	{
		Delete_OTreeNode(This);
		throw_current();
	}
	catch_end

	return This;
}

/**
 * Deletes the node and all its child nodes.
 * This function is called from the parent's ordered set Remove function
 * and so must not be called directly except for a root node.
 */

void Delete_OTreeNode(OTreeNode* This)
{
	if (This)
	{
		Delete_OrderedSet(This->pChilds);

		if (This->pVPtr->FClearData)
			This->pVPtr->FClearData(This, This->Data);

		mem_free(This);
	}
}

/**
 * Returns the VPtr table associated to the node.
 *
 * @returns     Pointer to the VPtr table associated to the node.
 */

const OTreeNodeVPtr* OTreeNode_GetVPtr(const OTreeNode* This)
{
	return This->pVPtr;
}

/**
 * Returns the data associated to the node.
 *
 * @returns     Pointer to the data associated to the node.
 */

const void* OTreeNode_GetData(const OTreeNode* This)
{
	return This->Data;
}

/**
 * Returns the number of child nodes associated to the node.
 *
 * @returns     Number of child nodes.
 */

int OTreeNode_CountChilds(const OTreeNode* This)
{
	return OrderedSet_Count(This->pChilds);
}

/**
 * Returns the parent node to this node.
 *
 * @returns     Pointer to parent node.
 */

OTreeNode* OTreeNode_GetParent(const OTreeNode* This)
{
	return This->pParent;
}

/**
 * Returns the firt child node to this node.
 *
 * @returns     Pointer to first child node or NULL if no child exists.
 */

OTreeNode* OTreeNode_GetFirstChild(const OTreeNode* This)
{
	OrderedNode* pNode = OrderedSet_GetSuccessor(This->pChilds, NULL);

	if (pNode) return (OTreeNode*) OrderedSet_GetNodeData(This->pChilds, pNode);

	return NULL;
}

/**
 * Returns the predecessor to this node.
 *
 * @returns     Pointer to sibling node or NULL if no such sibling.
 */

OTreeNode* OTreeNode_GetPredecessor(const OTreeNode* This)
{
	const OrderedSet* pSet = This->pParent->pChilds;
	OrderedNode* pNode = OrderedSet_GetPredeccessor(pSet, This->s.pSortNode);

	if (pNode) return (OTreeNode*) OrderedSet_GetNodeData(pSet, pNode);

	return NULL;
}

/**
 * Returns the successor to this node.
 *
 * @returns     Pointer to sibling node or NULL if no such sibling.
 */

OTreeNode* OTreeNode_GetSuccessor(const OTreeNode* This)
{
	const OrderedSet* pSet = This->pParent->pChilds;
	OrderedNode* pNode = OrderedSet_GetSuccessor(pSet, This->s.pSortNode);

	if (pNode) return (OTreeNode*) OrderedSet_GetNodeData(pSet, pNode);

	return NULL;
}

/**
 * Checks if a node may be expanded to show its child nodes.
 *
 * @returns     true if the node may be expanded.
 */

bool OTreeNode_AllowExpand(const OTreeNode* This)
{
	if (This->pVPtr->FOnExpand)
		This->pVPtr->FOnExpand(This);

	if (!OrderedSet_Count(This->pChilds))
		return false;

	return true;
}

/**
 * Determines if a node is expanded (displayed or memorized as being in such state).
 *
 * @returns     true if the node is expanded.
 */

bool OTreeNode_IsExpanded(const OTreeNode* This)
{
	return This->bExpanded;
}

/**
 * Marks a node as expanded (displayed or memorized as being in such state).
 */

void OTreeNode_SetExpanded(OTreeNode* This, bool bExpanded)
{
	This->bExpanded = bExpanded;
	This->bToExpand = false;
}

/**
 * Removes a node from the list of child nodes and deletes it.
 *
 * @param   pChild  Pointer to the child node.
 */
static void OTreeNode_DeleteChild(OTreeNode* This, const OTreeNode* pChild)
{
	// The following call will also delete the child node
	OrderedSet_RemoveNode(This->pChilds, pChild->s.pSortNode);
}

/**
 * Resort a node in the list of child nodes.
 *
 * @param   pChild  Pointer to the child node.
 *
 * @returns true if node was moved in the sorted list.
 */
static bool OTreeNode_UpdateChild(OTreeNode* This, const OTreeNode* pChild)
{
	OrderedNode* pSuccessor = OrderedSet_GetSuccessor(This->pChilds, pChild->s.pSortNode);
	OrderedNode* pOld = pChild->s.pSortNode;
	OrderedNode* pNew = OrderedSet_Update(This->pChilds, pChild->s.pSortNode);

	// Don't allow node merging
	throw_assert(pNew == pOld);

	return (OrderedSet_GetSuccessor(This->pChilds, pNew) != pSuccessor);
}

/**
 * Returns a tree node corresponding to the given data from
 * the ordered list of child nodes. The node either
 * corresponds to matching data already present in the
 * list or is a new one that was inserted and ordered
 * if the list of childs.
 *
 * @param   pNodeVPtr  Pointer to the node's class functions.
 * @param   size       Size of the node's data.
 * @param   pData      Pointer to the child node's data.
 *
 * @returns Pointer to a tree node.
 */

OTreeNode* throw_OTreeNode_InsertChild(OTreeNode* This, const OTreeNodeVPtr* pNodeVPtr, uint32_t size, const void* pData)
{
	OTreeNode* pNewNode = throw_New_OTreeNode(pNodeVPtr, size, pData);
	OTreeNode* pChild = NULL;

	try
	{
		OTreeList* pWList = OTreeNode_GetTreeList(This);
		OrderedNode* pNode = OrderedSet_Add(This->pChilds, pNewNode);
		pChild = (OTreeNode*) OrderedSet_GetNodeData(This->pChilds, pNode);

		// Duplicate ?
		if (pChild->s.pSortNode)
		{
			// Keep node counter to 1
			OrderedSet_RemoveNode(This->pChilds, pNode);
			// Remove or created node
			Delete_OTreeNode(pNewNode);
			pNewNode = NULL;
		}
		else
		{
			// Child is a new one
			pChild->pParent = This;
			pChild->s.pSortNode = pNode;

			// Show child if parent is visible and expanded
			if (OTreeNode_IsExpanded(This))
			{
				if (OTreeList_IsRefreshAllowed(pWList))
				{
					int pos = 0;

					if (pWList->m_pRootNode == This)
						OTreeList_Expand(pWList, -1, 0, false);
					else
					{
						pos = WListCore_Find(OTreeList_GetWListCore(pWList), 0, This);
						if (pos != -1)
							OTreeList_Expand(pWList, pos, 0, false);
						else Log("Parent was supposed to be visible\n");
					}
					pos = WListCore_Find(OTreeList_GetWListCore(pWList), pos, pChild);
					if (pos == -1)
						Log("Child was supposed to be made visible\n");
				}
				else
				{
					// mark parents has to be expanded
					OTreeNode* pParent = pChild;
					do
					{
						pParent = pParent->pParent;
						pParent->bToExpand = true;
					}
					while (pParent != pWList->m_pRootNode);
				}
			}
		}
	}
	catch
	{
		Delete_OTreeNode(pNewNode);
		throw_current();
	}
	catch_end

	return pChild;
}

/*-------------------------------------------------------------------------*
 *--- OTreeList management ------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Determines the index in the list after a node and its childs.
 *
 * @param   index  Index of the node in the displayed list.
 *
 * @returns        Index after the node and its childs.
 */

static int OTreeList_GetIndexAfterNode(OTreeList* This, int index) throws(index)
{
	int max = WListCore_Count(&This->m_Core);
	OTreeNode* pParent = WListCore_Get(&This->m_Core, index);
	OTreeNode* pNext;

	index++;

	while(index < max)
	{
		pNext = WListCore_Get(&This->m_Core, index);
		if (pNext->pParent == pParent)
			index = OTreeList_GetIndexAfterNode(This, index);
		else
			break;
	}

	return index;
}

/**
 * Collapse a node in the displayed list.
 *
 * @param   index  Index of the node in the displayed list or -1 to hide all nodes.
 * @param   b      True if child nodes are to be marked as not expanded.
 */

static void OTreeList_Collapse(OTreeList* This, int index) throws(index)
{
	OTreeNode* pParent;
	int end;

	WListCore_AllowRefresh(&This->m_Core, false);

	if (index == -1)
	{
		pParent = This->m_pRootNode;
		end = WListCore_Count(&This->m_Core);
	}
	else
	{
		pParent = WListCore_Get(&This->m_Core, index);
		end = OTreeList_GetIndexAfterNode(This, index);
	}

	WListCore_DelRange(&This->m_Core, index + 1, end);
	WListCore_Refresh(&This->m_Core, index, index);

	OTreeNode_SetExpanded(pParent, false);

	WListCore_AllowRefresh(&This->m_Core, true);
}

/**
 * Expands a node in the displayed list.
 *
 * @param   index   Index of the node in the displayed list or -1 to show the root nodes.
 * @param   mode    0 if child nodes are not to be reopened.
 *                  1 if child nodes are to be reopened accordingly to their expanded state.
 *                  2 if child nodes are to be reopened.
 *                  3 if child nodes are waiting to be opened.
 * @param   bShow   True if should be made visible in window work area.
 *
 * @returns         Index after the node and its childs.
 */

static bool OTreeNode_ShouldExpandChild(OTreeNode* pChild, int mode)
{
	switch(mode)
	{
		case 1: return OTreeNode_IsExpanded(pChild);
		case 2: return true;
		case 3: return pChild->bToExpand;
	}

	return false;
}

int OTreeList_Expand(OTreeList* This, int index, int mode, bool bShow)
{
	OTreeNode* pParent;
	OTreeNode* pChild;
	int pred;
	int sindex;

	if (index >= 0)
	{
		sindex = index;
		pParent = WListCore_Get(&This->m_Core, index);
	}
	else
	{
		sindex = 0;
		pParent = This->m_pRootNode;
	}

	if (!OTreeNode_AllowExpand(pParent))
		return index + 1;

	WListCore_AllowRefresh(&This->m_Core, false);

	OTreeNode_SetExpanded(pParent, true);

	pChild = OTreeNode_GetFirstChild(pParent);

	pred = index;
	index++;

	while(pChild)
	{
		if ((index >= WListCore_Count(&This->m_Core))
		||  (WListCore_Get(&This->m_Core, index) != pChild))
		{
			try
			{
				throw_WListCore_Insert(&This->m_Core, index, pChild);

				// Refresh parent or sibling
				WListCore_Refresh(&This->m_Core, pred, index);
				pred = index;

				// Expand?
				if (OTreeNode_ShouldExpandChild(pChild, mode))
					index = OTreeList_Expand(This, index, mode, false);
				else
				{
					OTreeNode_SetExpanded(pChild, false);
					index++;
				}
			}
			catch
			{
				break;
			}
			catch_end
		}
		else
		{
			pred = index;
			// Expand?
			if (OTreeNode_ShouldExpandChild(pChild, mode))
				index = OTreeList_Expand(This, index, mode, false);
			else
				index = OTreeList_GetIndexAfterNode(This, index);
		}
		pChild = OTreeNode_GetSuccessor(pChild);
	}

	WListCore_AllowRefresh(&This->m_Core, true);

	// Move all of it to the window's visible area if possible
	if (bShow)
	{
		WListCore_ShowItem(&This->m_Core, index);
		WListCore_ShowItem(&This->m_Core, sindex);
	}

	return index;
}

/**
 * Executes a command on the treelist.
 *
 * @param   cmd     Command to execute.
 */

static void OTreeList_ExecCommand(OTreeList* This, int cmd)
{
	switch(cmd)
	{
		case Cmd_ExpandOrCollapse:
		{
			int index = WListCore_GetFocus(&This->m_Core);
			OTreeNode* pNode = WListCore_Get(&This->m_Core, index);

			if (OTreeNode_IsExpanded(pNode))
				OTreeList_Collapse(This, index);
			else if (OTreeNode_AllowExpand(pNode))
				OTreeList_Expand(This, index, 0, true);
		}
		break;
		case Cmd_ExplodeOrCollapse:
		{
			int index = WListCore_GetFocus(&This->m_Core);
			OTreeNode* pNode = WListCore_Get(&This->m_Core, index);

			if (OTreeNode_IsExpanded(pNode))
				OTreeList_Collapse(This, index);
			else if (OTreeNode_AllowExpand(pNode))
				OTreeList_Expand(This, index, 1, true);
		}
		break;
		case Cmd_ExplodeAllOrCollapse:
		{
			int index = WListCore_GetFocus(&This->m_Core);
			OTreeNode* pNode = WListCore_Get(&This->m_Core, index);

			if (OTreeNode_IsExpanded(pNode))
				OTreeList_Collapse(This, index);
			else if (OTreeNode_AllowExpand(pNode))
				OTreeList_Expand(This, index, 2, true);
		}
		break;
	}
}

/**
 * TreeList event handler.
 */

static EListenerAction OTreeList_EventHandler(void* handle, const Event* e)
{
	OTreeList* This = handle;

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

			// Ignore key presses in icons
			if (m->i != HIcon_None)
				return EListenerAction_ContinueEvent;

			if (!WListCore_IsRefreshAllowed(&This->m_Core))
				return EListenerAction_StopEvent;

			WItemArea area;
			int item = WListCore_ItemAreaFromScreenPt(&This->m_Core, m, 0, &area);

			switch(area.id)
			{
				case EWOTreeItemArea_ExpandOrCollapse:
				{
					WListCore_SetFocus(&This->m_Core, item, true);
					OTreeList_ExecCommand(This, Cmd_ExpandOrCollapse);

					return EListenerAction_StopEvent;
				}
				break;
				case EWOTreeItemArea_ExplodeOrCollapse:
				{
					WListCore_SetFocus(&This->m_Core, item, true);
					OTreeList_ExecCommand(This, Cmd_ExplodeOrCollapse);

					return EListenerAction_StopEvent;
				}
				break;
				case EWOTreeItemArea_ExplodeAllOrCollapse:
				{
					WListCore_SetFocus(&This->m_Core, item, true);
					OTreeList_ExecCommand(This, Cmd_ExplodeAllOrCollapse);

					return EListenerAction_StopEvent;
				}
				break;
				case EWItemArea_Select:
				{
					WListCore_SetFocus(&This->m_Core, item, true);
				}
				break;
				case EWItemArea_Inactive:
				{
					return EListenerAction_StopEvent;
				}
				break;
			}
		}
		break;
		case EEvent_Key:
		{
			const Event_Key* key = e->pData;

			// Ignore key presses in icons
			if (key->caret.pos.i != HIcon_None)
				return EListenerAction_ContinueEvent;

			if (!WListCore_IsRefreshAllowed(&This->m_Core))
				return EListenerAction_StopEvent;

			switch(key->code)
			{
				case 0x1cd: // Ins
				case 0x1dd: // Shift + Ins
				case 0x1ed: // Ctrl + Ins
				{
					// Intercept
					return EListenerAction_StopEvent;
				}
				break;
				case 0x00d: // Return
				case 0x10d: // Shift + Return
				case 0x12d: // Ctrl + Return
				{
					int index = WListCore_GetFocus(&This->m_Core);

					if ((index >= 0) && (index < WListCore_Count(&This->m_Core)))
					{
						OTreeNode* pNode = WListCore_Get(&This->m_Core, index);

						if (OTreeNode_AllowExpand(pNode))
						{
							OTreeList_ExecCommand(This, Keyboard_PollCtrl() ? Cmd_ExplodeAllOrCollapse
						                          : Keyboard_PollShift() ? Cmd_ExplodeOrCollapse
						                          : Cmd_ExpandOrCollapse);

							return EListenerAction_StopEvent;
						}
					}
				}
				break;
				case 0x18c: // left arrow
				{
					int index = WListCore_GetFocus(&This->m_Core);

					if (index > 0)
					{
						OTreeNode* pChild = WListCore_Get(&This->m_Core, index);
						OTreeNode* pTarget;
						OTreeNode* pPrevious;

						// goto predecessor
						pTarget = OTreeNode_GetPredecessor(pChild);

						// if none, goto parent
						if (!pTarget)
						{
							// if none, move to previous entry
							if (OTreeNode_GetParent(pChild) == This->m_pRootNode)
								pTarget = WListCore_Get(&This->m_Core, index - 1);
							else
								pTarget = OTreeNode_GetParent(pChild);
						}

						if (!pTarget) return EListenerAction_StopEvent;

						do
						{
							index--;
							pPrevious = WListCore_Get(&This->m_Core, index);
						}
						while(pPrevious != pTarget);

						WListCore_MoveFocus(&This->m_Core, index);
					}

					return EListenerAction_StopEvent;
				}
				break;
				case 0x18d: // right arrow
				{
					int index = WListCore_GetFocus(&This->m_Core);
					int max = WListCore_Count(&This->m_Core);

					if ((index + 1) < max)
					{
						OTreeNode* pChild = WListCore_Get(&This->m_Core, index);
						OTreeNode* pTarget;

						// goto successor
						pTarget = OTreeNode_GetSuccessor(pChild);

						// if none, goto successor of parent
						while (!pTarget && (OTreeNode_GetParent(pChild) != This->m_pRootNode))
						{
							pChild = OTreeNode_GetParent(pChild);
							pTarget = OTreeNode_GetSuccessor(pChild);
						}

						// if none, goto next entry
						if (!pTarget)
							index++;
						else
						{
							index = WListCore_Find(&This->m_Core, index, pTarget);
							// just in case display is damaged
							if (index == -1)
								index = WListCore_GetFocus(&This->m_Core) + 1;
						}

						WListCore_MoveFocus(&This->m_Core, index);
					}

					return EListenerAction_StopEvent;
				}
				break;
				case 0x19c: // Shift + left arrow
				{
					int index = WListCore_GetFocus(&This->m_Core);

					if (index > 0)
					{
						OTreeNode* pChild = WListCore_Get(&This->m_Core, index);
						OTreeNode* pTarget;
						OTreeNode* pPrevious;

						// goto parent if possible, else goto predecessor
						if (OTreeNode_GetParent(pChild) != This->m_pRootNode)
							pTarget = OTreeNode_GetParent(pChild);
						else
					    	pTarget = OTreeNode_GetPredecessor(pChild);

						if (!pTarget) return EListenerAction_StopEvent;

						do
						{
							index--;
							pPrevious = WListCore_Get(&This->m_Core, index);
						}
						while(pPrevious != pTarget);

						WListCore_MoveFocus(&This->m_Core, index);
					}

					return EListenerAction_StopEvent;
				}
				break;
				case 0x19d: // Shift + right arrow
				{
					int index = WListCore_GetFocus(&This->m_Core);
					int max = WListCore_Count(&This->m_Core);

					if ((index + 1) < max)
					{
						OTreeNode* pChild = WListCore_Get(&This->m_Core, index);
						OTreeNode* pTarget = NULL;

						// goto successor of parent if possible
						while (!pTarget && (OTreeNode_GetParent(pChild) != This->m_pRootNode))
						{
							pChild = OTreeNode_GetParent(pChild);
							pTarget = OTreeNode_GetSuccessor(pChild);
						}

						// if none, goto successor
						if (!pTarget)
							pTarget = OTreeNode_GetSuccessor(pChild);

						// if none, goto next entry
						if (!pTarget)
							index++;
						else
						{
							index = WListCore_Find(&This->m_Core, index, pTarget);
							// just in case display is damaged
							if (index == -1)
								index = WListCore_GetFocus(&This->m_Core) + 1;
						}

						WListCore_MoveFocus(&This->m_Core, index);
					}

					return EListenerAction_StopEvent;
				}
				break;
			}
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			switch(((Msg*) e->pData)->hdr.action)
			{
				case EMsg_HelpRequest:
				{
					if (!WListCore_IsRefreshAllowed(&This->m_Core))
						return EListenerAction_StopEvent;

					const Msg_HelpRequest* msg = e->pData;
					WItemArea area;
					int item = WListCore_ItemAreaFromScreenPt(&This->m_Core, &msg->m, 0, &area);

   					switch(area.id)
   					{
						case EWOTreeItemArea_ExpandOrCollapse:
						case EWOTreeItemArea_ExplodeOrCollapse:
						case EWOTreeItemArea_ExplodeAllOrCollapse:
						{
							const OTreeNode* pNode = WListCore_Get(&This->m_Core, item);
							const char* txt;

							if (OTreeNode_IsExpanded(pNode))
								txt = "HlpTree-";
							else
								txt = "HlpTree+";
							Task_HelpReply(msg, Msg_Lookup(txt));

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

static WListCore_FWList FWList =
{
	{ OTreeList_Plot
	, OTreeList_GetSizes
	, OTreeList_GetBox
	, OTreeList_GetArea
	, NULL
	}
	, WOwner_PlotBackGround_White
};

OTreeList* throw_New_OTreeList(unsigned int flags, CTemplate* t, OTreeNode* pRootNode, void* pHandle, int NrSizes)
{
	OTreeList* volatile This = NULL;

	try
	{
		This = throw_mem_calloc(sizeof(OTreeList), 1);
	}
	catch
	{
		Delete_OTreeNode(pRootNode);
		throw_current();
	}
	catch_end;

	try
	{
		This->m_pRootNode = pRootNode;
		This->m_pHandle   = pHandle;
		OTreeNode_SetTreeList(pRootNode, This);

		throw_WListCore_WListCore(&This->m_Core, flags, t, This, NrSizes, &FWList);
		throw_Window_RegisterEventHandler(WListCore_GetWindow(&This->m_Core), WListCore_ListEventHandler, &This->m_Core, false);
		throw_Window_RegisterEventHandler(WListCore_GetWindow(&This->m_Core), OTreeList_EventHandler, This, true);
	}
	catch
	{
		Delete_OTreeList(This);
		throw_current();
	}
	catch_end;

	return This;
}

void Delete_OTreeList(OTreeList* This)
{
	if (This)
	{
		Window_DeRegisterEventHandler(WListCore_GetWindow(&This->m_Core), OTreeList_EventHandler, This);
		Window_DeRegisterEventHandler(WListCore_GetWindow(&This->m_Core), WListCore_ListEventHandler, &This->m_Core);
		WListCore_NotWListCore(&This->m_Core);
		Delete_OTreeNode(This->m_pRootNode);
		mem_free(This);
	}
}

OTreeNode* OTreeList_GetWListItem(const OTreeList* This, int index) throws(index)
{
	return WListCore_Get(&This->m_Core, index);
}

OTreeNode* OTreeList_GetRootNode(const OTreeList* This)
{
	return This->m_pRootNode;
}

void OTreeList_SetRootNode(OTreeList* This, OTreeNode* pRootNode)
{
	WListCore_Clear(&This->m_Core);
	Delete_OTreeNode(This->m_pRootNode);

	This->m_pRootNode = pRootNode;
	OTreeNode_SetTreeList(pRootNode, This);
}

/**
 * Expands the parent node of a given node in the treelist to render
 * the node visible and returns the index of the node in the list.
 *
 * @param pNode  Pointer to node to make visible.
 *
 * @returns Index of the node in the list.
 */

int OTreeList_ExpandParents(OTreeList* This, const OTreeNode* pNode)
{
	int index = -1;

	if (!pNode) pNode = This->m_pRootNode;

	if (pNode != This->m_pRootNode)
	{
		// Expand parent node
		index = OTreeList_ExpandParents(This, OTreeNode_GetParent(pNode));
	}

	OTreeList_Expand(This, index, 0, false);

	return WListCore_Find(&This->m_Core, index, pNode);
}

void OTreeList_ShowItem(OTreeList* This, int index)
{
	WListCore_ShowItem(&This->m_Core, index);
}

void OTreeList_Update(OTreeList* This, OTreeNode* pNode)
{
	int pos = WListCore_Find(&This->m_Core, 0, pNode);

	bool bMoved = OTreeNode_UpdateChild(OTreeNode_GetParent(pNode), pNode);

	if (pos != -1)
	{
		if (bMoved)
		{
			WListCore_Del(&This->m_Core, pos);
			OTreeList_ExpandParents(This, pNode);
		}
		else WListCore_Set(&This->m_Core, pos, WListCore_Get(&This->m_Core, pos));
	}
}

void OTreeList_Remove(OTreeList* This, OTreeNode* pNode)
{
	int pos = WListCore_Find(&This->m_Core, 0, pNode);

	if (pos != -1)
	{
		WListCore_Del(&This->m_Core, pos);

		// If child was the last one, everything starting
		// from the predecessor to the child must be redrawn (lines between nodes)
		// and if no predecessor the parent must be redrawn (no more collapse/expand box)
		if ((pos > 0) && !OTreeNode_GetSuccessor(pNode))
		{
			const OTreeNode* pPredNode = OTreeNode_GetPredecessor(pNode);
			int startpos;

			if (pPredNode != NULL)
				startpos = WListCore_Find(&This->m_Core, 0, pPredNode);
			else
				startpos = pos - 1;

			WListCore_Refresh(&This->m_Core, startpos, pos - 1);
		}
	}

	OTreeNode_DeleteChild(OTreeNode_GetParent(pNode), pNode);
}

HWind OTreeList_GetWindow(const OTreeList* This)
{
	return WListCore_GetWindow(&This->m_Core);
}

WListCore* OTreeList_GetWListCore(OTreeList* This)
{
	return &This->m_Core;
}

void OTreeList_AllowRefresh(OTreeList* This, bool bAllow)
{
	if (WListCore_IsRefreshAllowed(&This->m_Core) == bAllow)
		return;

	WListCore_AllowRefresh(&This->m_Core, bAllow);

	if (bAllow)
	{
		OTreeList_Expand(This, -1, 3, false);
	}
}
