#include "WimpLib:WListCore.h"

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

#include "WimpLib:mem.h"
#include "WimpLib:Coords.h"
#include "WimpLib:Desktop.h"
#include "WimpLib:Display.h"
#include "WimpLib:DragDrop.h"
#include "WimpLib:Exception.h"
#include "WimpLib:Hourglass.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:Menu.h"
#include "WimpLib:Task.h"
#include "WimpLib:Template.h"
#include "WimpLib:Window.h"
#include "WimpLib:Log.h"

static EListenerAction WListCore_MsgHandler(void* handle, const Event* e);
static EListenerAction WListCore_NullHandler(void* handle, const Event* e);
static void WListCore_Invalidate(WListCore* This, int min, int max, bool bUpdateExtent);

typedef struct WListItem
{
	void*        m_pobj;
	unsigned int m_state;
} WListItem;

/*-------------------------------------------------------------------------*
 *--- List creation/deletion ----------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * List constructor.
 *
 * @param  flags      List flags.
 * @param  t          Pointer to the template from which to create the window.
 * @param  pHandle    Handle for item management callbacks.
 * @param  NrSizes    Number of sizes used by an item.
 * @param  pFWList    Pointer to the item management callbacks.
 */

void throw_WListCore_WListCore(WListCore* This
		, unsigned int flags
		, CTemplate* t
		, void* pHandle
		, int NrSizes
		, WListCore_FWList* pFWList
		)
{
	CWind* pinfo = Template_GetWindow(t);
	int dx = pinfo->ex.x1 - pinfo->ex.x0;
	int dy = pinfo->ex.y1 - pinfo->ex.y0;

	memset(This, 0, sizeof(*This));
	This->m_wnd = HWind_None;

	try
	{
		WListCore_SetFlags(This, flags);
		This->m_coreflags = EWListCore_Init | EWListCore_ShrinkSize;

		// Select the corner to use as work area origin
		// and update the other limits accordingly
		if (This->m_flags & EWList_RightToLeft)
		{
			pinfo->ex.x0 = -dx;
			pinfo->ex.x1 = 0;
		}
		else
		{
			pinfo->ex.x0 = 0;
			pinfo->ex.x1 = dx;
		}

		if (This->m_flags & EWList_BottomToTop)
		{
			pinfo->ex.y0 = 0;
			pinfo->ex.y1 = dy;
		}
		else
		{
			pinfo->ex.y0 = -dy;
			pinfo->ex.y1 = 0;
		}

		// Create window
		This->m_wnd = throw_Window_Create(t, NULL);

		This->m_parentwnd = HWind_None;
		This->m_OwnerWnd  = HWind_None;

		This->m_pHandle = pHandle;

		This->m_NrSizes = (NrSizes > 0) ? NrSizes : 0;
		This->m_pFWList = pFWList;

		This->m_pList = New_Array(sizeof(WListItem) + This->m_NrSizes * sizeof(int), 50);

		This->m_RectToFit.x0 = 0;
		This->m_RectToFit.y0 = 0;
		This->m_RectToFit.x1 = 1;
		This->m_RectToFit.y1 = 1;
		This->m_MinSize.cx = 4;
		This->m_MinSize.cy = 4;
		This->m_Size = This->m_MinSize;
		This->m_pItemSizes = throw_mem_alloc(This->m_NrSizes * sizeof(int));
		This->m_pItemMaxs = throw_mem_alloc(This->m_NrSizes * sizeof(int));

		This->m_FocusPos = 0;
		This->m_HoverPos = -1;
		This->m_refresh.m_Min = -1;
		This->m_refresh.m_OldMax = -1;
		This->m_refresh.m_OldSize = This->m_Size;
		This->m_GhostCaret.x = -1;

		throw_Window_RegisterEventHandler(This->m_wnd, WListCore_EventHandler, This, false);
		throw_Task_AddListener(EEvent_Null, WListCore_NullHandler, This, false);
		throw_Task_AddMsgListener(EMsg_FontChanged, WListCore_MsgHandler, This, false);

		WListCore_RecalcSize(This);
	}
	catch
	{
		WListCore_NotWListCore(This);
		throw_current();
	}
	catch_end
}

/**
 * List destructor.
 */

void WListCore_NotWListCore(WListCore* This)
{
	if (This->m_wnd != HWind_None)
	{
		Task_RemoveMsgListener(EMsg_FontChanged, WListCore_MsgHandler, This);
		Task_RemoveListener(EEvent_Null, WListCore_NullHandler, This);
		Window_DeRegisterEventHandler(This->m_wnd, WListCore_EventHandler, This);

		Window_Delete(This->m_wnd);
		This->m_wnd = HWind_None;
	}

	Delete_Array(This->m_pList);
	This->m_pList = NULL;

	mem_free(This->m_pItemSizes);
	This->m_pItemSizes = NULL;

	mem_free(This->m_pItemMaxs);
	This->m_pItemMaxs = NULL;

	if (This->m_refresh.m_bShowHourglass)
	{
		This->m_refresh.m_bShowHourglass = false;
		WLib_Hourglass_Off();
	}
}

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

/**
 * Read an item's sizes and update the max sizes.
 *
 * @param  pItem      Pointer to an item.
 */

static void WListCore_UpdateItemSizes(WListCore* This, WListItem* pItem)
{
	int* psizes = (int*) (pItem + 1);
	unsigned int flag;
	int s;

	if (!This->m_NrSizes)
		return;

	This->m_pFWList->item.FGetSizes(pItem->m_pobj, This->m_pHandle, psizes);

	flag = EWListCore_ShrinkSize;

	for (s = 0; s < This->m_NrSizes; s++)
	{
		if (This->m_pItemSizes[s] == psizes[s])
		{
			This->m_pItemMaxs[s]++;
		}
		else if (This->m_pItemSizes[s] < psizes[s])
		{
			This->m_pItemMaxs[s] = 1;
			This->m_pItemSizes[s] = psizes[s];
		}

		// Shrink flag remains if shrinked size has not been maxed.
		if (!This->m_pItemMaxs[s])
			flag = 0;
	}

	// Remove unnecessary shrink flag
	This->m_coreflags &= ~flag;
}

/**
 * Removes the item's sizes from the max sizes count.
 *
 * @param  pItem      Pointer to an item.
 */

static void WListCore_RemoveItemSizes(WListCore* This, const WListItem* pItem)
{
	const int* psizes = (const int*) (pItem + 1);
	bool flag;
	int s;

	// Check if removed item data affects size of existing items
	flag = false;

	for (s = 0; s < This->m_NrSizes; s++)
	{
		if (This->m_pItemSizes[s] == psizes[s])
			This->m_pItemMaxs[s]--;

		if (!This->m_pItemMaxs[s])
			flag = true;
	}

	// If so we will need to recalculate new max sizes
	if (flag) This->m_coreflags |= EWListCore_ShrinkSize;
}

/*-------------------------------------------------------------------------*
 *--- List items management -----------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Returns the number of items in the list.
 *
 * @returns           Number of items in the list.
 */

int WListCore_Count(const WListCore* This)
{
	return Array_Count(This->m_pList);
}

/**
 * Inserts an item in the list before item of given index.
 *
 * @param  pobj       Pointer to the object to insert.
 * @param  Index      Index of item to insert before
 *                    or item count for insertion behind the last item
 *                    or -1 for insertion behind the last item.
 *
 * @returns           True if insertion succeeded.
 */

void throw_WListCore_Insert(WListCore* This, int Index, void* pobj)
{
	WListItem Item;
	WListItem* pItem;

	// Insert the item in the list
	Item.m_pobj = pobj;
	Item.m_state = 0;

	pItem = Array_Insert(This->m_pList, Index, &Item);

	// Insert at end of list ?
	if (Index == -1) Index = Array_Count(This->m_pList) - 1;

	// Check if item is bigger in size than existing items, set new size
	WListCore_UpdateItemSizes(This, pItem);

	// Move focus if required, the redraw will be done by the following lines
	if ((This->m_coreflags & EWListCore_HasFocus)
	&&  (Index <= This->m_FocusPos))
		This->m_FocusPos++;

	// Ask to set the new extent, redraw the new item, the moved ones and the moved focus
	WListCore_Invalidate(This, -1, Index, true);
}

/**
 * Gets the data associated to a given item.
 *
 * @param  Index      Index of the item.
 *
 * @returns           Pointer to the item's data.
 */

void* WListCore_Get(const WListCore* This, int Index) throws(index)
{
	const WListItem* pItem = Array_Get(This->m_pList, Index);

	return pItem->m_pobj;
}

/**
 * Changes the data associated to a given item.
 *
 * @param  Index      Index of item.
 * @param  pobj       Pointer to the new object.
 */

void WListCore_Set(WListCore* This, int Index, void* pobj) throws(index)
{
	WListItem* pItem = Array_Get(This->m_pList, Index);

	pItem->m_pobj = pobj;

	// Check if removed item data affects size of existing items
	WListCore_RemoveItemSizes(This, pItem);

	// Check if item is bigger in size than existing items, set new size
	WListCore_UpdateItemSizes(This, pItem);

	// Ask to set new extent, redraw the new item
	WListCore_Invalidate(This, Index, Index, true);
}

/**
 * Finds the index of the item associated to a given data.
 *
 * @param  i          Index in list to start with.
 * @param  pobj       Pointer to the object.
 *
 * @returns           Index of the object or -1 if not found.
 */

int WListCore_Find(const WListCore* This, int i, const void* pobj)
{
	int count = Array_Count(This->m_pList);
	if (i < 0) i = 0;

	for (; i < count; i++)
	{
		const WListItem* pItem = Array_Get(This->m_pList, i);

		if (pItem->m_pobj == pobj)
			return i;
	}

	return -1;
}

/**
 * Checks that the current item can have the focus
 * or moves to the nearest one in the given direction
 * which can have the focus.
 *
 * Returns the new item index or -1 if none is found
 */
static int WListCore_NewFocusPos(WListCore* This, int Index, int direction)
{
	const WListItem* pItem;
	int count = WListCore_Count(This);
	int pos = Index;

	if (direction > 0)
	{
		if (pos >= count) pos = count - 1;

		// try to find a previous one
		while (pos >= 0)
		{
			pItem = Array_Get(This->m_pList, pos);

			if (pItem->m_state & EWItem_ProtFocus)
				pos--;
			else
				return pos;
		}

		// try the other way around
		pos = Index + 1;
		if (pos < 0) pos = 0;

		while (pos < count)
		{
			pItem = Array_Get(This->m_pList, pos);

			if (pItem->m_state & EWItem_ProtFocus)
				pos++;
			else
				return pos;
		}

		return count;
	}
	else
	{
		if (pos < 0) pos = 0;

		// try to find a next one
		while (pos < count)
		{
			pItem = Array_Get(This->m_pList, pos);

			if (pItem->m_state & EWItem_ProtFocus)
				pos++;
			else
				return pos;
		}

		// try the other way around
		pos = Index - 1;
		if (pos >= count) pos = count - 1;

		while (pos >= 0)
		{
			pItem = Array_Get(This->m_pList, pos);

			if (pItem->m_state & EWItem_ProtFocus)
				pos--;
			else
				return pos;
		}

		return count;
	}
}

/**
 * Deletes a given item.
 *
 * @param  Index      Index of the item, -1 ignored.
 *
 * @returns           True if object existed.
 */

