/* =========================================================================== 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 . 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 "../../idlib/precompiled.h" #pragma hdrstop #include "../../sys/win32/rc/debugger_resource.h" #include "DebuggerApp.h" #include "../Common/OpenFileDialog.h" #include "DebuggerQuickWatchDlg.h" #include "DebuggerFindDlg.h" #define DEBUGGERWINDOWCLASS "QUAKE4_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 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 ( "Quake 4 Script Debugger v0.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*)GetWindowLong ( wnd, GWL_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; } } return CallWindowProc ( wndproc, wnd, msg, wparam, lparam ); } LRESULT CALLBACK rvDebuggerWindow::MarginWndProc ( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam ) { rvDebuggerWindow* window = (rvDebuggerWindow*) GetWindowLong ( wnd, GWL_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; int size = window->mMarginSize - 2; PAINTSTRUCT ps; RECT rect; GetClientRect ( wnd, &rect ); dc = BeginPaint ( wnd, &ps ); FillRect ( dc, &rect, GetSysColorBrush ( COLOR_3DFACE ) ); 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->mImageList, 2, dc, rect.left, pos.y, size, size, 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->mImageList, 3, dc, rect.left, pos.y, size, size, 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->mImageList, 1, dc, rect.left, pos.y, size, size, CLR_NONE, CLR_NONE, ILD_NORMAL ); } } } rect.right-=2; rect.left = rect.right + 1; HPEN pen = CreatePen ( PS_SOLID, 1, GetSysColor ( COLOR_3DSHADOW ) ); 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 = "Quake 4 Script Debugger - "; if ( mClient->IsConnected ( ) ) { if ( mClient->IsStopped ( ) ) { title += "[break]"; } else { title += "[run]"; } } else { title += "[disconnected]"; } if ( mScripts.Num ( ) ) { title += " - ["; title += idStr( mScripts[mActiveScript]->GetFilename() ).StripPath ( ); 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() ); } } /* ================ 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; } /* ================ 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 ); SendMessage ( mWndScript, EM_SETWORDBREAKPROC, 0, (LPARAM) ScriptWordBreakProc ); mOldScriptProc = (WNDPROC)GetWindowLong ( mWndScript, GWL_WNDPROC ); SetWindowLong ( mWndScript, GWL_USERDATA, (LONG)this ); SetWindowLong ( mWndScript, GWL_WNDPROC, (LONG)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 ) ); 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 ) ); mWndMargin = CreateWindow ( "STATIC", "", WS_VISIBLE|WS_CHILD, 0, 0, 0, 0, mWndScript, (HMENU)IDC_DBG_SPLITTER, mInstance, NULL ); SetWindowLong ( mWndMargin, GWL_USERDATA, (LONG)this ); SetWindowLong ( mWndMargin, GWL_WNDPROC, (LONG)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 ); 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 ); LVCOLUMN col; col.mask = LVCF_WIDTH|LVCF_TEXT; 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) ); ListView_SetImageList ( mWndThreads, mImageList, LVSIL_SMALL ); ListView_SetImageList ( mWndCallstack, mImageList, LVSIL_SMALL ); EnableWindows ( FALSE ); ListView_SetExtendedListViewStyle ( mWndCallstack, LVS_EX_FULLROWSELECT ); ListView_SetExtendedListViewStyle ( mWndThreads, 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 ); } 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_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", 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, (LONG)&num, (LONG)&dem ); if ( num != mZoomScaleNum || dem != mZoomScaleDem ) { mZoomScaleNum = num; mZoomScaleDem = dem; GetClientRect ( mWndScript, &t ); SendMessage ( mWnd, WM_SIZE, 0, MAKELPARAM(t.right-t.left,t.bottom-t.top) ); } else { InvalidateRect ( mWndMargin, NULL, TRUE ); } break; } 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; } } 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*) GetWindowLong ( wnd, GWL_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; SetWindowLong ( wnd, GWL_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: { RECT rect; window->mMarginSize = window->mZoomScaleDem ? ((long)(18.0f * (float)window->mZoomScaleNum / (float)window->mZoomScaleDem)):18; 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 -= 4 ; 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, 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 ); 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_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; SetWindowLong ( wnd, GWL_USERDATA, (LONG)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_TABS: if ( hdr->code == TCN_SELCHANGE ) { ShowWindow ( window->mWndOutput, SW_HIDE ); ShowWindow ( window->mWndConsole, SW_HIDE ); ShowWindow ( window->mWndCallstack, SW_HIDE ); ShowWindow ( window->mWndWatch, SW_HIDE ); ShowWindow ( window->mWndThreads, SW_HIDE ); switch ( TabCtrl_GetCurSel ( hdr->hwndFrom ) ) { case 0: ShowWindow ( window->mWndOutput, SW_SHOW ); break; case 1: ShowWindow ( window->mWndConsole, 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; } } 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?", "Quake 4 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 ( msg_t* msg ) { unsigned short command; command = (unsigned short)MSG_ReadShort ( msg ); switch ( command ) { case DBMSG_RESUMED: UpdateTitle ( ); UpdateToolbar ( ); break; case DBMSG_INSPECTVARIABLE: { char temp[1024]; char temp2[1024]; int i; MSG_ReadShort ( msg ); MSG_ReadString ( msg, temp, 1024 ); MSG_ReadString ( msg, 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: SendMessage ( mWndConsole, EM_SETSEL, -1, -1 ); SendMessage ( mWndConsole, EM_REPLACESEL, 0, (LPARAM)(const char*)(msg->data) + msg->readcount ); SendMessage ( mWndConsole, EM_SCROLLCARET, 0, 0 ); 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_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 ) { 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 ) ) { delete script; 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 ) { int 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, lineNumber - 1, 0 ); SendMessage ( mWndScript, EM_SETSEL, c, c + 1 ); SendMessage ( mWndScript, EM_SCROLLCARET, 0, 0 ); c = SendMessage ( mWndScript, EM_LINEINDEX, 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 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 ) { int 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 + 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 ) { int 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)>l, 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; }