/*****************************************************************************
The Dark Mod GPL Source Code

This file is part of the The Dark Mod Source Code, originally based
on the Doom 3 GPL Source Code as published in 2011.

The Dark Mod Source Code is free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version. For details, see LICENSE.TXT.

Project: The Dark Mod (http://www.thedarkmod.com/)

******************************************************************************/
#include "precompiled.h"

#include "../../sys/win32/rc/debugger_resource.h"
#include "DebuggerApp.h"
#include "../Common/OpenFileDialog.h"
#include "DebuggerQuickWatchDlg.h"
#include "DebuggerFindDlg.h"

#define DEBUGGERWINDOWCLASS		"DHEWM3_DEBUGGER_WINDOW"
#define ID_DBG_WINDOWMIN		18900
#define ID_DBG_WINDOWMAX		19900

#define	IDC_DBG_SCRIPT			31000
#define	IDC_DBG_OUTPUT			31001
#define IDC_DBG_SPLITTER		31002
#define IDC_DBG_TABS			31003
#define IDC_DBG_BORDER			31004
#define IDC_DBG_CONSOLE			31005
#define IDC_DBG_CALLSTACK		31006
#define IDC_DBG_WATCH			31007
#define IDC_DBG_THREADS			31008
#define IDC_DBG_TOOLBAR			31009
#define IDC_DBG_SCRIPTLIST		31010
#define IDC_DBG_CONSOLEINPUT	31011
#define IDC_DBG_BREAKLIST		31012

#define ID_DBG_FILE_MRU1		10000

// Hack: dhewm3 supports scaling all MFC GUIs for display DPI
//  and has Win_GetWindowScalingFactor() for getting the required factor.
//  TDM doesn't have this, so for now provide a stub so the script debugger works
static float Win_GetWindowScalingFactor(HWND window) {
	return 1.0f;
}

/*
================
rvDebuggerWindow::rvDebuggerWindow

Constructor
================
*/
rvDebuggerWindow::rvDebuggerWindow ( )
{
	mWnd		   = NULL;
	mWndScript	   = NULL;
	mInstance	   = NULL;
	mZoomScaleNum  = 0;
	mZoomScaleDem  = 0;
	mWindowMenuPos = 0;
	mActiveScript  = 0;
	mSplitterDrag  = false;
	mLastActiveScript = -1;
	mCurrentStackDepth = 0;
	mRecentFileInsertPos = 0;
	mRecentFileMenu = NULL;
	mClient = NULL;
}

/*
================
rvDebuggerWindow::~rvDebuggerWindow

Destructor
================
*/
rvDebuggerWindow::~rvDebuggerWindow ( )
{
	int i;

	if ( mWnd )
	{
		DestroyWindow ( mWnd );
	}

	if ( mImageList )
	{
		ImageList_Destroy ( mImageList );
	}

	for ( i = 0; i < mScripts.Num (); i ++ )
	{
		delete mScripts[i];
	}
}

/*
================
rvDebuggerWindow::RegisterClass

Registers the window class used by the debugger window.  This is called when
the window is created.
================
*/
bool rvDebuggerWindow::RegisterClass ( void )
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= (WNDPROC)WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= mInstance;
	wcex.hIcon			= NULL;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_APPWORKSPACE+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDR_DBG_MAIN);
	wcex.lpszClassName	= DEBUGGERWINDOWCLASS;
	wcex.hIconSm		= NULL;

	return RegisterClassEx(&wcex) ? true : false;
}

/*
================
rvDebuggerWindow::Create

Creates the debugger window
================
*/
bool rvDebuggerWindow::Create ( HINSTANCE instance )
{
	mInstance = instance;

	if ( !RegisterClass ( ) )
	{
		return false;
	}

	// Cache the client pointer for ease of use
	mClient = &gDebuggerApp.GetClient();

	// Create the debugger window
	mWnd = CreateWindow( DEBUGGERWINDOWCLASS, "",
						 WS_OVERLAPPEDWINDOW,
						 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, mInstance, this);

	if ( !mWnd )
	{
		return false;
	}

	// Determine where the window names will be added in the menus
	mWindowMenu    = GetSubMenu ( GetMenu ( mWnd ), 2 );
	mWindowMenuPos = GetMenuItemCount ( mWindowMenu );

	UpdateTitle ( );

	Printf ( "Dhewm3 Script Debugger v1.1\n\n" );

	ShowWindow ( mWnd, SW_SHOW );
	UpdateWindow ( mWnd );

	return true;
}

/*
================
rvDebuggerWindow::ScriptWordBreakProc

Determines where word breaks are in the script window.  This is used for determining
the word that someone is over with their mouse cursor.  Since the default windows one
doesnt understand the delimiters of the scripting language it had to be overridden.
================
*/
int CALLBACK rvDebuggerWindow::ScriptWordBreakProc (LPTSTR text, int current, int max, int action )
{
	static TCHAR delimiters[]=TEXT("!@#$%^&*()-+=[]{}|\\;:'\"/,.<>? \t\r\n") ;

	switch ( action )
	{
		default:
			break;

		case WB_ISDELIMITER:
			return _tcschr ( delimiters, *(text + current * 2) ) ? TRUE : FALSE;

		case WB_MOVEWORDLEFT:
		case WB_LEFT:

			current--;

			// Run as long as the current index is valid.
			while ( current > 0 )
			{
				// If we hit a delimiter then return the index + 1 since
				// it was the last character we were on that was valid
				if ( _tcschr ( delimiters, *(text + current * 2) ) )
				{
					return current + 1;
				}

				// Going backwards
				current--;
			}

			return current;

		case WB_MOVEWORDRIGHT:
		case WB_RIGHT:

			// If we are already on a delimiter then just return the current index
			if ( _tcschr ( delimiters, *(text + current * 2) ) )
			{
				return current;
			}

			// Run until we hit the end of the control
			while ( current < max )
			{
				// If we found a delimiter then return the index we are on
				if ( _tcschr ( delimiters, *(text + current * 2) ) )
				{
					return current;
				}

				current++;
			}

			return current;
	}

	return 0;
  }

LRESULT CALLBACK rvDebuggerWindow::ScriptWndProc ( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
	static int		  lastStart = -1;
	static int		  lastEnd   = -1;
	rvDebuggerWindow* window    = (rvDebuggerWindow*)GetWindowLongPtr ( wnd, GWLP_USERDATA );
	WNDPROC			  wndproc   = window->mOldScriptProc;

	switch ( msg )
	{
		case WM_RBUTTONUP:
			return SendMessage ( wnd, WM_LBUTTONUP, wparam, lparam );

		case WM_RBUTTONDOWN:
		{
			POINT point = { LOWORD(lparam), HIWORD(lparam) };
			HMENU menu;
			SendMessage ( wnd, WM_LBUTTONDOWN, wparam, lparam );
			menu = LoadMenu ( window->mInstance, MAKEINTRESOURCE(IDR_DBG_SCRIPT_POPUP) );
			ClientToScreen ( wnd, &point );
			TrackPopupMenu ( GetSubMenu( menu, 0 ), TPM_RIGHTBUTTON|TPM_LEFTALIGN, point.x, point.y, 0, window->mWnd, NULL );
			DestroyMenu ( menu );
			return 0;
		}

		case WM_MOUSEMOVE:
		{
			// Figure out the start and end of the mouse is over
			POINTL pos   = {LOWORD(lparam),HIWORD(lparam)};
			int    c     = SendMessage ( wnd, EM_CHARFROMPOS, 0, (WPARAM)&pos );
			int    start = SendMessage ( wnd, EM_FINDWORDBREAK, WB_LEFT, c );
			int    end   = SendMessage ( wnd, EM_FINDWORDBREAK, WB_RIGHT, c );

			// If the start and the end of the word we are over havent changed
			// then the word hasnt changed so no need to re-setup the tool tip
			if ( lastStart == start && lastEnd == end )
			{
				break;
			}

			// Save the current start and end for the next mouse move
			lastStart = start;
			lastEnd   = end;

			// Get rid of the last tool tip if there is one
			if ( window->mTooltipVar.Length() )
			{
				TOOLINFO ti;
				ti.cbSize = sizeof(TOOLINFO);
				ti.hwnd   = wnd;
				ti.uId    = 0;
				SendMessage ( window->mWndToolTips, TTM_DELTOOL, 0, (LPARAM) (LPTOOLINFO) &ti );
				window->mTooltipVar.Clear();
			}

			// If there is no word then ignore it
			if ( start == end )
			{
				break;
			}

			TEXTRANGE	range;
			TOOLINFO	ti;

			// grab the actual word from the edit control
			char* temp = new char[end-start+10];
			range.chrg.cpMin = start;
			range.chrg.cpMax = end;
			range.lpstrText  = temp;
			SendMessage ( wnd, EM_GETTEXTRANGE, 0, (LPARAM) &range );
			window->mTooltipVar = temp;
			delete[] temp;

			// Request the variable's value from the debugger server
			window->mClient->InspectVariable ( window->mTooltipVar.c_str(), window->mCurrentStackDepth );

			// Prepare to add the new tooltip
			ti.cbSize   = sizeof(TOOLINFO);
			ti.uFlags   = TTF_SUBCLASS;
			ti.hwnd     = wnd;
			ti.hinst    = (HINSTANCE)GetModuleHandle(NULL);
			ti.uId      = 0;
			ti.lpszText = (LPSTR)window->mTooltipVar.c_str();

			// Calculate the bounding box around the word we are over.  We do this
			// by getting to the top left from the start character and the right side
			// from the end character.  The bottom is the top from the start character
			// plus the height of one line
			SendMessage ( wnd, EM_POSFROMCHAR, (WPARAM)&pos, start );
			ti.rect.left = pos.x;
			ti.rect.top  = pos.y;
			SendMessage ( wnd, EM_POSFROMCHAR, (WPARAM)&pos, end );
			ti.rect.right = pos.x;
			SendMessage ( wnd, EM_POSFROMCHAR, (WPARAM)&pos, SendMessage ( wnd, EM_LINEINDEX, 0, 0 ) );
			ti.rect.bottom = ti.rect.top - pos.y;
			SendMessage ( wnd, EM_POSFROMCHAR, (WPARAM)&pos, SendMessage ( wnd, EM_LINEINDEX, 1, 0 ) );
			ti.rect.bottom = ti.rect.bottom + pos.y;

			// Add the new tool tip to the control
			SendMessage ( window->mWndToolTips, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti );
			SendMessage ( window->mWndToolTips, TTM_UPDATE, 0, 0 );

			break;
		}
		case WM_SIZE:
		{
			float scaling_factor = Win_GetWindowScalingFactor(wnd);
			int s18 = int(18 * scaling_factor);
			int s10 = int(10 * scaling_factor);

			RECT rect;
			window->mMarginSize = window->mZoomScaleDem ? ((long)(s18 * (float)window->mZoomScaleNum / (float)window->mZoomScaleDem)) : s18;

			GetWindowRect(window->mWndToolbar, &rect);
			MoveWindow(window->mWndMargin, 0, 0, window->mMarginSize, window->mSplitterRect.top - (rect.bottom - rect.top), TRUE);
			// FIXME: was *2.25, increased for line numbers up to 9999; but neither works particularly well
			//        if DPI scaling is involved, because script code text and linenumbers aren't DPI scaled
			int lmargin = window->GetMarginWidth();
			SendMessage(window->mWndScript, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(lmargin, s10));

		}
	}

	return CallWindowProc ( wndproc, wnd, msg, wparam, lparam );
}