bool WListCore_Del(WListCore* This, int Index) throws(index)
{
	if (Index == -1)
		return false;

	const WListItem* pItem = Array_Get(This->m_pList, Index);

	// Check if removed item data affects size of existing items
	WListCore_RemoveItemSizes(This, pItem);

	// Delete object
	Array_Remove(This->m_pList, Index);

	// Move focus if required, the redraw will be done by the following lines
	if (This->m_coreflags & EWListCore_HasFocus)
	{
		if (This->m_FocusPos > Index)
			This->m_FocusPos--;
		// When deleting last ones, redraw of new last one may be required for focus
		else if (This->m_FocusPos == Index)
		{
			This->m_FocusPos = WListCore_NewFocusPos(This, Index, (Index != Array_Count(This->m_pList)));
			WListCore_Refresh(This, This->m_FocusPos, This->m_FocusPos);
		}
	}

	// Ask to set the new extent, redraw items after the deleted item
	WListCore_Invalidate(This, -1, Index, true);

	return true;
}

/**
 * Deletes a given range of items.
 *
 * @param  Mix        Index of the first item.
 * @param  Max        Index of the item following the last one to delete.
 *
 * @returns           True if object(s) existed.
 */

bool WListCore_DelRange(WListCore* This, int Start, int End)
{
	if ((Start < 0)
	||  (Start > End)
	||  (End > Array_Count(This->m_pList)))
		return false;

	for (int i = Start; i < End; i++)
	{
		const WListItem* pItem = Array_Get(This->m_pList, i);

		// Check if removed item data affects size of existing items
		WListCore_RemoveItemSizes(This, pItem);
	}

	// Delete objects
	Array_Delete(This->m_pList, Start, End);

	// Move focus if required, the redraw will be done by the following lines
	if (This->m_coreflags & EWListCore_HasFocus)
	{
		if (This->m_FocusPos > End)
			This->m_FocusPos -= End - Start;
		else if (This->m_FocusPos >= Start)
		{
			// When deleting last ones, redraw of new last one may be required for focus
			This->m_FocusPos = WListCore_NewFocusPos(This, Start, (Start != Array_Count(This->m_pList)));
			WListCore_Refresh(This, This->m_FocusPos, This->m_FocusPos);
		}
	}

	// Ask to set the new extent, redraw items after the deleted item
	WListCore_Invalidate(This, -1, Start, true);

	return true;
}

/**
 * Removes all items in the list.
 */

void WListCore_Clear(WListCore* This)
{
	int s;

	if (Array_Count(This->m_pList) <= 0) return;

	Array_Clear(This->m_pList);

	for (s = 0; s < This->m_NrSizes; s++)
	{
		This->m_pItemSizes[s] = 1;
		This->m_pItemMaxs[s] = 0;
	}

	// Move focus if required, the redraw will be done by the following lines
	if (This->m_coreflags & EWListCore_HasFocus)
		This->m_FocusPos = 0;

	// Ask to set the new extent, redraw all items
	WListCore_Invalidate(This, -1, -1, true);
}

/*-------------------------------------------------------------------------*
 *--- List coords conversions ---------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Maps an internal row/col rectangle into a work area rectangle.
 *
 * @param  pbox       Pointer to a row/col rectangle.
 *
 * @returns           Work area rectangle.
 */

CRect WListCore_RowColsToPixels(const WListCore* This, const CRect* pbox)
{
	CRect box;

	if (This->m_flags & EWList_RightToLeft)
	{
		box.x0 = -This->m_Offset.cx - pbox->x1 * This->m_Size.cx;
		box.x1 = -This->m_Offset.cx - pbox->x0 * This->m_Size.cx;
	}
	else
	{
		box.x0 = This->m_Offset.cx + pbox->x0 * This->m_Size.cx;
		box.x1 = This->m_Offset.cx + pbox->x1 * This->m_Size.cx;
	}

	if (This->m_flags & EWList_BottomToTop)
	{
		box.y0 = This->m_Offset.cy + pbox->y0 * This->m_Size.cy;
		box.y1 = This->m_Offset.cy + pbox->y1 * This->m_Size.cy;
	}
	else
	{
		box.y0 = -This->m_Offset.cy - pbox->y1 * This->m_Size.cy;
		box.y1 = -This->m_Offset.cy -pbox->y0 * This->m_Size.cy;
	}

	return box;
}

/**
 * Maps a work area rectangle into an internal row/col rectangle.
 *
 * @param  pbox       Pointer to a work area rectangle.
 *
 * @returns           row/col rectangle.
 */

CRect WListCore_PixelsToRowCols(const WListCore* This, const CRect* pbox)
{
	CRect box;
	int tmp;
	uint32_t size = This->m_Size.cx ? This->m_Size.cx : 1;

	if (This->m_flags & EWList_RightToLeft)
	{
		box.x0 = (-pbox->x1 - This->m_Offset.cx) / size;
		box.x1 = (-pbox->x0 - This->m_Offset.cx - 1) / size;
	}
	else
	{
		box.x0 = (pbox->x0 - This->m_Offset.cx) / size;
		box.x1 = (pbox->x1 - This->m_Offset.cx - 1) / size;
	}

	if (box.x0 > box.x1)
	{
		tmp = box.x0;
		box.x0 = box.x1;
		box.x1 = tmp;
	}

	size = This->m_Size.cy ? This->m_Size.cy : 1;

	if (This->m_flags & EWList_BottomToTop)
	{
		box.y0 = (pbox->y0 - This->m_Offset.cy) / size;
		box.y1 = (pbox->y1 - This->m_Offset.cy - 1) / size;
	}
	else
	{
		box.y0 = (-pbox->y1 - This->m_Offset.cy) / size;
		box.y1 = (-pbox->y0 - This->m_Offset.cy - 1) / size;
	}

	if (box.y0 > box.y1)
	{
		tmp = box.y0;
		box.y0 = box.y1;
		box.y1 = tmp;
	}

	return box;
}

/**
 * Maps an item index to the work area rectangle it occupies.
 *
 * @param  index      List item index.
 *
 * @returns           Work area rectangle.
 */

CRect WListCore_ItemRect(const WListCore* This, int index)
{
	CRect box;

	if (This->m_flags & EWList_GrowHorz)
	{
		box.x0 = (index / This->m_RowCols.cy);
		box.y0 = (index % This->m_RowCols.cy);
	}
	else
	{
		box.x0 = (index % This->m_RowCols.cx);
		box.y0 = (index / This->m_RowCols.cx);
	}

	box.x1 = box.x0 + 1;
	box.y1 = box.y0 + 1;

	return WListCore_RowColsToPixels(This, &box);
}

/**
 * Maps an item index into internal row/col coordinates.
 *
 * @param  index      List item index.
 *
 * @returns           Row/col position.
 */

CPoint WListCore_IndexToRowCol(const WListCore* This, int index)
{
	CPoint pt;

	if (This->m_flags & EWList_GrowHorz)
	{
		pt.x = (index / This->m_RowCols.cy);
		pt.y = (index % This->m_RowCols.cy);
	}
	else
	{
		pt.x = (index % This->m_RowCols.cx);
		pt.y = (index / This->m_RowCols.cx);
	}

	return pt;
}

/**
 * Maps internal row/col position into an item index.
 *
 * @param  pos        Row/col position.
 *
 * @returns           List item index.
 */

int WListCore_RowColToIndex(const WListCore* This, CPoint pos)
{
	if (This->m_flags & EWList_GrowHorz)
		return pos.y + pos.x * This->m_RowCols.cy;
	else
		return pos.x + pos.y * This->m_RowCols.cx;
}

/*-------------------------------------------------------------------------*
 *--- List window management ----------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Returns the handle of the window used by the list.
 *
 * @returns           Window handle.
 */

HWind WListCore_GetWindow(const WListCore* This)
{
	return This->m_wnd;
}

/**
 * Shows the list as a sub-menu.
 *
 * @param  pmsg       Pointer to the MenuWarning message providing
 *                    the parent menus sequence.
 */

void WListCore_SubMenu(WListCore* This)
{
	// Need to keep track of that to avoid use of calls to wimp_open_window
	This->m_coreflags |= EWListCore_OpenAsMenu;
	Menu_OpenSubWindow(This->m_wnd);
}

/**
 * Shows the list as a normal menu or as a popup menu attached to an icon.
 *
 * @param  w          Window handle of icon to attach as a popup
 *                    or HWind_None to open as normal menu.
 * @param  i          Handle of icon to attach as a popup.
 */

void WListCore_Popup(WListCore* This, HWind w, HIcon i)
{
	// Need to keep track of that to avoid use of calls to wimp_open_window
	This->m_coreflags |= EWListCore_OpenAsMenu;
	if (w == HWind_None)
		Menu_OpenWindow(This->m_wnd);
	else
		Menu_PopupWindow(w, i, This->m_wnd);
}

/**
 * Calculates the window's extent taking into account new contents,
 * current visible area, current extent, imposed minimal size
 * and possible imposed visible area.
 *
 * @param pvisbox     Pointer to imposed visible area box or NULL if none.
 *
 * @returns
 */

static CRect WListCore_FixExtent(WListCore* This, CRect* pvisbox)
{
	CRect box;
	CSize OldRowCols = This->m_RowCols;
	int dx, dy;
	int max = Array_Count(This->m_pList);
	CWind info = Window_GetInfo(This->m_wnd);

	if (max < 1) max = 1;
	if (pvisbox) info.o.o.cvt.box = *pvisbox;

	// Get the number of row and columns of items
	if (This->m_flags & EWList_MultiColumn)
	{
		// Multiple columns/rows mode
		// Limit the number of elements in one direction
		// to either the extent area or the visible area
		if (This->m_flags & EWList_FitVisibleArea)
			box = info.o.o.cvt.box;
		else
			box = info.ex;

		if (This->m_flags & EWList_GrowHorz)
		{
			// The height or the area is used to limit the number of columns
			This->m_RowCols.cy = (box.y1 - box.y0) / This->m_MinSize.cy;
			if (This->m_RowCols.cy > max) This->m_RowCols.cy = max;
			if (This->m_RowCols.cy < 1) This->m_RowCols.cy = 1;
			This->m_RowCols.cx = (max + This->m_RowCols.cy - 1) / This->m_RowCols.cy;
		}
		else
		{
			// The width of the area is used to limit the number of rows
			This->m_RowCols.cx = (box.x1 - box.x0) / This->m_MinSize.cx;
			if (This->m_RowCols.cx > max) This->m_RowCols.cx = max;
			if (This->m_RowCols.cx < 1) This->m_RowCols.cx = 1;
			This->m_RowCols.cy = (max + This->m_RowCols.cx - 1) / This->m_RowCols.cx;
		}
	}
	else
	{
		// Single column/row mode
		// Limit the number of elements in one direction to 1
		if (This->m_flags & EWList_GrowHorz)
		{
			// Single row mode
			This->m_RowCols.cx = max;
			This->m_RowCols.cy = 1;
		}
		else
		{
			// Single column mode
			This->m_RowCols.cx = 1;
			This->m_RowCols.cy = max;
		}
	}

	// Force a complete redraw if the above limit is modified
	if (WListCore_IsRefreshAllowed(This))
	{
		if (((OldRowCols.cx != This->m_RowCols.cx) && !(This->m_flags & EWList_GrowHorz))
		||  ((OldRowCols.cy != This->m_RowCols.cy) && (This->m_flags & EWList_GrowHorz)))
		{
			CSize size = This->m_RowCols;

			// Invalidate old area covered by items
			This->m_RowCols = OldRowCols;
			WListCore_ForceRedraw(This, 0, max - 1);

			// Invalidate new area covered by items
			This->m_RowCols = size;
			WListCore_ForceRedraw(This, 0, max - 1);
		}
	}

	// Get the grid size by multiplying by the size of the items
	dx = This->m_Offset.cx + This->m_RowCols.cx * This->m_MinSize.cx;
	dy = This->m_Offset.cy + This->m_RowCols.cy * This->m_MinSize.cy;

	if (This->m_flags & EWList_FilerExtent)
	{
		const Mode_Info* Mode = Task_GetModeInfo();

		if (This->m_flags & EWList_GrowHorz)
		{
			if ((This->m_RowCols.cx > 1)
			&&  (Mode->box.y1 >= dy + This->m_MinSize.cy))
				dy += This->m_MinSize.cy;
		}
		else
		{
			if ((This->m_RowCols.cy > 1)
			&&  (Mode->box.x1 >= dx + This->m_MinSize.cx))
				dx += This->m_MinSize.cx;
		}
	}
	else
	{
		// Take account of a possible imposed visible size
		if (pvisbox)
		{
			if (dx < pvisbox->x1 - pvisbox->x0)
				dx = pvisbox->x1 - pvisbox->x0;

			if (dy < pvisbox->y1 - pvisbox->y0)
				dy = pvisbox->y1 - pvisbox->y0;
		}
	}

	// Take account of a imposed minimal size
	if (This->m_OwnerWnd == HWind_None)
	{
		if (dx < This->m_RectToFit.x1 - This->m_RectToFit.x0)
			dx = This->m_RectToFit.x1 - This->m_RectToFit.x0;

		if (dy < This->m_RectToFit.y1 - This->m_RectToFit.y0)
			dy = This->m_RectToFit.y1 - This->m_RectToFit.y0;
	}

	// Select the corner to use as work area origin
	// and update the other limits accordingly
	if (This->m_flags & EWList_RightToLeft)
	{
		box.x0 = -dx;
		box.x1 = 0;
	}
	else
	{
		box.x0 = 0;
		box.x1 = dx;
	}

	if (This->m_flags & EWList_BottomToTop)
	{
		box.y0 = 0;
		box.y1 = dy;
	}
	else
	{
		box.y0 = -dy;
		box.y1 = 0;
	}

	return box;
}

