///////////////////////////////////////////////////////////////////////////////
//
// File   : Asura_MessageTable_Dialog.cpp
// Author : Dan Clarke
// Date   : 05-Nov-2008
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_TOOLS

// This file's header...
#include "Interface/Asura_MessageTable_Dialog.h"

// Asura independent header-files...
#include "Interface/Asura_Cursor.h"
#include "Interface/Asura_Gadget.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"
#include "Rendering/Asura_Overlay_Quad.h"
#include "Rendering/Asura_Text_Output.h"
#include "Rendering/Asura_Render.h"
#include "Core/Asura_UserInput.h"
#include "Core/Asura_TextureManagement.h"
#include "Debugging/Asura_DebugText.h"
#include "Entity/Triggers/Asura_ServerEntity_SplitterBlock.h"
#include "Entity/Triggers/Asura_ServerEntity_TimelineTrigger.h"
#include "Entity/Tools_Message_Editing.h"
#include "Objects/Tools_Object_System.h"
#include "Entity/Tools_Entity_Object.h"
#include "Entity/Tools_MessageEditor.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Entity/EntityTemplate/Asura_ServerEntity_Template.h"
#include "Entity/Triggers/Asura_ServerEntity_LogicTrigger.h"
#include "EntityTemplate/Tools_Entity_Template_Object.h"
#include "Entity/Tools_Entity_LogicTrigger_Object.h"

extern unsigned int Asura_OverlayBrightness;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define HEADER_HEIGHT			(1.0f)
#define ROW_HEIGHT				(1.3f)
#define ROW_BACKGROUND_MARGIN	(0.1f)
#define MIN_HEIGHT				(10.0f)
#define BLOCK_EXPAND_ICON_SIZE	(16.0f)
#define DRAG_DELAY_IN_SECONDS	(0.2f)
//#define MOUSE_WHEEL_SCROLL_RATE	(3.0f)
#define MOUSE_WHEEL_SCROLL_RATE	(0.1f)
#define DRAGDROP_SCROLL_RATE	(0.01f)
//#define DEBUG_DISABLE_CLIPPING

// Colours
#define ROW_COLOUR				(0x32324e)
#define SELECTED_ROW_COLOUR		(0x320000ff)
#define ROW_COLOUR_EVENTHEADER	(0x565787)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static const Asura_Vector_2 g_xCellOffset(0.0f, 0.2f);