LRESULT CALLBACK rvDebuggerWindow::MarginWndProc ( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
	rvDebuggerWindow* window = (rvDebuggerWindow*) GetWindowLongPtr ( wnd, GWLP_USERDATA );

	switch ( msg )
	{
		case WM_RBUTTONDOWN:
			return SendMessage ( window->mWndScript, WM_RBUTTONDOWN, wparam, lparam );

		case WM_RBUTTONUP:
			return SendMessage ( window->mWndScript, WM_RBUTTONUP, wparam, lparam );

		case WM_LBUTTONDBLCLK:
		{
			int result = SendMessage ( window->mWndScript, WM_LBUTTONDBLCLK, wparam, lparam );
			window->ToggleBreakpoint ( );
			return result;
		}

		case WM_LBUTTONDOWN:
		{
			int result = SendMessage ( window->mWndScript, WM_LBUTTONDOWN, wparam, lparam );
			window->ToggleBreakpoint ( );
			return result;
		}

		case WM_LBUTTONUP:
			return SendMessage ( window->mWndScript, WM_LBUTTONUP, wparam, lparam );

		case WM_PAINT:
		{
			HDC dc;
			float scaling_factor = Win_GetWindowScalingFactor(wnd);
			int s2 = int(2 * scaling_factor);
			int s4 = int(4 * scaling_factor);
			int width,height;

			window->ResizeImageList(width,height);

			PAINTSTRUCT ps;
			RECT rect;
			GetClientRect ( wnd, &rect );
			dc = BeginPaint( wnd, &ps );
			FillRect( dc, &rect, GetSysColorBrush( COLOR_3DSHADOW ) );

			//draw line nrs
			int iMaxNumberOfLines = ( (rect.bottom - rect.top ) / height ) + height;
			int iFirstVisibleLine = SendMessage( window->mWndScript, EM_GETFIRSTVISIBLELINE, 0, 0 );
			HFONT hf = CreateFont( height, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Courier New" );
			HFONT hfOld = ( HFONT ) SelectObject( dc, hf );
			SetBkMode( dc, OPAQUE );
			// I think it looks nicer when the  line number background is white
			SetBkColor( dc, RGB( 255, 255, 255 ) );
			SetTextColor( dc, RGB( 0, 0, 255 ) );
			
			int lnrWidth = 8;
			GetCharWidth32( dc, '9', '9', &lnrWidth );
			lnrWidth *= 4; // we want enough width for 4 chars ("9999"), not just one
			lnrWidth += 2 * s4; // we want some space around the line number

			RECT lnrRect = rect;
			lnrRect.left = rect.right;
			lnrRect.right = lnrRect.left + lnrWidth;
			FillRect( dc, &lnrRect, WHITE_BRUSH );

			for (int i = 0; i < iMaxNumberOfLines; ++i )
			{
				int		c;
				POINTL	pos;
				c = SendMessage( window->mWndScript, EM_LINEINDEX, iFirstVisibleLine + i , 0 );
				SendMessage( window->mWndScript, EM_POSFROMCHAR, ( WPARAM ) &pos, c );

				RECT t = lnrRect;
				t.top = pos.y;
				t.bottom = t.top + height;
				t.right -= s4; // a little space between text and "border" to code part of window
				
				idStr lntxt( iFirstVisibleLine + i + 1);
				DrawText( dc, lntxt, lntxt.Length(), &t, DT_RIGHT );
			}
			DeleteObject( hf );

			//draw breakpoints
			if ( window->mScripts.Num ( ) )
			{
				for ( int i = 0; i < window->mClient->GetBreakpointCount(); i ++ )
				{
					rvDebuggerBreakpoint* bp = window->mClient->GetBreakpoint ( i );
					assert( bp );

					if ( !idStr::Icmp ( window->mScripts[window->mActiveScript]->GetFilename ( ), bp->GetFilename ( ) ) )
					{
						int		c;
						POINTL	pos;

						c = SendMessage ( window->mWndScript, EM_LINEINDEX, bp->GetLineNumber ( ) - 1, 0 );
						SendMessage ( window->mWndScript, EM_POSFROMCHAR, (WPARAM)&pos, c );
						ImageList_DrawEx ( window->mTmpImageList, 2, dc, rect.left, pos.y, width, height, CLR_NONE, CLR_NONE, ILD_NORMAL );

					}
				}

				if ( window->mClient->IsStopped ( ) )
				{
					if ( !idStr::Icmp ( window->mClient->GetBreakFilename(),
										window->mScripts[window->mActiveScript]->GetFilename ( ) ) )
					{
						int		c;
						POINTL	pos;

						c = SendMessage ( window->mWndScript, EM_LINEINDEX, window->mClient->GetBreakLineNumber() - 1, 0 );
						SendMessage ( window->mWndScript, EM_POSFROMCHAR, (WPARAM)&pos, c );
						ImageList_DrawEx ( window->mTmpImageList, 3, dc, rect.left, pos.y, width, height, CLR_NONE, CLR_NONE, ILD_NORMAL );
					}
				}

				if ( window->mCurrentStackDepth != 0 )
				{
					if ( !window->mClient->GetCallstack()[window->mCurrentStackDepth]->mFilename.Icmp ( window->mScripts[window->mActiveScript]->GetFilename ( ) ) )
					{
						int		c;
						POINTL	pos;

						c = SendMessage ( window->mWndScript, EM_LINEINDEX, window->mClient->GetCallstack()[window->mCurrentStackDepth]->mLineNumber - 1, 0 );
						SendMessage ( window->mWndScript, EM_POSFROMCHAR, (WPARAM)&pos, c );
						ImageList_DrawEx ( window->mTmpImageList, 1, dc, rect.left, pos.y, width, height, CLR_NONE, CLR_NONE, ILD_NORMAL );
					}
				}
			}
			RECT tmp = rect;

			rect.right -= s2;
			rect.left = rect.right + s2;
			HPEN pen = CreatePen ( PS_SOLID, s2, GetSysColor ( COLOR_BACKGROUND ) );
			HPEN old = (HPEN)SelectObject ( dc, pen );
			MoveToEx ( dc, rect.right, rect.top, NULL );
			LineTo ( dc, rect.right, rect.bottom );
			
			SelectObject ( dc, old );
			DeleteObject ( pen );
			EndPaint ( wnd, &ps );
			break;
		}
	}

	return DefWindowProc ( wnd, msg, wparam, lparam );
}

/*
================
rvDebuggerWindow::UpdateTitle

Updates the window title of the script debugger to show a few states
================
*/
void rvDebuggerWindow::UpdateTitle ( void )
{
	idStr title;

	title = "Dhewm3 Script Debugger - ";

	if ( mClient->IsConnected ( ) )
	{
		if ( mClient->IsStopped ( ) )
		{
			title += "[break]";
		}
		else
		{
			title += "[run]";
		}
	}
	else
	{
		title += "[disconnected]";
	}

	if ( mScripts.Num ( ) )
	{
		title += " - [";
		if (mActiveScript != -1)
			title += idStr( mScripts[mActiveScript]->GetFilename() ).StripPath ( );
		else
			title += "Load Error";
		title += "]";
	}

	SetWindowText ( mWnd, title );
}

/*
================
rvDebuggerWindow::UpdateScript

Updates the edit window to contain the current script
================
*/
void rvDebuggerWindow::UpdateScript ( void )
{
	UpdateTitle ( );

	// Dont reupdate if the given active script is the one being displayed.
	if ( mActiveScript == mLastActiveScript )
	{
		return;
	}

	mLastActiveScript = mActiveScript;

	// Show and hide the script window depending on whether or not
	// there are loaded scripts
	if ( mScripts.Num ( ) < 1 )
	{
		ShowWindow ( mWndScript, SW_HIDE );
		return;
	}
	else
	{
		ShowWindow ( mWndScript, SW_SHOW );
	}

	// Update the script
	SendMessage ( mWndScript, EM_SETSEL, 0, -1 );
	SendMessage ( mWndScript, EM_REPLACESEL, 0, (LPARAM)"" );
	SendMessage ( mWndScript, EM_SETSEL, 0, -1 );
	SendMessage ( mWndScript, EM_REPLACESEL, 0, (LPARAM)mScripts[mActiveScript]->GetContents ( ) );
}

/*
================
rvDebuggerWindow::UpdateWindowMenu

Updates the windows displayed in the window menu
================
*/
void rvDebuggerWindow::UpdateWindowMenu ( void )
{
	while ( GetMenuItemCount ( mWindowMenu ) > mWindowMenuPos )
	{
		DeleteMenu ( mWindowMenu, mWindowMenuPos, MF_BYPOSITION );
	}

	if ( mScripts.Num() )
	{
		AppendMenu ( mWindowMenu, MF_SEPARATOR, 0, "" );
	}

	int i;
	for ( i = 0; i < mScripts.Num(); i ++ )
	{
		idStr name;
		name = mScripts[i]->GetFilename ( );
		name.StripPath ( );
		name = idStr(va("&%d ", i + 1 )) + name;
		AppendMenu ( mWindowMenu, MF_STRING, ID_DBG_WINDOWMIN + i, name );
	}
}

/*
================
rvDebuggerWindow::UpdateCallstack

Updates the contents of teh callastack
================
*/
void rvDebuggerWindow::UpdateCallstack ( void )
{
	LVITEM item;
	ListView_DeleteAllItems ( mWndCallstack );
	ZeroMemory ( &item, sizeof(item) );
	item.mask = LVIF_TEXT|LVIF_IMAGE;

	for ( int i = 0; i < mClient->GetCallstack().Num(); i ++ )
	{
		rvDebuggerCallstack* entry = mClient->GetCallstack()[i];
		item.iItem = ListView_GetItemCount ( mWndCallstack );
		item.pszText = "";
		item.iImage = (i == mCurrentStackDepth) ? 1 : 0;
		ListView_InsertItem ( mWndCallstack, &item );

		ListView_SetItemText ( mWndCallstack, item.iItem, 1, (LPSTR)entry->mFunction.c_str() );
		ListView_SetItemText ( mWndCallstack, item.iItem, 2, va("%d", entry->mLineNumber ) );
		ListView_SetItemText ( mWndCallstack, item.iItem, 3, (LPSTR)entry->mFilename.c_str() );
	}
}

void rvDebuggerWindow::UpdateScriptList(void)
{
	LVITEM item;
	ListView_DeleteAllItems(mWndScriptList);
	ZeroMemory(&item, sizeof(item));
	item.mask = LVIF_TEXT | LVIF_IMAGE;

	idStrList& scripts = mClient->GetServerScripts();
	for (int i = 0; i < scripts.Num(); i++)
	{
		item.iItem = ListView_GetItemCount(mWndScriptList);
		item.pszText = "";
		//find in activeScripts
		item.iImage = 0;
		for (int j = 0; j < mScripts.Num(); j++)
		{
			if (!idStr::Icmp(mScripts[j]->GetFilename(), scripts[i]))
			{
				item.iImage = 1;
				break;
			}
		}
		ListView_InsertItem(mWndScriptList, &item);
		ListView_SetItemText(mWndScriptList, item.iItem, 1, (LPSTR)scripts[i].c_str());
	}
}


void rvDebuggerWindow::UpdateBreakpointList( void )
{
	LVITEM item;
	ListView_DeleteAllItems( mWndBreakList );
	ZeroMemory( &item, sizeof( item ) );
	item.mask = LVIF_TEXT | LVIF_IMAGE;

	int numBreakPoints = mClient->GetBreakpointCount();
	for ( int i = 0; i < numBreakPoints; i++ )
	{
		rvDebuggerBreakpoint* bp = mClient->GetBreakpoint( i );
		item.iItem = ListView_GetItemCount( mWndBreakList );
		item.pszText = "";
		item.iImage = 2; // breakpoint
		ListView_InsertItem( mWndBreakList, &item );
		
		idStr lineStr( bp->GetLineNumber() );
		ListView_SetItemText( mWndBreakList, item.iItem, 1, (LPSTR)bp->GetFilename() );
		ListView_SetItemText( mWndBreakList, item.iItem, 2, (LPSTR)lineStr.c_str() );
	}
}

/*
================
rvDebuggerWindow::UpdateWatch

Updates the contents of the watch window
================
*/
void rvDebuggerWindow::UpdateWatch ( void )
{
	int i;

	// Inspect all the variables we are watching
	for ( i = 0; i < mWatches.Num(); i ++ )
	{
		mWatches[i]->mModified = false;
		mClient->InspectVariable ( mWatches[i]->mVariable, mCurrentStackDepth );
	}

	InvalidateRect ( mWndWatch, NULL, FALSE );
	UpdateWindow ( mWndWatch );
}

/*
================
rvDebuggerWindow::HandleInitMenu

Handles the initialization of the main menu
================
*/
int rvDebuggerWindow::HandleInitMenu ( WPARAM wParam, LPARAM lParam )
{
	int		cMenuItems = GetMenuItemCount((HMENU)wParam);
	int		nPos;
	int		id;
	UINT	flags;
	HMENU	hmenu;

	hmenu = (HMENU) wParam;

	// Run through all the menu items in the menu and see if any of them need
	// modification in any way
	for (nPos = 0; nPos < cMenuItems; nPos++)
	{
		id    = GetMenuItemID(hmenu, nPos);
		flags = 0;

		// Handle popup menus too
		if ( id < 0 )
		{
			HMENU sub = GetSubMenu ( hmenu, nPos );
			if ( sub )
			{
				HandleInitMenu ( (WPARAM) sub, 0 );
				continue;
			}
		}

		// Handle the dynamic menu items specially
		if ( id >= ID_DBG_WINDOWMIN && id <= ID_DBG_WINDOWMAX )
		{
			if ( id - ID_DBG_WINDOWMIN == mActiveScript )
			{
				CheckMenuItem ( hmenu, nPos, MF_BYPOSITION|MF_CHECKED );
			}
			else
			{
				CheckMenuItem ( hmenu, nPos, MF_BYPOSITION|MF_UNCHECKED );
			}
			continue;
		}

		// Menu items that are completely unrelated to the workspace
		switch ( id )
		{
			case ID_DBG_DEBUG_RUN:
			{
				MENUITEMINFO info;
				idStr		 run;

				info.cbSize = sizeof(info);
				info.fMask = MIIM_TYPE|MIIM_STATE;
				info.fType = MFT_STRING;

				if ( !mClient->IsConnected() )
				{
					run = "Run";
					info.fState = MFS_ENABLED;
				}
				else
				{
					run = "Continue";
					info.fState = mClient->IsStopped()?MFS_ENABLED:MFS_GRAYED;
				}

				info.dwTypeData = (LPSTR)run.c_str();
				info.cch = run.Length ( );

				SendMessage ( mWndToolbar, TB_ENABLEBUTTON, id, MAKELONG(((info.fState==MFS_ENABLED) ? TRUE : FALSE), 0) );

				SetMenuItemInfo ( hmenu, id, FALSE, &info );

				break;
			}

			case ID_DBG_DEBUG_BREAK:
				if ( !mClient->IsConnected() || mClient->IsStopped() )
				{
					EnableMenuItem ( hmenu, nPos, MF_GRAYED|MF_BYPOSITION);
					SendMessage ( mWndToolbar, TB_ENABLEBUTTON, id, MAKELONG(FALSE,0) );
				}
				else
				{
					EnableMenuItem ( hmenu, nPos, MF_ENABLED|MF_BYPOSITION);
					SendMessage ( mWndToolbar, TB_ENABLEBUTTON, id, MAKELONG(TRUE,0) );
				}
				break;

			case ID_DBG_DEBUG_RUNTOCURSOR:
			case ID_DBG_DEBUG_STEPOUT:
			case ID_DBG_DEBUG_STEPOVER:
			case ID_DBG_DEBUG_STEPINTO:
			case ID_DBG_DEBUG_SHOWNEXTSTATEMENT:
			case ID_DBG_DEBUG_QUICKWATCH:
				if ( !mClient->IsConnected() || !mClient->IsStopped() )
				{
					EnableMenuItem ( hmenu, nPos, MF_GRAYED|MF_BYPOSITION );
					SendMessage ( mWndToolbar, TB_ENABLEBUTTON, id, MAKELONG(FALSE,0) );
				}
				else
				{
					EnableMenuItem ( hmenu, nPos, MF_ENABLED|MF_BYPOSITION );
					SendMessage ( mWndToolbar, TB_ENABLEBUTTON, id, MAKELONG(TRUE,0) );
				}
				break;

			case ID_DBG_WINDOW_CLOSEALL:
			case ID_DBG_FILE_CLOSE:
			case ID_DBG_DEBUG_TOGGLEBREAKPOINT:
			case ID_DBG_EDIT_FIND:
				EnableMenuItem ( hmenu, nPos, (mScripts.Num()?MF_ENABLED:MF_GRAYED)|MF_BYPOSITION);
				break;
		}
	}

	return 0;
}


void rvDebuggerWindow::ResizeImageList(int& widthOut, int& heightOut)
{
	//mTmpImageList
	float scaling_factor = Win_GetWindowScalingFactor(mWnd);
	int s16 = int(16 * scaling_factor);

	TEXTMETRIC	tm;
	HDC			dc;
	dc = GetDC(mWndScript);

	GetTextMetrics(dc, &tm);
	int height = mZoomScaleDem ? (tm.tmHeight * (float)mZoomScaleNum / (float)mZoomScaleDem)  : tm.tmHeight ;
	height *= scaling_factor;
	int width = mZoomScaleDem ? ( tm.tmMaxCharWidth * (float)mZoomScaleNum / (float)mZoomScaleDem) : tm.tmMaxCharWidth;
	width *= scaling_factor;

	ImageList_Destroy(mTmpImageList);
	mTmpImageList = ImageList_Create(width, height, ILC_COLOR | ILC_MASK , 0, 2);
	ImageList_AddIcon(mTmpImageList, (HICON)LoadImage(mInstance, MAKEINTRESOURCE(IDI_DBG_EMPTY), IMAGE_ICON, width, height, LR_DEFAULTSIZE | LR_DEFAULTCOLOR));
	ImageList_AddIcon(mTmpImageList, (HICON)LoadImage(mInstance, MAKEINTRESOURCE(IDI_DBG_CURRENT), IMAGE_ICON, width, height, LR_DEFAULTSIZE | LR_DEFAULTCOLOR));
	ImageList_AddIcon(mTmpImageList, (HICON)LoadImage(mInstance, MAKEINTRESOURCE(IDI_DBG_BREAKPOINT), IMAGE_ICON, width, height, LR_DEFAULTSIZE | LR_DEFAULTCOLOR));
	ImageList_AddIcon(mTmpImageList, (HICON)LoadImage(mInstance, MAKEINTRESOURCE(IDI_DBG_CURRENTLINE), IMAGE_ICON, width, height, LR_DEFAULTSIZE | LR_DEFAULTCOLOR));

	widthOut = width;
	heightOut = height;
}

float rvDebuggerWindow::GetMarginWidth ( void )
{
	TEXTMETRIC	tm;
	HDC			dc;

	dc = GetDC( mWndScript );
	GetTextMetrics( dc, &tm );

	float scaling_factor = Win_GetWindowScalingFactor( mWndScript );

	return scaling_factor * (4 * tm.tmMaxCharWidth + tm.tmMaxCharWidth);
}
/*
================
rvDebuggerWindow::HandleCreate

Handles the WM_CREATE command
================
*/
int rvDebuggerWindow::HandleCreate ( WPARAM wparam, LPARAM lparam )
{
	UINT		tabsize = 16;
	TEXTMETRIC	tm;
	HDC			dc;
	LOGFONT		lf;
	int			i;

	gDebuggerApp.GetOptions().GetWindowPlacement ( "wp_main", mWnd );

	// Create the main toolbar
	CreateToolbar ( );

	// Create the script window
	LoadLibrary ( "Riched20.dll" );
	mWndScript = CreateWindow ( "RichEdit20A", "", WS_CHILD|WS_BORDER|ES_NOHIDESEL|ES_READONLY|ES_MULTILINE|ES_WANTRETURN|ES_AUTOVSCROLL|ES_AUTOHSCROLL|WS_VSCROLL|WS_HSCROLL, 0, 0, 100, 100, mWnd, (HMENU) IDC_DBG_SCRIPT, mInstance, 0 );
	SendMessage ( mWndScript, EM_SETEVENTMASK, 0, ENM_SCROLL | ENM_CHANGE | ENM_UPDATE | ENM_SCROLLEVENTS | ENM_REQUESTRESIZE) ;
	SendMessage ( mWndScript, EM_SETWORDBREAKPROC, 0, (LPARAM) ScriptWordBreakProc );
	mOldScriptProc = (WNDPROC)GetWindowLongPtr ( mWndScript, GWLP_WNDPROC );
	SetWindowLongPtr ( mWndScript, GWLP_USERDATA, (LONG_PTR)this );
	SetWindowLongPtr ( mWndScript, GWLP_WNDPROC, (LONG_PTR)ScriptWndProc );

	SendMessage ( mWndScript, EM_SETTABSTOPS, 1, (LPARAM)&tabsize );

	dc = GetDC ( mWndScript );
	GetTextMetrics ( dc, &tm );
	ZeroMemory ( &lf, sizeof(lf) );
	lf.lfHeight = tm.tmHeight * Win_GetWindowScalingFactor( mWndScript );
	strcpy ( lf.lfFaceName, "Courier New" );

	SendMessage ( mWndScript, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
	SendMessage ( mWndScript, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, MAKELONG( GetMarginWidth(),10) );
	SendMessage ( mWndScript, EM_SETBKGNDCOLOR, 0, GetSysColor ( COLOR_3DFACE ) );

	mWndOutput = CreateWindow ( "RichEdit20A", "", WS_CHILD|ES_READONLY|ES_MULTILINE|ES_WANTRETURN|ES_AUTOVSCROLL|ES_AUTOHSCROLL|WS_VSCROLL|WS_HSCROLL|WS_VISIBLE, 0, 0, 100, 100, mWnd, (HMENU) IDC_DBG_OUTPUT, mInstance, 0 );
	SendMessage ( mWndOutput, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
	SendMessage ( mWndOutput, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, MAKELONG(18,10) );
	SendMessage ( mWndOutput, EM_SETBKGNDCOLOR, 0, GetSysColor ( COLOR_3DFACE ) );
	SendMessage ( mWndOutput, EM_SETEVENTMASK, 0, ENM_SCROLL | ENM_CHANGE | ENM_UPDATE | ENM_SCROLLEVENTS);

	mWndConsole = CreateWindow ( "RichEdit20A", "", WS_CHILD|ES_READONLY|ES_MULTILINE|ES_WANTRETURN|ES_AUTOVSCROLL|ES_AUTOHSCROLL|WS_VSCROLL|WS_HSCROLL, 0, 0, 100, 100, mWnd, (HMENU) IDC_DBG_CONSOLE, mInstance, 0 );
	SendMessage ( mWndConsole, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
	SendMessage ( mWndConsole, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, MAKELONG(18,10) );
	SendMessage ( mWndConsole, EM_SETBKGNDCOLOR, 0, GetSysColor ( COLOR_3DFACE ) );

	mWndConsoleInput = CreateWindow( "RichEdit20A", "", WS_CHILD | ES_WANTRETURN | ES_AUTOVSCROLL |  WS_VSCROLL | WS_BORDER, 0, 0, 100, 18, mWnd, ( HMENU ) IDC_DBG_CONSOLEINPUT, mInstance, 0 );
	lf.lfHeight = -MulDiv( 8, GetDeviceCaps( dc, LOGPIXELSY ), 72 );
	strcpy( lf.lfFaceName, "Arial" );
	SendMessage( mWndConsoleInput, WM_SETFONT, ( WPARAM ) CreateFontIndirect( &lf ), 0 );
	SendMessage( mWndConsoleInput, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG( 18, 10 ) );

	mWndMargin = CreateWindow ( "STATIC", "", WS_VISIBLE|WS_CHILD, 0, 0, 0, 0, mWndScript, (HMENU)IDC_DBG_SPLITTER, mInstance, NULL );
	SetWindowLongPtr ( mWndMargin, GWLP_USERDATA, (LONG_PTR)this );
	SetWindowLongPtr ( mWndMargin, GWLP_WNDPROC, (LONG_PTR)MarginWndProc );

	mWndBorder = CreateWindow ( "STATIC", "", WS_VISIBLE|WS_CHILD|SS_GRAYFRAME, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_BORDER, mInstance, NULL );

	GetClientRect ( mWnd, &mSplitterRect );
	mSplitterRect.top = (mSplitterRect.bottom-mSplitterRect.top) / 2;
	mSplitterRect.bottom = mSplitterRect.top + 4;

	mWndTabs = CreateWindow ( WC_TABCONTROL, "", TCS_BOTTOM|WS_CHILD|WS_VISIBLE|TCS_FOCUSNEVER, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_TABS, mInstance, NULL );
	lf.lfHeight = -MulDiv(8, GetDeviceCaps(dc, LOGPIXELSY), 72);
	strcpy ( lf.lfFaceName, "Arial" );
	SendMessage ( mWndTabs, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
	ReleaseDC ( mWndScript, dc );

	TCITEM item;
	item.mask = TCIF_TEXT;
	item.pszText = "Output";
	TabCtrl_InsertItem ( mWndTabs, 0, &item );
	item.pszText = "Console";
	TabCtrl_InsertItem ( mWndTabs, 1, &item );
	item.pszText = "Call Stack";
	TabCtrl_InsertItem ( mWndTabs, 2, &item );
	item.pszText = "Watch";
	TabCtrl_InsertItem ( mWndTabs, 3, &item );
	item.pszText = "Threads";
	TabCtrl_InsertItem ( mWndTabs, 4, &item );
	item.pszText = "Scripts";
	TabCtrl_InsertItem ( mWndTabs, 5, &item );
	item.pszText = "Breakpoints";
	TabCtrl_InsertItem ( mWndTabs, 6, &item );

	mWndCallstack = CreateWindow ( WC_LISTVIEW, "", LVS_REPORT|WS_CHILD|LVS_SHAREIMAGELISTS, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_CALLSTACK, mInstance, NULL );
	mWndWatch     = CreateWindow ( WC_LISTVIEW, "", LVS_REPORT|WS_CHILD|LVS_EDITLABELS|LVS_OWNERDRAWFIXED, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_WATCH, mInstance, NULL );
	mWndThreads   = CreateWindow ( WC_LISTVIEW, "", LVS_REPORT|WS_CHILD|LVS_SHAREIMAGELISTS, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_THREADS, mInstance, NULL );
	mWndScriptList = CreateWindow( WC_LISTVIEW, "", LVS_REPORT|WS_CHILD|LVS_SHAREIMAGELISTS, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_SCRIPTLIST, mInstance, NULL );
	mWndBreakList =  CreateWindow( WC_LISTVIEW, "", LVS_REPORT|WS_CHILD|LVS_SHAREIMAGELISTS, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_BREAKLIST, mInstance, NULL );

	LVCOLUMN col;
	col.mask = LVCF_WIDTH|LVCF_TEXT;

	col.cx = 20;
	col.pszText = "";
	ListView_InsertColumn( mWndBreakList, 0, &col );
#if 0 // TODO: figure out how to get the function name in UpdateBreakpointList()
	col.cx = 150;
	col.pszText = "Function";
	ListView_InsertColumn( mWndBreakList, 1, &col );
#endif
	col.cx = 350;
	col.pszText = "Filename";
	ListView_InsertColumn( mWndBreakList, 1, &col );
	col.cx = 50;
	col.pszText = "Line";
	ListView_InsertColumn( mWndBreakList, 2, &col );

	col.cx = 20;
	col.pszText = "";
	ListView_InsertColumn ( mWndScriptList, 0, &col);
	col.cx = 350;
	col.pszText = "Filename";
	ListView_InsertColumn ( mWndScriptList, 1, &col );

	col.cx = 20;
	col.pszText = "";
	ListView_InsertColumn ( mWndCallstack, 0, &col );
	col.cx = 150;
	col.pszText = "Function";
	ListView_InsertColumn ( mWndCallstack, 1, &col );
	col.cx = 50;
	col.pszText = "Line";
	ListView_InsertColumn ( mWndCallstack, 2, &col );
	col.cx = 350;
	col.pszText = "Filename";
	ListView_InsertColumn ( mWndCallstack, 3, &col );

	col.cx = 20;
	col.pszText = "";
	ListView_InsertColumn ( mWndThreads, 0, &col );
	col.cx = 25;
	col.pszText = "ID";
	ListView_InsertColumn ( mWndThreads, 1, &col );
	col.cx = 150;
	col.pszText = "Name";
	ListView_InsertColumn ( mWndThreads, 2, &col );
	col.cx = 100;
	col.pszText = "State";
	ListView_InsertColumn ( mWndThreads, 3, &col );

	col.cx = 150;
	col.pszText = "Name";
	ListView_InsertColumn ( mWndWatch, 0, &col );
	col.cx = 200;
	col.pszText = "Value";
	ListView_InsertColumn ( mWndWatch, 1, &col );

	// Create the image list that is used by the threads window, callstack window, and
	// margin window
	mImageList = ImageList_Create ( 16, 16, ILC_COLOR|ILC_MASK, 0, 2 );
	ImageList_AddIcon ( mImageList, (HICON)LoadImage ( mInstance, MAKEINTRESOURCE(IDI_DBG_EMPTY), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE|LR_DEFAULTCOLOR) );
	ImageList_AddIcon ( mImageList, (HICON)LoadImage ( mInstance, MAKEINTRESOURCE(IDI_DBG_CURRENT), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE|LR_DEFAULTCOLOR) );
	ImageList_AddIcon ( mImageList, (HICON)LoadImage ( mInstance, MAKEINTRESOURCE(IDI_DBG_BREAKPOINT), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE|LR_DEFAULTCOLOR) );
	ImageList_AddIcon ( mImageList, (HICON)LoadImage ( mInstance, MAKEINTRESOURCE(IDI_DBG_CURRENTLINE), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE|LR_DEFAULTCOLOR) );
	
	int w, h;
	ResizeImageList(w, h);
	ListView_SetImageList ( mWndScriptList, mTmpImageList, LVSIL_SMALL );
	ListView_SetImageList ( mWndThreads, mImageList, LVSIL_SMALL );
	ListView_SetImageList ( mWndCallstack, mImageList, LVSIL_SMALL );
	ListView_SetImageList ( mWndBreakList, mImageList, LVSIL_SMALL );

	EnableWindows ( FALSE );
	EnableWindow ( mWndScriptList, true );
	
	ListView_SetExtendedListViewStyle ( mWndCallstack, LVS_EX_FULLROWSELECT );
	ListView_SetExtendedListViewStyle ( mWndThreads, LVS_EX_FULLROWSELECT );
	ListView_SetExtendedListViewStyle ( mWndScriptList, LVS_EX_FULLROWSELECT );
	ListView_SetExtendedListViewStyle ( mWndBreakList, LVS_EX_FULLROWSELECT );

	gDebuggerApp.GetOptions().GetColumnWidths ( "cw_callstack", mWndCallstack );
	gDebuggerApp.GetOptions().GetColumnWidths ( "cw_threads", mWndThreads );
	gDebuggerApp.GetOptions().GetColumnWidths ( "cw_watch", mWndWatch );

	mWndToolTips = CreateWindowEx ( WS_EX_TOPMOST,
									TOOLTIPS_CLASS,
									NULL,
									WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
									CW_USEDEFAULT,
									CW_USEDEFAULT,
									CW_USEDEFAULT,
									CW_USEDEFAULT,
									mWnd,
									NULL,
									mInstance,
									NULL
									);

	SendMessage ( mWndToolTips, TTM_SETDELAYTIME, TTDT_INITIAL, MAKELONG(400,0) );
	SendMessage ( mWndToolTips, TTM_SETDELAYTIME, TTDT_RESHOW, MAKELONG(400,0) );

	SetWindowPos( mWndToolTips, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

	LVITEM lvItem;
	lvItem.iItem = 0;
	lvItem.iSubItem = 0;
	lvItem.mask = LVIF_TEXT;
	lvItem.pszText = "";
	ListView_InsertItem ( mWndWatch, &lvItem );
	ListView_SetExtendedListViewStyle ( mWndWatch, LVS_EX_FULLROWSELECT );

	// Recent files
	InitRecentFiles ( );
	UpdateRecentFiles ( );

	HandleInitMenu ( (WPARAM)GetMenu ( mWnd ), 0 );

	// Read in the watches
	for ( i = 0; ; i ++ )
	{
		const char* s = gDebuggerApp.GetOptions().GetString ( va("watch%d", i ) );
		if ( !s || !s[0] )
		{
			break;
		}

		AddWatch ( s );
	}

	RECT t;
	GetClientRect(mWndScript, &t);
	SendMessage(mWndScript, WM_SIZE, 0, MAKELPARAM(t.right - t.left, t.bottom - t.top));

	return 0;
}

/*
================
rvDebuggerWindow::HandleCommand

Handles the WM_COMMAND message for this window
================
*/
int rvDebuggerWindow::HandleCommand ( WPARAM wparam, LPARAM lparam )
{
	int event = HIWORD(wparam);
	int id    = LOWORD(wparam);

	// The window menu list needs to be handled specially
	if ( id >= ID_DBG_WINDOWMIN && id <= ID_DBG_WINDOWMAX )
	{
		mActiveScript = id - ID_DBG_WINDOWMIN;
		UpdateScript ( );
		return 0;
	}

	// The recent file list needs to be handled specially
	if ( LOWORD(wparam) >= ID_DBG_FILE_MRU1 && LOWORD(wparam) < ID_DBG_FILE_MRU1 + rvRegistryOptions::MAX_MRU_SIZE )
	{
		idStr filename;
		filename = gDebuggerApp.GetOptions().GetRecentFile ( gDebuggerApp.GetOptions().GetRecentFileCount() - (LOWORD(wparam)-ID_DBG_FILE_MRU1) - 1 );
		if ( !OpenScript ( filename ) )
		{
			MessageBox ( mWnd, va("Failed to open script '%s'", filename.c_str() ), "Quake 4 Script Debugger", MB_OK );
		}
		return 0;
	}

	switch ( id )
	{
		case ID_DBG_SEND_COMMAND:
		{
			if ( mClient->IsConnected( ) && GetFocus( ) == mWndConsoleInput ) {
				GETTEXTLENGTHEX textLen;
				int				chars;
				textLen.flags = GTL_DEFAULT | GTL_USECRLF;
				textLen.codepage = CP_ACP;
				chars = SendMessage( mWndConsoleInput, EM_GETTEXTLENGTHEX, ( WPARAM ) &textLen, 0 );

				char *text = new char[chars + 1];

				GETTEXTEX getText;
				getText.cb = chars + 1;
				getText.codepage = CP_ACP;
				getText.flags = GT_DEFAULT | GT_USECRLF;
				getText.lpDefaultChar = NULL;
				getText.lpUsedDefChar = NULL;
				SendMessage( mWndConsoleInput, EM_GETTEXTEX, ( WPARAM ) &getText, ( LPARAM ) text );
				idStr parse = text;
				delete[] text;

				mClient->SendCommand( parse.c_str() );

				SendMessage( mWndConsoleInput, EM_SETSEL, 0, -1 );
				SendMessage( mWndConsoleInput, EM_REPLACESEL, FALSE, ( LPARAM ) "" );
				UpdateWindow( mWndConsoleInput );
			}
			break;
		}

		case ID_DBG_EDIT_FINDSELECTED:
		{
			idStr text;
			GetSelectedText ( text );
			FindNext ( text );
			break;
		}

		case ID_DBG_EDIT_FINDSELECTEDPREV:
		{
			idStr text;
			GetSelectedText ( text );
			FindPrev ( text );
			break;
		}

		case ID_DBG_EDIT_FINDNEXT:
			FindNext ( );
			break;

		case ID_DBG_EDIT_FINDPREV:
			FindPrev ( );
			break;

		case ID_DBG_DEBUG_QUICKWATCH:
		{
			idStr text;

			GetSelectedText ( text );
			rvDebuggerQuickWatchDlg dlg;
			dlg.DoModal ( this, mCurrentStackDepth, text );
			break;
		}

		case ID_DBG_HELP_ABOUT:
			DialogBox ( mInstance, MAKEINTRESOURCE(IDD_DBG_ABOUT), mWnd, AboutDlgProc );
			break;

		case ID_DBG_DEBUG_BREAK:
			mClient->Break ( );
			break;

		case ID_DBG_DEBUG_STEPOVER:
			EnableWindows ( FALSE );
			mClient->StepOver ( );
			break;

		case ID_DBG_DEBUG_STEPINTO:
			EnableWindows ( FALSE );
			mClient->StepInto ( );
			break;

		case ID_DBG_DEBUG_RUN:
			// Run the game if its not running
			if ( !mClient->IsConnected ( ) )
			{
				char exeFile[MAX_PATH];
				char curDir[MAX_PATH];

				STARTUPINFO			startup;
				PROCESS_INFORMATION	process;

				ZeroMemory ( &startup, sizeof(startup) );
				startup.cb = sizeof(startup);

				GetCurrentDirectory ( MAX_PATH, curDir );

				GetModuleFileName ( NULL, exeFile, MAX_PATH );
				const char* s = va("%s +set fs_game %s +set fs_cdpath %s +set com_enableDebuggerServer 1", exeFile, cvarSystem->GetCVarString( "fs_game" ), cvarSystem->GetCVarString( "fs_cdpath" ) );
				CreateProcess ( NULL, (LPSTR)s,
				NULL, NULL, FALSE, 0, NULL, curDir, &startup, &process );

				CloseHandle ( process.hThread );
				CloseHandle ( process.hProcess );
			}
			else if ( mClient->IsStopped() )
			{
				EnableWindows ( FALSE );
				mClient->Resume ( );
				UpdateToolbar ( );
				UpdateTitle ( );
				InvalidateRect ( mWnd, NULL, FALSE );
			}

			break;

		case IDC_DBG_SCRIPT:
		{
			RECT	t;
			LONG	num;
			LONG	dem;

			SendMessage ( mWndScript, EM_GETZOOM, (WPARAM)&num, (LPARAM)&dem );
			if ( num != mZoomScaleNum || dem != mZoomScaleDem )
			{
				mZoomScaleNum = num;
				mZoomScaleDem = dem;
				GetClientRect ( mWndScript, &t );
				SendMessage ( mWndScript, WM_SIZE, 0, MAKELPARAM(t.right-t.left ,t.bottom-t.top) );
			}
			else
			{
				InvalidateRect ( mWndMargin, NULL, TRUE );
			}
			break;
		}

		case 111: // DG: Debugger.rc has 'MENUITEM "Toggle &Breakpoint\tF9", 111' for the context menu no idea why 111 but this works
		case ID_DBG_DEBUG_TOGGLEBREAKPOINT:
			ToggleBreakpoint ( );
			break;

		case ID_DBG_FILE_EXIT:
			PostMessage ( mWnd, WM_CLOSE, 0, 0 );
			break;

		case ID_DBG_FILE_CLOSE:
			if ( mScripts.Num() )
			{
				delete mScripts[mActiveScript];
				mScripts.RemoveIndex ( mActiveScript );
				if ( mActiveScript >= mScripts.Num ( ) )
				{
					mActiveScript --;
				}
				UpdateWindowMenu ( );
				UpdateScript ( );
			}
			break;

		case ID_DBG_FILE_NEXT:
			if ( mScripts.Num ( ) > 0 )
			{
				mActiveScript++;
				if ( mActiveScript >= mScripts.Num ( ) )
				{
					mActiveScript = 0;
				}
				UpdateWindowMenu ( );
				UpdateScript ( );
			}
			break;

		case ID_DBG_FILE_OPEN:
		{
			rvOpenFileDialog dlg;
			dlg.SetTitle ( "Open Script" );
			dlg.SetFilter ( "*.script; *.gui; *.state" );
			dlg.SetFlags ( OFD_MUSTEXIST );
			if ( dlg.DoModal ( mWnd ) )
			{
				if ( !OpenScript ( dlg.GetFilename ( ) ) )
				{
					MessageBox ( mWnd, va("Failed to open script '%s'",dlg.GetFilename ( )), "Quake 4 Script Debugger", MB_OK );
				}
			}
			break;
		}

		case ID_DBG_EDIT_FIND:
		{
			rvDebuggerFindDlg dlg;
			if ( dlg.DoModal ( this ) )
			{
				FindNext ( dlg.GetFindText ( ) );
			}
			break;
		}

		case ID_DBG_WINDOW_CLOSEALL:
		{
			for ( int i = 0; i < mScripts.Num(); i ++ )
			{
				delete mScripts[i];
			}

			mScripts.Clear ( );
			mActiveScript = -1;

			UpdateWindowMenu ( );
			UpdateScript ( );
			break;
		}

		// DG: support "Run To Cursor" from context menu
		case ID_DBG_DEBUG_RUNTOCURSOR:
		{
			// Find the currently selected line
			DWORD sel;
			SendMessage( mWndScript, EM_GETSEL, (WPARAM)&sel, 0 );
			int lineNumber = SendMessage( mWndScript, EM_LINEFROMCHAR, sel, 0 ) + 1;

			const char* filename = mScripts[mActiveScript]->GetFilename();
			mClient->AddBreakpoint( filename, lineNumber, true );
			mClient->Resume();
			break;
		}

		// TODO: case ID_DBG_DEBUG_SHOWNEXTSTATEMENT:
		//       whatever this is supposed to do (also from context menu)
	}

	return 0;
}

/*
================
rvDebuggerWindow::WndProc

Window procedure for the deubgger window
================
*/
LRESULT CALLBACK rvDebuggerWindow::WndProc ( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
	rvDebuggerWindow* window = (rvDebuggerWindow*) GetWindowLongPtr ( wnd, GWLP_USERDATA );

	switch ( msg )
	{
		case WM_INITMENUPOPUP:
			window->HandleInitMenu ( wparam, lparam );
			break;

		case WM_DESTROY:
		{
			gDebuggerApp.GetOptions().SetColumnWidths ( "cw_callstack", window->mWndCallstack );
			gDebuggerApp.GetOptions().SetColumnWidths ( "cw_threads", window->mWndThreads );
			gDebuggerApp.GetOptions().SetColumnWidths ( "cw_watch", window->mWndWatch );
			gDebuggerApp.GetOptions().SetWindowPlacement ( "wp_main", wnd );

			int i;
			for ( i = 0; i < window->mWatches.Num ( ); i ++ )
			{
				gDebuggerApp.GetOptions().SetString ( va("watch%d", i ), window->mWatches[i]->mVariable );
			}
			gDebuggerApp.GetOptions().SetString ( va("watch%d", i ), "" );

			window->mWnd = NULL;
			SetWindowLongPtr ( wnd, GWLP_USERDATA, 0 );
			break;
		}

		case WM_ERASEBKGND:
			return 0;

		case WM_COMMAND:
			window->HandleCommand ( wparam, lparam );
			break;

		case WM_SETCURSOR:
		{
			POINT point;
			GetCursorPos ( &point );
			ScreenToClient ( wnd, &point );
			if ( PtInRect ( &window->mSplitterRect, point ) )
			{
				SetCursor ( LoadCursor ( NULL, IDC_SIZENS ) );
				return true;
			}
			break;
		}

		case WM_SIZE:
		{
			float scaling_factor = Win_GetWindowScalingFactor(wnd);
			int s18 = int(18 * scaling_factor);
			int s4 = int(4 * scaling_factor);
			int s10 = int(10 * scaling_factor);

			RECT rect;
			window->mMarginSize = window->mZoomScaleDem ? ((long)(s18 * (float)window->mZoomScaleNum / (float)window->mZoomScaleDem)): s18;
			window->mSplitterRect.left = 0;
			window->mSplitterRect.right = LOWORD(lparam);

			SendMessage ( window->mWndToolbar, TB_AUTOSIZE, 0, 0 );
			GetWindowRect ( window->mWndToolbar, &rect );
			MoveWindow ( window->mWndScript, 0, rect.bottom-rect.top, LOWORD(lparam), window->mSplitterRect.top-(rect.bottom-rect.top), TRUE );
			MoveWindow ( window->mWndMargin, 0, 0, window->mMarginSize, window->mSplitterRect.top-(rect.bottom-rect.top), TRUE );

			SetRect ( &rect, 0, window->mSplitterRect.bottom, LOWORD(lparam), HIWORD(lparam) );
			MoveWindow ( window->mWndTabs, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
			SendMessage ( window->mWndTabs, TCM_ADJUSTRECT, FALSE, (LPARAM)&rect );
			rect.bottom -= s4;
			MoveWindow ( window->mWndBorder, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
			InflateRect ( &rect, -1, -1 );
			MoveWindow ( window->mWndOutput, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
			MoveWindow ( window->mWndConsole, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top - s18, TRUE );
			MoveWindow ( window->mWndConsoleInput, rect.left, rect.bottom-s18, rect.right - rect.left, s18, TRUE );
			MoveWindow ( window->mWndCallstack, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
			MoveWindow ( window->mWndWatch, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
			MoveWindow ( window->mWndThreads, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
			MoveWindow ( window->mWndScriptList, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
			MoveWindow ( window->mWndBreakList, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );

			// FIXME: was *2.25, increased for line numbers up to 9999; but neither works particularly well
			//        if DPI scaling is involved, because script code text and linenumbers aren't DPI scaled
			int lmargin = window->GetMarginWidth();
			SendMessage(window->mWndScript, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(lmargin, s10));
			SendMessage(window->mWndCallstack, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(s18, s10));
			SendMessage(window->mWndOutput, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(s18, s10));
			SendMessage(window->mWndConsole, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(s18, s10));
			SendMessage( window->mWndConsoleInput, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG( s18, s10 ) );

			break;
		}

		case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HDC dc = BeginPaint ( wnd, &ps );
			FillRect ( dc, &window->mSplitterRect, GetSysColorBrush ( COLOR_3DFACE ) );

			if ( !window->mScripts.Num() )
			{
				RECT rect;
				GetClientRect ( wnd, &rect );
				rect.bottom = window->mSplitterRect.top;
				FillRect ( dc, &rect, GetSysColorBrush ( COLOR_APPWORKSPACE ) );
			}

			EndPaint ( wnd, &ps );
			break;
		}

		case WM_LBUTTONDOWN:
		{
			POINT pt = { LOWORD(lparam),HIWORD(lparam) };
			if ( PtInRect ( &window->mSplitterRect, pt ) )
			{
				window->mSplitterDrag = true;
				SetCapture ( wnd );

				HDC dc = GetDC ( wnd );
				DrawFocusRect ( dc, &window->mSplitterRect );
				ReleaseDC ( wnd, dc );
			}
			break;
		}
		case WM_MOUSEWHEEL:
		{
			HDC dc = GetDC(wnd);
			DrawFocusRect(dc, &window->mSplitterRect);
			ReleaseDC(wnd, dc);

			RECT client;
			GetClientRect(wnd, &client);
			SendMessage(wnd, WM_SIZE, 0, MAKELPARAM(client.right - client.left, client.bottom - client.top));

		}
		case WM_LBUTTONUP:
			if ( window->mSplitterDrag )
			{
				HDC dc = GetDC ( wnd );
				DrawFocusRect ( dc, &window->mSplitterRect );
				ReleaseDC ( wnd, dc );

				window->mSplitterDrag = false;

				RECT client;
				GetClientRect ( wnd, &client );
				SendMessage ( wnd, WM_SIZE, 0, MAKELPARAM(client.right-client.left,client.bottom-client.top) );

				InvalidateRect ( wnd, NULL, TRUE );

				ReleaseCapture ( );
			}
			break;

		case WM_MOUSEMOVE:
		{
			if ( window->mSplitterDrag )
			{
				HDC dc = GetDC ( wnd );
				DrawFocusRect ( dc, &window->mSplitterRect );
				ReleaseDC ( wnd, dc );

				if ( GetCapture ( ) != wnd )
				{
					break;
				}

				RECT client;
				GetClientRect ( wnd, &client );

				window->mSplitterRect.top = HIWORD(lparam);
				if ( window->mSplitterRect.top < client.top )
				{
					window->mSplitterRect.top = client.top;
				}
				else if ( window->mSplitterRect.top + 4 > client.bottom )
				{
					window->mSplitterRect.top = client.bottom - 4;
				}
				window->mSplitterRect.bottom = window->mSplitterRect.top + 4;

				dc = GetDC ( wnd );
				DrawFocusRect ( dc, &window->mSplitterRect );
				ReleaseDC ( wnd, dc );
			}
			break;
		}

		case WM_DRAWITEM:
			window->HandleDrawItem ( wparam, lparam );
			break;

		case WM_CREATE:
		{
			CREATESTRUCT* cs = (CREATESTRUCT*) lparam;
			window = (rvDebuggerWindow*) cs->lpCreateParams;
			SetWindowLongPtr ( wnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams );

			window->mWnd = wnd;
			window->HandleCreate ( wparam, lparam );

			break;
		}

		case WM_ACTIVATE:
			window->HandleActivate ( wparam, lparam );
			break;

		case WM_NOTIFY:
		{
			NMHDR*	hdr;
			hdr = (NMHDR*)lparam;

			// Tool tips
			if ( hdr->code == TTN_GETDISPINFO )
			{
				window->HandleTooltipGetDispInfo ( wparam, lparam );
				break;
			}

			switch ( hdr->idFrom )
			{
				case IDC_DBG_WATCH:
					switch ( hdr->code )
					{
						case LVN_KEYDOWN:
						{
							NMLVKEYDOWN* key = (NMLVKEYDOWN*) hdr;
							switch ( key->wVKey )
							{
								case VK_DELETE:
								{
									int sel = ListView_GetNextItem ( hdr->hwndFrom, -1, LVNI_SELECTED );
									if ( sel != -1 && sel != ListView_GetItemCount ( hdr->hwndFrom ) - 1 )
									{
										LVITEM item;
										item.iItem = sel;
										item.mask = LVIF_PARAM;
										ListView_GetItem ( hdr->hwndFrom, &item );
										ListView_DeleteItem ( hdr->hwndFrom, sel );

										window->mWatches.Remove ( (rvDebuggerWatch*)item.lParam );
										delete (rvDebuggerWatch*)item.lParam;

										ListView_SetItemState ( hdr->hwndFrom,
																sel,
																LVIS_SELECTED, LVIS_SELECTED );

										if ( ListView_GetNextItem ( hdr->hwndFrom, -1, LVNI_SELECTED ) == -1 )
										{
											ListView_SetItemState ( hdr->hwndFrom,
																	ListView_GetItemCount ( hdr->hwndFrom ) - 1,
																	LVIS_SELECTED, LVIS_SELECTED );
										}
									}
									break;
								}

								case VK_RETURN:
								{
									int sel = ListView_GetNextItem ( hdr->hwndFrom, -1, LVNI_SELECTED );
									if ( sel != -1 )
									{
										ListView_EditLabel ( hdr->hwndFrom, sel );
									}
									break;
								}
							}
							break;
						}

						case LVN_BEGINLABELEDIT:
						{
							NMLVDISPINFO* di = (NMLVDISPINFO*)hdr;

							DWORD style = GetWindowLong ( ListView_GetEditControl ( hdr->hwndFrom ), GWL_STYLE );
							SetWindowLong ( ListView_GetEditControl ( hdr->hwndFrom ), GWL_STYLE, style & (~WS_BORDER) );

							rvDebuggerWatch* watch = (rvDebuggerWatch*)di->item.lParam;
							if ( watch )
							{
								SetWindowText ( ListView_GetEditControl ( hdr->hwndFrom ), watch->mVariable );
							}

							return FALSE;
						}

						case LVN_ENDLABELEDIT:
						{
							NMLVDISPINFO* di = (NMLVDISPINFO*)hdr;

							if ( di->item.iItem == ListView_GetItemCount ( hdr->hwndFrom ) - 1 )
							{
								if ( !di->item.pszText || !di->item.pszText[0] )
								{
									return FALSE;
								}

								window->AddWatch ( ((NMLVDISPINFO*)hdr)->item.pszText );
								window->UpdateWatch ( );
								return FALSE;
							}
							else
							{
								rvDebuggerWatch* watch = (rvDebuggerWatch*) di->item.lParam;
								if ( watch && di->item.pszText && di->item.pszText[0] )
								{
									watch->mVariable = di->item.pszText;
								}
							}

							return TRUE;
						}
					}
					break;
				case IDC_DBG_CALLSTACK:
					if ( hdr->code == NM_DBLCLK )
					{
						int sel = ListView_GetNextItem ( hdr->hwndFrom, -1, LVNI_SELECTED );
						if ( sel != -1 )
						{
							window->mCurrentStackDepth = sel;
							window->UpdateCallstack ( );
							window->UpdateWatch ( );
							window->OpenScript ( window->mClient->GetCallstack()[window->mCurrentStackDepth]->mFilename,
												 window->mClient->GetCallstack()[window->mCurrentStackDepth]->mLineNumber );
						}
					}
					break;
				case IDC_DBG_SCRIPTLIST:
					if ( hdr->code == NM_DBLCLK )
					{
						int sel = ListView_GetNextItem(hdr->hwndFrom, -1, LVNI_SELECTED);

						if (sel != -1)
						{
							LVITEM item = { 0 }; 
							char   temp[1024] = { 0 };
							item.mask = LVIF_TEXT;
							item.pszText = temp;
							item.cchTextMax = sizeof(temp) - 1;
							item.iSubItem = 1;
							item.iItem = sel;

							ListView_GetItem(hdr->hwndFrom, &item);

							if (strlen(item.pszText) > 0)
							{
								window->OpenScript(item.pszText);
								window->UpdateScriptList();
							}
						}
					}
					break;

				case IDC_DBG_BREAKLIST:
					if ( hdr->code == NM_DBLCLK || hdr->code == NM_CLICK ) {
						LPNMITEMACTIVATE ia = (LPNMITEMACTIVATE)lparam;
						int sel = ia->iItem;
						if ( sel != -1 ) {
							rvDebuggerBreakpoint* bp = window->mClient->GetBreakpoint( sel );
							if ( bp != NULL ) {
								if ( hdr->code == NM_DBLCLK ) {
									// double clicked breakpoint => show it in its file
									window->OpenScript( bp->GetFilename(), bp->GetLineNumber() - 1 );
								} else if( ia->iSubItem == 0 ) {
									// clicked breakpoint symbol => delete breakpoint
									window->mClient->RemoveBreakpoint( bp->GetID() );
									window->UpdateBreakpointList();
								}
							}
						}
					} else if ( hdr->code == LVN_KEYDOWN ) {
						// when user selects a breakpoints and presses the Del key, remove the breakpoint
						int sel = ListView_GetNextItem( hdr->hwndFrom, -1, LVNI_SELECTED );
						if ( sel != -1 ) {
							LPNMLVKEYDOWN kd = (LPNMLVKEYDOWN)lparam;
							rvDebuggerBreakpoint* bp = window->mClient->GetBreakpoint( sel );
							if ( kd->wVKey == VK_DELETE && bp != NULL ) {
								window->mClient->RemoveBreakpoint( bp->GetID() );
								window->UpdateBreakpointList();
							}
						}
					}
					break;

				case IDC_DBG_TABS:
					if ( hdr->code == TCN_SELCHANGE )
					{
						ShowWindow ( window->mWndOutput, SW_HIDE );
						ShowWindow ( window->mWndConsole, SW_HIDE );
						ShowWindow ( window->mWndConsoleInput, SW_HIDE );
						ShowWindow ( window->mWndCallstack, SW_HIDE );
						ShowWindow ( window->mWndWatch, SW_HIDE );
						ShowWindow ( window->mWndThreads, SW_HIDE );
						ShowWindow ( window->mWndScriptList, SW_HIDE );
						ShowWindow ( window->mWndBreakList, SW_HIDE );
						switch ( TabCtrl_GetCurSel ( hdr->hwndFrom ) )
						{
							case 0:
								ShowWindow ( window->mWndOutput, SW_SHOW );
								break;

							case 1:
								ShowWindow ( window->mWndConsole, SW_SHOW );
								ShowWindow( window->mWndConsoleInput, SW_SHOW );
								break;

							case 2:
								ShowWindow ( window->mWndCallstack, SW_SHOW );
								break;

							case 3:
								ShowWindow ( window->mWndWatch, SW_SHOW );
								break;

							case 4:
								ShowWindow ( window->mWndThreads, SW_SHOW );
								break;

							case 5:
								ShowWindow(window->mWndScriptList, SW_SHOW);
								break;

							case 6:
								ShowWindow( window->mWndBreakList, SW_SHOW );
								break;
						}
					}
					break;
			}
			break;
		}

		case WM_CLOSE:
			if ( window->mClient->IsConnected ( ) )
			{
				if ( IDNO == MessageBox ( wnd, "The debugger is currently connected to a running version of the game.  Are you sure you want to close now?", "Dhewm3 Script Debugger", MB_YESNO|MB_ICONQUESTION ) )
				{
					return 0;
				}
			}
			PostQuitMessage ( 0 );
			break;
	}

	return DefWindowProc ( wnd, msg, wparam, lparam );
}

/*
================
rvDebuggerWindow::Activate

Static method that will activate the currently running debugger.  If one is found
and activated then true will be returned.
================
*/
bool rvDebuggerWindow::Activate ( void )
{
	HWND find;

	find = FindWindow ( DEBUGGERWINDOWCLASS, NULL );
	if ( !find )
	{
		return false;
	}

	SetForegroundWindow ( find );

	return true;
}

/*
================
rvDebuggerWindow::ProcessNetMessage

Process an incoming network message
================
*/
void rvDebuggerWindow::ProcessNetMessage ( idBitMsg* msg )
{
	short command;

	command = msg->ReadShort( );

	switch ( command )
	{
		case DBMSG_REMOVEBREAKPOINT:
			MessageBeep(MB_ICONEXCLAMATION);
			InvalidateRect(mWndScript, NULL, FALSE);
			UpdateBreakpointList();
			break;

		case DBMSG_RESUMED:
			UpdateTitle ( );
			UpdateToolbar ( );
			break;

		case DBMSG_INSPECTVARIABLE:
		{
			char temp[1024];
			char temp2[1024];
			int	 i;

			msg->ReadShort ( );
			msg->ReadString (  temp, 1024 );
			msg->ReadString (  temp2, 1024 );
			if ( mTooltipVar.Icmp ( temp ) == 0 )
			{
				mTooltipValue = temp2;

				TOOLINFO info;
				info.cbSize   = sizeof(info);
				info.hwnd     = mWndScript;
				info.uId      = 0;
				info.hinst    = mInstance;
				info.lpszText = va("%s=%s", mTooltipVar.c_str(), mTooltipValue.c_str() );
				SendMessage ( mWndToolTips, TTM_UPDATETIPTEXT, 0, (LPARAM)&info );
				SendMessage ( mWndToolTips, TTM_UPDATE, 0, 0 );
			}

			// See if any of the watches were updated by this inspect
			for ( i = 0; i < mWatches.Num(); i ++ )
			{
				rvDebuggerWatch* watch = mWatches[i];

				// See if the name matches the variable
				if ( watch->mVariable.Cmp ( temp ) )
				{
					continue;
				}

				// Has the value changed?
				if ( !watch->mValue.Cmp ( temp2 ) )
				{
					continue;
				}

				watch->mModified = true;
				watch->mValue = temp2;

				// Find the list view item that is storing this watch info and redraw it
				for ( int l = 0; l < ListView_GetItemCount(mWndWatch); l ++ )
				{
					LVITEM item;
					item.mask = LVIF_PARAM;
					item.iItem = l;
					ListView_GetItem ( mWndWatch, &item );
					if ( item.lParam == (LPARAM) watch )
					{
						ListView_RedrawItems ( mWndWatch, l, l );
						UpdateWindow ( mWndWatch );
						break;
					}
				}
			}

			break;
		}

		case DBMSG_CONNECT:
		case DBMSG_CONNECTED:
			UpdateTitle ( );
			UpdateToolbar ( );
			Printf ( "Connected...\n" );
			break;

		case DBMSG_DISCONNECT:
			UpdateTitle ( );
			UpdateToolbar ( );
			Printf ( "Disconnected...\n" );
			break;

		case DBMSG_PRINT:
		{
			HWND prevFocus = GetFocus();
			SetFocus ( mWndConsole );
			SendMessage ( mWndConsole, EM_SETSEL, -1, -1 );
			SendMessage ( mWndConsole, EM_REPLACESEL, 0, (LPARAM)(const char*)(msg->GetData()) + msg->GetReadCount() );
			SendMessage( mWndConsole, EM_SETSEL, -1, -1 );
			SendMessage ( mWndConsole, EM_SCROLLCARET, 0, 0 );
			UpdateWindow( mWndConsole );
			SetFocus( prevFocus );
			break;
		}

		case DBMSG_BREAK:
		{
			Printf ( "Break:  line=%d  file='%s'\n", mClient->GetBreakLineNumber ( ), mClient->GetBreakFilename() );

			mCurrentStackDepth = 0;
			mClient->InspectVariable ( mTooltipVar, mCurrentStackDepth );
			UpdateWatch ( );
			UpdateBreakpointList();
			EnableWindows ( TRUE );
			OpenScript ( mClient->GetBreakFilename(), mClient->GetBreakLineNumber() - 1 );
			UpdateTitle ( );
			UpdateToolbar ( );
			SetForegroundWindow ( mWnd );
			break;
		}
		case DBMSG_INSPECTSCRIPTS:
		{
			UpdateScriptList ( );
			break;
		}
		case DBMSG_INSPECTCALLSTACK:
		{
			UpdateCallstack ( );
			break;
		}

		case DBMSG_INSPECTTHREADS:
		{
			LVITEM item;
			ListView_DeleteAllItems ( mWndThreads );
			ZeroMemory ( &item, sizeof(item) );
			item.mask = LVIF_TEXT|LVIF_IMAGE;

			for ( int i = 0; i < mClient->GetThreads().Num(); i ++ )
			{
				rvDebuggerThread* entry = mClient->GetThreads()[i];
				item.iItem = ListView_GetItemCount ( mWndThreads );
				item.pszText = "";
				item.iImage = entry->mCurrent ? 1 : 0;
				ListView_InsertItem ( mWndThreads, &item );

				ListView_SetItemText ( mWndThreads, item.iItem, 1, (LPSTR)va("%d", entry->mID) );
				ListView_SetItemText ( mWndThreads, item.iItem, 2, (LPSTR)entry->mName.c_str() );

				if ( entry->mDying )
				{
					ListView_SetItemText ( mWndThreads, item.iItem, 3, "Dying" );
				}
				else if ( entry->mWaiting )
				{
					ListView_SetItemText ( mWndThreads, item.iItem, 3, "Waiting" );
				}
				else if ( entry->mDoneProcessing )
				{
					ListView_SetItemText ( mWndThreads, item.iItem, 3, "Stopped" );
				}
				else
				{
					ListView_SetItemText ( mWndThreads, item.iItem, 3, "Running" );
				}
			}
			break;
		}
	}
}

/*
================
rvDebuggerWindow::Printf

Sends a formatted message to the output window
================
*/
void rvDebuggerWindow::Printf ( const char* fmt, ... )
{
	va_list		argptr;
	char		msg[4096];

	va_start (argptr,fmt);
	vsprintf (msg,fmt,argptr);
	va_end (argptr);

	SendMessage ( mWndOutput, EM_SETSEL, -1, -1 );
	SendMessage ( mWndOutput, EM_REPLACESEL, 0, (LPARAM)msg );
	SendMessage ( mWndOutput, EM_SCROLLCARET, 0, 0 );
}

/*
================
rvDebuggerWindow::OpenScript

Opens the script with the given filename and will scroll to the given line
number if one is specified
================
*/
bool rvDebuggerWindow::OpenScript ( const char* filename, int lineNumber, idProgram* program )
{
	int i;

	SetCursor ( LoadCursor ( NULL, IDC_WAIT ) );

	mActiveScript = -1;

	// See if the script is already loaded
	for ( i = 0; i < mScripts.Num(); i ++ )
	{
		if ( !idStr::Icmp ( mScripts[i]->GetFilename ( ), filename ) )
		{
			if ( mActiveScript != i )
			{
				mActiveScript = i;
				break;
			}
		}
	}

	// If the script isnt open already then open it
	if ( mActiveScript == -1 )
	{
		rvDebuggerScript* script = new rvDebuggerScript;

		// Load the script
		if ( !script->Load ( filename ) )
		{
			SetCursor ( LoadCursor ( NULL, IDC_ARROW ) );
			return false;
		}

		gDebuggerApp.GetOptions().AddRecentFile ( filename );
		UpdateRecentFiles ( );

		mActiveScript = mScripts.Append ( script );
	}

	// Test code that will place a breakpoint on all valid lines of code
#if 0

	for ( i = 0; i < mScripts[mActiveScript]->GetProgram().NumStatements(); i ++ )
	{
		dstatement_t* st = &mScripts[mActiveScript]->GetProgram().GetStatement ( i );
		rvDebuggerBreakpoint* bp = new rvDebuggerBreakpoint ( filename, st->linenumber );
		mBreakpoints.Append ( bp );
	}

#endif

	UpdateScript ( );
	UpdateWindowMenu ( );

	// Move to a specific line number?
	if ( lineNumber != -1 )
	{
		long	c;

		// Put the caret on the line number specified and scroll it into position.
		// This is a bit of a hack since we set the selection twice, but setting the
		// selection to (c,c) didnt work for scrolling the caret so we do (c,c+1)
		// and then scroll before going back to (c,c).
		// NOTE: We scroll to the line before the one we want so its more visible
		SetFocus ( mWndScript );
		c = SendMessage ( mWndScript, EM_LINEINDEX, (long)lineNumber - 1, 0 );
		SendMessage ( mWndScript, EM_SETSEL, c, c + 1 );
		SendMessage ( mWndScript, EM_SCROLLCARET, 0, 0 );
		c = SendMessage ( mWndScript, EM_LINEINDEX, (long)lineNumber, 0 );
		SendMessage ( mWndScript, EM_SETSEL, c, c );
	}
	else
	{
		SendMessage ( mWndScript, EM_SETSEL, 0, 0 );
	}

	// Make sure the script window is visible
	ShowWindow ( mWndScript, SW_SHOW );
	UpdateWindow ( mWndScript );

	SetCursor ( LoadCursor ( NULL, IDC_ARROW ) );

	return true;
}

/*
================
rvDebuggerWindow::ToggleBreakpoint

Toggles the breakpoint on the current script line
================
*/
void rvDebuggerWindow::ToggleBreakpoint ( void )
{
	rvDebuggerBreakpoint* bp;
	DWORD				  sel;
	int				      line;

	// Find the currently selected line
	SendMessage ( mWndScript, EM_GETSEL, (WPARAM)&sel, 0 );
	line = SendMessage ( mWndScript, EM_LINEFROMCHAR, sel, 0 ) + 1;

	// If there is already a breakpoint there then just remove it, otherwise
	// we need to create a new breakpoint
	bp = mClient->FindBreakpoint ( mScripts[mActiveScript]->GetFilename ( ), line );
	if ( !bp )
	{
		// Make sure the line is code before letting them add a breakpoint there
		if ( !mScripts[mActiveScript]->IsLineCode ( line ) )
		{
			MessageBeep ( MB_ICONEXCLAMATION );
			return;
		}

		mClient->AddBreakpoint ( mScripts[mActiveScript]->GetFilename(), line );
	}
	else
	{
		mClient->RemoveBreakpoint ( bp->GetID ( ) );
	}

	// Force a repaint of the script window
	InvalidateRect ( mWndScript, NULL, FALSE );

	UpdateBreakpointList();
}

/*
================
rvDebuggerWindow::AboutDlgProc

Dialog box procedure for the about box
================
*/
INT_PTR CALLBACK rvDebuggerWindow::AboutDlgProc ( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
	switch ( msg )
	{
		case WM_COMMAND:
			EndDialog ( wnd, 0 );
			break;
	}

	return FALSE;
}

/*
================
rvDebuggerWindow::CreateToolbar

Create the toolbar and and all of its buttons
================
*/
void rvDebuggerWindow::CreateToolbar ( void )
{
	// Create the toolbar control
	mWndToolbar = CreateWindowEx ( 0, TOOLBARCLASSNAME, "", WS_CHILD|WS_VISIBLE,0,0,0,0, mWnd, (HMENU)IDC_DBG_TOOLBAR, mInstance, NULL );

	// Initialize the toolbar
	SendMessage ( mWndToolbar, TB_BUTTONSTRUCTSIZE, ( WPARAM )sizeof( TBBUTTON ), 0 );
	SendMessage ( mWndToolbar, TB_SETBUTTONSIZE, 0, MAKELONG(16,16) );
	SendMessage ( mWndToolbar, TB_SETSTYLE, 0, SendMessage ( mWndToolbar, TB_GETSTYLE, 0, 0 ) | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS );

	TBMETRICS tbmet;
	tbmet.cbSize = sizeof(TBMETRICS);
	SendMessage ( mWndToolbar, TB_GETMETRICS, 0, (LPARAM)&tbmet );
	tbmet.cyPad = 0;
	tbmet.cyBarPad = 0;
	SendMessage ( mWndToolbar, TB_SETMETRICS, 0, (LPARAM)&tbmet );

	// Add the bitmap containing button images to the toolbar.
	TBADDBITMAP	tbab;
	tbab.hInst = mInstance;
	tbab.nID = IDB_DBG_TOOLBAR;
	SendMessage( mWndToolbar, TB_ADDBITMAP, (WPARAM)4, (LPARAM) &tbab );

	// Add the buttons to the toolbar
	TBBUTTON tbb[] = { { 0, 0,					TBSTATE_ENABLED, BTNS_SEP,    {0, 0}, size_t(-1) },
					   { 8, ID_DBG_FILE_OPEN,	TBSTATE_ENABLED, BTNS_BUTTON, {0, 0}, size_t(-1) },
					   { 0, 0,					TBSTATE_ENABLED, BTNS_SEP,    {0, 0}, size_t(-1) },
					   { 0, ID_DBG_DEBUG_RUN,	TBSTATE_ENABLED, BTNS_BUTTON, {0, 0}, size_t(-1) },
					   { 1, ID_DBG_DEBUG_BREAK, TBSTATE_ENABLED, BTNS_BUTTON, {0, 0}, size_t(-1) },
					   { 0, 0,					TBSTATE_ENABLED, BTNS_SEP,    {0, 0}, size_t(-1) },
					   { 4, ID_DBG_DEBUG_STEPINTO, TBSTATE_ENABLED, BTNS_BUTTON, {0, 0}, size_t(-1) },
					   { 5, ID_DBG_DEBUG_STEPOVER, TBSTATE_ENABLED, BTNS_BUTTON, {0, 0}, size_t(-1) },
					   { 6, ID_DBG_DEBUG_STEPOUT, TBSTATE_ENABLED, BTNS_BUTTON, {0, 0}, size_t(-1) },
					   { 0, 0,					TBSTATE_ENABLED, BTNS_SEP,    {0, 0}, size_t(-1) } };

	SendMessage( mWndToolbar, TB_ADDBUTTONS, (WPARAM)sizeof(tbb)/sizeof(TBBUTTON), (LPARAM) tbb );
}

/*
================
rvDebuggerWindow::HandleTooltipGetDispInfo

Handle the getdispinfo notification message for tooltips by responding with the
tooptip text for the given toolbar button.
================
*/
void rvDebuggerWindow::HandleTooltipGetDispInfo ( WPARAM wparam, LPARAM lparam )
{
	NMTTDISPINFO* ttdi = (NMTTDISPINFO*) lparam;
	switch ( ttdi->hdr.idFrom )
	{
		case ID_DBG_FILE_OPEN:
			strcpy ( ttdi->szText, "Open Script" );
			break;

		case ID_DBG_DEBUG_STEPINTO:
			strcpy ( ttdi->szText, "Step Into" );
			break;

		case ID_DBG_DEBUG_STEPOVER:
			strcpy ( ttdi->szText, "Step Over" );
			break;

		case ID_DBG_DEBUG_STEPOUT:
			strcpy ( ttdi->szText, "Step Out" );
			break;

		case ID_DBG_DEBUG_BREAK:
			strcpy ( ttdi->szText, "Break" );
			break;

		case ID_DBG_DEBUG_RUN:
			if ( mClient->IsConnected() )
			{
				strcpy ( ttdi->szText, "Continue" );
			}
			else
			{
				strcpy ( ttdi->szText, "Run" );
			}
			break;

		default:
			strcpy ( ttdi->szText, "" );
			break;
	}
}

/*
================
rvDebuggerWindow::HandleActivate

When the main window is activated, check all the loaded scripts and see if any of them
have been modified since the last time they were loaded.  If they have then reload
them and adjust all breakpoints that now fall on invalid lines.
================
*/
int rvDebuggerWindow::HandleActivate ( WPARAM wparam, LPARAM lparam )
{
	int i;

	// We are only interested in the activation, not deactivation
	if ( !LOWORD(wparam) )
	{
		return 0;
	}

	// Run through all of the loaded scripts and see if any of them have been modified
	for ( i = 0; i < mScripts.Num(); i ++ )
	{
		if ( mScripts[i]->IsFileModified ( true ) )
		{
			if ( IDYES == MessageBox ( mWnd, va("%s\n\nThis file has been modified outside of the debugger.\nDo you want to reload it?", mScripts[i]->GetFilename() ), "Quake 4 Script Debugger", MB_YESNO|MB_ICONQUESTION ) )
			{
				mScripts[i]->Reload ( );

				// Update the script if it was the active one
				if ( mActiveScript == i )
				{
					mLastActiveScript = -1;
					UpdateScript ( );
				}

				// Loop through the breakpoints and see if any of them have
				// moved to invalid lines within the script.  If so then just remove
				// them.
				for ( int b = mClient->GetBreakpointCount() - 1; b >= 0 ; b-- )
				{
					rvDebuggerBreakpoint* bp = mClient->GetBreakpoint(b);
					assert ( bp );

					if ( !idStr::Icmp ( bp->GetFilename(), mScripts[i]->GetFilename() ) )
					{
						if ( !mScripts[i]->IsLineCode ( bp->GetLineNumber ( ) ) )
						{
							mClient->RemoveBreakpoint ( bp->GetID ( ) );
						}
					}
				}
			}
		}
	}
	UpdateBreakpointList();

	return 1;
}

/*
================
rvDebuggerWindow::EnableWindows

Enables and disables all windows with a enable state dependent on the current
connected and paused state of the debugger.
================
*/
void rvDebuggerWindow::EnableWindows ( bool state )
{
	EnableWindow ( mWndCallstack, state );
	EnableWindow ( mWndThreads, state );
	EnableWindow ( mWndWatch, state );
}

/*
================
rvDebuggerWindow::AddWatch

Add a variable to the watch window.  If update is set to true then also query the
debugger client for the value
================
*/
void rvDebuggerWindow::AddWatch ( const char* varname, bool update )
{
	rvDebuggerWatch* watch;

	watch = new rvDebuggerWatch;
	watch->mVariable = varname;
	watch->mModified = false;
	watch->mValue = "???";
	mWatches.Append ( watch );

	// Add the variable to the watch control
	LVITEM item;
	item.mask = LVIF_PARAM;
	item.iItem = ListView_GetItemCount ( mWndWatch ) - 1;
	item.iSubItem = 0;
	item.lParam = (LPARAM)watch;
	ListView_InsertItem ( mWndWatch, &item );

	// If update is set then request the value from the debugger client
	if ( update )
	{
		mClient->InspectVariable ( varname, mCurrentStackDepth );
	}
}

/*
================
rvDebuggerWindow::InitRecentFiles

Finds the file menu and the location within it where the MRU should
be added.
================
*/
bool rvDebuggerWindow::InitRecentFiles ( void )
{
	int	i;
	int count;

	mRecentFileMenu = GetSubMenu ( GetMenu(mWnd), 0 );
	count			= GetMenuItemCount ( mRecentFileMenu );

	for ( i = 0; i < count; i ++ )
	{
		if ( GetMenuItemID ( mRecentFileMenu, i ) == ID_DBG_FILE_MRU )
		{
			mRecentFileInsertPos = i;
			DeleteMenu ( mRecentFileMenu, mRecentFileInsertPos, MF_BYPOSITION );
			return true;
		}
	}

	return false;
}

/*
================
rvDebuggerWindow::UpdateRecentFiles

Updates the mru in the menu
================
*/
void rvDebuggerWindow::UpdateRecentFiles ( void )
{
	int i;
	int j;

	// Make sure everything is initialized
	if ( !mRecentFileMenu )
	{
		InitRecentFiles ( );
	}

	// Delete all the old recent files from the menu's
	for ( i = 0; i < rvRegistryOptions::MAX_MRU_SIZE; i ++ )
	{
		DeleteMenu ( mRecentFileMenu, ID_DBG_FILE_MRU1 + i, MF_BYCOMMAND );
	}

	// Make sure there is a separator after the recent files
	if ( gDebuggerApp.GetOptions().GetRecentFileCount() )
	{
		MENUITEMINFO info;
		ZeroMemory ( &info, sizeof(info) );
		info.cbSize = sizeof(info);
		info.fMask = MIIM_FTYPE;
		GetMenuItemInfo ( mRecentFileMenu, mRecentFileInsertPos+1,TRUE, &info );
		if ( !(info.fType & MFT_SEPARATOR ) )
		{
			InsertMenu ( mRecentFileMenu, mRecentFileInsertPos, MF_BYPOSITION|MF_SEPARATOR|MF_ENABLED, 0, NULL );
		}
	}

	// Add the recent files to the menu now
	for ( j = 0, i = gDebuggerApp.GetOptions().GetRecentFileCount ( ) - 1; i >= 0; i --, j++ )
	{
		UINT id = ID_DBG_FILE_MRU1 + j;
		idStr str = va("&%d ", j+1);
		str.Append ( gDebuggerApp.GetOptions().GetRecentFile ( i ) );
		InsertMenu ( mRecentFileMenu, mRecentFileInsertPos+j+1, MF_BYPOSITION|MF_STRING|MF_ENABLED, id, str );
	}
}

/*
================
rvDebuggerWindow::GetSelectedText

Function to retrieve the text that is currently selected in the
script control
================
*/
int rvDebuggerWindow::GetSelectedText ( idStr& text )
{
	TEXTRANGE	range;
	int			start;
	int			end;
	char*		temp;

	text.Clear();

	if ( mScripts.Num ( ) )
	{
		SendMessage ( mWndScript, EM_GETSEL, (WPARAM)&start, (LPARAM)&end );
		if ( start == end )
		{
			end   = SendMessage ( mWndScript, EM_FINDWORDBREAK, WB_RIGHT, start );
			start = SendMessage ( mWndScript, EM_FINDWORDBREAK, WB_LEFT, start );
		}

		temp = new char[end-start+10];
		range.chrg.cpMin = start;
		range.chrg.cpMax = end;
		range.lpstrText  = temp;
		SendMessage ( mWndScript, EM_GETTEXTRANGE, 0, (LPARAM) &range );
		text = temp;
		delete[] temp;

		return start;
	}

	return -1;
}

/*
================
rvDebuggerWindow::FindNext

Finds the next match of the find text in the active script.  The next is
always relative to the current selection.  If the text parameter is NULL
then the last text used will be searched for.
================
*/
bool rvDebuggerWindow::FindNext ( const char* text )
{
	long	 start;
	FINDTEXT ft;

	if ( text )
	{
		mFind = text;
	}

	if ( !mFind.Length ( ) )
	{
		return false;
	}

	SendMessage ( mWndScript, EM_GETSEL, (WPARAM)&start, (LPARAM)0 );
	if ( start < 0 )
	{
		start = 0;
	}

	ft.chrg.cpMin = start + 1;
	ft.chrg.cpMax = -1;
	ft.lpstrText = mFind.c_str();
	start = SendMessage ( mWndScript, EM_FINDTEXT, FR_DOWN, (LPARAM)&ft );

	if ( start < 0 )
	{
		ft.chrg.cpMin = 0;
		ft.chrg.cpMax = -1;
		ft.lpstrText = mFind.c_str();
		start = SendMessage ( mWndScript, EM_FINDTEXT, FR_DOWN, (LPARAM)&ft );

		if ( start < 0 )
		{
			return false;
		}
	}

	SendMessage ( mWndScript, EM_SETSEL, start, start + (long)mFind.Length() );
	SendMessage ( mWndScript, EM_SCROLLCARET, 0, 0 );

	return true;
}

/*
================
rvDebuggerWindow::FindPrev

Finds the previous match of the find text in the active script.  The previous is
always relative to the current selection.  If the text parameter is NULL
then the last text used will be searched for.
================
*/
bool rvDebuggerWindow::FindPrev ( const char* text )
{
	long	 start;
	FINDTEXT ft;

	if ( text )
	{
		mFind = text;
	}

	if ( !mFind.Length ( ) )
	{
		return false;
	}

	SendMessage ( mWndScript, EM_GETSEL, (WPARAM)&start, (LPARAM)0 );
	if ( start < 0 )
	{
		start = 0;
	}

	ft.chrg.cpMin = start;
	ft.chrg.cpMax = -1;
	ft.lpstrText = mFind.c_str();
	start = SendMessage ( mWndScript, EM_FINDTEXT, 0, (LPARAM)&ft );

	if ( start < 0 )
	{
		GETTEXTLENGTHEX gtl;
		gtl.flags = GTL_DEFAULT;
		gtl.codepage = CP_ACP;
		ft.chrg.cpMin = SendMessage ( mWndScript, EM_GETTEXTLENGTHEX, (WPARAM)&gtl, 0 );
		ft.chrg.cpMax = 0;
		ft.lpstrText = mFind.c_str();
		start = SendMessage ( mWndScript, EM_FINDTEXT, 0, (LPARAM)&ft );

		if ( start < 0 )
		{
			return false;
		}
	}

	SendMessage ( mWndScript, EM_SETSEL, start, start + mFind.Length() );
	SendMessage ( mWndScript, EM_SCROLLCARET, 0, 0 );

	return true;
}

/*
================
rvDebuggerWindow::HandleDrawItem

Handled the WM_DRAWITEM message.  The watch window is custom drawn so a grid can be displayed.
================
*/
int rvDebuggerWindow::HandleDrawItem ( WPARAM wparam, LPARAM lparam )
{
	DRAWITEMSTRUCT*		dis;
	LVCOLUMN			col;
	int					index;
	idStr				widths;
	RECT				rect;
	rvDebuggerWatch*	watch;
	bool				selected;

	dis        = (DRAWITEMSTRUCT*) lparam;
	watch	   = (rvDebuggerWatch*)dis->itemData;

	col.mask   = LVCF_WIDTH;
	rect       = dis->rcItem;
	rect.left  = rect.left - 1;
	rect.right = rect.left;
	rect.bottom++;

	selected = ((dis->itemState & ODS_SELECTED) && GetFocus()==mWndWatch);

	// Set the colors based on the selected state and draw the item background
	if ( selected )
	{
		FillRect ( dis->hDC, &dis->rcItem, GetSysColorBrush ( COLOR_HIGHLIGHT ) );
	}
	else
	{
		FillRect ( dis->hDC, &dis->rcItem, GetSysColorBrush ( IsWindowEnabled ( mWndWatch ) ? COLOR_WINDOW : COLOR_3DFACE ) );
	}

	// Run through the columns and draw each with a frame around it and the text
	// vertically centered in it
	for ( index = 0; ListView_GetColumn ( mWndWatch, index, &col ); index ++ )
	{
		rect.right = rect.left + col.cx;
		FrameRect ( dis->hDC, &rect, GetSysColorBrush ( COLOR_3DFACE ) );

		// Draw info on the watch if available
		if ( watch  )
		{
			RECT textrect;
			textrect = rect;
			textrect.left += 5;

			switch ( index )
			{
				case 0:
					SetTextColor ( dis->hDC, GetSysColor ( selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT ) );
					DrawText ( dis->hDC, watch->mVariable, -1, &textrect, DT_LEFT|DT_VCENTER );
					break;

				case 1:
					if ( watch && watch->mModified && (IsWindowEnabled ( mWndWatch ) ) )
					{
						SetTextColor ( dis->hDC, RGB(255,50,50) );
					}
					else
					{
						SetTextColor ( dis->hDC, GetSysColor ( selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT ) );
					}
					DrawText ( dis->hDC, watch->mValue, -1, &textrect, DT_LEFT|DT_VCENTER );
					break;
			}
		}

		rect.left = rect.right - 1;
	}

	return 0;
}