/**
 * Updates the list's window extent after a change.
 */

void WListCore_SetExtent(WListCore* This)
{
	CRect box = WListCore_FixExtent(This, NULL);

	// Set window extent
	Window_SetExtent(This->m_wnd, &box, true);
}

/**
 * Updates the list's window extent after a change.
 */

void  WListCore_SetOffset(WListCore* This, CSize s)
{
	This->m_Offset = s;

	WListCore_Invalidate(This, -1, -1, true);
}

/**
 * Builds an items plot parameters.
 *
 * @param pPlot       Pointer to the PlotItem structure to fill.
 * @param index       Index of the item to plot.
 *
 * @returns           Pointer to the object to plot.
 */

static void* WListCore_GetPlotItem(const WListCore* This, WPlotItem* pPlot, int index) throws(index)
{
	WListItem* pItem = Array_Get(This->m_pList, index);

	// Prepare the plotitem structure
	pPlot->m_index = index;
	pPlot->m_box = WListCore_ItemRect(This, index);
	pPlot->m_flags = pItem->m_state;
	pPlot->m_psizes = (const int*) (pItem + 1);
	pPlot->m_pmaxsizes = This->m_pItemSizes;

	// Update the item's state to take into account the hover.
	if (index == This->m_HoverPos)
	{
		if ((This->m_flags & EWList_HoverSensitive)
		||  ((This->m_flags & EWList_SelModeMask) == EWList_SelModePopup))
			pPlot->m_flags |= EWItem_Hover;
	}

	// Take selection by dragging into account
	// to show tentative selection or deselection
	if (This->m_coreflags & (EWListCore_DragMouse | EWListCore_DragKey))
	{
		if ((pPlot->m_flags & (EWItem_Active | EWItem_ProtSelect)) == EWItem_Active)
		{
			if (This->m_coreflags & EWListCore_DragAdjust)
				pPlot->m_flags ^= EWItem_Selected;
			else
				pPlot->m_flags |= EWItem_Selected;
		}

		// Reset hover
		pPlot->m_flags &= ~EWItem_Hover;
	}

	// Update the item's state to take into account the focus.
	if (This->m_coreflags & EWListCore_HasFocus)
	{
		unsigned int flags = EWItem_HasFocus;

		// In Popup selection mode, Focus and Hover flags are linked.
		if ((This->m_flags & EWList_SelModeMask) == EWList_SelModePopup)
			flags |= EWItem_Hover;

		if (index == This->m_FocusPos)
			pPlot->m_flags |= flags;
		else
			pPlot->m_flags &= ~flags;
	}

	return pItem->m_pobj;
}

/**
 * Redraws the list.
 *
 * @param  r          Pointer to the window's redraw structure.
 */

static void WListCore_Redraw(const WListCore* This, const CWindRedraw* r)
{
	int index;
	int max = Array_Count(This->m_pList);
	CRect box  = RectToWindow(&r->bounds, &r->cvt);
	CRect nbox = WListCore_PixelsToRowCols(This, &box);
	CPoint pos;
	WPlotItem Plot;

	Plot.m_cvtinfo = Window_GetPosInfo(This->m_wnd);

	// Safety cf m_Offset
	if (nbox.x0 < 0) nbox.x0 = 0;
	if (nbox.y0 < 0) nbox.y0 = 0;

	// Plot background
	if (This->m_pFWList->FPlotBackGround)
	{
		if (This->m_flags & EWList_Transparent)
			// Plot background on whole area
			This->m_pFWList->FPlotBackGround(This->m_pHandle, &Plot.m_cvtinfo, &box);
		else
		{
			// Plot background to fill last row/column
			// and areas behind last row, last column
			CRect nbox2;
			CPoint size = WListCore_IndexToRowCol(This, max);

			nbox2.x0 = 0;
			nbox2.y0 = This->m_RowCols.cy;
			nbox2.x1 = nbox.x1 + 1;
			nbox2.y1 = nbox.y1 + 1;
			nbox2 = WListCore_RowColsToPixels(This, &nbox2);

			if (nbox2.x0 < box.x0) nbox2.x0 = box.x0;
			if (nbox2.y0 < box.y0) nbox2.y0 = box.y0;
			if (nbox2.x1 > box.x1) nbox2.x1 = box.x1;
			if (nbox2.y1 > box.y1) nbox2.y1 = box.y1;

			if ((nbox2.x0 < nbox2.x1)
			&&  (nbox2.y0 < nbox2.y1))
				This->m_pFWList->FPlotBackGround(This->m_pHandle, &Plot.m_cvtinfo, &nbox2);

			nbox2.x0 = size.x;
			nbox2.y0 = size.y;
			nbox2.x1 = nbox.x1 + 1;
			nbox2.y1 = This->m_RowCols.cy;
			nbox2 = WListCore_RowColsToPixels(This, &nbox2);

			if (nbox2.x0 < box.x0) nbox2.x0 = box.x0;
			if (nbox2.y0 < box.y0) nbox2.y0 = box.y0;
			if (nbox2.x1 > box.x1) nbox2.x1 = box.x1;
			if (nbox2.y1 > box.y1) nbox2.y1 = box.y1;

			if ((nbox2.x0 < nbox2.x1)
			&&  (nbox2.y0 < nbox2.y1))
				This->m_pFWList->FPlotBackGround(This->m_pHandle, &Plot.m_cvtinfo, &nbox2);

			nbox2.x0 = This->m_RowCols.cx;
			nbox2.y0 = 0;
			nbox2.x1 = nbox.x1 + 1;
			nbox2.y1 = size.y;
			nbox2 = WListCore_RowColsToPixels(This, &nbox2);

			if (nbox2.x0 < box.x0) nbox2.x0 = box.x0;
			if (nbox2.y0 < box.y0) nbox2.y0 = box.y0;
			if (nbox2.x1 > box.x1) nbox2.x1 = box.x1;
			if (nbox2.y1 > box.y1) nbox2.y1 = box.y1;

			if ((nbox2.x0 < nbox2.x1)
			&&  (nbox2.y0 < nbox2.y1))
				This->m_pFWList->FPlotBackGround(This->m_pHandle, &Plot.m_cvtinfo, &nbox2);
		}
	}

	// Plot list items
	if (nbox.x1 >= This->m_RowCols.cx) nbox.x1 = This->m_RowCols.cx - 1;
	if (nbox.y1 >= This->m_RowCols.cy) nbox.y1 = This->m_RowCols.cy - 1;

	for (pos.y = nbox.y0; pos.y <= nbox.y1; pos.y++)
	{
		for (pos.x = nbox.x0; pos.x <= nbox.x1; pos.x++)
		{
			index = WListCore_RowColToIndex(This, pos);
			if (index < max)
			{
				void* pobj = WListCore_GetPlotItem(This, &Plot, index);

				This->m_pFWList->item.FPlot(pobj, This->m_pHandle, &Plot);
			}
		}
	}

	// Plot visual selector?
	if (This->m_pSelector && This->m_pSelector->pFDraw)
		This->m_pSelector->pFDraw(This->m_pSelector, r);
}

/*-------------------------------------------------------------------------*
 *--- List items redraw management ----------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Invalidates a range of items for redraw.
 * Condition: do not call when item size changes.
 *
 * @param  min        Minimum list item index, inclusive.
 * @param  max        Maximum list item index, inclusive.
 */

void WListCore_ForceRedraw(const WListCore* This, int min, int max)
{
	CRect  box;
	CPoint smin;
	CPoint smax;

	if ((min > max) || !Window_IsOpen(This->m_wnd)) return;

	// Get row/col indexs of min and max
	smin = WListCore_IndexToRowCol(This, min);
	smax = WListCore_IndexToRowCol(This, max);

	// redraw the window
	if (This->m_flags & EWList_GrowHorz)
	{
		// If on same column, there is only one rect to redraw
		if (smin.x == smax.x)
		{
			box.x0 = smin.x;
			box.x1 = smin.x + 1;
			box.y0 = smin.y;
			box.y1 = smax.y + 1;
			box = WListCore_RowColsToPixels(This, &box);

			Window_Invalidate(This->m_wnd, &box);
		}
		else
		{
			// Redraw from min to end of first column
			box.x0 = smin.x;
			box.x1 = smin.x + 1;
			box.y0 = smin.y;
			box.y1 = This->m_RowCols.cy;
			box = WListCore_RowColsToPixels(This, &box);

			Window_Invalidate(This->m_wnd, &box);

			// Redraw rect of intermediate columns from min to max
			if ((smin.x + 1) != smax.x)
			{
				box.x0 = smin.x + 1;
				box.x1 = smax.x;
				box.y0 = 0;
				box.y1 = This->m_RowCols.cy;
				box = WListCore_RowColsToPixels(This, &box);

				Window_Invalidate(This->m_wnd, &box);
			}

			// Redraw from start of last column to max
			box.x0 = smax.x;
			box.x1 = smax.x + 1;
			box.y0 = 0;
			box.y1 = smax.y + 1;
			box = WListCore_RowColsToPixels(This, &box);

			Window_Invalidate(This->m_wnd, &box);
		}
	}
	else
	{
		// If on same row, there is only one rect to redraw
		if (smin.y == smax.y)
		{
			box.x0 = smin.x;
			box.x1 = smax.x + 1;
			box.y0 = smin.y;
			box.y1 = smin.y + 1;
			box = WListCore_RowColsToPixels(This, &box);

			Window_Invalidate(This->m_wnd, &box);
		}
		else
		{
			// Redraw from min to end of first row
			box.x0 = smin.x;
			box.x1 = This->m_RowCols.cx;
			box.y0 = smin.y;
			box.y1 = smin.y + 1;
			box = WListCore_RowColsToPixels(This, &box);

			Window_Invalidate(This->m_wnd, &box);

			// Redraw rect of intermediate rows from min to max
			if ((smin.y + 1) != smax.y)
			{
				box.x0 = 0;
				box.x1 = This->m_RowCols.cx;
				box.y0 = smin.y + 1;
				box.y1 = smax.y;
				box = WListCore_RowColsToPixels(This, &box);

				Window_Invalidate(This->m_wnd, &box);
			}

			// Redraw from start of last row to max
			box.x0 = 0;
			box.x1 = smax.x + 1;
			box.y0 = smax.y;
			box.y1 = smax.y + 1;
			box = WListCore_RowColsToPixels(This, &box);

			Window_Invalidate(This->m_wnd, &box);
		}
	}
}