static int s_iTextureIndex = ASURA_TEXTURE_NONE;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct HeaderInfo
{
	const char* pszText;
	float fXPosCoeff;
	float fWidthCoeff;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//
// For outgoing messages
//

enum OutgoingHeadingsEnum
{
	HEADING_OUTGOING_DELAY,
	HEADING_OUTGOING_MESSSAGE,
	HEADING_OUTGOING_IO,
	HEADING_OUTGOING_TARGET,
	HEADING_OUTGOING_PROBABILITY,
	HEADING_OUTGOING_DISABLE,

	NUM_OUTGOING_HEADINGS,
};

static HeaderInfo s_axOutgoingHeaderInfo[]=
{
	{"Delay",	0.0f,	0.15f},
	{"Message",	0.0f,	0.25f},
	{"I/O",		0.0f,	0.15f},
	{"Target",	0.0f,	0.25f},
	{"Prob.",	0.0f,	0.13f},
	{"Disable",	0.0f,	0.07f},
};

//
// For incoming messages
//

enum IncomingHeadingsEnum
{
	HEADING_INCOMING_MESSAGE,
	HEADING_INCOMING_SOURCE,

	NUM_INCOMING_HEADINGS,
};

static HeaderInfo s_axIncomingHeaderInfo[]=
{
	{"Message",	0.0f,	0.4f},
	{"Source",	0.0f,	0.6f},
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma warning(disable:4355)

Asura_MessageTable_Dialog::Asura_MessageTable_Dialog(const Asura_Vector_2& xOffset, float fWidth, Asura_Tabs_Dialog* pxParentTabDialog, bool& bIsMessageEditingMode) : PARENT(xOffset, Asura_Vector_2(fWidth, MIN_HEIGHT), 0x00000000), m_xDragDrop(*this), m_bIsMessageEditingMode(bIsMessageEditingMode)
{
	Asura_Assert((sizeof(s_axOutgoingHeaderInfo) / sizeof(HeaderInfo)) == NUM_OUTGOING_HEADINGS, "Number of enums for headings don't match up with number of entries in s_axOutgoingHeaderInfo");
	Asura_Assert((sizeof(s_axIncomingHeaderInfo) / sizeof(HeaderInfo)) == NUM_INCOMING_HEADINGS, "Number of enums for headings don't match up with number of entries in s_axIncomingHeaderInfo");

	float fXPosCoeff = 0.0f;
	float fWidthTotal = 0.0f;

	for(u_int n = 0; n < NUM_OUTGOING_HEADINGS; n++)
	{
		s_axOutgoingHeaderInfo[n].fXPosCoeff = fXPosCoeff;
		fXPosCoeff += s_axOutgoingHeaderInfo[n].fWidthCoeff;
		fWidthTotal += s_axOutgoingHeaderInfo[n].fWidthCoeff;
	}

	Asura_Assert(fWidthTotal == 1.0f, "Message Editor: The sum of the widths need to be 1.0f");

	fXPosCoeff = 0.0f;
	fWidthTotal = 0.0f;

	for(u_int n = 0; n < NUM_INCOMING_HEADINGS; n++)
	{
		s_axIncomingHeaderInfo[n].fXPosCoeff = fXPosCoeff;
		fXPosCoeff += s_axIncomingHeaderInfo[n].fWidthCoeff;
		fWidthTotal += s_axIncomingHeaderInfo[n].fWidthCoeff;
	}

	Asura_Assert(fWidthTotal == 1.0f, "Message Editor: The sum of the widths need to be 1.0f");

	m_bOverlayDirty = true;
	m_pxParentTabDialog = pxParentTabDialog;
	m_pfnRowSelChangeCallback = NULL;
	m_bUpdateSize = false;
	m_eType = TYPE_NORMAL;
	m_iLastRowSelection = 0;
	m_bIsTableVisible = false;

	Asura_Vector_2 xScrollPos = xOffset;
	xScrollPos.x += GetWidth() - (17.0f / ASURA_TEXT_OUTPUT_FONT_HEIGHT);
	xScrollPos.y += HEADER_HEIGHT;
	m_fScrollValue = 0.0f;
	m_bScrollChanged = false;
	m_pxVerticalBar = new Asura_Scrollbar_Gadget(xScrollPos, &m_fScrollValue, 0.0f, 1.0f, false, 10.0f, NULL, &m_bScrollChanged);
	//m_pxScrollbar = new Asura_Scrollbar_Gadget(xOffset,pfValue,fMin,fMax,bHorizontal,fLength,szPrintFormat,pbChanged);

	AddGadget(m_pxVerticalBar);

	SetRenderBorder(true);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_MessageTable_Dialog::~Asura_MessageTable_Dialog()
{
	Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

	for(; !xIter.Done(); xIter.Next())
	{
		delete xIter.GetCurrent();
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTable_Dialog::Update(const Asura_Vector_2& xOrigin)
{
	if(!m_bIsTableVisible)
	{
		return;
	}

	// Mouse hittest

	bool bIsHit = false;
	bool bIsCtrlDown = false;
	bool bIsShiftDown = false;

	float fMouseX, fMouseY;

	Asura_Cursor::GetScreenCoords(fMouseX, fMouseY);

	fMouseX -= (m_xOffset.x + xOrigin.x);
	fMouseY -= (m_xOffset.y + xOrigin.y);

	if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_MOUSEBUTTON1))
	{
		if((fMouseX >= 0.0f) && (fMouseX <= (GetWidth() - m_pxVerticalBar->GetBarThickness())) && (fMouseY >= 0.0f) && (fMouseY <= GetHeight()))
		{
			bIsHit = true;

			bIsCtrlDown = Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL);
			bIsShiftDown = Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT);
		}
	}

	// Handle mouse scroll wheel

	if(MOUSE_WHEEL_SCROLL_RATE > 0.0f && !Asura_Overlay_System::s_bUsedKeypressThisFrame)
	{
		//if(IsCursorWithinChildRect())
		{
			if(Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEWHEELUP))
			{
				m_fScrollValue = Asura_Maths::Max(m_fScrollValue - MOUSE_WHEEL_SCROLL_RATE, 0.0f);

				m_bScrollChanged = true;

				Asura_Overlay_System::s_bUsedKeypressThisFrame = true;
			}

			if(Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEWHEELDOWN))
			{
				//if(m_xChildOffset.y > m_pxVerticalBar->GetMax()) m_xChildOffset.y = m_pxVerticalBar->GetMax();

				m_fScrollValue = Asura_Maths::Min(m_fScrollValue + MOUSE_WHEEL_SCROLL_RATE, 1.0f);

				m_bScrollChanged = true;

				Asura_Overlay_System::s_bUsedKeypressThisFrame = true;
			}
		}
	}

	static bool s_bIsWaitingForUnpress = false;
	static Asura_Vector_2 s_xWaitingForUnpressStartPosition;
	static Asura_MessageTableRow* s_pxUnpressRow;

	if(Asura_UserInput::DebouncedKeyNotPressed(ASURA_KEYID_MOUSEBUTTON1))
	{
		if(s_bIsWaitingForUnpress)
		{
			s_bIsWaitingForUnpress = false;

			float fDeltaX = fMouseX - s_xWaitingForUnpressStartPosition.x;
			float fDeltaY = fMouseY - s_xWaitingForUnpressStartPosition.y;

			if((Asura_Maths::Abs(fDeltaX) < 0.5f) && (Asura_Maths::Abs(fDeltaY) < 0.5f))
			{
				Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

				for(; !xIter.Done(); xIter.Next())
				{
					if(xIter.GetCurrent() != s_pxUnpressRow)
					{
						xIter.GetCurrent()->SetSelected(false);
					}
				}

				s_pxUnpressRow->SetSelected(true);
			}
		}
	}

	bool bIsReleasing = false;

	if(m_xDragDrop.IsDragging() && !Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEBUTTON1))
	{
		bIsReleasing = true;
	}

	// Internal-code hittests

	bool bHitAlreadySelectedRow = false;

	if(bIsHit)
	{
		Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

		bool bSkipUntilHeader = false;

		for(int iIndex = 0; !xIter.Done(); xIter.Next(), iIndex++)
		{
			Asura_MessageTableRow* pxRow = xIter.GetCurrent();

			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER)
			{
				bSkipUntilHeader = false;
			}

			if(!bSkipUntilHeader)
			{
				float fRowYOffset = pxRow->GetRowYOffset();

				if((fMouseY >= fRowYOffset) && ((fMouseY <= (fRowYOffset + ROW_HEIGHT))))
				{
					if(pxRow->InternalHitTest(fMouseX, fMouseY - fRowYOffset))
					{
						bIsHit = false;
						break;
					}
					else
					{
						if(pxRow->IsSelected())
						{
							bHitAlreadySelectedRow = true;
						}
					}
				}
			}

			if(!pxRow->IsExpanded())
			{
				bSkipUntilHeader = true;
			}
		}
	}

	// Update dialogs

	int iHitIndex = -1;

	bool bSkipUntilHeader = false;

	bool bSelectedChanged = false;

	Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

	for(int iIndex = 0; !xIter.Done(); xIter.Next(), iIndex++)
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		pxRow->Update(NULL, false, false);

		if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER)
		{
			bSkipUntilHeader = false;
		}

		if(!bSkipUntilHeader)
		{
			if(bIsHit || bIsReleasing)
			{
				float fRowYOffset = pxRow->GetRowYOffset();

				if((fMouseY >= fRowYOffset) && ((fMouseY <= (fRowYOffset + ROW_HEIGHT))))
				{
					if(bIsHit)
					{
						//if(!pxRow->InternalHitTest(fMouseX, fMouseY - fRowYOffset))
						{
							bool bSelectFlag = pxRow->IsSelected() ? false : true;

							if(bSelectFlag || Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
							{
								pxRow->SetSelected(bSelectFlag);
								bSelectedChanged = true;
							}
						}
					}

					if(bIsShiftDown)
					{
						int iStart = Asura_Maths::Min(m_iLastRowSelection, iIndex);
						int iEnd = Asura_Maths::Max(m_iLastRowSelection, iIndex);

						for(int n = iStart; n < iEnd; n++)
						{
							m_xRowList.GetEntryByIndex(n)->SetSelected(true);
						}
					}

					if(!bIsCtrlDown && !bIsShiftDown)
					{
						if(bHitAlreadySelectedRow)
						{
							s_bIsWaitingForUnpress = true;
							s_xWaitingForUnpressStartPosition.x = fMouseX;
							s_xWaitingForUnpressStartPosition.y = fMouseY;
							s_pxUnpressRow = pxRow;
						}
					}

					iHitIndex = iIndex;
					m_iLastRowSelection = iHitIndex;
				}
				else
				{
					if(bIsHit) // Another row other than this row has been hit
					{
						if(!bIsCtrlDown && !bIsShiftDown)
						{
							if(!bHitAlreadySelectedRow) // If the row that we hit is already selected, then only deselect other rows on mouse-up (so drag and drop works)
							{
								pxRow->SetSelected(false);
							}

							bSelectedChanged = true;
						}
					}
				}
			}
		}

		if(!pxRow->IsExpanded())
		{
			bSkipUntilHeader = true;
		}
	}

	// Notify on selection change

	if(bSelectedChanged && m_pfnRowSelChangeCallback)
	{
		xIter.Restart();

		for(; !xIter.Done(); xIter.Next())
		{
			Asura_MessageTableRow* pxRow = xIter.GetCurrent();

			m_pfnRowSelChangeCallback(pxRow->IsSelected(), pxRow);
		}
	}

	// Drag and drop

	if(iHitIndex != -1)
	{
		if(bIsReleasing)
		{
			m_xDragDrop.Drop(iHitIndex);
			m_xDragDrop.End();
		}
		else
		{
			m_xDragDrop.Begin(iHitIndex, fMouseX, fMouseY);
		}
	}
	else
	{
		if(!Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEBUTTON1))
		{
			m_xDragDrop.End();
		}
	}

	m_xDragDrop.Process(fMouseX, fMouseY, m_fScrollValue, m_bScrollChanged);

	PARENT::Update(xOrigin);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Asura_MessageTable_Dialog::SwapMessages(int iIndex1, int iIndex2)
{
	int iNumMessages = m_xRowList.GetNumEntries();
	if(iIndex1 >= iNumMessages || iIndex2 >= iNumMessages) return false;
	if(iIndex1 < 0 || iIndex2 < 0) return false;

	Asura_MessageTableRow* pxRow1 = m_xRowList.GetEntryByIndex(iIndex1);
	Asura_MessageTableRow* pxRow2 = m_xRowList.GetEntryByIndex(iIndex2);

	Asura_MessageTableRow_Outgoing* pxDerivedRow1 = static_cast<Asura_MessageTableRow_Outgoing*> (pxRow1);
	Asura_MessageTableRow_Outgoing* pxDerivedRow2 = static_cast<Asura_MessageTableRow_Outgoing*> (pxRow2);

	if((pxRow1->GetType() != Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE) || (pxRow2->GetType() != Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE))
	{
		return false;
	}

	Asura_StaticMessage* pxMessage1 = pxDerivedRow1->GetOutgoingMessagePointer();
	Asura_StaticMessage* pxMessage2 = pxDerivedRow2->GetOutgoingMessagePointer();

	if(!pxMessage1 || !pxMessage2) return false;

	Asura_StaticMessage xTemp = *pxMessage1;
	*pxMessage1 = *pxMessage2;
	*pxMessage2 = xTemp;

	return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTable_Dialog::PerFrameUpdate()
{
	if(!m_bIsTableVisible)
	{
		return;
	}

	// Update dialog positions (if required)

	Asura_Vector_2 xNewOffset(0.0f, 0.0f);
	xNewOffset.y += HEADER_HEIGHT;

	Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);
	bool bSelectedChanged = false;

	if(m_bOverlayDirty || m_bScrollChanged)
	{
		m_bOverlayDirty = false;
		m_bScrollChanged = false;

		bool bHideUntilHeader = false;

		// Count how many rows should be visible (ignoring the scrollbar, but taking into account collapsable blocks)

		int iVisibleRowCounter = 0;

		for(; !xIter.Done(); xIter.Next())
		{
			Asura_MessageTableRow* pxRow = xIter.GetCurrent();

			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER)
			{
				bHideUntilHeader = false;
			}

			if(!bHideUntilHeader)
			{
				iVisibleRowCounter++;
			}

			if(!pxRow->IsExpanded())
			{
				// Not expanded, so skip rows until next header
				bHideUntilHeader = true;
			}
		}

		xIter.Restart();

		// Work out the percentage of the required height that will fit in the current table height

		m_iScrollNumHiddenRows = 0;

		if(iVisibleRowCounter > 0)
		{
			float fScrollViewCoeff = (1.0f - ((GetHeight() - HEADER_HEIGHT) / ((float)iVisibleRowCounter * ROW_HEIGHT))) * m_fScrollValue;

			m_iScrollNumHiddenRows = (int)((fScrollViewCoeff * (float)iVisibleRowCounter) + 0.5f);
		}

		int iRowCounter = 0;
		bool bHideRestOfRows = false;
		bHideUntilHeader = false;

		for(; !xIter.Done(); xIter.Next())
		{
			Asura_MessageTableRow* pxRow = xIter.GetCurrent();

			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER)
			{
				bHideUntilHeader = false;
			}

			bool bHideThisRow = (iRowCounter < m_iScrollNumHiddenRows) || bHideUntilHeader || bHideRestOfRows;

			Asura_Vector_2 xTempVec = xNewOffset + g_xCellOffset;

			pxRow->Update(&xTempVec, true, bHideThisRow);

			pxRow->SetRowYOffset(xNewOffset.y);

			if(!bHideThisRow)
			{
				xNewOffset.y += ROW_HEIGHT;
			}

			if(!bHideUntilHeader)
			{
				iRowCounter++;
			}

			if(!pxRow->IsExpanded())
			{
				// Not expanded, so skip rows until next header
				bHideUntilHeader = true;
			}

			if(xNewOffset.y > GetHeight())
			{
				bHideRestOfRows = true;
			}
		}

		// Update the scrollbar height

		const float fViewHeight = GetHeight() - HEADER_HEIGHT;
		const float fRequiredViewHeight = iVisibleRowCounter * ROW_HEIGHT;

		float fCoeff = (fRequiredViewHeight > 0.0f) ? (fViewHeight / fRequiredViewHeight) : 1.0f;

		if(fCoeff >= 1.0f)
		{
			m_pxVerticalBar->SetHidden(true);
		}
		else
		{
			m_pxVerticalBar->SetHidden(false);
			m_pxVerticalBar->SetBarLength(fViewHeight);
			m_pxVerticalBar->SetSliderLength(GetHeight() * fCoeff);
		}
	}

	// Keyboard shortcuts

	if(GetTableType() == TABLE_TYPE_OUTGOING)
	{
		if(Tools_MessageEditor::GetSelectedTabIndex() == Tools_MessageEditor::TAB_OUTGOING_MESSAGES)
		{
			if(Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT))
			{
				if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_UP))
				{
					if(Asura_MessageTable_Dialog::SwapMessages(m_iLastRowSelection, m_iLastRowSelection - 1))
					{
						Asura_MessageTableRow_Outgoing* pxSelectedRow = static_cast<Asura_MessageTableRow_Outgoing*> (m_xRowList.GetEntryByIndex(m_iLastRowSelection - 1));
						pxSelectedRow->GetOutgoingMessagePointer()->m_uToolsFlags |= uTOOLS_STATICMESSAGE_FLAG_MESSAGEEDITOR_SELECTED;
						m_iLastRowSelection--;
						RequestParentToRecreateTable(true);
					}
				}

				if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_DOWN))
				{
					if(Asura_MessageTable_Dialog::SwapMessages(m_iLastRowSelection, m_iLastRowSelection + 1))
					{
						Asura_MessageTableRow_Outgoing* pxSelectedRow = static_cast<Asura_MessageTableRow_Outgoing*> (m_xRowList.GetEntryByIndex(m_iLastRowSelection + 1));
						pxSelectedRow->GetOutgoingMessagePointer()->m_uToolsFlags |= uTOOLS_STATICMESSAGE_FLAG_MESSAGEEDITOR_SELECTED;
						m_iLastRowSelection++;
						RequestParentToRecreateTable(true);
					}
				}
			}

			if(Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
			{
				if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_UP))
				{
					if(m_xRowList.GetNumEntries() > 0)
					{
						DeselectAllRows();

						if(m_iLastRowSelection > 0)
						{
							m_iLastRowSelection--;
						}

						m_xRowList.GetEntryByIndex(m_iLastRowSelection)->SetSelected(true);
						ScrollToRow(m_iLastRowSelection);
						bSelectedChanged = true;
					}
				}

				if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_DOWN))
				{
					if(m_xRowList.GetNumEntries() == (m_iLastRowSelection + 1))
					{
						DeselectAllRows();
						m_xRowList.GetEntryByIndex(m_iLastRowSelection)->SetSelected(true);
						ScrollToRow(m_iLastRowSelection);
						bSelectedChanged = true;

					}
					else if(m_xRowList.GetNumEntries() > (m_iLastRowSelection + 1))
					{
						DeselectAllRows();
						m_xRowList.GetEntryByIndex(m_iLastRowSelection + 1)->SetSelected(true);
						m_iLastRowSelection++;
						ScrollToRow(m_iLastRowSelection);
						bSelectedChanged = true;

					}
				}

				/*
				if(Asura_UserInput::DebouncedKeyPressed('c'))
				{
					Tools_MessageEditor::CopyToClipboard();
				}

				if(Asura_UserInput::DebouncedKeyPressed('v'))
				{
					Tools_MessageEditor::PasteFromClipboard();
				}
				*/
			}
		}
	}

	// Notify on selection change
	if(bSelectedChanged && m_pfnRowSelChangeCallback)
	{
		xIter.Restart();

		for(; !xIter.Done(); xIter.Next())
		{
			Asura_MessageTableRow* pxRow = xIter.GetCurrent();

			m_pfnRowSelChangeCallback(pxRow->IsSelected(), pxRow);
		}
	}

	PARENT::PerFrameUpdate();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

