dhewm3/neo/tools/debugger/DebuggerWindow.cpp

2600 lines
No EOL
69 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 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.
Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "tools/edit_gui_common.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 ID_DBG_FILE_MRU1 10000
/*
================
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.0\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.Empty ( );
}
// 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);
SendMessage(window->mWndScript, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(s18, 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 s22 = int(22 * 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 ) );
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());
}
}
/*
================
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) : 16;
int width = mZoomScaleDem ? (s16 * (float)mZoomScaleNum / (float)mZoomScaleDem) : 16;
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;
}
/*
================
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;
strcpy ( lf.lfFaceName, "Courier New" );
SendMessage ( mWndScript, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
SendMessage ( mWndScript, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, MAKELONG(18,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 );
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 );
LVCOLUMN col;
col.mask = LVCF_WIDTH|LVCF_TEXT;
col.cx = 20;
col.pszText = "";
ListView_InsertColumn ( mWndScriptList, 0, &col);
col.cx = 150;
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 = 150;
col.pszText = "Line";
ListView_InsertColumn ( mWndCallstack, 2, &col );
col.cx = 150;
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 );
EnableWindows ( FALSE );
EnableWindow ( mWndScriptList, true );
ListView_SetExtendedListViewStyle ( mWndCallstack, LVS_EX_FULLROWSELECT );
ListView_SetExtendedListViewStyle ( mWndThreads, LVS_EX_FULLROWSELECT );
ListView_SetExtendedListViewStyle ( mWndScriptList, 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 );
SendMessage(window->mWndScript, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(s18, 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_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 );
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;
}
}
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);
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 ( );
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 );
}
/*
================
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
// FIXME: warning C4838: conversion from 'int' to 'BYTE' requires a narrowing conversion
// most probably because TBBUTTON has 4 more bytes in bReserved for alignment on _WIN64
TBBUTTON tbb[] = { { 0, 0, TBSTATE_ENABLED, BTNS_SEP, 0, 0, -1 },
{ 8, ID_DBG_FILE_OPEN, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
{ 0, 0, TBSTATE_ENABLED, BTNS_SEP, 0, 0, -1 },
{ 0, ID_DBG_DEBUG_RUN, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
{ 1, ID_DBG_DEBUG_BREAK, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
{ 0, 0, TBSTATE_ENABLED, BTNS_SEP, 0, 0, -1 },
{ 4, ID_DBG_DEBUG_STEPINTO, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
{ 5, ID_DBG_DEBUG_STEPOVER, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
{ 6, ID_DBG_DEBUG_STEPOUT, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
{ 0, 0, TBSTATE_ENABLED, BTNS_SEP, 0, 0, -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 ( ) );
}
}
}
}
}
}
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.Empty ( );
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;
}