/**
 * Invalidates all items marked for redraw.
 */

static void WListCore_ForceDelayedRedraws(WListCore* This)
{
	WListItem* pItem;
	int i, min;
	int count = Array_Count(This->m_pList);

	// Mark items higher than minimal insertion/suppression point
	if (This->m_refresh.m_Min != -1)
	{
		for (i = This->m_refresh.m_Min; i < count; i++)
		{
			pItem = Array_Get(This->m_pList, i);
			pItem->m_state |= EWItem_Redraw;
		}

		This->m_refresh.m_Min = -1;
	}

	// Redraw groups of marked items to minimize redraw rectangles
	for (i = 0; i < count; i++)
	{
		pItem = Array_Get(This->m_pList, i);
		if (pItem->m_state & EWItem_Redraw)
		{
			min = i;

			do
			{
				pItem->m_state &= ~EWItem_Redraw;

				i++;
				if (i >= count)
					break;

				pItem = Array_Get(This->m_pList, i);
			}
			while(pItem->m_state & EWItem_Redraw);

			WListCore_ForceRedraw(This, min, i - 1);
		}
	}
}

/**
 * Enables of disable individual refresh of list items.
 * When the refresh becomes enabled again all items pending
 * are invalidated in one go.
 *
 * @param  bAllow     Boolean.
 */

void WListCore_AllowRefresh(WListCore* This, bool bAllow)
{
	if (bAllow)
	{
		if (This->m_refresh.m_DisableCount > 0)
		{
			This->m_refresh.m_DisableCount--;
			if (!This->m_refresh.m_DisableCount)
			{
				WListCore_Invalidate(This, -1, -1, true);
				if (This->m_refresh.m_bShowHourglass)
				{
					This->m_refresh.m_bShowHourglass = false;
					WLib_Hourglass_Off();
				}
			}
		}
	}
	else
	{
		if (!This->m_refresh.m_DisableCount)
		{
			if (!WLib_Hourglass_IsOn())
			{
//				This->m_refresh.m_bShowHourglass = true;
//				WLib_Hourglass_On();
			}
			else
				This->m_refresh.m_bShowHourglass = false;
		}
		This->m_refresh.m_DisableCount++;
	}
}

/**
 * Returns true if refresh is allowed.
 */
bool WListCore_IsRefreshAllowed(const WListCore* This)
{
	return (This->m_refresh.m_DisableCount == 0);
}

/**
 * Marks a range of items for redraw then invalidates them if refresh
 * is alllowed.
 * Condition: do not call when item size changes.
 *
 * @param  min        Minimum list item index, inclusive.
 * @param  max        Maximum list item index, inclusive.
 */

void WListCore_Refresh(WListCore* This, int min, int max)
{
	WListItem* pItem;
	int i;

	if (max >= Array_Count(This->m_pList))
		max = Array_Count(This->m_pList) - 1;

	if (min >= 0)
	{
		// Mark range of items as to be redrawn
		for (i = min; i <= max; i++)
		{
			pItem = Array_Get(This->m_pList, i);
			pItem->m_state |= EWItem_Redraw;
		}
	}
	else if (max >= 0)
	{
		if ((This->m_refresh.m_Min < 0) || (This->m_refresh.m_Min >= max))
			This->m_refresh.m_Min = max;
	}

	if (WListCore_IsRefreshAllowed(This))
		WListCore_ForceDelayedRedraws(This);
}

/**
 * Marks a range of items for redraw then invalidates them if refresh
 * is alllowed.
 * Condition: do not call when item size changes.
 *
 * @param  min        Minimum list item index, inclusive or -1.
 * @param  max        Maximum list item index, inclusive of if (min==-1) [max, list count[.
 * @param  bUpdateExtent
 *                    True if the window's extent must be calculated.
 */

static void WListCore_Invalidate(WListCore* This, int min, int max, bool bUpdateExtent)
{
	int count = Array_Count(This->m_pList);
	int i, s;

	if (WListCore_IsRefreshAllowed(This))
	{
		CWind info = Window_GetInfo(This->m_wnd);

		// Check if we need to recalculate shrinken item size
		if (This->m_coreflags & EWListCore_ShrinkSize)
		{
			This->m_coreflags &= ~EWListCore_ShrinkSize;

			for (s = 0; s < This->m_NrSizes; s++)
			{
				This->m_pItemSizes[s] = 1;
				This->m_pItemMaxs[s] = 0;
			}

			for (i = 0; i < count; i++)
			{
				const WListItem* pItem = Array_Get(This->m_pList, i);
				const int* psizes = (const int*) (pItem + 1);

				for (s = 0; s < This->m_NrSizes; s++)
				{
					if (This->m_pItemSizes[s] == psizes[s])
					{
						This->m_pItemMaxs[s]++;
					}
					else if (This->m_pItemSizes[s] < psizes[s])
					{
						This->m_pItemMaxs[s] = 1;
						This->m_pItemSizes[s] = psizes[s];
					}
				}
			}
		}

		if (count)
		{
			const WListItem* pItem = Array_Get(This->m_pList, 0);
			This->m_MinSize = This->m_pFWList->item.FGetBox( pItem->m_pobj
														   , This->m_pHandle
														   , NULL // unused
														   , This->m_pItemSizes);
		}
		else
		{
			This->m_MinSize = This->m_pFWList->item.FGetBox( NULL // unused
														   , This->m_pHandle
														   , NULL // unused
														   , This->m_pItemSizes);
		}

		// Round up rect for display
		RoundUpSize(&This->m_MinSize);

		This->m_Size = This->m_MinSize;

		// Expand size of single column elements to window extent.
		if (!(This->m_flags & EWList_MultiColumn))
		{
			if (This->m_flags & EWList_GrowHorz)
			{
				if ((info.ex.y1 - info.ex.y0) > This->m_Size.cy)
					This->m_Size.cy = info.ex.y1 - info.ex.y0;
			}
			else
			{
				if ((info.ex.x1 - info.ex.x0) > This->m_Size.cx)
					This->m_Size.cx = info.ex.x1 - info.ex.x0;
			}
		}

		// If item size changes, we need a complete refresh
		if ((This->m_refresh.m_OldSize.cx != This->m_Size.cx)
		||  (This->m_refresh.m_OldSize.cy != This->m_Size.cy))
		{
			// Invalidate area covered by all old items
			CSize size = This->m_Size;

			This->m_Size = This->m_refresh.m_OldSize;
			WListCore_ForceRedraw(This, 0, This->m_refresh.m_OldMax);
			This->m_Size = size;

			// We need to refresh of all items
			min = 0;
			max = count - 1;
		}
		else
		{
			// Invalidate area covered by removed items
			WListCore_ForceRedraw(This, count, This->m_refresh.m_OldMax);
		}

		// Store new old settings
		This->m_refresh.m_OldMax = count - 1;
		This->m_refresh.m_OldSize = This->m_Size;

		// Set new window extent
		if (bUpdateExtent)
			WListCore_SetExtent(This);
	}

	// Refresh marked items
	WListCore_Refresh(This, min, max);
}

/**
 * Forces a recalculation of all item sizes (e.g. after a font change)
 */

void WListCore_RecalcSize(WListCore* This)
{
	int count = Array_Count(This->m_pList);
	int i;
	WListItem* pItem;
	int* psizes;

	This->m_coreflags |= EWListCore_ShrinkSize;

	if (This->m_NrSizes)
	{
		for (i = 0; i < count; i++)
		{
			pItem = Array_Get(This->m_pList, i);
			psizes = (int*) (pItem + 1);
			This->m_pFWList->item.FGetSizes(pItem->m_pobj, This->m_pHandle, psizes);
		}
	}

	WListCore_Invalidate(This, -1, 0, true);
}

/**
 * Scrolls the window if necessary to ensure that the given item
 * is (if possible) entirely within the window's visible area.
 *
 * @param  index      Index of list item.
 */

void WListCore_ShowItem(WListCore* This, int index)
{
	if (Window_IsOpen(This->m_wnd))
	{
		// If required, scroll window so that item which has focus is visible
		CWindState info = Window_GetState(This->m_wnd);
		CSize s = This->m_Offset;
		bool b = false;
		CRect box;

		// First force as if no offset, for scrolling below covered area
		This->m_Offset.cx = This->m_Offset.cy = 0;
		box = WListCore_ItemRect(This, index);
		b |= Window_ScrollToRect(&info.o, &box);
		// Then reset for normal scrolling
		This->m_Offset = s;
		box = WListCore_ItemRect(This, index);
		b |= Window_ScrollToRect(&info.o, &box);

		if (b) Window_OpenAt(This->m_wnd, &info.o);
	}
}

/*-------------------------------------------------------------------------*
 *--- List focus management -----------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Returns the current focus position or its memorized position
 * if the caret is not attached to the window.
 *
 * @returns           Index of list item or -1.
 */

int WListCore_GetFocus(const WListCore* This)
{
	return This->m_FocusPos;
}

/**
 * Sets the focus position to a given list item.
 * This function:
 * - sets the caret to the window if necessary
 * - memorizes which item has the index
 * -
 * In Popup selection mode, the item owning the focus
 *
 * @param  focus      Index of the list item which should be given the focus.
 * @param  showItem   True to ensure that the item is visible by scrolling
 *                    the list if neccessary.
 */

void WListCore_SetFocus(WListCore* This, int focus, bool showItem)
{
	int oldfocus;

	// Do not allow to set focus while popped up as a menu
	// because the window will not close automatically
	// and selection could be made by hand
	if (This->m_coreflags & EWListCore_OpenAsMenu) return;

	// Focus can only be set on an existing item
	focus = WListCore_NewFocusPos(This, focus, 0);

	if (This->m_coreflags & EWListCore_HasFocus)
		oldfocus = This->m_FocusPos;
	else
	{
		// If no focus was visible, set the caret to the window.
		This->m_coreflags |= EWListCore_HasFocus;

		if (!(This->m_coreflags & EWListCore_DragMouse))
			Caret_SetInvisible(This->m_wnd);

		oldfocus = -1;
	}

	This->m_FocusPos = focus;

	if (oldfocus == focus)
	{
		// If asked to, ensure that the new focus is visible
		if (showItem) WListCore_ShowItem(This, focus);
	}
	else
	{
		// The items state flags will be updated by the redraw itself
		// instead of being performed here.

		// Force the redraw of item which had the focus
		WListCore_Refresh(This, oldfocus, oldfocus);

		// If asked to, ensure that the new focus is visible
		if (showItem) WListCore_ShowItem(This, focus);

		// Force the redraw of item which has focus
		WListCore_Refresh(This, focus, focus);

		// Update activation states if keyboard dragging
		if (This->m_coreflags & EWListCore_DragKey)
			This->m_pSelector->pFCheck(This->m_pSelector);

		// Hide hover info
		if ((This->m_flags & EWList_SelModeMask) == EWList_SelModePopup)
			WListCore_Refresh(This, This->m_HoverPos, This->m_HoverPos);
	}
}