u_int Asura_MessageTable_Dialog::GetNumSelectedRows()
{
	Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

	u_int uCounter = 0;

	for(; !xIter.Done(); xIter.Next())
	{
		if(xIter.GetCurrent()->IsSelected())
		{
			uCounter++;
		}
	}

	return uCounter;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTable_Dialog::DeselectAllRows()
{
	Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

	for(; !xIter.Done(); xIter.Next())
	{
		xIter.GetCurrent()->SetSelected(false);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTableRow_Outgoing::Update(Asura_Vector_2* pxOffset, bool bUpdatePositions, bool bHideControls)
{
	// Update the positions if they require it

	if(bUpdatePositions)
	{
		pxOffset->x = 0.0f;

		for(u_int uHeadingIndex = 0; uHeadingIndex < NUM_OUTGOING_HEADINGS; uHeadingIndex++)
		{
			switch(uHeadingIndex)
			{
				case HEADING_OUTGOING_DELAY:
				{
					if(bHideControls)
					{
						m_pxDelayNumericalControl->SetHidden(true);
					}
					else
					{
						m_pxDelayNumericalControl->SetHidden(false);
						m_pxDelayNumericalControl->MoveDialog(*pxOffset);
					}
					break;
				}
				case HEADING_OUTGOING_MESSSAGE:
				{
					if(bHideControls)
					{
						m_pxMessageDropdown->SetHidden(true);
					}
					else
					{
						m_pxMessageDropdown->SetHidden(false);
						m_pxMessageDropdown->MoveDialog(*pxOffset);
					}
					break;
				}
				case HEADING_OUTGOING_TARGET:
				{
					if(bHideControls)
					{
						m_pxTargetString->SetHidden(true);
					}
					else
					{
						m_pxTargetString->SetHidden(false);
						m_pxTargetString->MoveDialog(*pxOffset);
					}
					break;
				}
				case HEADING_OUTGOING_IO:
				{
					if(bHideControls)
					{
						m_pxIODropdown->SetHidden(true);
					}
					else
					{
						m_pxIODropdown->SetHidden(false);
						m_pxIODropdown->MoveDialog(*pxOffset);
					}
				}
				case HEADING_OUTGOING_PROBABILITY:
				{
					if(bHideControls)
					{
						m_pxProbNumericalControl->SetHidden(true);
					}
					else
					{
						m_pxProbNumericalControl->SetHidden(false);
						m_pxProbNumericalControl->MoveDialog(*pxOffset);
					}
					break;
				}
				case HEADING_OUTGOING_DISABLE:
				{
					if(bHideControls)
					{
						m_pxDisableTickbox->SetHidden(true);
					}
					else
					{
						m_pxDisableTickbox->SetHidden(false);
						Asura_Vector_2 xTempVec = *pxOffset;
						xTempVec.x += s_axOutgoingHeaderInfo[uHeadingIndex].fWidthCoeff * m_xTable.GetTableWidth() * 0.5f;
						xTempVec.x += m_pxDisableTickbox->m_pxGadgetList[0].m_fWidth / 2;
						m_pxDisableTickbox->MoveDialog(xTempVec);
					}
					break;
				}
			}

			pxOffset->x += s_axOutgoingHeaderInfo[uHeadingIndex].fWidthCoeff * m_xTable.GetTableWidth();
		}
	}

	// Check the flags that monitor changes in control values

	bool bChanged = false;

	if(m_bDisableTickboxChanged)
	{
		m_bDisableTickboxChanged = false;

		if(m_bIsDisabled)
		{
			m_pxOutgoingMessage->m_uToolsFlags |= uTOOLS_STATICMESSAGE_FLAG_DISABLED;
		}
		else
		{
			m_pxOutgoingMessage->m_uToolsFlags &= ~uTOOLS_STATICMESSAGE_FLAG_DISABLED;
		}

		bChanged = true;
	}

	if(m_bProbNumberControlChanged)
	{
		m_bProbNumberControlChanged = false;

		m_pxOutgoingMessage->m_fProbability = (float)m_uProbability / 100.0f;

		// Update any other selected rows by the same delta value

		float fDelta = static_cast<float> (((int)m_uProbability - (int)m_uLastProbability) / 100.0f);
		m_uLastProbability = m_uProbability;

		Asura_List_It<Asura_MessageTableRow> xIter(m_xTable.GetRows());

		for(; !xIter.Done(); xIter.Next())
		{
			if(xIter.GetCurrent()->IsSelected())
			{
				if((xIter.GetCurrent() != this) && (xIter.GetCurrent()->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE))
				{
					Asura_MessageTableRow_Outgoing* pxRow = static_cast<Asura_MessageTableRow_Outgoing*> (xIter.GetCurrent());
					Asura_StaticMessage* pxMessage = pxRow->GetOutgoingMessagePointer();

					pxMessage->m_fProbability += fDelta;
					pxRow->SetProbability(static_cast<u_int> (pxMessage->m_fProbability * 100.0f));
				}
			}
		}

		bChanged = true;
	}

	if(m_bDelayNumberControlChanged)
	{
		m_bDelayNumberControlChanged = false;

		m_pxOutgoingMessage->m_fDelay = m_fDelay;

		Asura_List_It<Asura_MessageTableRow> xIter(m_xTable.GetRows());

		// If the current row is not selected, then select it and deselect the other rows.

		if(IsSelected() == false)
		{
			SetSelected(true);

			for(; !xIter.Done(); xIter.Next())
			{
				if(xIter.GetCurrent()->IsSelected())
				{
					if((xIter.GetCurrent() != this) && (xIter.GetCurrent()->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE))
					{
						xIter.GetCurrent()->SetSelected(false);
					}
				}
			}

			xIter.Restart();
		}

		// Update any other selected rows by the same delta value

		float fDelta = m_fDelay - m_fLastDelay;
		m_fLastDelay = m_fDelay;

		for(; !xIter.Done(); xIter.Next())
		{
			if(xIter.GetCurrent()->IsSelected())
			{
				if((xIter.GetCurrent() != this) && (xIter.GetCurrent()->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE))
				{
					Asura_MessageTableRow_Outgoing* pxRow = static_cast<Asura_MessageTableRow_Outgoing*> (xIter.GetCurrent());
					Asura_StaticMessage* pxMessage = pxRow->GetOutgoingMessagePointer();

					pxMessage->m_fDelay += fDelta;
					pxRow->SetDelay(pxMessage->m_fDelay);
				}
			}
		}

		bChanged = true;
	}

	if(m_bMessageDropdownChanged)
	{
		m_bMessageDropdownChanged = false;

		m_pxOutgoingMessage->m_usMsgID = m_pausIds[m_iMessageDropdownIndex];

		Tools_Message_Editing::s_pxThis->InitialiseMessageData(m_pxOutgoingMessage, false);

		m_pxOutgoingMessage->m_uToolsFlags |= uTOOLS_STATICMESSAGE_FLAG_MESSAGEEDITOR_SELECTED;

		m_xTable.RequestParentToRecreateTable();

		bChanged = true;
	}

	if(m_bIODropdownChanged)
	{
		m_bIODropdownChanged = false;

		HandleIODropdownOnChange();

		bChanged = true;
	}

	if(bChanged)
	{
		// Update the object's attribute overlay
		Tools_MessageEditor::UpdateObjectOverlay();

		Tools_MessageEditor::CreateMessageAttributesOverlay(m_pxOutgoingMessage);

		Tools_SaveAsura::SetEntityModified(true);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTableRow_Incoming::Update(Asura_Vector_2* pxOffset, bool bUpdatePositions, bool bHideControls)
{
	// Update the positions if they require it

	if(bUpdatePositions)
	{
		pxOffset->x = 0.0f;

		for(u_int uHeadingIndex = 0; uHeadingIndex < NUM_OUTGOING_HEADINGS; uHeadingIndex++)
		{
			switch(uHeadingIndex)
			{
				case HEADING_INCOMING_MESSAGE:
				{
					if(bHideControls)
					{
						m_pxMessageString->SetHidden(true);
					}
					else
					{
						m_pxMessageString->SetHidden(false);
						m_pxMessageString->MoveDialog(*pxOffset);
					}
					break;
				}
				case HEADING_INCOMING_SOURCE:
				{
					if(bHideControls)
					{
						m_pxSourceString->SetHidden(true);
					}
					else
					{
						m_pxSourceString->SetHidden(false);
						m_pxSourceString->MoveDialog(*pxOffset);
					}
					break;
				}
			}

			pxOffset->x += s_axIncomingHeaderInfo[uHeadingIndex].fWidthCoeff * m_xTable.GetTableWidth();
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTable_Dialog::Render(const Asura_Vector_2& xOrigin)
{
	if(!IsHidden())
	{
		Asura_Vector_2 xNewOffset(xOrigin + m_xOffset);

		RenderHeader(xNewOffset);

		float fYOffset = xNewOffset.y + HEADER_HEIGHT;

		bool bSkipUntilHeader = false;

		Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

		//int m_iScrollNumHiddenRows = (int)((float)m_xRowList.GetNumEntries() * m_fScrollValue);

		int iRowCounter = 0;

		// Handle the clipping

		m_fClipX = xOrigin.x * ASURA_TEXT_OUTPUT_FONT_WIDTH;
		m_fClipY = (xOrigin.y + m_pxParentTabDialog->m_xOffset.y) * ASURA_TEXT_OUTPUT_FONT_HEIGHT;
		m_fClipWidth = m_pxOverlay->m_fWidth * ASURA_TEXT_OUTPUT_FONT_WIDTH;
		//m_fClipHeight = (m_pxParentTabDialog->GetMaxCoords().y - m_pxParentTabDialog->m_xOffset.y - 0.5f) * ASURA_TEXT_OUTPUT_FONT_HEIGHT;
		m_fClipHeight = (GetMaxCoords().y - m_xOffset.y) * ASURA_TEXT_OUTPUT_FONT_HEIGHT;
		m_fClipHeight += 33.0f;

		for(; !xIter.Done(); xIter.Next())
		{
			Asura_MessageTableRow* pxRow = xIter.GetCurrent();

			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER)
			{
				bSkipUntilHeader = false;
			}

			bool bHideThisRow = (iRowCounter < m_iScrollNumHiddenRows) || bSkipUntilHeader;

			if(!bHideThisRow)
			{
				pxRow->Render(xNewOffset.x, fYOffset, GetWidth());

				if(!pxRow->IsExpanded())
				{
					// Not expanded, so skip rows until next header
					bSkipUntilHeader = true;
				}

				fYOffset += ROW_HEIGHT;
			}

			if(fYOffset > (m_pxParentTabDialog->GetHeight() + xOrigin.y + (m_xOffset.y - m_pxParentTabDialog->m_xOffset.y)))
			{
				break;
			}

			if(!bSkipUntilHeader)
			{
				iRowCounter++;
			}
		}

		Asura_Render::FlushDisplay();
	}

	m_xDragDrop.Render();

	PARENT::Render(xOrigin);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int Asura_MessageTable_Dialog::RenderList(const Asura_Vector_2& xOrigin, int iPriority)
{
	Asura_Render::FlushDisplay();

	#ifndef DEBUG_DISABLE_CLIPPING
	if(!IsHidden())
	{
		Asura_Render::Tools_SetClippingRectangle(m_fClipX, m_fClipY, m_fClipWidth, m_fClipHeight);
	}
	#endif

	int iRetValue = Asura_Panel_Dialog::RenderList(xOrigin, iPriority);

	Asura_Render::FlushDisplay();

	#ifndef DEBUG_DISABLE_CLIPPING
	if(!IsHidden())
	{
		Asura_Render::AllowFullScreenRendering();
	}
	#endif

	#if 0 // Debug code to render the clip rectangle
	{
		if(!IsHidden())
		{
			const float fLeft = m_fClipX;
			const float fRight = m_fClipX + m_fClipWidth;
			const float fTop = m_fClipY;
			const float fBottom = m_fClipY + m_fClipHeight;

			Asura_Render::Render2DLine(fLeft,	fTop,		fRight,	fTop,		0xffff0000);
			Asura_Render::Render2DLine(fRight,	fTop,		fRight,	fBottom,	0xffff0000);
			Asura_Render::Render2DLine(fRight,	fBottom,	fLeft,	fBottom,	0xffff0000);
			Asura_Render::Render2DLine(fLeft,	fBottom,	fLeft,	fTop,		0xffff0000);
		}
	}
	#endif

	return iRetValue;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTable_Dialog::LoadTexture()
{
	if(s_iTextureIndex == ASURA_TEXTURE_NONE)
	{
		u_int uOldResetGroup = Asura_Texture_Management::GetTextureResetGroup();
		Asura_Texture_Management::SetTextureResetGroup(ASURA_TEXTURE_LOADED_PERMANENT);
		s_iTextureIndex = Asura_Texture_Management::AddTexture("\\Tools\\MessageEditorIcons.tga");
		Asura_Texture_Management::SetTextureFlags(s_iTextureIndex, AsuraTextureFlag_HighQualityCompression);
		Asura_Texture_Management::SetTextureResetGroup(uOldResetGroup);

		Asura_Texture_Management::LoadTextures();
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Asura_MessageTable_Dialog::IsPointInDialog(const Asura_Vector_2& xOrigin,const Asura_Vector_2& xPos) const
{
	Asura_Vector_2 xRelativePos = xPos - xOrigin;

	if ((xRelativePos.x > m_xOffset.x) && (xRelativePos.x < (m_xOffset.x + GetWidth())))
	{
		if ((xRelativePos.y > m_xOffset.y) && (xRelativePos.y < (m_xMaxCoord.y)))
		{
			return true;
		}
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTable_Outgoing_Dialog::RenderHeader(const Asura_Vector_2& xOrigin)
{
	const float fLeft	= xOrigin.x * ASURA_TEXT_OUTPUT_FONT_WIDTH;
	const float fTop	= xOrigin.y * ASURA_TEXT_OUTPUT_FONT_HEIGHT;
	const float fRight	= (xOrigin.x + (float)GetWidth()) * ASURA_TEXT_OUTPUT_FONT_WIDTH;
	const float fBottom	= fTop + (HEADER_HEIGHT * ASURA_TEXT_OUTPUT_FONT_HEIGHT);

	Asura_Render::Render2DLine(fLeft, fBottom, fRight, fBottom, GetIsMessageEditingMode() ? 0xffff0000 : 0xffffffff);

	float fXOffset = xOrigin.x;

	for(u_int n = 0; n < NUM_OUTGOING_HEADINGS; n++)
	{
		Asura_Text_Output::Print(fXOffset, xOrigin.y, GetColour(), ASURA_TEXT_FORMAT_SMALLFONT, s_axOutgoingHeaderInfo[n].pszText);

		fXOffset += s_axOutgoingHeaderInfo[n].fWidthCoeff * (float) GetWidth();
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTable_Incoming_Dialog::RenderHeader(const Asura_Vector_2& xOrigin)
{
	const float fLeft	= xOrigin.x * ASURA_TEXT_OUTPUT_FONT_WIDTH;
	const float fTop	= xOrigin.y * ASURA_TEXT_OUTPUT_FONT_HEIGHT;
	const float fRight	= (xOrigin.x + (float)GetWidth()) * ASURA_TEXT_OUTPUT_FONT_WIDTH;
	const float fBottom	= fTop + (HEADER_HEIGHT * ASURA_TEXT_OUTPUT_FONT_HEIGHT);

	Asura_Render::Render2DLine(fLeft, fBottom, fRight, fBottom, 0xffffffff);

	float fXOffset = xOrigin.x;

	for(u_int n = 0; n < NUM_INCOMING_HEADINGS; n++)
	{
		Asura_Text_Output::Print(fXOffset, xOrigin.y, GetColour(), ASURA_TEXT_FORMAT_SMALLFONT, s_axIncomingHeaderInfo[n].pszText);

		fXOffset += s_axIncomingHeaderInfo[n].fWidthCoeff * (float) GetWidth();
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTable_Dialog::ClearAllRows()
{
	Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

	for(; !xIter.Done(); xIter.Next())
	{
		delete xIter.GetCurrent();
	}

	m_xRowList.ClearList();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTable_Dialog::CollapseAllRows()
{
	Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER)
		{
			Asura_MessageTableRow_OutgoingEventHeader* pxEventHeaderRow = static_cast<Asura_MessageTableRow_OutgoingEventHeader*> (pxRow);

			pxEventHeaderRow->SetExpanded(false);
		}
	}

	SetDirty();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTable_Dialog::ExpandAllRows()
{
	Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER)
		{
			Asura_MessageTableRow_OutgoingEventHeader* pxEventHeaderRow = static_cast<Asura_MessageTableRow_OutgoingEventHeader*> (pxRow);

			pxEventHeaderRow->SetExpanded(true);
		}
	}

	SetDirty();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Asura_MessageTable_Dialog::ScrollToRow(int iRowIndex)
{
	Asura_List_It<Asura_MessageTableRow> xIter(m_xRowList);

	//determine the number of visible rows
	//and how far down the list of visible rows the target row is
	int iVisibleRowCounter = 0;
	bool bHideUntilHeader = false;
	int iTargetRowVisibleIndex = -1;
	for(int iIndex=0; !xIter.Done(); xIter.Next(), iIndex++)
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER)
		{
			bHideUntilHeader = false;
		}

		if(iIndex == iRowIndex)
		{
			//store how far down the list of visible rows this is
			iTargetRowVisibleIndex = iVisibleRowCounter;
		}
		
		if(!bHideUntilHeader)
		{
			iVisibleRowCounter++;
		}

		if(!pxRow->IsExpanded())
		{
			// Not expanded, so skip rows until next header
			bHideUntilHeader = true;
		}
	}
	if(iTargetRowVisibleIndex < 0)
	{
		Asura_Assert(0, "Failed to find the row to scroll to");
		return;
	}

	const float fViewHeight = GetHeight() - HEADER_HEIGHT;
	const float fTotalHeightOfVisibleRows = iVisibleRowCounter * ROW_HEIGHT;
	const float fRowsVisibleInView = fViewHeight / ROW_HEIGHT;

	if(fTotalHeightOfVisibleRows < fRowsVisibleInView)
	{
		//everything fits in the view , so nothing to do
		return;
	}

	//determine how much the scrollbar translates to in rows
	const float fRowsPerScroll = (iVisibleRowCounter-fRowsVisibleInView);
	const float fScrollPerRow = 1.0f/fRowsPerScroll;

	//and determine the first and last visible row
	const float fFirstVisibleInView = m_fScrollValue*fRowsPerScroll;
	const float fLastVisibleInView = fFirstVisibleInView + fRowsVisibleInView;


	if(fFirstVisibleInView > (float)iTargetRowVisibleIndex)
	{
		//need to scroll up to the row we want
		m_iScrollNumHiddenRows = iTargetRowVisibleIndex;
		m_fScrollValue = m_iScrollNumHiddenRows*fScrollPerRow;
		m_fScrollValue = Asura_Maths::ClampToRange(m_fScrollValue, 0.0f, 1.0f);
		m_bScrollChanged = true;
	}
	else if(fLastVisibleInView-1.0f < (float)iTargetRowVisibleIndex)
	{
		//need to scroll down to see the row we want
		m_iScrollNumHiddenRows = iTargetRowVisibleIndex+ 1 - Asura_Maths::FloatToInt_Chop(fRowsVisibleInView);
		m_fScrollValue = m_iScrollNumHiddenRows*fScrollPerRow;
		m_fScrollValue = Asura_Maths::ClampToRange(m_fScrollValue, 0.0f, 1.0f);
		m_bScrollChanged = true;
	}
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_MessageTableRow::Asura_MessageTableRow(Asura_MessageTable_Dialog& xTable) : m_xTable(xTable)
{
	m_bIsSelected = false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char** Asura_MessageTableRow_Outgoing::CalculateIODropdownStrings()
{
	const char** pszNames;

	bool bDone = false;

	switch (m_pxOutgoingMessage->m_usMsgID)
	{
		case ASURA_MSG_SPLITTER_ACTIVATEOUTPUT:
		case ASURA_MSG_SPLITTER_DEACTIVATEOUTPUT:
		case ASURA_MSG_SPLITTER_TRIGGEROUTPUT:
		{
			Asura_Guid uDestinationGuid = Tools_Message_Editing::GetFinalDestinationGuid(m_pxOutgoingMessage);
			Asura_ServerEntity_SplitterBlock* pxEntity = Asura_ServerEntity_SplitterBlock::FindEntity(uDestinationGuid);

			if(pxEntity)
			{
				const Asura_Message_SplitterBlock_Output* pxData = (const Asura_Message_SplitterBlock_Output*) m_pxOutgoingMessage->m_pData;
				m_iIODropdownIndex = pxData->m_uOutputIndex;
				
				u_int uNumOutputs = pxEntity->GetNumberOfOutputs();
				u_int uNumNames = uNumOutputs;
				
				if(m_iIODropdownIndex >= (int)uNumOutputs || m_iIODropdownIndex < 0)
				{
					//Output referred to doesn't exist , so add an extra 'invalid' entry
					m_iIODropdownIndex = uNumOutputs;
					uNumNames++;
				}
				
				
				pszNames = new const char*[uNumNames];

				m_uNumIODropdownStrings = uNumNames;

				const Asura_SplitterBlockOutput* paxOutputs = pxEntity->GetOutputs();


				for(u_int n = 0; n < uNumOutputs; n++)
				{
					u_int uOutputNameLength = paxOutputs[n].m_xName.GetLength();

					char* pszBuffer = new char[uOutputNameLength + 1];

					if(uOutputNameLength > 0)
					{
						strncpy(pszBuffer, paxOutputs[n].m_xName.GetString(), uOutputNameLength);
					}

					pszBuffer[uOutputNameLength] = 0x00;

					pszNames[n] = pszBuffer;
				}
				
				if(uNumNames > uNumOutputs)
				{
					char* pszBuffer = new char[10];
					strcpy(pszBuffer, "*Invalid*");
					pszNames[uNumOutputs] = pszBuffer;
				}

				bDone = true;
			}

			break;
		}

		case ASURA_MSG_LOGICTRIGGER_INPUTON:
		case ASURA_MSG_LOGICTRIGGER_INPUTOFF:
		{
			Asura_Guid uDestinationGuid = Tools_Message_Editing::GetFinalDestinationGuid(m_pxOutgoingMessage);
			Asura_ServerEntity_LogicTrigger* pxEntity = Asura_ServerEntity_LogicTrigger::FindEntity(uDestinationGuid);

			if(pxEntity)
			{
				Tools_Entity_LogicTrigger_Object* pxToolsEntity = static_cast<Tools_Entity_LogicTrigger_Object*> (Tools_Entity_LogicTrigger_Object::FindEntity(uDestinationGuid));

				const Asura_Message_LogicTrigger_Input* pxData = (const Asura_Message_LogicTrigger_Input*) m_pxOutgoingMessage->m_pData;

				const char* pszInputName = pxToolsEntity->GetInputNameFromHash(pxData->m_uInputHash);

				u_int uNumInputs = pxEntity->GetNumberOfInputs();
				u_int uNumNames = uNumInputs;

				bool bFound = false;

				for(u_int n = 0; n < uNumInputs; n++)
				{
					if(!pxEntity->GetInputName(n).Compare(pszInputName, false))
					{
						m_iIODropdownIndex = n;
						bFound = true;
						break;
					}
				}

				if(m_iIODropdownIndex >= (int)uNumInputs || m_iIODropdownIndex < 0 || !bFound)
				{
					// Input referred to doesn't exist, so add an extra 'invalid' entry
					m_iIODropdownIndex = uNumInputs;
					uNumNames++;
				}

				pszNames = new const char*[uNumNames];

				m_uNumIODropdownStrings = uNumNames;

				for(u_int n = 0; n < uNumInputs; n++)
				{
					Asura_String xInputName = pxEntity->GetInputName(n);

					u_int uInputNameLength = xInputName.GetLength();

					char* pszBuffer = new char[uInputNameLength + 1];

					if(uInputNameLength > 0)
					{
						strncpy(pszBuffer, xInputName.GetString(), uInputNameLength);
					}

					pszBuffer[uInputNameLength] = 0x00;

					pszNames[n] = pszBuffer;
				}
				
				if(uNumNames > uNumInputs)
				{
					char* pszBuffer = new char[10];
					strcpy(pszBuffer, "*Invalid*");
					pszNames[uNumInputs] = pszBuffer;
				}

				bDone = true;
			}

			break;
		}

		case ASURA_MSG_TEMPLATE_TRIGGERINPUT:
		case ASURA_MSG_TEMPLATE_TRIGGEROUTPUT:
		{
			Asura_Guid uDestinationGuid = Tools_Message_Editing::GetFinalDestinationGuid(m_pxOutgoingMessage);
			Asura_ServerEntity_Template* pxEntity = Asura_ServerEntity_Template::FindEntity(uDestinationGuid);
			Tools_Entity_Template_Object* pxToolsEntity = Tools_Entity_Template_Object::FindEntity(uDestinationGuid);

			if(pxEntity)
			{
				u_int uNumPuts = 0;
				const Asura_String* paxPuts;

				if(m_pxOutgoingMessage->m_usMsgID == ASURA_MSG_TEMPLATE_TRIGGERINPUT)
				{
					uNumPuts = pxEntity->GetNumInputNames();
					paxPuts = pxEntity->GetInputNames();
				}
				else
				{
					uNumPuts = pxEntity->GetNumOutputNames();
					paxPuts = pxEntity->GetOutputNames();
				}

				pszNames = new const char*[uNumPuts + 1];

				char *pszNotSetString = new char[strlen("*Not set*") + 1];
				strcpy(pszNotSetString, "*Not set*");
				pszNames[0] = pszNotSetString;

				m_uNumIODropdownStrings = uNumPuts + 1;
				m_iIODropdownIndex = 0;

				if(uNumPuts > 0)
				{
					const Asura_Message_Template_Trigger* pxData = (const Asura_Message_Template_Trigger*) m_pxOutgoingMessage->m_pData;

					const char* pszSelectedPutName;

					if(m_pxOutgoingMessage->m_usMsgID == ASURA_MSG_TEMPLATE_TRIGGERINPUT)
					{
						pszSelectedPutName = pxToolsEntity->GetInputNameFromHash(pxData->m_uNameHash);
					}
					else
					{
						pszSelectedPutName = pxToolsEntity->GetOutputNameFromHash(pxData->m_uNameHash);
					}

					for(u_int n = 0; n < uNumPuts; n++)
					{
						u_int uPutNameLength = paxPuts[n].GetLength();

						if(!paxPuts[n].Compare(pszSelectedPutName, false))
						{
							m_iIODropdownIndex = n + 1;
						}

						char* pszBuffer = new char[uPutNameLength + 1];

						if(uPutNameLength > 0)
						{
							strncpy(pszBuffer, paxPuts[n].GetString(), uPutNameLength);
						}

						pszBuffer[uPutNameLength] = 0x00;

						pszNames[n + 1] = pszBuffer;
					}
				}

				bDone = true;
			}

			break;
		}
	}

	if(!bDone)
	{
		pszNames = new const char*[1];
		char* pcEmpty = new char[1];
		pcEmpty[0] = 0x00;
		pszNames[0] = pcEmpty;

		m_uNumIODropdownStrings = 1;
		m_iIODropdownIndex = 0;
	}

	return pszNames;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTableRow_Outgoing::HandleIODropdownOnChange()
{
	switch(m_pxOutgoingMessage->m_usMsgID)
	{
		case ASURA_MSG_SPLITTER_ACTIVATEOUTPUT:
		case ASURA_MSG_SPLITTER_DEACTIVATEOUTPUT:
		case ASURA_MSG_SPLITTER_TRIGGEROUTPUT:
		{
			Asura_Message_SplitterBlock_Output* pxData = (Asura_Message_SplitterBlock_Output*) m_pxOutgoingMessage->m_pData;

			pxData->m_uOutputIndex = m_iIODropdownIndex;

			break;
		}

		case ASURA_MSG_LOGICTRIGGER_INPUTON:
		case ASURA_MSG_LOGICTRIGGER_INPUTOFF:
		{
			Asura_Message_LogicTrigger_Input* pxData = (Asura_Message_LogicTrigger_Input*) m_pxOutgoingMessage->m_pData;

			pxData->m_uInputHash = Asura_GetHashID(m_pxDropdownStrings[m_iIODropdownIndex]);

			break;
		}

		case ASURA_MSG_TEMPLATE_TRIGGERINPUT:
		case ASURA_MSG_TEMPLATE_TRIGGEROUTPUT:
		{
			Asura_Message_Template_Trigger* pxData = (Asura_Message_Template_Trigger*) m_pxOutgoingMessage->m_pData;

			pxData->m_uNameHash = Asura_GetHashID(m_pxDropdownStrings[m_iIODropdownIndex]);

			break;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_MessageTableRow_Outgoing::Asura_MessageTableRow_Outgoing(Asura_MessageTable_Dialog& xTable, Asura_StaticMessage* pxMessage) : Asura_MessageTableRow(xTable)
{
	m_bDisableTickboxChanged = false;
	m_bProbNumberControlChanged = false;
	m_bDelayNumberControlChanged = false;
	m_bMessageDropdownChanged = false;
	m_bIODropdownChanged = false;
	m_bStringsNeedsFreeing = false;
	m_paszNames = NULL;
	m_pxDropdownStrings = NULL;
	m_pausIds = NULL;
	m_pxOutgoingMessage = pxMessage;

	// Create the number control for the delay

	static float fMinValue = 0.0f;
	static float fMaxValue = 100.0f;

	m_fDelay = 0.0f;

	m_pxDelayNumericalControl = new Asura_Numerical_Dialog("", Asura_Maths::ZeroVector2, &m_fDelay, fMinValue, fMaxValue, 0.1f, "%.2f", &m_bDelayNumberControlChanged);
	m_xTable.AddDialogToPanel(m_pxDelayNumericalControl);

	// Create the dropdown control for the messages

	static const char* pszNames[1] = {"Empty"};
	m_iMessageDropdownIndex = 0;

	m_pxMessageDropdown = new Asura_DropDown_List_Dialog(Asura_Maths::ZeroVector2, pszNames, &m_iMessageDropdownIndex, 10, 1, NULL, &m_bMessageDropdownChanged, "", true);
	m_xTable.AddDialogToPanel(m_pxMessageDropdown);

	m_pxMessageDropdown->SetMinimumWidth(18.0f);
	m_pxMessageDropdown->SetMaximumWidth(18.0f);

	// Create the string display for the message target entity

	m_pxTargetString = new Asura_StringDisplay_Dialog("", Asura_Maths::ZeroVector2);
	m_xTable.AddDialogToPanel(m_pxTargetString);

	// Create the dropdown control for the IO

	m_pxDropdownStrings = CalculateIODropdownStrings();

	m_pxIODropdown = new Asura_DropDown_List_Dialog(Asura_Maths::ZeroVector2, m_pxDropdownStrings, &m_iIODropdownIndex, 10, m_uNumIODropdownStrings, NULL, &m_bIODropdownChanged, "", true);
	m_xTable.AddDialogToPanel(m_pxIODropdown);

	// Create the number control for the probability

	static u_int uMinValue = 0;
	static u_int uMaxValue = 100;

	m_uProbability = 0;

	m_pxProbNumericalControl = new Asura_Numerical_Dialog("", Asura_Maths::ZeroVector2, &m_uProbability, uMinValue, uMaxValue, 1, "%u%%", &m_bProbNumberControlChanged);
	m_xTable.AddDialogToPanel(m_pxProbNumericalControl);

	// Create the tickbox for disabled state

	m_bIsDisabled = false;

	m_pxDisableTickbox = new Asura_Tickbox_Dialog("", Asura_Maths::ZeroVector2, &m_bIsDisabled, &m_bDisableTickboxChanged);
	m_xTable.AddDialogToPanel(m_pxDisableTickbox);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_MessageTableRow_Incoming::Asura_MessageTableRow_Incoming(Asura_MessageTable_Dialog& xTable): Asura_MessageTableRow(xTable)
{
	// Create the string display for the message source entity

	m_pxSourceString = new Asura_StringDisplay_Dialog("", Asura_Maths::ZeroVector2);
	m_xTable.AddDialogToPanel(m_pxSourceString);

	// Create the string display for the message string entity

	m_pxMessageString = new Asura_StringDisplay_Dialog("", Asura_Maths::ZeroVector2);
	m_xTable.AddDialogToPanel(m_pxMessageString);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_MessageTableRow_Outgoing::~Asura_MessageTableRow_Outgoing()
{
	m_xTable.RemoveDialogFromPanel(m_pxDelayNumericalControl);
	m_xTable.RemoveDialogFromPanel(m_pxMessageDropdown);
	m_xTable.RemoveDialogFromPanel(m_pxTargetString);
	m_xTable.RemoveDialogFromPanel(m_pxIODropdown);
	m_xTable.RemoveDialogFromPanel(m_pxProbNumericalControl);
	m_xTable.RemoveDialogFromPanel(m_pxDisableTickbox);

	delete m_pxDelayNumericalControl;
	delete m_pxMessageDropdown;
	delete m_pxTargetString;
	delete m_pxIODropdown;
	delete m_pxProbNumericalControl;
	delete m_pxDisableTickbox;

	if(m_paszNames)
	{
		if(m_bStringsNeedsFreeing)
		{
			for(u_int n = 0; n < m_uNumNames; n++)
			{
				delete [] m_paszNames[n];
			}
		}

		delete [] m_paszNames;
	}

	if(m_pausIds)
	{
		delete [] m_pausIds;
	}

	if(m_pxDropdownStrings)
	{
		for(u_int n = 0; n < m_uNumIODropdownStrings; n++)
		{
			delete [] m_pxDropdownStrings[n];
		}

		delete [] m_pxDropdownStrings;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_MessageTableRow_Incoming::~Asura_MessageTableRow_Incoming()
{
	m_xTable.RemoveDialogFromPanel(m_pxSourceString);
	m_xTable.RemoveDialogFromPanel(m_pxMessageString);

	delete m_pxSourceString;
	delete m_pxMessageString;
	delete m_pxMessageData;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTableRow::RenderRow(const float fXOffset, const float fYOffset, const float fRowWidth)
{
	// Render the row background

	if(Asura_OverlayBrightness == 0xff000000)
	{
		const float fLeft = fXOffset * ASURA_TEXT_OUTPUT_FONT_WIDTH;
		const float fTop = (fYOffset + ROW_BACKGROUND_MARGIN) * ASURA_TEXT_OUTPUT_FONT_HEIGHT;
		const float fWidth = fRowWidth * ASURA_TEXT_OUTPUT_FONT_WIDTH;
		const float fHeight = (ROW_HEIGHT - ROW_BACKGROUND_MARGIN) * ASURA_TEXT_OUTPUT_FONT_HEIGHT;

		Asura_Render::Overlay.SetRectXY(fLeft, fTop, fWidth, fHeight);
		Asura_Render::Overlay.SetRectUV(0,0,1,1);
		Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_NORMAL;
		Asura_Render::Overlay.TextureIndex = ASURA_TEXTURE_NONE;
		Asura_Render::Overlay.Colour = ROW_COLOUR | Asura_OverlayBrightness;
		Asura_Render::Overlay.Render();

		if(m_bIsSelected)
		{
			Asura_Render::Overlay.Colour = SELECTED_ROW_COLOUR;
			Asura_Render::Overlay.Render();
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_MessageTableRow_OutgoingEventHeader::Asura_MessageTableRow_OutgoingEventHeader(Asura_MessageTable_Dialog& xTable) : Asura_MessageTableRow(xTable)
{
	m_pxDelayNumericalControl = NULL;
	m_pxWeightNumericalControl = NULL;
	m_pxDisableTickbox = NULL;
	m_pxWeightLabel = NULL;

	m_bDisableTickboxChanged = false;
	m_bWeightNumberControlChanged = false;
	m_bDelayNumberControlChanged = false;

	m_bIsExpanded = true;

	if(m_xTable.GetType() == Asura_MessageTable_Dialog::TYPE_TIMELINE_TRIGGER)
	{
		// Create the number control for the delay

		static float fMinValue = 0.0f;
		static float fMaxValue = 100.0f;

		m_fDelay = 0.0f;

		m_pxDelayNumericalControl = new Asura_Numerical_Dialog("", Asura_Maths::ZeroVector2, &m_fDelay, fMinValue, fMaxValue, 0.1f, "%.2f", &m_bDelayNumberControlChanged);
		m_xTable.AddDialogToPanel(m_pxDelayNumericalControl);
	}

	if(m_xTable.GetType() == Asura_MessageTable_Dialog::TYPE_SPLITTER_BLOCK)
	{
		// Create the number control for the weight

		m_pxWeightLabel = new Asura_StringDisplay_Dialog("Weight:", Asura_Maths::ZeroVector2);
		m_xTable.AddDialogToPanel(m_pxWeightLabel);

		static u_int uMinValue = 0;
		static u_int uMaxValue = 100;

		m_uWeight = 0;

		m_pxWeightNumericalControl = new Asura_Numerical_Dialog("", Asura_Maths::ZeroVector2, &m_uWeight, uMinValue, uMaxValue, 1, "%u", &m_bWeightNumberControlChanged);
		m_xTable.AddDialogToPanel(m_pxWeightNumericalControl);
	}

	if((m_xTable.GetType() == Asura_MessageTable_Dialog::TYPE_SPLITTER_BLOCK) || (m_xTable.GetType() == Asura_MessageTable_Dialog::TYPE_TIMELINE_TRIGGER))
	{
		// Create the tickbox for disabled state

		m_bIsDisabled = false;

		m_pxDisableTickbox = new Asura_Tickbox_Dialog("", Asura_Maths::ZeroVector2, &m_bIsDisabled, &m_bDisableTickboxChanged);
		m_xTable.AddDialogToPanel(m_pxDisableTickbox);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_MessageTableRow_OutgoingEventHeader::~Asura_MessageTableRow_OutgoingEventHeader()
{
	if(m_pxDelayNumericalControl != NULL)	{ m_xTable.RemoveDialogFromPanel(m_pxDelayNumericalControl);	delete m_pxDelayNumericalControl;	m_pxDelayNumericalControl = NULL; }
	if(m_pxWeightNumericalControl != NULL)	{ m_xTable.RemoveDialogFromPanel(m_pxWeightNumericalControl);	delete m_pxWeightNumericalControl;	m_pxWeightNumericalControl = NULL; }
	if(m_pxDisableTickbox != NULL)			{ m_xTable.RemoveDialogFromPanel(m_pxDisableTickbox);			delete m_pxDisableTickbox;			m_pxDisableTickbox = NULL; }
	if(m_pxWeightLabel != NULL)				{ /*m_xTable.RemoveDialogFromPanel(m_pxWeightLabel);*/			delete m_pxWeightLabel;				m_pxWeightLabel = NULL; }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Asura_MessageTableRow_OutgoingEventHeader::InternalHitTest(float fx, float fy)
{
	// Has the user clicked on our +/- icon?

	if(fx < (BLOCK_EXPAND_ICON_SIZE / ASURA_TEXT_OUTPUT_FONT_WIDTH))
	{
		m_bIsExpanded = m_bIsExpanded ? false : true;
		m_xTable.SetDirty();
		return true;
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*
bool Asura_MessageTableRow_OutgoingEventHeader::IsRenameable() const
{
	// Is this a splitter block?

	Asura_ServerEntity_SplitterBlock* pxSplitterBlock = static_cast<Asura_ServerEntity_SplitterBlock*> (Asura_ServerEntity_SplitterBlock::FindEntity(Tools_MessageEditor::GetReferenceObject()->GetGuid()));

	if(pxSplitterBlock)
	{
		return true;
	}

	// Is this a timeline trigger

	Asura_ServerEntity_TimelineTrigger* pxTimelineTrigger = static_cast<Asura_ServerEntity_TimelineTrigger*> (Asura_ServerEntity_TimelineTrigger::FindEntity(Tools_MessageEditor::GetReferenceObject()->GetGuid()));

	if(pxTimelineTrigger)
	{
		return true;
	}

	return false;
}
*/

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTableRow_OutgoingEventHeader::Render(const float fXOffset, const float fYOffset, const float fRowWidth)
{
	// Render the row background

	if(Asura_OverlayBrightness == 0xff000000)
	{
		const float fLeft = fXOffset * ASURA_TEXT_OUTPUT_FONT_WIDTH;
		const float fTop = (fYOffset + ROW_BACKGROUND_MARGIN) * ASURA_TEXT_OUTPUT_FONT_HEIGHT;
		const float fWidth = fRowWidth * ASURA_TEXT_OUTPUT_FONT_WIDTH;
		const float fHeight = (ROW_HEIGHT - ROW_BACKGROUND_MARGIN) * ASURA_TEXT_OUTPUT_FONT_HEIGHT;

		Asura_Render::Overlay.SetRectXY(fLeft, fTop, fWidth, fHeight);
		Asura_Render::Overlay.SetRectUV(0,0,1,1);
		Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_NORMAL;
		Asura_Render::Overlay.TextureIndex = ASURA_TEXTURE_NONE;
		Asura_Render::Overlay.Colour = ROW_COLOUR_EVENTHEADER | Asura_OverlayBrightness;
		Asura_Render::Overlay.Render();
		Asura_Render::Overlay.Colour = ROW_COLOUR_EVENTHEADER | Asura_OverlayBrightness;

		if(m_bIsSelected && (Asura_OverlayBrightness == 0xff000000))
		{
			Asura_Render::Overlay.Colour = SELECTED_ROW_COLOUR;
			Asura_Render::Overlay.Render();
		}
	}

	// Render the +/- icon

	const float fLeft = fXOffset * ASURA_TEXT_OUTPUT_FONT_WIDTH;
	const float fTop = (fYOffset + g_xCellOffset.y) * ASURA_TEXT_OUTPUT_FONT_HEIGHT;
	const float fWidth = BLOCK_EXPAND_ICON_SIZE;
	const float fHeight = BLOCK_EXPAND_ICON_SIZE;

	Asura_MessageTable_Dialog::LoadTexture();

	if(m_bIsExpanded)
	{
		Asura_Render::Overlay.SetRectUV(0.5f,0.0f,0.5f,0.25f);
	}
	else
	{
		Asura_Render::Overlay.SetRectUV(0.0f,0.0f,0.5f,0.25f);
	}

	Asura_Render::Overlay.SetRectXY(fLeft, fTop, fWidth, fHeight);
	Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_NORMAL;
	Asura_Render::Overlay.TextureIndex = s_iTextureIndex;
	Asura_Render::Overlay.Colour = 0xffffffff;//ROW_COLOUR_EVENTHEADER | Asura_OverlayBrightness;
	Asura_Render::Overlay.Render();

	Asura_Render::FlushDisplay();

	// Render the text

	float fTextX = fXOffset;

	if(m_pxDelayNumericalControl)
	{
		fTextX += 12.0f;
	}

	Asura_Text_Output::Print(fTextX + (fWidth / ASURA_TEXT_OUTPUT_FONT_WIDTH), fYOffset + g_xCellOffset.y, m_xTable.GetColour(), ASURA_TEXT_FORMAT_SMALLFONT, m_xText.GetString());
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTableRow_OutgoingEventHeader::Update(Asura_Vector_2* pxOffset, bool bUpdatePositions, bool bHideControls)
{
	// Update the positions if they require it

	if(bUpdatePositions)
	{
		pxOffset->x = 0.0f;

		for(u_int uHeadingIndex = 0; uHeadingIndex < NUM_OUTGOING_HEADINGS; uHeadingIndex++)
		{
			switch(uHeadingIndex)
			{
				case HEADING_OUTGOING_DELAY:
				{
					if(m_pxDelayNumericalControl)
					{
						if(bHideControls)
						{
							m_pxDelayNumericalControl->SetHidden(true);
						}
						else
						{
							m_pxDelayNumericalControl->SetHidden(false);
							m_pxDelayNumericalControl->MoveDialog(*pxOffset + Asura_Vector_2(1.5f, 0.0f)); // The +AsuraVec is to allow for the +/- icon
						}
					}
					break;
				}
				case HEADING_OUTGOING_PROBABILITY:
				{
					if(m_pxWeightNumericalControl)
					{
						if(bHideControls)
						{
							m_pxWeightLabel->SetHidden(true);
							m_pxWeightNumericalControl->SetHidden(true);
						}
						else
						{
							m_pxWeightLabel->SetHidden(false);
							m_pxWeightLabel->MoveDialog(*pxOffset);
							m_pxWeightNumericalControl->MoveDialog(*pxOffset + Asura_Vector_2(m_pxWeightLabel->GetWidth(), 0.0f));
						}
					}
					break;
				}
				case HEADING_OUTGOING_DISABLE:
				{
					if(m_pxDisableTickbox)
					{
						if(bHideControls)
						{
							m_pxDisableTickbox->SetHidden(true);
						}
						else
						{
							m_pxDisableTickbox->SetHidden(false);
							Asura_Vector_2 xTempVec = *pxOffset;

							xTempVec.x += s_axOutgoingHeaderInfo[uHeadingIndex].fWidthCoeff * m_xTable.GetTableWidth() * 0.5f;
							xTempVec.x += m_pxDisableTickbox->m_pxGadgetList[0].m_fWidth / 2;
							m_pxDisableTickbox->MoveDialog(xTempVec);
						}
					}
					break;
				}
			}

			pxOffset->x += s_axOutgoingHeaderInfo[uHeadingIndex].fWidthCoeff * m_xTable.GetTableWidth();
		}
	}

	// Check the flags that monitor changes in control values

	bool bChanged = false;

	if(m_bDisableTickboxChanged)
	{
		m_bDisableTickboxChanged = false;

		switch(m_xTable.GetType())
		{
			case Asura_MessageTable_Dialog::TYPE_SPLITTER_BLOCK:
			{
				m_pxSplitterOutputBlock->m_bActive = !m_bIsDisabled;
				*m_pbActiveArrayElementPtr = !m_bIsDisabled;

				bChanged = true;
				break;
			}
			case Asura_MessageTable_Dialog::TYPE_TIMELINE_TRIGGER:
			{
				m_pxTimelineMessageBlock->m_bActive = !m_bIsDisabled;
				*m_pbActiveArrayElementPtr = !m_bIsDisabled;

				bChanged = true;
				break;
			}
		}
	}

	if(m_bWeightNumberControlChanged)
	{
		m_bWeightNumberControlChanged = false;

		if(m_xTable.GetType() == Asura_MessageTable_Dialog::TYPE_SPLITTER_BLOCK)
		{
			m_pxSplitterOutputBlock->m_uWeight = m_uWeight;

			bChanged = true;
		}
	}

	if(m_bDelayNumberControlChanged)
	{
		m_bDelayNumberControlChanged = false;

		if(m_xTable.GetType() == Asura_MessageTable_Dialog::TYPE_TIMELINE_TRIGGER)
		{
			m_pxTimelineMessageBlock->m_fTime = m_fDelay;
			*m_pfDelayActiveArrayElementPtr = m_fDelay;

			bChanged = true;
		}
	}

	if(bChanged)
	{
		// Update the object's attribute overlay
		Tools_MessageEditor::UpdateObjectOverlay();

		Tools_SaveAsura::SetEntityModified(true);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Asura_MessageTableRow_Outgoing::InternalHitTest(float fx, float fy)
{
	for(u_int uHeadingIndex = 0; uHeadingIndex < NUM_OUTGOING_HEADINGS; uHeadingIndex++)
	{
		switch(uHeadingIndex)
		{
			case HEADING_OUTGOING_DELAY:
			{
				const float fLeft = s_axOutgoingHeaderInfo[uHeadingIndex].fXPosCoeff * m_xTable.GetTableWidth();
				const float fRight = fLeft + m_pxDelayNumericalControl->GetWidth();

				if((fx >= fLeft) && (fx <= fRight))
				{
					return true;
				}

				break;
			}
			case HEADING_OUTGOING_MESSSAGE:
			{
				break;
			}
			case HEADING_OUTGOING_TARGET:
			{
				break;
			}
			case HEADING_OUTGOING_IO:
			{
				break;
			}
			case HEADING_OUTGOING_PROBABILITY:
			{
				const float fLeft = s_axOutgoingHeaderInfo[uHeadingIndex].fXPosCoeff * m_xTable.GetTableWidth();
				const float fRight = fLeft + m_pxProbNumericalControl->GetWidth();

				if((fx >= fLeft) && (fx <= fRight))
				{
					return true;
				}

				break;
			}
			case HEADING_OUTGOING_DISABLE:
			{
				break;
			}
		}
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Asura_MessageTableRow_Outgoing::SetAvailableMessages(const char** paszNames, const u_short* pausIds, const u_int uNumStrings, bool bStringNeedsFreeing /* = false */)
{
	if(m_paszNames)
	{
		if(m_bStringsNeedsFreeing)
		{
			for(u_int n = 0; n < m_uNumNames; n++)
			{
				delete [] m_paszNames[n];
			}
		}

		delete [] m_paszNames;
	}

	if(m_pausIds)
	{
		delete [] m_pausIds;
	}

	if(uNumStrings == 0)
	{
		m_pxMessageDropdown->ReplaceStrings(0, NULL);
		return;
	}

	m_iMessageDropdownIndex = 0;

	m_pxMessageDropdown->ReplaceStrings(uNumStrings, paszNames);

	m_paszNames = paszNames;
	m_pausIds = pausIds;
	m_uNumNames = uNumStrings;

	m_bStringsNeedsFreeing = bStringNeedsFreeing;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void MessageTable_DragDrop_System::Begin(int iHitIndex, float fX, float fY)
{
	if(!m_bIsDragging)
	{
		m_pxSourceRow = m_xTable.GetRows().GetEntryByIndex(iHitIndex);

		m_xDragStartTickCount = Asura_Timers::GetCurTicks();

		m_bIsDragging = true;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void MessageTable_DragDrop_System::End()
{
	m_bIsDragging = false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void MessageTable_DragDrop_System::Process(float fX, float fY, float& fScrollValue, bool& bScrollChanged)
{
	if(m_bIsDragging)
	{
		m_fMouseX = fX;
		m_fMouseY = fY;

		// Autoscroll the scrollbar if we drag beyond the extents

		if(m_fMouseY < 0.0f)
		{
			fScrollValue = Asura_Maths::Max(fScrollValue - DRAGDROP_SCROLL_RATE, 0.0f);

			bScrollChanged = true;
		}

		if(m_fMouseY > m_xTable.GetHeight())
		{
			fScrollValue = Asura_Maths::Min(fScrollValue + DRAGDROP_SCROLL_RATE, 1.0f);

			bScrollChanged = true;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void MessageTable_DragDrop_System::Drop(int iIndex)
{
	const Asura_TickCount xElapsedTicks = Asura_Timers::GetElapsedTicks(m_xDragStartTickCount);
	const float fElapsedSeconds = Asura_Timers::TicksToSeconds(xElapsedTicks);

	if(fElapsedSeconds < DRAG_DELAY_IN_SECONDS)
	{
		return;
	}

	bool bRet = false;
	switch(m_xTable.GetType())
	{
		case Asura_MessageTable_Dialog::TYPE_NORMAL:				bRet = DropNormalMessage(iIndex);				break;
		case Asura_MessageTable_Dialog::TYPE_SPLITTER_BLOCK:		bRet = DropSplitterBlockMessage(iIndex);		break;
		case Asura_MessageTable_Dialog::TYPE_TIMELINE_TRIGGER:		bRet = DropTimelineTriggerMessage(iIndex);		break;
	}

	if(bRet)
	{
		Tools_MessageEditor::UpdateObjectOverlay();
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool MessageTable_DragDrop_System::DropNormalMessage(int iIndex)
{
	m_pxTargetRow = m_xTable.GetRows().GetEntryByIndex(iIndex);

	Asura_List_It<Asura_MessageTableRow> xIter(m_xTable.m_xRowList);

	bool bRetValue = false;

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxSourceRow = xIter.GetCurrent();

		if(pxSourceRow->IsSelected())
		{
			switch(pxSourceRow->GetType())
			{
				case Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE:
				{
					Asura_MessageTableRow_Outgoing* pxDerivedSourceRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxSourceRow);

					Asura_StaticMessage* pxSourceMessage = pxDerivedSourceRow->GetOutgoingMessagePointer();

					u_short* pusTargetBlockId = m_pxTargetRow->GetBlockId();

					u_short* pusSourceBlockId = pxDerivedSourceRow->GetBlockId();

					if(pusSourceBlockId == pusTargetBlockId)
					{
						// Don't drop into the same block
						break;
					}

					Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_MESSAGEEDIT);

					// Dupliate the message

					Asura_StaticMessage xMessage;
					xMessage.Initialise();

					xMessage.Copy(pxSourceMessage);

					// Add the message to the target block
					Asura_StaticMessageSetIndex eSet = Tools_MessageEditor::GetMessageSetFromBlockId(pusTargetBlockId);

					Asura_StaticMessage_System::AddMessage(eSet, xMessage, *pusTargetBlockId);
					Tools_MessageEditor::SetModified(pusTargetBlockId);

					// Remove the message from the source block

					int iNumMsgsInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusSourceBlockId);

					bool bFound = false;

					for(int n = 0; n < iNumMsgsInBlock; n++)
					{
						Asura_StaticMessage* pxMessage2 = Asura_StaticMessage_System::GetMessagePointer(*pusSourceBlockId, n);

						if(pxSourceMessage == pxMessage2)
						{
							Tools_MessageEditor::SetModified(pxDerivedSourceRow->GetBlockId());
							Asura_StaticMessage_System::DeleteMessage((pxDerivedSourceRow->GetBlockId()), n);
							bFound = true;
							break;
						}
					}

					Asura_Assert(bFound, "Could not find message in row to delete.");

					// Patch up any pointer references to messages in this same block

					if(*pusSourceBlockId != ASURA_STATICMESSAGE_EMPTYBLOCK)
					{
						Asura_List_It<Asura_MessageTableRow> xIter2(xIter);
						xIter2.Next();

						int iDeletedMessageIndex = pxDerivedSourceRow->GetMessageIndex();

						for(; !xIter2.Done(); xIter2.Next())
						{
							Asura_MessageTableRow* pxSourceRow2 = xIter2.GetCurrent();

							if(*pxSourceRow2->GetBlockId() == *pusSourceBlockId)
							{
								Asura_MessageTableRow_Outgoing* pxDerivedSourceRow2 = static_cast<Asura_MessageTableRow_Outgoing*> (pxSourceRow2);

								int iThisMessageIndex = pxDerivedSourceRow2->GetMessageIndex();

								if(iThisMessageIndex > iDeletedMessageIndex)
								{
									Asura_StaticMessage* pxPatchedMsgPtr = Asura_StaticMessage_System::GetMessagePointer(*pusSourceBlockId, iThisMessageIndex - 1);

									pxDerivedSourceRow2->SetOutgoingMessagePointer(pxPatchedMsgPtr);
									pxDerivedSourceRow2->SetMessageIndex(iThisMessageIndex - 1);
								}
							}
						}
					}

					// Request table recreation

					m_xTable.SetDirty();
					m_xTable.RequestParentToRecreateTable();

					bRetValue = true;

					break;
				}
			}
		}
	}

	return bRetValue;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool MessageTable_DragDrop_System::DropSplitterBlockMessage(int iIndex)
{
	m_pxTargetRow = m_xTable.GetRows().GetEntryByIndex(iIndex);

	Asura_List_It<Asura_MessageTableRow> xIter(m_xTable.m_xRowList);

	bool bRetValue = false;

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxSourceRow = xIter.GetCurrent();

		if(pxSourceRow->IsSelected())
		{
			switch(pxSourceRow->GetType())
			{
				case Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE:
				{
					Asura_MessageTableRow_Outgoing* pxDerivedSourceRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxSourceRow);

					Asura_StaticMessage* pxSourceMessage = pxDerivedSourceRow->GetOutgoingMessagePointer();

					Asura_SplitterBlockOutput* pxSourceSplitterBlockOutput = pxDerivedSourceRow->GetSplitterOutputBlockPointer();

					u_short* pusSourceBlockId = &pxSourceSplitterBlockOutput->m_usMessageBlock;

					u_short* pusTargetBlockId = m_pxTargetRow->GetBlockId();

					if(pusSourceBlockId == pusTargetBlockId)
					{
						// Don't drop into the same block
						break;
					}

					Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_MESSAGEEDIT);

					// Dupliate the message

					Asura_StaticMessage xMessage;
					xMessage.Initialise();

					xMessage.Copy(pxSourceMessage);

					// Add the message to the target block

					Asura_StaticMessage_System::AddMessage(ASURA_STATICMESSAGE_ENTITYSET, xMessage, *pusTargetBlockId);
					Tools_MessageEditor::SetModified(pusTargetBlockId);

					// Remove the message from the source block

					int iNumMsgsInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusSourceBlockId);

					bool bFound = false;

					for(int n = 0; n < iNumMsgsInBlock; n++)
					{
						Asura_StaticMessage* pxMessage2 = Asura_StaticMessage_System::GetMessagePointer(*pusSourceBlockId, n);

						if(pxSourceMessage == pxMessage2)
						{
							Tools_MessageEditor::SetModified(pxDerivedSourceRow->GetBlockId());
							Asura_StaticMessage_System::DeleteMessage((pxDerivedSourceRow->GetBlockId()), n);
							bFound = true;
							break;
						}
					}

					Asura_Assert(bFound, "Could not find message in row to delete.");

					// Patch up any pointer references to messages in this same block

					if(*pusSourceBlockId != ASURA_STATICMESSAGE_EMPTYBLOCK)
					{
						Asura_List_It<Asura_MessageTableRow> xIter2(xIter);
						xIter2.Next();

						int iDeletedMessageIndex = pxDerivedSourceRow->GetMessageIndex();

						for(; !xIter2.Done(); xIter2.Next())
						{
							Asura_MessageTableRow* pxSourceRow2 = xIter2.GetCurrent();

							if(*pxSourceRow2->GetBlockId() == *pusSourceBlockId)
							{
								Asura_MessageTableRow_Outgoing* pxDerivedSourceRow2 = static_cast<Asura_MessageTableRow_Outgoing*> (pxSourceRow2);

								int iThisMessageIndex = pxDerivedSourceRow2->GetMessageIndex();

								if(iThisMessageIndex > iDeletedMessageIndex)
								{
									Asura_StaticMessage* pxPatchedMsgPtr = Asura_StaticMessage_System::GetMessagePointer(*pusSourceBlockId, iThisMessageIndex - 1);

									pxDerivedSourceRow2->SetOutgoingMessagePointer(pxPatchedMsgPtr);
									pxDerivedSourceRow2->SetMessageIndex(iThisMessageIndex - 1);
								}
							}
						}
					}

					// Request table recreation

					m_xTable.SetDirty();
					m_xTable.RequestParentToRecreateTable();

					bRetValue = true;

					break;
				}
			}
		}
	}

	return bRetValue;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool MessageTable_DragDrop_System::DropTimelineTriggerMessage(int iIndex)
{
	m_pxTargetRow = m_xTable.GetRows().GetEntryByIndex(iIndex);

	Asura_List_It<Asura_MessageTableRow> xIter(m_xTable.m_xRowList);

	bool bRetValue = false;

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxSourceRow = xIter.GetCurrent();

		if(pxSourceRow->IsSelected())
		{
			switch(pxSourceRow->GetType())
			{
				case Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE:
				{
					Asura_MessageTableRow_Outgoing* pxDerivedSourceRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxSourceRow);

					Asura_StaticMessage* pxSourceMessage = pxDerivedSourceRow->GetOutgoingMessagePointer();

					Asura_TimelineTrigger_MessageBlock* pxSourceTimelineTriggerOutput = pxDerivedSourceRow->GetTimelineTriggerMessageBlock();

					u_short* pusSourceBlockId = &pxSourceTimelineTriggerOutput->m_usMessageBlock;

					u_short* pusTargetBlockId = m_pxTargetRow->GetBlockId();

					if(pusSourceBlockId == pusTargetBlockId)
					{
						// Don't drop into the same block
						break;
					}

					Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_MESSAGEEDIT);

					// Dupliate the message

					Asura_StaticMessage xMessage;
					xMessage.Initialise();

					xMessage.Copy(pxSourceMessage);

					// Add the message to the target block

					Asura_StaticMessage_System::AddMessage(ASURA_STATICMESSAGE_ENTITYSET, xMessage, *pusTargetBlockId);
					Tools_MessageEditor::SetModified(pusTargetBlockId);

					// Remove the message from the source block

					int iNumMsgsInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusSourceBlockId);

					bool bFound = false;

					for(int n = 0; n < iNumMsgsInBlock; n++)
					{
						Asura_StaticMessage* pxMessage2 = Asura_StaticMessage_System::GetMessagePointer(*pusSourceBlockId, n);

						if(pxSourceMessage == pxMessage2)
						{
							Tools_MessageEditor::SetModified(pxDerivedSourceRow->GetBlockId());
							Asura_StaticMessage_System::DeleteMessage((pxDerivedSourceRow->GetBlockId()), n);
							bFound = true;
							break;
						}
					}

					Asura_Assert(bFound, "Could not find message in row to delete.");

					// Patch up any pointer references to messages in this same block

					if(*pusSourceBlockId != ASURA_STATICMESSAGE_EMPTYBLOCK)
					{
						Asura_List_It<Asura_MessageTableRow> xIter2(xIter);
						xIter2.Next();

						int iDeletedMessageIndex = pxDerivedSourceRow->GetMessageIndex();

						for(; !xIter2.Done(); xIter2.Next())
						{
							Asura_MessageTableRow* pxSourceRow2 = xIter2.GetCurrent();

							if(*pxSourceRow2->GetBlockId() == *pusSourceBlockId)
							{
								Asura_MessageTableRow_Outgoing* pxDerivedSourceRow2 = static_cast<Asura_MessageTableRow_Outgoing*> (pxSourceRow2);

								int iThisMessageIndex = pxDerivedSourceRow2->GetMessageIndex();

								if(iThisMessageIndex > iDeletedMessageIndex)
								{
									Asura_StaticMessage* pxPatchedMsgPtr = Asura_StaticMessage_System::GetMessagePointer(*pusSourceBlockId, iThisMessageIndex - 1);

									pxDerivedSourceRow2->SetOutgoingMessagePointer(pxPatchedMsgPtr);
									pxDerivedSourceRow2->SetMessageIndex(iThisMessageIndex - 1);
								}
							}
						}
					}

					// Request table recreation

					m_xTable.SetDirty();
					m_xTable.RequestParentToRecreateTable();

					bRetValue = true;

					break;
				}
			}
		}
	}

	return bRetValue;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void MessageTable_DragDrop_System::Render()
{
	if(m_bIsDragging)
	{
		const Asura_TickCount xElapsedTicks = Asura_Timers::GetElapsedTicks(m_xDragStartTickCount);
		const float fElapsedSeconds = Asura_Timers::TicksToSeconds(xElapsedTicks);

		if(fElapsedSeconds < DRAG_DELAY_IN_SECONDS)
		{
			return;
		}

		Asura_MessageTable_Dialog::LoadTexture();

		float fX = m_xTable.m_pxOverlay->m_xOrigin.x + m_xTable.m_xOffset.x + m_fMouseX;
		float fY = m_xTable.m_pxOverlay->m_xOrigin.y + m_xTable.m_xOffset.y + m_fMouseY;

		fX *= ASURA_TEXT_OUTPUT_FONT_WIDTH;
		fY *= ASURA_TEXT_OUTPUT_FONT_HEIGHT;

		Asura_Render::Overlay.SetRectXY(fX, fY, 20.0f, 20.0f);
		Asura_Render::Overlay.SetRectUV(0,0.5,1,0.5);
		Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_NORMAL;
		Asura_Render::Overlay.TextureIndex = s_iTextureIndex;
		Asura_Render::Overlay.Colour = 0xffffffff;
		Asura_Render::Overlay.Render();
		Asura_Render::FlushDisplay();
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#endif // ASURA_TOOLS

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