/**
 * Sets the current item above which the pointer hovers.
 * Redraw the old and new hovered items when required.
 */

static void WListCore_SetHover(WListCore* This, int hover)
{
	int oldhover = This->m_HoverPos;
	bool bRedraw = ((This->m_flags & EWList_HoverSensitive) != 0);

	// Special case for popup mode
	if ((This->m_flags & EWList_SelModeMask) == EWList_SelModePopup)
	{
		 if (This->m_coreflags & EWListCore_HasFocus)
		 	bRedraw = false;
		 else
			bRedraw = true;
	}

	// Disabled when dragging
	if (This->m_coreflags & (EWListCore_DragMouse | EWListCore_DragKey))
		bRedraw = false;

	This->m_HoverPos = hover;

	if ((oldhover != hover) & bRedraw)
	{
		// Force a redraw of old and new hover pos
		// The redraw sets the appropriate flags
		WListCore_Refresh(This, oldhover, oldhover);
		WListCore_Refresh(This, hover, hover);
	}
}

/*-------------------------------------------------------------------------*
 *--- List ghost caret management -----------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Draws a red filled rectangle as ghost caret.
 *
 * @param  prect      Rectangle to draw.
 * @param  r          Pointer to the window's redraw structure.
 */

#define caret_width 4

static void WListCore_DrawGhostCaret(CRect* prect, const CWindRedraw* r)
{
	CRect box = RectToScreen(prect, &r->cvt);

	// plot filled rectangle
	Desktop_SetStdColour(11); // Red
	Display_Plot(4, box.x0, box.y0);
	Display_Plot(101, box.x1, box.y1);
}

/**
 * Plots a ghost caret or hide it from view.
 *
 * @param  bShow      True to show, false to hide.
 */

void WListCore_PlotGhostCaret(WListCore* This, bool bShow)
{
	CRect rect;
	CRect box;

	// Nothing to do if no ghost caret defined.
	if (This->m_GhostCaret.x < 0) return;

	// Calculate the caret's position
	box.x0 = This->m_GhostCaret.x;
	box.y0 = This->m_GhostCaret.y;
	box.x1 = box.x0 + 1;
	box.y1 = box.y0 + 1;
	box = WListCore_RowColsToPixels(This, &box);

	if (((This->m_flags & EWList_GrowHorz) == 0) ^ ((This->m_flags & EWList_MultiColumn) != 0))
	{
		if (This->m_GhostCaret.y >= This->m_RowCols.cy)
		{
			if (This->m_flags & EWList_BottomToTop)
				box.y0 -= caret_width;
			else
				box.y1 += caret_width;
		}

		if (This->m_flags & EWList_BottomToTop)
			box.y1 = box.y0 + caret_width;
		else
			box.y0 = box.y1 - caret_width;
	}
	else
	{
		if (This->m_GhostCaret.x >= This->m_RowCols.cx)
		{
			if (This->m_flags & EWList_RightToLeft)
				box.x1 += caret_width;
			else
				box.x0 -= caret_width;
		}

		if (This->m_flags & EWList_RightToLeft)
			box.x0 = box.x1 - caret_width;
		else
			box.x1 = box.x0 + caret_width;
	}

	RoundRect(&box);
	rect = box;

	// Show or hide the caret
	if (bShow)
	{
		CWindState info = Window_GetState(This->m_wnd);

		if (Window_ScrollToRect(&info.o, &rect))
			Window_OpenAt(This->m_wnd, &info.o);

		Window_Update(This->m_wnd
				, &rect
				, (Window_Drawer) WListCore_DrawGhostCaret
				, &box);
	}
	else
	{
		Window_Invalidate(This->m_wnd, &rect);
	}
}

/**
 * Defines a ghost caret positionned at the nearest insertion position
 * from a given screen point and plot it.
 *
 * @param  ppt        Pointer to a screen point.
 */

void WListCore_ShowGhostCaret(WListCore* This, const CPoint* ppt)
{
	int item = WListCore_ItemFromScreenPt(This, ppt, EWListPtrMode_Insert);
	CPoint pos = WListCore_IndexToRowCol(This, item);
	CPoint pt;
	CWindCvt cvt = Window_GetPosInfo(This->m_wnd);

	pt = PointToWindow(ppt, &cvt);

	if (((This->m_flags & EWList_GrowHorz) == 0) ^ ((This->m_flags & EWList_MultiColumn) != 0))
	{
		if ((pos.y == 0)
		&&  (pos.x > 0)
		&&  ((abs(pt.y) > This->m_Offset.cy + This->m_Size.cy) || (item >= Array_Count(This->m_pList))))
		{
			pos.x--;
			pos.y = This->m_RowCols.cy;
		}
	}
	else
	{
		if ((pos.x == 0)
		&&  (pos.y > 0)
		&&  ((abs(pt.x) > This->m_Offset.cx + This->m_Size.cx) || (item >= Array_Count(This->m_pList))))
		{
			pos.y--;
			pos.x = This->m_RowCols.cx;
		}
	}

	if (memcmp(&This->m_GhostCaret, &pos, sizeof(pos)))
	{
		WListCore_PlotGhostCaret(This, false);
		This->m_GhostCaret = pos;
		WListCore_PlotGhostCaret(This, true);
	}
}

/**
 * Hides and undefines the ghost caret.
 */

void WListCore_HideGhostCaret(WListCore* This)
{
	WListCore_PlotGhostCaret(This, false);
	This->m_GhostCaret.x = -1;
}

/**
 * Returns the ghost caret insertion position.
 *
 * @returns           Index of the item to insert before
 *                    or item count for insertion behind the last item
 *                    or -1 if caret is defined.
 */

int WListCore_GetGhostCaretIndex(const WListCore* This)
{
	int index = WListCore_RowColToIndex(This, This->m_GhostCaret);

	if (index > Array_Count(This->m_pList)) index = Array_Count(This->m_pList);

	return index;
}

/*-------------------------------------------------------------------------*
 *--- List global settings ------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Gets the list's flags.
 *
 * @returns           The list's flags.
 */

unsigned int WListCore_GetFlags(const WListCore* This)
{
	return This->m_flags;
}

/**
 * Sets the list's flags.
 *
 * @param  flags      The list's new flags.
 */

void WListCore_SetFlags(WListCore* This, unsigned int flags)
{
	int i;
	int oldflags = This->m_flags;

	flags &= EWList_FlagsMask;

	if ((This->m_flags & EWList_FlagsMask) == flags)
		return;

	This->m_flags = (oldflags & ~EWList_FlagsMask) | flags;

	if (This->m_coreflags & EWListCore_Init)
	{
		// Retain only flags that changed
		flags ^= oldflags;

		if (flags & EWList_SelModeMask)
		{
			This->m_coreflags &= ~EWListCore_MoveOnlyMode;

			if ((oldflags & EWList_SelModeMask) == EWList_SelModeMulti)
			{
				// Force to single selection
				if (WListCore_GetItemState(This, This->m_FocusPos) & EWItem_Selected)
					i = This->m_FocusPos;
				else
					i = WListCore_FindItemState(This, 0, EWItem_Selected, EWItem_Selected);

				if (i != -1)
				{
					WListCore_SetItemsState(This, 0, EWItem_Selected, 0, 0);
					WListCore_SetItemState(This, This->m_FocusPos, EWItem_Selected, EWItem_Selected);
					WListCore_NotifySelChange(This, This->m_FocusPos);
				}
			}
			if ((This->m_flags & EWList_SelModeMask) == EWList_SelModePopup)
			{
				// Clear any selection
				if (WListCore_SetItemsState(This, 0, EWItem_Selected, 0, 0))
					WListCore_NotifySelChange(This, -1);
			}
		}

		if (flags & ( EWList_MultiColumn
		            | EWList_GrowHorz
		            | EWList_RightToLeft
		            | EWList_BottomToTop))
		{
			// Force a complete redraw
			WListCore_SetExtent(This);
			if (This->m_coreflags & EWListCore_HasFocus)
				WListCore_SetFocus(This, This->m_FocusPos, true);
		}

		if (flags & ( EWList_MultiColumn
		            | EWList_GrowHorz
		            | EWList_Transparent
		            | EWList_RightToLeft
		            | EWList_BottomToTop))
		{
			CWind info = Window_GetInfo(This->m_wnd);
			// Invalidate current visible area
			Window_Invalidate(This->m_wnd, &info.ex);
		}
	}
}

void WListCore_SetParent(WListCore* This, HWind parentwnd)
{
	This->m_parentwnd = parentwnd;
}

/**
 * Attaches the list as a pane to another window.
 *
 * @param  ownerwnd   Handle of the window to become a pane of,
 *                    or -1 to detach the list.
 * @param  pbox       A pointer to the window's work area rect we must fit into.
 *
 * @returns           True if the operation was succesful.
 */

void throw_WListCore_SetPane(WListCore* This, HWind ownerwnd, const CRect* pbox)
{
	if (This->m_OwnerWnd != HWind_None)
		Window_RemovePane(This->m_OwnerWnd, This->m_wnd);

	This->m_OwnerWnd = ownerwnd;
	if (pbox) This->m_RectToFit = *pbox;

	if (This->m_OwnerWnd != HWind_None)
		throw_Window_AddPane(This->m_OwnerWnd, This->m_wnd);
}

/*-------------------------------------------------------------------------*
 *--- List item state management ------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Reads an item's display state flags.
 *
 * @param  index      Index of the item in the list.
 *
 * @returns           The item's display state flags
 *                    or EWItem_Deleted if the does not exist.
 */

unsigned int WListCore_GetItemState(const WListCore* This, int index) throws(index)
{
	const WListItem* pItem = Array_Get(This->m_pList, index);

	if (pItem == NULL) return EWItem_Deleted;

	return pItem->m_state;
}

/**
 * Sets an item's display state flags.
 *
 * @param  index      Index of the item in the list.
 * @param  val        The new value for the flags.
 * @param  mask       The flags to update.
 *
 * @returns           True if the list was modified.
 */

bool WListCore_SetItemState(WListCore* This, int index, unsigned int val, unsigned int mask) throws(index)
{
	WListItem* pItem = Array_Get(This->m_pList, index);
	unsigned int state;

	throw_assert((mask & ~EWItem_SetMask) == 0);

	if (pItem == NULL) return false;

	// Check if it leads to a change
	val &= mask;
	if (!(mask & EWItem_ProtSelect)
	&&   (pItem->m_state & EWItem_ProtSelect))
		val &= ~EWItem_Selected;
	else if (val & EWItem_ProtSelect)
	{
		val &= ~EWItem_Selected;
		mask |= EWItem_Selected;
	}
	state = pItem->m_state & mask;

	if (val == state) return false;

	// Multi state checks
	if (val & EWItem_Selected)
	{
		if ((This->m_flags & EWList_SelModeMask) != EWList_SelModeMulti)
			WListCore_SetItemsState(This, 0, EWItem_Selected, 0, 0);
	}
	if (val & EWItem_Marked)
	{
		if (!(This->m_flags & EWList_MultiMark))
			WListCore_SetItemsState(This, 0, EWItem_Marked, 0, 0);
	}

	// Set new state
	pItem->m_state &= ~mask;
	pItem->m_state |= val;

	// Redraw the item
	WListCore_Refresh(This, index, index);

	return true;
}

/**
 * Inverts some display state flags of an item.
 *
 * @param  index      Index of the item in the list.
 * @param  mask       The flags to invert.
 *
 * @returns           True if the list was modified.
 */

bool WListCore_InvertItemState(WListCore* This, int index, unsigned int mask) throws(index)
{
	WListItem* pItem = Array_Get(This->m_pList, index);
	unsigned int val;

	throw_assert((mask & ~EWItem_SetMask) == 0);

	if (pItem == NULL) return false;

	val = (~pItem->m_state) & mask;
	return WListCore_SetItemState(This, index, val, mask);
}

/**
 * Sets display state flags of every item matching given flags.
 *
 * @param  val        The new value for the flags.
 * @param  mask       The flags to update.
 * @param  selval     The value for the flags which must be mached.
 * @param  selmask    The flags to check.
 *
 * @returns           True if the list was modified.
 */

bool WListCore_SetItemsState(WListCore* This, unsigned int val, unsigned int mask, unsigned int selval, unsigned int selmask)
{
	int count = Array_Count(This->m_pList);
	bool bChanged = false;
	unsigned int ival;
	unsigned int state;
	int i;

	throw_assert((mask & ~EWItem_SetMask) == 0);
	ival = val & mask;
	if (ival & EWItem_ProtSelect)
	{
		ival &= ~EWItem_Selected;
		mask |= EWItem_Selected;
	}
	selval &= selmask;

	for (i = 0; i < count; i++)
	{
		WListItem* pItem = Array_Get(This->m_pList, i);

		if ((pItem->m_state & selmask) == selval)
		{
			val = ival;
			if (!(mask & EWItem_ProtSelect)
			&&   (pItem->m_state & EWItem_ProtSelect))
				val &= ~EWItem_Selected;
			state = pItem->m_state & mask;

			if (val != state)
			{
				// Set new state
				pItem->m_state &= ~mask;
				pItem->m_state |= val;

				// Redraw the window
				pItem->m_state |= EWItem_Redraw;

				bChanged = true;
			}
		}
	}

	if (bChanged) WListCore_Refresh(This, -1, -1);

	return bChanged;
}

/**
 * Inverts display state flags of every item matching given flags.
 *
 * @param  mask       The flags to invert.
 * @param  selval     The value for the flags which must be mached.
 * @param  selmask    The flags to check.
 *
 * @returns           True if the list was modified.
 */

bool WListCore_InvertItemsState(WListCore* This, unsigned int mask, unsigned int selval, unsigned int selmask)
{
	int count = Array_Count(This->m_pList);
	bool bChanged = false;
	int i;

	throw_assert((mask & ~EWItem_SetMask) == 0);
	selval &= selmask;

	// Multi state checks
	if (mask & EWItem_Selected)
		throw_assert((This->m_flags & EWList_SelModeMask) == EWList_SelModeMulti);
	if (mask & EWItem_Marked)
		throw_assert((This->m_flags & EWList_MultiMark));

	for (i = 0; i < count; i++)
	{
		WListItem* pItem = Array_Get(This->m_pList, i);

		if ((pItem->m_state & selmask) == selval)
		{
			// Set new state
			pItem->m_state ^= mask;
			if (pItem->m_state & EWItem_ProtSelect)
				pItem->m_state &= ~EWItem_Selected;

			// Redraw the window
			pItem->m_state |= EWItem_Redraw;

			bChanged = true;
		}
	}

	if (bChanged) WListCore_Refresh(This, -1, -1);

	return bChanged;
}

/**
 * Find the first item starting from a given position matching
 * given display state flags.
 *
 * @param  start      Index of the item to start from.
 * @param  val        The flags values to match.
 * @param  mask       The flags to check.
 *
 * @returns           Index of the first matching item or -1 if no match found.
 */

int WListCore_FindItemState(const WListCore* This, int start, unsigned int val, unsigned int mask)
{
	int count = Array_Count(This->m_pList);
	int i;

	val &= mask;

	for (i = start; i < count; i++)
	{
		WListItem* pItem = Array_Get(This->m_pList, i);

		if ((pItem->m_state & mask) == val)
			return i;
	}

	return -1;
}

/*-------------------------------------------------------------------------*
 *--- List window management ----------------------------------------------*
 *-------------------------------------------------------------------------*/

int WListCore_ItemFromScreenPt(const WListCore* This, const CPoint* ppt, unsigned int flags)
{
	CPoint pt, pto;
	CWindCvt cvt = Window_GetPosInfo(This->m_wnd);
	int ret;

	pto = PointToWindow(ppt, &cvt);
	pto.x = abs(pto.x) - This->m_Offset.cx;
	pto.y = abs(pto.y) - This->m_Offset.cy;

	pt.x = pto.x / This->m_Size.cx;
	pt.y = pto.y / This->m_Size.cy;

	if (flags & EWListPtrMode_Insert)
	{
		if (((This->m_flags & EWList_GrowHorz) != 0) ^ ((This->m_flags & EWList_MultiColumn) != 0))
			pt.x = (pto.x + This->m_Size.cx / 3) / This->m_Size.cx;
		else
			pt.y = (pto.y + This->m_Size.cy / 3) / This->m_Size.cy;
	}

	if (This->m_flags & EWList_GrowHorz)
	{
		if (pt.y >= This->m_RowCols.cy)
		{
			if (flags & EWListPtrMode_Insert)
			{
				pt.x = (pto.x / This->m_Size.cx) + 1;
				pt.y = 0;
			}
			else
			{
				pt.x = 0;
				pt.y = -1;
			}
		}
		ret = pt.y + pt.x * This->m_RowCols.cy;
	}
	else
	{
		if (pt.x >= This->m_RowCols.cx)
		{
			if (flags & EWListPtrMode_Insert)
			{
				pt.y = (pto.y / This->m_Size.cy) + 1;
				pt.x = 0;
			}
			else
			{
				pt.y = 0;
				pt.x = -1;
			}
		}
		ret = pt.x + pt.y * This->m_RowCols.cx;
	}

	if (flags & EWListPtrMode_Insert)
	{
		if (ret < 0) ret = 0;
		if (ret > Array_Count(This->m_pList)) ret = Array_Count(This->m_pList);
	}
	else if (flags & EWListPtrMode_InRange)
	{
		if (ret < 0) ret = 0;
		if (ret >= Array_Count(This->m_pList)) ret = Array_Count(This->m_pList) - 1;
	}
	else if ((ret < 0) || (ret >= Array_Count(This->m_pList))) ret = -1;

	return ret;
}

int WListCore_ItemAreaFromScreenPt(const WListCore* This, const Mouse* pMouse, unsigned int flags, WItemArea* pArea)
{
	int item = WListCore_ItemFromScreenPt(This, &pMouse->pt, flags);

	if ((item >= 0)
	&&  (item < WListCore_Count(This)))
	{
		WPlotItem Plot;
		Mouse m = *pMouse;
		void* pobj = WListCore_GetPlotItem(This, &Plot, item);

		Plot.m_cvtinfo = Window_GetPosInfo(This->m_wnd);
		m.pt = PointToWindow(&pMouse->pt, &Plot.m_cvtinfo);
		if (This->m_pFWList->item.FGetArea)
		{
			This->m_pFWList->item.FGetArea(pobj, This->m_pHandle, &Plot, &m, pArea);
		}
		else
		{
			pArea->id = EWItemArea_Select;
			pArea->rect = Plot.m_box;
		}
		return item;
	}

	pArea->id = EWItemArea_Outside;
	return -1;
}

void WListCore_ItemArea(const WListCore* This, int item, WItemArea* pArea)
{
	if ((item >= 0)
	&&  (item < WListCore_Count(This)))
	{
		WPlotItem Plot;
		void* pobj = WListCore_GetPlotItem(This, &Plot, item);

		Plot.m_cvtinfo = Window_GetPosInfo(This->m_wnd);
		if (This->m_pFWList->item.FGetArea)
			This->m_pFWList->item.FGetArea(pobj, This->m_pHandle, &Plot, NULL, pArea);
		else
			pArea->rect = Plot.m_box;
	}
}

const int* WListCore_GetSizes(const WListCore* This)
{
	return This->m_pItemSizes;
}

const int* WListCore_ItemGetSizes(const WListCore* This, int item) throws(index)
{
	WListItem* pItem = Array_Get(This->m_pList, item);

	return (int*) (pItem + 1);
}

void WListCore_NotifySelChange(WListCore* This, int item)
{
	Event e;
	Event_SelectionChange el;

	e.Type = EEvent_SelectionChange;
	e.pData = &el;

	el.pWList = This;
	el.Index  = item;

	if (This->m_parentwnd != HWind_None) Window_ProcessEvent(This->m_parentwnd, &e);
}

void WListCore_StartSelection(WListCore* This, unsigned int mode, WSelector* pSel)
{
	throw_assert((mode & ~(EWListCore_DragKey | EWListCore_DragMouse | EWListCore_DragAdjust)) == 0);
	throw_assert(mode);

	// Only works in multiple selection mode
	if ((This->m_flags & EWList_SelModeMask) != EWList_SelModeMulti)
		return;

	This->m_coreflags |= mode;

	// In Select mode, clear previous selection
	if (!(mode & EWListCore_DragAdjust))
	{
		if (WListCore_SetItemsState(This, 0, EWItem_Selected, EWItem_Selected, EWItem_Selected))
			WListCore_NotifySelChange(This, -1);
	}

	This->m_pSelector = pSel;
	This->m_pSelector->pFStart(This->m_pSelector);
	This->m_pSelector->pFCheck(This->m_pSelector);
}

void WListCore_EndSelection(WListCore* This, bool bSuccess)
{
	int count = Array_Count(This->m_pList);
	bool bChanged = false;
	bool bSelChanged = false;

	throw_assert(This->m_coreflags & (EWListCore_DragKey | EWListCore_DragMouse));

	// If successed, update selection staus
	if (bSuccess)
	{
		int i;

		// Perform a final check of the selector
		This->m_pSelector->pFCheck(This->m_pSelector);

		if (This->m_coreflags & EWListCore_DragAdjust)
		{
			bSelChanged = WListCore_InvertItemsState(This, EWItem_Selected | EWItem_Active, EWItem_Active, EWItem_Active);
		}
		else
		{
			for (i = 0; i < count; i++)
			{
				WListItem* pItem = Array_Get(This->m_pList, i);

				if ((pItem->m_state & (EWItem_Active | EWItem_ProtSelect)) == EWItem_Active)
				{
					if (!(pItem->m_state & EWItem_Selected))
					{
						pItem->m_state |= EWItem_Selected;
						bSelChanged = true;
					}
					pItem->m_state &= ~EWItem_Active;
					pItem->m_state |= EWItem_Redraw;
					bChanged = true;
				}
				else
				{
					if (pItem->m_state & EWItem_Selected)
					{
						pItem->m_state &= ~EWItem_Selected;
						bSelChanged = true;
						pItem->m_state |= EWItem_Redraw;
						bChanged = true;
					}
				}
			}
		}

		// Notify parent of selection update
		if (bSelChanged) WListCore_NotifySelChange(This, -1);
	}
	else
	{
		bChanged = WListCore_SetItemsState(This, 0, EWItem_Active, EWItem_Active, EWItem_Active);
	}

	if (bChanged) WListCore_Refresh(This, -1, -1);

	This->m_coreflags &= ~(EWListCore_DragKey | EWListCore_DragMouse | EWListCore_DragAdjust);

	This->m_pSelector->pFStop(This->m_pSelector);
	This->m_pSelector = NULL;
}

bool WListCore_MoveFocus(WListCore* This, int focus)
{
	if (This->m_FocusPos == focus) return false;

	WListCore_SetFocus(This, focus, true);

	if (!(This->m_coreflags & (EWListCore_MoveOnlyMode | EWListCore_DragKey)))
	{
		if ((This->m_flags & EWList_SelModeMask) != EWList_SelModePopup)
		{
			WListCore_SetItemsState(This, 0, EWItem_Selected, 0, 0);
			WListCore_SetItemState(This, This->m_FocusPos, EWItem_Selected, EWItem_Selected);
			WListCore_NotifySelChange(This, This->m_FocusPos);
		}
	}

	return true;
}

void WListCore_BoxActivation(WListCore* This, const CRect* obox, const CRect* nbox)
{
	WPlotItem Plot;
	CRect rangeo;
	CRect rangen;
	int index, count;
	CPoint pos;

	if (!This->m_pFWList->item.FTouchArea) return;

	Plot.m_cvtinfo = Window_GetPosInfo(This->m_wnd);

	// Determine covered item range
	rangeo = WListCore_PixelsToRowCols(This, obox);
	rangen = WListCore_PixelsToRowCols(This, nbox);
	if (rangeo.x0 > rangen.x0) rangeo.x0 = rangen.x0;
	if (rangeo.y0 > rangen.y0) rangeo.y0 = rangen.y0;
	if (rangeo.x1 < rangen.x1) rangeo.x1 = rangen.x1;
	if (rangeo.y1 < rangen.y1) rangeo.y1 = rangen.y1;
	if (rangeo.x0 < 0) rangeo.x0 = 0;
	if (rangeo.y0 < 0) rangeo.y0 = 0;
	if (rangeo.x1 >= This->m_RowCols.cx) rangeo.x1 = This->m_RowCols.cx;
	if (rangeo.y1 >= This->m_RowCols.cy) rangeo.y1 = This->m_RowCols.cy;

	// Normalize new rect
	if (nbox->x0 <= nbox->x1)
	{
		rangen.x0 = nbox->x0;
		rangen.x1 = nbox->x1;
	}
	else
	{
		rangen.x0 = nbox->x1;
		rangen.x1 = nbox->x0;
	}

	if (nbox->y0 <= nbox->y1)
	{
		rangen.y0 = nbox->y0;
		rangen.y1 = nbox->y1;
	}
	else
	{
		rangen.y0 = nbox->y1;
		rangen.y1 = nbox->y0;
	}

	// Update items
	count = Array_Count(This->m_pList);

	for (pos.y = rangeo.y0; pos.y <= rangeo.y1; pos.y++)
	{
		for (pos.x = rangeo.x0; pos.x <= rangeo.x1; pos.x++)
		{
			index = WListCore_RowColToIndex(This, pos);
			if (index < count)
			{
				unsigned int state = 0;
				void* pobj = WListCore_GetPlotItem(This, &Plot, index);

				if (This->m_pFWList->item.FTouchArea(pobj, This->m_pHandle, &Plot, EWItemArea_Select, &rangen))
					state = EWItem_Active;
				WListCore_SetItemState(This, index, state, EWItem_Active);
			}
		}
	}
}

static EListenerAction WListCore_MsgHandler(void* handle, const Event* e)
{
	WListCore* This = handle;

	IGNORE(e);

	if (WListCore_IsRefreshAllowed(This))
		WListCore_RecalcSize(This);

	return EListenerAction_ContinueEvent;
}

static EListenerAction WListCore_NullHandler(void* handle, const Event* e)
{
	WListCore* This = handle;
	const Mouse* m = Mouse_Get();
	CWindCvt Info = Window_GetPosInfo(This->m_wnd);
	int item = -1;

	IGNORE(e);

	if (!Window_IsOpen(This->m_wnd))
		return EListenerAction_ContinueEvent;

	// Update items activation state when a selector is active
	if (This->m_coreflags & EWListCore_DragMouse)
		This->m_pSelector->pFCheck(This->m_pSelector);

	// Update item above which the pointer currently hovers
	if ((m->w == This->m_wnd)
	&&  (IsPointInWimpRect(&m->pt, &Info.box)))
	{
		WItemArea area;
		item = WListCore_ItemAreaFromScreenPt(This, m, EWListPtrMode_Hover, &area);
		if (area.id != EWItemArea_Select)
			item = -1;
	}

	WListCore_SetHover(This, item);

	return EListenerAction_ContinueEvent;
}

EListenerAction WListCore_EventHandler(void* handle, const Event* e)
{
	WListCore* This = handle;

	switch(e->Type)
	{
		case EEvent_WindowScroll:
		{
			Event_WindowScroll* po = (Event_WindowScroll*) e->pData;
			int delta, step, remain;

			delta = 0;
            step = (This->m_flags & EWList_GrowHorz) ? This->m_Size.cx : 32;
			switch (po->dx)
			{
				case -2: delta = -((po->box.x1 - po->box.x0) - This->m_Offset.cx); break;
				case -1: delta = -step; break;
				case +1: delta = +step; break;
				case +2: delta = (po->box.x1 - po->box.x0) - This->m_Offset.cx; break;
			}
			po->scx += delta;
			if (This->m_flags & EWList_GrowHorz)
			{
				remain = po->scx % step;
				if (remain < 0) remain += step;

				if (delta > 0)
					po->scx -= remain;
				else if (remain && (delta < 0))
					po->scx += step - remain;
			}

			delta = 0;
			step = (!(This->m_flags & EWList_GrowHorz)) ? This->m_Size.cy : 32;
			switch (po->dy)
			{
				case -2: delta = -((po->box.y1 - po->box.y0) - This->m_Offset.cy); break;
				case -1: delta = -step; break;
				case +1: delta = +step; break;
				case +2: delta = (po->box.y1 - po->box.y0) - This->m_Offset.cy; break;
			}
			po->scy += delta;
			if (!(This->m_flags & EWList_GrowHorz))
			{
				remain = po->scy % step;
				if (remain < 0) remain += step;

				if (delta > 0)
					po->scy -= remain;
				else if (remain && (delta < 0))
					po->scy += step - remain;
			}
		}
		// no break;
		case EEvent_WindowOpen:
		{
			CWindOpen* po = (CWindOpen*) e->pData;
			CRect ex = Window_GetInfo(This->m_wnd).ex;
			CRect rect;

			if (This->m_OwnerWnd != HWind_None)
			{
				CWindOpen o = Window_GetOwnerInfo(This->m_wnd);

				Window_OpenPane(po, &o, &This->m_RectToFit, pane_fit_hgadgets | pane_fit_vgadgets);
			}

			rect = WListCore_FixExtent(This, &po->cvt.box);

			if (memcmp(&ex, &rect, sizeof(CRect)))
				Window_SetExtent(This->m_wnd, &rect, false);

			// Fit in extent
			if ((po->cvt.box.x1 - po->cvt.box.x0) > (rect.x1 - rect.x0))
				po->cvt.box.x1 = rect.x1 - rect.x0 + po->cvt.box.x0;
			if ((po->cvt.box.y1 - po->cvt.box.y0) > (rect.y1 - rect.y0))
				po->cvt.box.y0 = rect.y0 - rect.y1 + po->cvt.box.y1;

			// Ensure sizes of box to redraw
			WListCore_Invalidate(This, -1, -1, false);

			return EListenerAction_ContinueEvent;
		}
		break;
		case EEvent_WindowClose:
		{
			// when a popup is closed, we need to remove its popup mark
			This->m_coreflags &= ~EWListCore_OpenAsMenu;
			// Ensure no item owns the caret
			This->m_coreflags &= ~EWListCore_HasFocus;

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

			if (This->m_GhostCaret.x >= 0)
				WListCore_PlotGhostCaret(This, true);

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

			// Abort existing keyboard dragging
			if (This->m_coreflags & EWListCore_DragKey)
			{
				WListCore_EndSelection(This, false);
				WListCore_SetFocus(This, This->m_FocusPos, true);
			}

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

			if (!WListCore_IsRefreshAllowed(This))
				return EListenerAction_StopEvent;

			if ((This->m_flags & EWList_SelModeMask) == EWList_SelModePopup)
			{
				if (m->but & (EBut_ClickSelect | EBut_Menu | EBut_ClickAdjust))
				{
					int item = WListCore_ItemFromScreenPt(This, &m->pt, 0);

					if (item != -1) WListCore_NotifySelChange(This, item);
				}

				return EListenerAction_StopEvent;
			}

			// Ignore middle button
			if (m->but == EBut_Menu) return EListenerAction_ContinueEvent;

			// Set focus to window?
			if ((This->m_coreflags & (EWListCore_OpenAsMenu | EWListCore_HasFocus)) == 0)
			{
				Caret_SetInvisible(This->m_wnd);
				if (m->but == EBut_ClickAdjust)
				{
					WListCore_SetFocus(This, This->m_FocusPos, true);
					return EListenerAction_StopEvent;
				}
				This->m_coreflags &= ~EWListCore_MoveOnlyMode;
			}

			return EListenerAction_ContinueEvent;
		}
		break;
		case EEvent_Key:
		{
			const Event_Key* key = e->pData;

			// Ignore events within icons
			if ((key->caret.pos.i != HIcon_None)
			||  !WListCore_IsRefreshAllowed(This))
				return EListenerAction_ContinueEvent;

			if (This->m_coreflags & EWListCore_DragKey)
			{
				// Treat keypresses handled only while dragging with keyboard
				switch(key->code)
				{
					case 0x01b: // Esc
					{
						WListCore_EndSelection(This, false);
						WListCore_SetFocus(This, This->m_FocusPos, true);

						return EListenerAction_StopEvent;
					}
					break;
					case 0x1dd: // Shift + Ins, stop keyboard dragging
					case 0x1ed: // Ctrl + Ins, stop keyboard adjust dragging
					{
						WListCore_EndSelection(This, true);
						WListCore_SetFocus(This, This->m_FocusPos, true);

						return EListenerAction_StopEvent;
					}
					break;
				}
			}
			else
			{
				unsigned int selmode = This->m_flags & EWList_SelModeMask;

				// Treat keypresses that handled only while NOT dragging with keyboard
				switch(key->code)
				{
					case 0x001: // ^A Select All
					{
						if (selmode == EWList_SelModeMulti)
						{
							if (WListCore_SetItemsState(This, EWItem_Selected, EWItem_Selected, 0, 0))
								WListCore_NotifySelChange(This, -1);

							return EListenerAction_StopEvent;
						}
					}
					break;
					case 0x01a:// ^Z Clear Selection
					{
						if ((selmode == EWList_SelModeMulti)
						||  (selmode == EWList_SelModeSingle))
						{
							if (WListCore_SetItemsState(This, 0, EWItem_Selected, 0, 0))
								WListCore_NotifySelChange(This, -1);

							return EListenerAction_StopEvent;
						}
					}
					break;
					case 0x020: // Space
					{
						if ((selmode == EWList_SelModeMulti)
						||  (selmode == EWList_SelModeSingle))
						{
							WListCore_InvertItemState(This, This->m_FocusPos, EWItem_Selected);
							WListCore_NotifySelChange(This, This->m_FocusPos);

							return EListenerAction_StopEvent;
						}
					}
					break;
					case 0x1cd: // Ins, toggle follow mode
					{
						if ((selmode == EWList_SelModeMulti)
						||  (selmode == EWList_SelModeSingle))
						{
							This->m_coreflags ^= EWListCore_MoveOnlyMode;

							return EListenerAction_StopEvent;
						}
					}
					break;
					case 0x1dd: // Shift + Ins, start keyboard dragging
					case 0x1ed: // Ctrl + Ins, start keyboard adjust dragging
					{
						if (selmode == EWList_SelModeMulti)
						{
							if ((This->m_FocusPos >= 0)
							&&  (This->m_FocusPos < Array_Count(This->m_pList)))
							{
								try
								{
									WListCore_StartSelection(This
										, EWListCore_DragKey
										  | ((key->code == 0x1ed) ? EWListCore_DragAdjust : 0)
									    , throw_New_WListKeySelect(This, This->m_FocusPos)
									    );
								}
								catch
								{
									Task_ReportException();
								}
								catch_end
							}

							return EListenerAction_StopEvent;
						}
					}
					break;
				}
			}

			// Process deplacement keypresses
			switch(key->code)
			{
				case 0x18c: // left arrow
				case 0x18d: // right arrow
				case 0x18e: // down arrow
				case 0x18f: // up arrow
				{
					int focus = This->m_FocusPos;
					int delta = 0;

					switch(key->code)
					{
						case 0x18c: // left arrow
						{
							delta = (This->m_flags & EWList_GrowHorz) ? This->m_RowCols.cy : 1;
							if (!(This->m_flags & EWList_RightToLeft))
								delta = -delta;
						}
						break;
						case 0x18d: // right arrow
						{
							delta = (This->m_flags & EWList_GrowHorz) ? This->m_RowCols.cy : 1;
							if (This->m_flags & EWList_RightToLeft)
								delta = -delta;
						}
						break;
						case 0x18e: // down arrow
						{
							delta = (This->m_flags & EWList_GrowHorz) ? 1 : This->m_RowCols.cx;
							if (This->m_flags & EWList_BottomToTop)
								delta = -delta;
						}
						break;
						case 0x18f: // up arrow
						{
							delta = (This->m_flags & EWList_GrowHorz) ? 1 : This->m_RowCols.cx;
							if (!(This->m_flags & EWList_BottomToTop))
								delta = -delta;
						}
						break;
					}

					focus += delta;

					if ((focus >= 0)
					&&  (focus < Array_Count(This->m_pList)))
						WListCore_MoveFocus(This, focus);

					return EListenerAction_StopEvent;
				}
				break;
				case 0x19c: // shift left arrow
				case 0x19d: // shift right arrow
				case 0x19e: // page down or shift down arrow
				case 0x19f: // page up or shift up arrow
				{
					if (Array_Count(This->m_pList) > 0)
					{
						CWindOpen open = Window_GetState(This->m_wnd).o;
						CPoint    pos = WListCore_IndexToRowCol(This, This->m_FocusPos);
						int deltax = 0;
						int deltay = 0;
						int focus;

						switch(key->code)
						{
							case 0x19c: // shift left arrow
							{
								deltax = -((open.cvt.box.x1 + This->m_Size.cx/2 - open.cvt.box.x0)/This->m_Size.cx);
							}
							break;
							case 0x19d: // shift right arrow
							{
								deltax = (open.cvt.box.x1 + This->m_Size.cx/2 - open.cvt.box.x0)/This->m_Size.cx;
							}
							break;
							case 0x19e: // page down or shift down arrow
							{
								deltay = (open.cvt.box.y1 + This->m_Size.cy/2 - open.cvt.box.y0)/This->m_Size.cy;
							}
							break;
							case 0x19f: // page up or shift up arrow
							{
								deltay = -((open.cvt.box.y1 + This->m_Size.cy/2 - open.cvt.box.y0)/This->m_Size.cy);
							}
							break;
						}

						if (This->m_flags & EWList_RightToLeft)
							pos.x -= deltax;
						else
							pos.x += deltax;

						if (pos.x < 0) pos.x = 0;
						if (pos.x >= This->m_RowCols.cx) pos.x = This->m_RowCols.cx - 1;

						if (This->m_flags & EWList_BottomToTop)
							pos.y -= deltay;
						else
							pos.y += deltay;

						if (pos.y < 0) pos.y = 0;
						if (pos.y >= This->m_RowCols.cy) pos.y = This->m_RowCols.cy - 1;

						focus = WListCore_RowColToIndex(This, pos);

						if (focus >= Array_Count(This->m_pList)) focus = Array_Count(This->m_pList) - 1;

						if (focus != This->m_FocusPos)
						{
							open.cvt.s.cx += deltax * This->m_Size.cx;
							open.cvt.s.cy -= deltay * This->m_Size.cy;
							Window_OpenAt(This->m_wnd, &open);

							WListCore_MoveFocus(This, focus);
						}
					}
					return EListenerAction_StopEvent;
				}
				break;
				case 0x1ac: // ctrl left arrow
				case 0x1ad: // ctrl right arrow
				case 0x1ae: // ctrl down arrow
				case 0x1af: // ctrl up arrow
				{
					if (Array_Count(This->m_pList) > 0)
					{
						CPoint pos = WListCore_IndexToRowCol(This, This->m_FocusPos);
						int focus;

						switch(key->code)
						{
							case 0x1ac: // ctrl left arrow
								pos.x = (This->m_flags & EWList_RightToLeft) ? This->m_RowCols.cx - 1 : 0;
							break;
							case 0x1ad: // ctrl right arrow
								pos.x = (This->m_flags & EWList_RightToLeft) ? 0 : This->m_RowCols.cx - 1;
							break;
							case 0x1ae: // ctrl down arrow
								pos.y = (This->m_flags & EWList_BottomToTop) ? 0 : This->m_RowCols.cy - 1;
							break;
							case 0x1af: // ctrl up arrow
								pos.y = (This->m_flags & EWList_BottomToTop) ? This->m_RowCols.cy - 1 : 0;
							break;
						}

						focus = WListCore_RowColToIndex(This, pos);

						if (focus >= Array_Count(This->m_pList)) focus = Array_Count(This->m_pList) - 1;

						WListCore_MoveFocus(This, focus);
					}
					return EListenerAction_StopEvent;
				}
				break;
			}
			return EListenerAction_ContinueEvent;
		}
		break;
		case EEvent_WindowGainCaret:
		{
			// Force redraw of item owning the caret
			This->m_coreflags |= EWListCore_HasFocus;
			WListCore_SetFocus(This, This->m_FocusPos, false);
			return EListenerAction_ContinueEvent;
		}
		break;
		case EEvent_WindowLoseCaret:
		{
			// Abort existing keyboard dragging
			if (This->m_coreflags & EWListCore_DragKey)
			{
				WListCore_EndSelection(This, false);
				WListCore_SetFocus(This, This->m_FocusPos, true);
			}

			// Force redraw of item owning the caret
			This->m_coreflags &= ~EWListCore_HasFocus;
			WListCore_Refresh(This, This->m_FocusPos, This->m_FocusPos);
			WListCore_Refresh(This, This->m_HoverPos, This->m_HoverPos);

			return EListenerAction_ContinueEvent;
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

/*-------------------------------------------------------------------------*
 *--- Mouse Event handler for normal lists---------------------------------*
 *-------------------------------------------------------------------------*/

EListenerAction WListCore_ListEventHandler(void* handle, const Event* e)
{
	if (e->Type != EEvent_Mouse) return EListenerAction_ContinueEvent;

	WListCore* This = handle;
	const Mouse* m = e->pData;
	WItemArea area;
	int item;

	// Ignore click on icons or clicks while busy
	if ((m->i != HIcon_None)
	||  !WListCore_IsRefreshAllowed(This))
		return EListenerAction_ContinueEvent;

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

	// Clicked on inactive, outside or user area ?
	if (area.id != EWItemArea_Select)
		return EListenerAction_ContinueEvent;

	/*
	 * Left button click on selected item can only start dragging selection.
	 * For single selection, Left button click on unselected item select item.
	 * For multiple selection, Left button click on unselected item starts a new
	 * selection by dragging, Right button click starts inverting selection by dragging.
	 */
	bool bAdjust = ((m->but & (EBut_ClickAdjust|EBut_DragAdjust|EBut_Adjust)) != 0);

	switch(m->but)
	{
		case EBut_ClickSelect:
		case EBut_ClickAdjust:
		case EBut_DragSelect:
		case EBut_DragAdjust:
		{
			// Already dragging?
			if (This->m_coreflags & EWListCore_DragMouse)
				return EListenerAction_StopEvent;

			WListCore_SetFocus(This, item, true);

			if ((WListCore_GetFlags(This) & EWList_SelModeMask) != EWList_SelModeMulti)
			{
				if (!(WListCore_GetItemState(This, item) & EWItem_Selected))
				{
					// Clearing of previous selection is handled automatically
					WListCore_SetItemState(This, item, EWItem_Selected, EWItem_Selected);
					WListCore_NotifySelChange(This, item);
				}

				return EListenerAction_StopEvent;
			}
			else
			{
				if (!bAdjust)
				{
					// Click on selected object is just passed to next handler
					if (WListCore_GetItemState(This, item) & EWItem_Selected)
						return EListenerAction_ContinueEvent;
				}

				// Prepare dragging
				WListCore_StartSelection(This
					, EWListCore_DragMouse
					  | (bAdjust ? EWListCore_DragAdjust : 0)
					, throw_New_WListSelect(This, item)
					);

				CWindCvt info = Window_GetPosInfo(WListCore_GetWindow(This));
				bool bCompleted = false;

				try
				{
					bCompleted = Drag_DragPoint(NULL, NULL, &info.box, NULL);
				}
				catch
				{
					Task_ReportException();
				}
				catch_end

				// If dropped area to ensure area to refresh is up to date
				// If canceled we must reset selected area
				if (bCompleted)
				{
					m = Mouse_Get();
					item = WListCore_ItemFromScreenPt(This, &m->pt, EWListPtrMode_InRange);
				}

				// Clear activation state and eventually update selection
				// Could already be cleared by a double-click
				if (This->m_coreflags & EWListCore_DragMouse)
				{
					WListCore_EndSelection(This, bCompleted);
					WListCore_SetFocus(This, item, true);
					// Always select first item even if drag aborted
					if (!bAdjust)
					{
						WListCore_SetItemState(This, item, EWItem_Selected, EWItem_Selected);
						WListCore_NotifySelChange(This, item);
					}
				}

				return EListenerAction_StopEvent;
			}
		}
		break;
		case EBut_Select:
		case EBut_Adjust:
		{
			// Dragging, Stop it?
			if (This->m_coreflags & EWListCore_DragMouse)
			{
				WListCore_EndSelection(This, true);
				WListCore_SetFocus(This, This->m_FocusPos, true);
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}
