doom3-gpl
Doom 3 GPL source release
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
DebuggerWindow.cpp
Go to the documentation of this file.
1 /*
2 ===========================================================================
3 
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
8 
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
21 
22 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.
23 
24 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.
25 
26 ===========================================================================
27 */
28 
29 #include "../../idlib/precompiled.h"
30 #pragma hdrstop
31 
32 #include "../../sys/win32/rc/debugger_resource.h"
33 #include "DebuggerApp.h"
34 #include "../Common/OpenFileDialog.h"
35 #include "DebuggerQuickWatchDlg.h"
36 #include "DebuggerFindDlg.h"
37 
38 #define DEBUGGERWINDOWCLASS "QUAKE4_DEBUGGER_WINDOW"
39 #define ID_DBG_WINDOWMIN 18900
40 #define ID_DBG_WINDOWMAX 19900
41 
42 #define IDC_DBG_SCRIPT 31000
43 #define IDC_DBG_OUTPUT 31001
44 #define IDC_DBG_SPLITTER 31002
45 #define IDC_DBG_TABS 31003
46 #define IDC_DBG_BORDER 31004
47 #define IDC_DBG_CONSOLE 31005
48 #define IDC_DBG_CALLSTACK 31006
49 #define IDC_DBG_WATCH 31007
50 #define IDC_DBG_THREADS 31008
51 #define IDC_DBG_TOOLBAR 31009
52 
53 #define ID_DBG_FILE_MRU1 10000
54 
55 /*
56 ================
57 rvDebuggerWindow::rvDebuggerWindow
58 
59 Constructor
60 ================
61 */
63 {
64  mWnd = NULL;
65  mWndScript = NULL;
66  mInstance = NULL;
67  mZoomScaleNum = 0;
68  mZoomScaleDem = 0;
69  mWindowMenuPos = 0;
70  mActiveScript = 0;
71  mSplitterDrag = false;
72  mLastActiveScript = -1;
76  mClient = NULL;
77 }
78 
79 /*
80 ================
81 rvDebuggerWindow::~rvDebuggerWindow
82 
83 Destructor
84 ================
85 */
87 {
88  int i;
89 
90  if ( mWnd )
91  {
92  DestroyWindow ( mWnd );
93  }
94 
95  if ( mImageList )
96  {
97  ImageList_Destroy ( mImageList );
98  }
99 
100  for ( i = 0; i < mScripts.Num (); i ++ )
101  {
102  delete mScripts[i];
103  }
104 }
105 
106 /*
107 ================
108 rvDebuggerWindow::RegisterClass
109 
110 Registers the window class used by the debugger window. This is called when
111 the window is created.
112 ================
113 */
115 {
116  WNDCLASSEX wcex;
117 
118  wcex.cbSize = sizeof(WNDCLASSEX);
119 
120  wcex.style = CS_HREDRAW | CS_VREDRAW;
121  wcex.lpfnWndProc = (WNDPROC)WndProc;
122  wcex.cbClsExtra = 0;
123  wcex.cbWndExtra = 0;
124  wcex.hInstance = mInstance;
125  wcex.hIcon = NULL;
126  wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
127  wcex.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
128  wcex.lpszMenuName = MAKEINTRESOURCE(IDR_DBG_MAIN);
129  wcex.lpszClassName = DEBUGGERWINDOWCLASS;
130  wcex.hIconSm = NULL;
131 
132  return RegisterClassEx(&wcex) ? true : false;
133 }
134 
135 /*
136 ================
137 rvDebuggerWindow::Create
138 
139 Creates the debugger window
140 ================
141 */
142 bool rvDebuggerWindow::Create ( HINSTANCE instance )
143 {
144  mInstance = instance;
145 
146  if ( !RegisterClass ( ) )
147  {
148  return false;
149  }
150 
151  // Cache the client pointer for ease of use
153 
154  // Create the debugger window
155  mWnd = CreateWindow( DEBUGGERWINDOWCLASS, "",
156  WS_OVERLAPPEDWINDOW,
157  CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, mInstance, this);
158 
159  if ( !mWnd )
160  {
161  return false;
162  }
163 
164  // Determine where the window names will be added in the menus
165  mWindowMenu = GetSubMenu ( GetMenu ( mWnd ), 2 );
166  mWindowMenuPos = GetMenuItemCount ( mWindowMenu );
167 
168  UpdateTitle ( );
169 
170  Printf ( "Quake 4 Script Debugger v0.1\n\n" );
171 
172  ShowWindow ( mWnd, SW_SHOW );
173  UpdateWindow ( mWnd );
174 
175  return true;
176 }
177 
178 /*
179 ================
180 rvDebuggerWindow::ScriptWordBreakProc
181 
182 Determines where word breaks are in the script window. This is used for determining
183 the word that someone is over with their mouse cursor. Since the default windows one
184 doesnt understand the delimiters of the scripting language it had to be overridden.
185 ================
186 */
187 int CALLBACK rvDebuggerWindow::ScriptWordBreakProc (LPTSTR text, int current, int max, int action )
188 {
189  static TCHAR delimiters[]=TEXT("!@#$%^&*()-+=[]{}|\\;:'\"/,.<>? \t\r\n") ;
190 
191  switch ( action )
192  {
193  default:
194  break;
195 
196  case WB_ISDELIMITER:
197  return _tcschr ( delimiters, *(text + current * 2) ) ? TRUE : FALSE;
198 
199  case WB_MOVEWORDLEFT:
200  case WB_LEFT:
201 
202  current--;
203 
204  // Run as long as the current index is valid.
205  while ( current > 0 )
206  {
207  // If we hit a delimiter then return the index + 1 since
208  // it was the last character we were on that was valid
209  if ( _tcschr ( delimiters, *(text + current * 2) ) )
210  {
211  return current + 1;
212  }
213 
214  // Going backwards
215  current--;
216  }
217 
218  return current;
219 
220  case WB_MOVEWORDRIGHT:
221  case WB_RIGHT:
222 
223  // If we are already on a delimiter then just return the current index
224  if ( _tcschr ( delimiters, *(text + current * 2) ) )
225  {
226  return current;
227  }
228 
229  // Run until we hit the end of the control
230  while ( current < max )
231  {
232  // If we found a delimiter then return the index we are on
233  if ( _tcschr ( delimiters, *(text + current * 2) ) )
234  {
235  return current;
236  }
237 
238  current++;
239  }
240 
241  return current;
242  }
243 
244  return 0;
245  }
246 
247 LRESULT CALLBACK rvDebuggerWindow::ScriptWndProc ( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam )
248 {
249  static int lastStart = -1;
250  static int lastEnd = -1;
251  rvDebuggerWindow* window = (rvDebuggerWindow*)GetWindowLong ( wnd, GWL_USERDATA );
252  WNDPROC wndproc = window->mOldScriptProc;
253 
254  switch ( msg )
255  {
256  case WM_RBUTTONUP:
257  return SendMessage ( wnd, WM_LBUTTONUP, wparam, lparam );
258 
259  case WM_RBUTTONDOWN:
260  {
261  POINT point = { LOWORD(lparam), HIWORD(lparam) };
262  HMENU menu;
263  SendMessage ( wnd, WM_LBUTTONDOWN, wparam, lparam );
264  menu = LoadMenu ( window->mInstance, MAKEINTRESOURCE(IDR_DBG_SCRIPT_POPUP) );
265  ClientToScreen ( wnd, &point );
266  TrackPopupMenu ( GetSubMenu( menu, 0 ), TPM_RIGHTBUTTON|TPM_LEFTALIGN, point.x, point.y, 0, window->mWnd, NULL );
267  DestroyMenu ( menu );
268  return 0;
269  }
270 
271  case WM_MOUSEMOVE:
272  {
273  // Figure out the start and end of the mouse is over
274  POINTL pos = {LOWORD(lparam),HIWORD(lparam)};
275  int c = SendMessage ( wnd, EM_CHARFROMPOS, 0, (WPARAM)&pos );
276  int start = SendMessage ( wnd, EM_FINDWORDBREAK, WB_LEFT, c );
277  int end = SendMessage ( wnd, EM_FINDWORDBREAK, WB_RIGHT, c );
278 
279  // If the start and the end of the word we are over havent changed
280  // then the word hasnt changed so no need to re-setup the tool tip
281  if ( lastStart == start && lastEnd == end )
282  {
283  break;
284  }
285 
286  // Save the current start and end for the next mouse move
287  lastStart = start;
288  lastEnd = end;
289 
290  // Get rid of the last tool tip if there is one
291  if ( window->mTooltipVar.Length() )
292  {
293  TOOLINFO ti;
294  ti.cbSize = sizeof(TOOLINFO);
295  ti.hwnd = wnd;
296  ti.uId = 0;
297  SendMessage ( window->mWndToolTips, TTM_DELTOOL, 0, (LPARAM) (LPTOOLINFO) &ti );
298  window->mTooltipVar.Empty ( );
299  }
300 
301  // If there is no word then ignore it
302  if ( start == end )
303  {
304  break;
305  }
306 
307  TEXTRANGE range;
308  TOOLINFO ti;
309 
310  // grab the actual word from the edit control
311  char* temp = new char[end-start+10];
312  range.chrg.cpMin = start;
313  range.chrg.cpMax = end;
314  range.lpstrText = temp;
315  SendMessage ( wnd, EM_GETTEXTRANGE, 0, (LPARAM) &range );
316  window->mTooltipVar = temp;
317  delete[] temp;
318 
319  // Request the variable's value from the debugger server
320  window->mClient->InspectVariable ( window->mTooltipVar.c_str(), window->mCurrentStackDepth );
321 
322  // Prepare to add the new tooltip
323  ti.cbSize = sizeof(TOOLINFO);
324  ti.uFlags = TTF_SUBCLASS;
325  ti.hwnd = wnd;
326  ti.hinst = (HINSTANCE)GetModuleHandle(NULL);
327  ti.uId = 0;
328  ti.lpszText = (LPSTR)window->mTooltipVar.c_str();
329 
330  // Calculate the bounding box around the word we are over. We do this
331  // by getting to the top left from the start character and the right side
332  // from the end character. The bottom is the top from the start character
333  // plus the height of one line
334  SendMessage ( wnd, EM_POSFROMCHAR, (WPARAM)&pos, start );
335  ti.rect.left = pos.x;
336  ti.rect.top = pos.y;
337  SendMessage ( wnd, EM_POSFROMCHAR, (WPARAM)&pos, end );
338  ti.rect.right = pos.x;
339  SendMessage ( wnd, EM_POSFROMCHAR, (WPARAM)&pos, SendMessage ( wnd, EM_LINEINDEX, 0, 0 ) );
340  ti.rect.bottom = ti.rect.top - pos.y;
341  SendMessage ( wnd, EM_POSFROMCHAR, (WPARAM)&pos, SendMessage ( wnd, EM_LINEINDEX, 1, 0 ) );
342  ti.rect.bottom = ti.rect.bottom + pos.y;
343 
344  // Add the new tool tip to the control
345  SendMessage ( window->mWndToolTips, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti );
346  SendMessage ( window->mWndToolTips, TTM_UPDATE, 0, 0 );
347 
348  break;
349  }
350  }
351 
352  return CallWindowProc ( wndproc, wnd, msg, wparam, lparam );
353 }
354 
355 LRESULT CALLBACK rvDebuggerWindow::MarginWndProc ( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam )
356 {
357  rvDebuggerWindow* window = (rvDebuggerWindow*) GetWindowLong ( wnd, GWL_USERDATA );
358 
359  switch ( msg )
360  {
361  case WM_RBUTTONDOWN:
362  return SendMessage ( window->mWndScript, WM_RBUTTONDOWN, wparam, lparam );
363 
364  case WM_RBUTTONUP:
365  return SendMessage ( window->mWndScript, WM_RBUTTONUP, wparam, lparam );
366 
367  case WM_LBUTTONDBLCLK:
368  {
369  int result = SendMessage ( window->mWndScript, WM_LBUTTONDBLCLK, wparam, lparam );
370  window->ToggleBreakpoint ( );
371  return result;
372  }
373 
374  case WM_LBUTTONDOWN:
375  {
376  int result = SendMessage ( window->mWndScript, WM_LBUTTONDOWN, wparam, lparam );
377  window->ToggleBreakpoint ( );
378  return result;
379  }
380 
381  case WM_LBUTTONUP:
382  return SendMessage ( window->mWndScript, WM_LBUTTONUP, wparam, lparam );
383 
384  case WM_PAINT:
385  {
386  HDC dc;
387 
388  int size = window->mMarginSize - 2;
389 
390  PAINTSTRUCT ps;
391  RECT rect;
392  GetClientRect ( wnd, &rect );
393  dc = BeginPaint ( wnd, &ps );
394  FillRect ( dc, &rect, GetSysColorBrush ( COLOR_3DFACE ) );
395 
396  if ( window->mScripts.Num ( ) )
397  {
398  for ( int i = 0; i < window->mClient->GetBreakpointCount(); i ++ )
399  {
400  rvDebuggerBreakpoint* bp = window->mClient->GetBreakpoint ( i );
401  assert( bp );
402 
403  if ( !idStr::Icmp ( window->mScripts[window->mActiveScript]->GetFilename ( ), bp->GetFilename ( ) ) )
404  {
405  int c;
406  POINTL pos;
407 
408  c = SendMessage ( window->mWndScript, EM_LINEINDEX, bp->GetLineNumber ( ) - 1, 0 );
409  SendMessage ( window->mWndScript, EM_POSFROMCHAR, (WPARAM)&pos, c );
410  ImageList_DrawEx ( window->mImageList, 2, dc, rect.left, pos.y, size, size, CLR_NONE, CLR_NONE, ILD_NORMAL );
411  }
412  }
413 
414  if ( window->mClient->IsStopped ( ) )
415  {
416  if ( !idStr::Icmp ( window->mClient->GetBreakFilename(),
417  window->mScripts[window->mActiveScript]->GetFilename ( ) ) )
418  {
419  int c;
420  POINTL pos;
421 
422  c = SendMessage ( window->mWndScript, EM_LINEINDEX, window->mClient->GetBreakLineNumber() - 1, 0 );
423  SendMessage ( window->mWndScript, EM_POSFROMCHAR, (WPARAM)&pos, c );
424  ImageList_DrawEx ( window->mImageList, 3, dc, rect.left, pos.y, size, size, CLR_NONE, CLR_NONE, ILD_NORMAL );
425  }
426  }
427 
428  if ( window->mCurrentStackDepth != 0 )
429  {
430  if ( !window->mClient->GetCallstack()[window->mCurrentStackDepth]->mFilename.Icmp ( window->mScripts[window->mActiveScript]->GetFilename ( ) ) )
431  {
432  int c;
433  POINTL pos;
434 
435  c = SendMessage ( window->mWndScript, EM_LINEINDEX, window->mClient->GetCallstack()[window->mCurrentStackDepth]->mLineNumber - 1, 0 );
436  SendMessage ( window->mWndScript, EM_POSFROMCHAR, (WPARAM)&pos, c );
437  ImageList_DrawEx ( window->mImageList, 1, dc, rect.left, pos.y, size, size, CLR_NONE, CLR_NONE, ILD_NORMAL );
438  }
439  }
440  }
441 
442  rect.right-=2;
443  rect.left = rect.right + 1;
444  HPEN pen = CreatePen ( PS_SOLID, 1, GetSysColor ( COLOR_3DSHADOW ) );
445  HPEN old = (HPEN)SelectObject ( dc, pen );
446  MoveToEx ( dc, rect.right, rect.top, NULL );
447  LineTo ( dc, rect.right, rect.bottom );
448  SelectObject ( dc, old );
449  DeleteObject ( pen );
450  EndPaint ( wnd, &ps );
451  break;
452  }
453  }
454 
455  return DefWindowProc ( wnd, msg, wparam, lparam );
456 }
457 
458 /*
459 ================
460 rvDebuggerWindow::UpdateTitle
461 
462 Updates the window title of the script debugger to show a few states
463 ================
464 */
466 {
467  idStr title;
468 
469  title = "Quake 4 Script Debugger - ";
470 
471  if ( mClient->IsConnected ( ) )
472  {
473  if ( mClient->IsStopped ( ) )
474  {
475  title += "[break]";
476  }
477  else
478  {
479  title += "[run]";
480  }
481  }
482  else
483  {
484  title += "[disconnected]";
485  }
486 
487  if ( mScripts.Num ( ) )
488  {
489  title += " - [";
490  title += idStr( mScripts[mActiveScript]->GetFilename() ).StripPath ( );
491  title += "]";
492  }
493 
494  SetWindowText ( mWnd, title );
495 }
496 
497 /*
498 ================
499 rvDebuggerWindow::UpdateScript
500 
501 Updates the edit window to contain the current script
502 ================
503 */
505 {
506  UpdateTitle ( );
507 
508  // Dont reupdate if the given active script is the one being displayed.
510  {
511  return;
512  }
513 
515 
516  // Show and hide the script window depending on whether or not
517  // there are loaded scripts
518  if ( mScripts.Num ( ) < 1 )
519  {
520  ShowWindow ( mWndScript, SW_HIDE );
521  return;
522  }
523  else
524  {
525  ShowWindow ( mWndScript, SW_SHOW );
526  }
527 
528  // Update the script
529  SendMessage ( mWndScript, EM_SETSEL, 0, -1 );
530  SendMessage ( mWndScript, EM_REPLACESEL, 0, (LPARAM)"" );
531  SendMessage ( mWndScript, EM_SETSEL, 0, -1 );
532  SendMessage ( mWndScript, EM_REPLACESEL, 0, (LPARAM)mScripts[mActiveScript]->GetContents ( ) );
533 }
534 
535 /*
536 ================
537 rvDebuggerWindow::UpdateWindowMenu
538 
539 Updates the windows displayed in the window menu
540 ================
541 */
543 {
544  while ( GetMenuItemCount ( mWindowMenu ) > mWindowMenuPos )
545  {
546  DeleteMenu ( mWindowMenu, mWindowMenuPos, MF_BYPOSITION );
547  }
548 
549  if ( mScripts.Num() )
550  {
551  AppendMenu ( mWindowMenu, MF_SEPARATOR, 0, "" );
552  }
553 
554  int i;
555  for ( i = 0; i < mScripts.Num(); i ++ )
556  {
557  idStr name;
558  name = mScripts[i]->GetFilename ( );
559  name.StripPath ( );
560  name = idStr(va("&%d ", i + 1 )) + name;
561  AppendMenu ( mWindowMenu, MF_STRING, ID_DBG_WINDOWMIN + i, name );
562  }
563 }
564 
565 /*
566 ================
567 rvDebuggerWindow::UpdateCallstack
568 
569 Updates the contents of teh callastack
570 ================
571 */
573 {
574  LVITEM item;
575  ListView_DeleteAllItems ( mWndCallstack );
576  ZeroMemory ( &item, sizeof(item) );
577  item.mask = LVIF_TEXT|LVIF_IMAGE;
578 
579  for ( int i = 0; i < mClient->GetCallstack().Num(); i ++ )
580  {
582  item.iItem = ListView_GetItemCount ( mWndCallstack );
583  item.pszText = "";
584  item.iImage = (i == mCurrentStackDepth) ? 1 : 0;
585  ListView_InsertItem ( mWndCallstack, &item );
586 
587  ListView_SetItemText ( mWndCallstack, item.iItem, 1, (LPSTR)entry->mFunction.c_str() );
588  ListView_SetItemText ( mWndCallstack, item.iItem, 2, va("%d", entry->mLineNumber ) );
589  ListView_SetItemText ( mWndCallstack, item.iItem, 3, (LPSTR)entry->mFilename.c_str() );
590  }
591 }
592 
593 /*
594 ================
595 rvDebuggerWindow::UpdateWatch
596 
597 Updates the contents of the watch window
598 ================
599 */
601 {
602  int i;
603 
604  // Inspect all the variables we are watching
605  for ( i = 0; i < mWatches.Num(); i ++ )
606  {
607  mWatches[i]->mModified = false;
609  }
610 
611  InvalidateRect ( mWndWatch, NULL, FALSE );
612  UpdateWindow ( mWndWatch );
613 }
614 
615 /*
616 ================
617 rvDebuggerWindow::HandleInitMenu
618 
619 Handles the initialization of the main menu
620 ================
621 */
622 int rvDebuggerWindow::HandleInitMenu ( WPARAM wParam, LPARAM lParam )
623 {
624  int cMenuItems = GetMenuItemCount((HMENU)wParam);
625  int nPos;
626  int id;
627  UINT flags;
628  HMENU hmenu;
629 
630  hmenu = (HMENU) wParam;
631 
632  // Run through all the menu items in the menu and see if any of them need
633  // modification in any way
634  for (nPos = 0; nPos < cMenuItems; nPos++)
635  {
636  id = GetMenuItemID(hmenu, nPos);
637  flags = 0;
638 
639  // Handle popup menus too
640  if ( id < 0 )
641  {
642  HMENU sub = GetSubMenu ( hmenu, nPos );
643  if ( sub )
644  {
645  HandleInitMenu ( (WPARAM) sub, 0 );
646  continue;
647  }
648  }
649 
650  // Handle the dynamic menu items specially
651  if ( id >= ID_DBG_WINDOWMIN && id <= ID_DBG_WINDOWMAX )
652  {
653  if ( id - ID_DBG_WINDOWMIN == mActiveScript )
654  {
655  CheckMenuItem ( hmenu, nPos, MF_BYPOSITION|MF_CHECKED );
656  }
657  else
658  {
659  CheckMenuItem ( hmenu, nPos, MF_BYPOSITION|MF_UNCHECKED );
660  }
661  continue;
662  }
663 
664  // Menu items that are completely unrelated to the workspace
665  switch ( id )
666  {
667  case ID_DBG_DEBUG_RUN:
668  {
669  MENUITEMINFO info;
670  idStr run;
671 
672  info.cbSize = sizeof(info);
673  info.fMask = MIIM_TYPE|MIIM_STATE;
674  info.fType = MFT_STRING;
675 
676  if ( !mClient->IsConnected() )
677  {
678  run = "Run";
679  info.fState = MFS_ENABLED;
680  }
681  else
682  {
683  run = "Continue";
684  info.fState = mClient->IsStopped()?MFS_ENABLED:MFS_GRAYED;
685  }
686 
687  info.dwTypeData = (LPSTR)run.c_str();
688  info.cch = run.Length ( );
689 
690  SendMessage ( mWndToolbar, TB_ENABLEBUTTON, id, MAKELONG(((info.fState==MFS_ENABLED) ? TRUE : FALSE), 0) );
691 
692  SetMenuItemInfo ( hmenu, id, FALSE, &info );
693 
694  break;
695  }
696 
697  case ID_DBG_DEBUG_BREAK:
698  if ( !mClient->IsConnected() || mClient->IsStopped() )
699  {
700  EnableMenuItem ( hmenu, nPos, MF_GRAYED|MF_BYPOSITION);
701  SendMessage ( mWndToolbar, TB_ENABLEBUTTON, id, MAKELONG(FALSE,0) );
702  }
703  else
704  {
705  EnableMenuItem ( hmenu, nPos, MF_ENABLED|MF_BYPOSITION);
706  SendMessage ( mWndToolbar, TB_ENABLEBUTTON, id, MAKELONG(TRUE,0) );
707  }
708  break;
709 
715 // case ID_DBG_DEBUG_QUICKWATCH:
716  if ( !mClient->IsConnected() || !mClient->IsStopped() )
717  {
718  EnableMenuItem ( hmenu, nPos, MF_GRAYED|MF_BYPOSITION );
719  SendMessage ( mWndToolbar, TB_ENABLEBUTTON, id, MAKELONG(FALSE,0) );
720  }
721  else
722  {
723  EnableMenuItem ( hmenu, nPos, MF_ENABLED|MF_BYPOSITION );
724  SendMessage ( mWndToolbar, TB_ENABLEBUTTON, id, MAKELONG(TRUE,0) );
725  }
726  break;
727 
729  case ID_DBG_FILE_CLOSE:
731  case ID_DBG_EDIT_FIND:
732  EnableMenuItem ( hmenu, nPos, (mScripts.Num()?MF_ENABLED:MF_GRAYED)|MF_BYPOSITION);
733  break;
734  }
735  }
736 
737  return 0;
738 }
739 
740 /*
741 ================
742 rvDebuggerWindow::HandleCreate
743 
744 Handles the WM_CREATE command
745 ================
746 */
747 int rvDebuggerWindow::HandleCreate ( WPARAM wparam, LPARAM lparam )
748 {
749  UINT tabsize = 16;
750  TEXTMETRIC tm;
751  HDC dc;
752  LOGFONT lf;
753  int i;
754 
756 
757  // Create the main toolbar
758  CreateToolbar ( );
759 
760  // Create the script window
761  LoadLibrary ( "Riched20.dll" );
762  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 );
763  SendMessage ( mWndScript, EM_SETEVENTMASK, 0, ENM_SCROLL|ENM_CHANGE );
764  SendMessage ( mWndScript, EM_SETWORDBREAKPROC, 0, (LPARAM) ScriptWordBreakProc );
765  mOldScriptProc = (WNDPROC)GetWindowLong ( mWndScript, GWL_WNDPROC );
766  SetWindowLong ( mWndScript, GWL_USERDATA, (LONG)this );
767  SetWindowLong ( mWndScript, GWL_WNDPROC, (LONG)ScriptWndProc );
768 
769  SendMessage ( mWndScript, EM_SETTABSTOPS, 1, (LPARAM)&tabsize );
770 
771  dc = GetDC ( mWndScript );
772  GetTextMetrics ( dc, &tm );
773  ZeroMemory ( &lf, sizeof(lf) );
774  lf.lfHeight = tm.tmHeight;
775  strcpy ( lf.lfFaceName, "Courier New" );
776 
777  SendMessage ( mWndScript, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
778  SendMessage ( mWndScript, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, MAKELONG(18,10) );
779  SendMessage ( mWndScript, EM_SETBKGNDCOLOR, 0, GetSysColor ( COLOR_3DFACE ) );
780 
781  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 );
782  SendMessage ( mWndOutput, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
783  SendMessage ( mWndOutput, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, MAKELONG(18,10) );
784  SendMessage ( mWndOutput, EM_SETBKGNDCOLOR, 0, GetSysColor ( COLOR_3DFACE ) );
785 
786  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 );
787  SendMessage ( mWndConsole, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
788  SendMessage ( mWndConsole, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, MAKELONG(18,10) );
789  SendMessage ( mWndConsole, EM_SETBKGNDCOLOR, 0, GetSysColor ( COLOR_3DFACE ) );
790 
791  mWndMargin = CreateWindow ( "STATIC", "", WS_VISIBLE|WS_CHILD, 0, 0, 0, 0, mWndScript, (HMENU)IDC_DBG_SPLITTER, mInstance, NULL );
792  SetWindowLong ( mWndMargin, GWL_USERDATA, (LONG)this );
793  SetWindowLong ( mWndMargin, GWL_WNDPROC, (LONG)MarginWndProc );
794 
795  mWndBorder = CreateWindow ( "STATIC", "", WS_VISIBLE|WS_CHILD|SS_GRAYFRAME, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_BORDER, mInstance, NULL );
796 
797  GetClientRect ( mWnd, &mSplitterRect );
798  mSplitterRect.top = (mSplitterRect.bottom-mSplitterRect.top) / 2;
799  mSplitterRect.bottom = mSplitterRect.top + 4;
800 
801  mWndTabs = CreateWindow ( WC_TABCONTROL, "", TCS_BOTTOM|WS_CHILD|WS_VISIBLE|TCS_FOCUSNEVER, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_TABS, mInstance, NULL );
802  lf.lfHeight = -MulDiv(8, GetDeviceCaps(dc, LOGPIXELSY), 72);
803  strcpy ( lf.lfFaceName, "Arial" );
804  SendMessage ( mWndTabs, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
805  ReleaseDC ( mWndScript, dc );
806 
807  TCITEM item;
808  item.mask = TCIF_TEXT;
809  item.pszText = "Output";
810  TabCtrl_InsertItem ( mWndTabs, 0, &item );
811  item.pszText = "Console";
812  TabCtrl_InsertItem ( mWndTabs, 1, &item );
813  item.pszText = "Call Stack";
814  TabCtrl_InsertItem ( mWndTabs, 2, &item );
815  item.pszText = "Watch";
816  TabCtrl_InsertItem ( mWndTabs, 3, &item );
817  item.pszText = "Threads";
818  TabCtrl_InsertItem ( mWndTabs, 4, &item );
819 
820  mWndCallstack = CreateWindow ( WC_LISTVIEW, "", LVS_REPORT|WS_CHILD|LVS_SHAREIMAGELISTS, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_CALLSTACK, mInstance, NULL );
821  mWndWatch = CreateWindow ( WC_LISTVIEW, "", LVS_REPORT|WS_CHILD|LVS_EDITLABELS|LVS_OWNERDRAWFIXED, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_WATCH, mInstance, NULL );
822  mWndThreads = CreateWindow ( WC_LISTVIEW, "", LVS_REPORT|WS_CHILD|LVS_SHAREIMAGELISTS, 0, 0, 0, 0, mWnd, (HMENU)IDC_DBG_THREADS, mInstance, NULL );
823 
824  LVCOLUMN col;
825  col.mask = LVCF_WIDTH|LVCF_TEXT;
826  col.cx = 20;
827  col.pszText = "";
828  ListView_InsertColumn ( mWndCallstack, 0, &col );
829  col.cx = 150;
830  col.pszText = "Function";
831  ListView_InsertColumn ( mWndCallstack, 1, &col );
832  col.cx = 150;
833  col.pszText = "Line";
834  ListView_InsertColumn ( mWndCallstack, 2, &col );
835  col.cx = 150;
836  col.pszText = "Filename";
837  ListView_InsertColumn ( mWndCallstack, 3, &col );
838 
839  col.cx = 20;
840  col.pszText = "";
841  ListView_InsertColumn ( mWndThreads, 0, &col );
842  col.cx = 25;
843  col.pszText = "ID";
844  ListView_InsertColumn ( mWndThreads, 1, &col );
845  col.cx = 150;
846  col.pszText = "Name";
847  ListView_InsertColumn ( mWndThreads, 2, &col );
848  col.cx = 100;
849  col.pszText = "State";
850  ListView_InsertColumn ( mWndThreads, 3, &col );
851 
852  col.cx = 150;
853  col.pszText = "Name";
854  ListView_InsertColumn ( mWndWatch, 0, &col );
855  col.cx = 200;
856  col.pszText = "Value";
857  ListView_InsertColumn ( mWndWatch, 1, &col );
858 
859  // Create the image list that is used by the threads window, callstack window, and
860  // margin window
861  mImageList = ImageList_Create ( 16, 16, ILC_COLOR|ILC_MASK, 0, 2 );
862  ImageList_AddIcon ( mImageList, (HICON)LoadImage ( mInstance, MAKEINTRESOURCE(IDI_DBG_EMPTY), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE|LR_DEFAULTCOLOR) );
863  ImageList_AddIcon ( mImageList, (HICON)LoadImage ( mInstance, MAKEINTRESOURCE(IDI_DBG_CURRENT), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE|LR_DEFAULTCOLOR) );
864  ImageList_AddIcon ( mImageList, (HICON)LoadImage ( mInstance, MAKEINTRESOURCE(IDI_DBG_BREAKPOINT), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE|LR_DEFAULTCOLOR) );
865  ImageList_AddIcon ( mImageList, (HICON)LoadImage ( mInstance, MAKEINTRESOURCE(IDI_DBG_CURRENTLINE), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE|LR_DEFAULTCOLOR) );
866  ListView_SetImageList ( mWndThreads, mImageList, LVSIL_SMALL );
867  ListView_SetImageList ( mWndCallstack, mImageList, LVSIL_SMALL );
868 
869  EnableWindows ( FALSE );
870 
871  ListView_SetExtendedListViewStyle ( mWndCallstack, LVS_EX_FULLROWSELECT );
872  ListView_SetExtendedListViewStyle ( mWndThreads, LVS_EX_FULLROWSELECT );
873 
877 
878  mWndToolTips = CreateWindowEx ( WS_EX_TOPMOST,
879  TOOLTIPS_CLASS,
880  NULL,
881  WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
882  CW_USEDEFAULT,
883  CW_USEDEFAULT,
884  CW_USEDEFAULT,
885  CW_USEDEFAULT,
886  mWnd,
887  NULL,
888  mInstance,
889  NULL
890  );
891 
892  SendMessage ( mWndToolTips, TTM_SETDELAYTIME, TTDT_INITIAL, MAKELONG(400,0) );
893  SendMessage ( mWndToolTips, TTM_SETDELAYTIME, TTDT_RESHOW, MAKELONG(400,0) );
894 
895  SetWindowPos( mWndToolTips, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
896 
897  LVITEM lvItem;
898  lvItem.iItem = 0;
899  lvItem.iSubItem = 0;
900  lvItem.mask = LVIF_TEXT;
901  lvItem.pszText = "";
902  ListView_InsertItem ( mWndWatch, &lvItem );
903  ListView_SetExtendedListViewStyle ( mWndWatch, LVS_EX_FULLROWSELECT );
904 
905  // Recent files
906  InitRecentFiles ( );
907  UpdateRecentFiles ( );
908 
909  HandleInitMenu ( (WPARAM)GetMenu ( mWnd ), 0 );
910 
911  // Read in the watches
912  for ( i = 0; ; i ++ )
913  {
914  const char* s = gDebuggerApp.GetOptions().GetString ( va("watch%d", i ) );
915  if ( !s || !s[0] )
916  {
917  break;
918  }
919 
920  AddWatch ( s );
921  }
922 
923  return 0;
924 }
925 
926 /*
927 ================
928 rvDebuggerWindow::HandleCommand
929 
930 Handles the WM_COMMAND message for this window
931 ================
932 */
933 int rvDebuggerWindow::HandleCommand ( WPARAM wparam, LPARAM lparam )
934 {
935  int event = HIWORD(wparam);
936  int id = LOWORD(wparam);
937 
938  // The window menu list needs to be handled specially
939  if ( id >= ID_DBG_WINDOWMIN && id <= ID_DBG_WINDOWMAX )
940  {
942  UpdateScript ( );
943  return 0;
944  }
945 
946  // The recent file list needs to be handled specially
947  if ( LOWORD(wparam) >= ID_DBG_FILE_MRU1 && LOWORD(wparam) < ID_DBG_FILE_MRU1 + rvRegistryOptions::MAX_MRU_SIZE )
948  {
949  idStr filename;
951  if ( !OpenScript ( filename ) )
952  {
953  MessageBox ( mWnd, va("Failed to open script '%s'", filename.c_str() ), "Quake 4 Script Debugger", MB_OK );
954  }
955  return 0;
956  }
957 
958  switch ( id )
959  {
961  {
962  idStr text;
963  GetSelectedText ( text );
964  FindNext ( text );
965  break;
966  }
967 
969  {
970  idStr text;
971  GetSelectedText ( text );
972  FindPrev ( text );
973  break;
974  }
975 
977  FindNext ( );
978  break;
979 
981  FindPrev ( );
982  break;
983 
985  {
986  idStr text;
987 
988  GetSelectedText ( text );
990  dlg.DoModal ( this, mCurrentStackDepth, text );
991  break;
992  }
993 
994  case ID_DBG_HELP_ABOUT:
995  DialogBox ( mInstance, MAKEINTRESOURCE(IDD_DBG_ABOUT), mWnd, AboutDlgProc );
996  break;
997 
998  case ID_DBG_DEBUG_BREAK:
999  mClient->Break ( );
1000  break;
1001 
1002  case ID_DBG_DEBUG_STEPOVER:
1003  EnableWindows ( FALSE );
1004  mClient->StepOver ( );
1005  break;
1006 
1007  case ID_DBG_DEBUG_STEPINTO:
1008  EnableWindows ( FALSE );
1009  mClient->StepInto ( );
1010  break;
1011 
1012  case ID_DBG_DEBUG_RUN:
1013  // Run the game if its not running
1014  if ( !mClient->IsConnected ( ) )
1015  {
1016  char exeFile[MAX_PATH];
1017  char curDir[MAX_PATH];
1018 
1019  STARTUPINFO startup;
1020  PROCESS_INFORMATION process;
1021 
1022  ZeroMemory ( &startup, sizeof(startup) );
1023  startup.cb = sizeof(startup);
1024 
1025  GetCurrentDirectory ( MAX_PATH, curDir );
1026 
1027  GetModuleFileName ( NULL, exeFile, MAX_PATH );
1028  const char* s = va("%s +set fs_game %s +set fs_cdpath %s", exeFile, cvarSystem->GetCVarString( "fs_game" ), cvarSystem->GetCVarString( "fs_cdpath" ) );
1029  CreateProcess ( NULL, (LPSTR)s,
1030  NULL, NULL, FALSE, 0, NULL, curDir, &startup, &process );
1031 
1032  CloseHandle ( process.hThread );
1033  CloseHandle ( process.hProcess );
1034  }
1035  else if ( mClient->IsStopped() )
1036  {
1037  EnableWindows ( FALSE );
1038  mClient->Resume ( );
1039  UpdateToolbar ( );
1040  UpdateTitle ( );
1041  InvalidateRect ( mWnd, NULL, FALSE );
1042  }
1043 
1044  break;
1045 
1046  case IDC_DBG_SCRIPT:
1047  {
1048  RECT t;
1049  LONG num;
1050  LONG dem;
1051 
1052  SendMessage ( mWndScript, EM_GETZOOM, (LONG)&num, (LONG)&dem );
1053  if ( num != mZoomScaleNum || dem != mZoomScaleDem )
1054  {
1055  mZoomScaleNum = num;
1056  mZoomScaleDem = dem;
1057  GetClientRect ( mWndScript, &t );
1058  SendMessage ( mWnd, WM_SIZE, 0, MAKELPARAM(t.right-t.left,t.bottom-t.top) );
1059  }
1060  else
1061  {
1062  InvalidateRect ( mWndMargin, NULL, TRUE );
1063  }
1064  break;
1065  }
1066 
1068  ToggleBreakpoint ( );
1069  break;
1070 
1071  case ID_DBG_FILE_EXIT:
1072  PostMessage ( mWnd, WM_CLOSE, 0, 0 );
1073  break;
1074 
1075  case ID_DBG_FILE_CLOSE:
1076  if ( mScripts.Num() )
1077  {
1078  delete mScripts[mActiveScript];
1080  if ( mActiveScript >= mScripts.Num ( ) )
1081  {
1082  mActiveScript --;
1083  }
1084  UpdateWindowMenu ( );
1085  UpdateScript ( );
1086  }
1087  break;
1088 
1089  case ID_DBG_FILE_NEXT:
1090  if ( mScripts.Num ( ) > 0 )
1091  {
1092  mActiveScript++;
1093  if ( mActiveScript >= mScripts.Num ( ) )
1094  {
1095  mActiveScript = 0;
1096  }
1097  UpdateWindowMenu ( );
1098  UpdateScript ( );
1099  }
1100  break;
1101 
1102  case ID_DBG_FILE_OPEN:
1103  {
1104  rvOpenFileDialog dlg;
1105  dlg.SetTitle ( "Open Script" );
1106  dlg.SetFilter ( "*.script; *.gui; *.state" );
1107  dlg.SetFlags ( OFD_MUSTEXIST );
1108  if ( dlg.DoModal ( mWnd ) )
1109  {
1110  if ( !OpenScript ( dlg.GetFilename ( ) ) )
1111  {
1112  MessageBox ( mWnd, va("Failed to open script '%s'",dlg.GetFilename ( )), "Quake 4 Script Debugger", MB_OK );
1113  }
1114  }
1115  break;
1116  }
1117 
1118  case ID_DBG_EDIT_FIND:
1119  {
1120  rvDebuggerFindDlg dlg;
1121  if ( dlg.DoModal ( this ) )
1122  {
1123  FindNext ( dlg.GetFindText ( ) );
1124  }
1125  break;
1126  }
1127 
1129  {
1130  for ( int i = 0; i < mScripts.Num(); i ++ )
1131  {
1132  delete mScripts[i];
1133  }
1134 
1135  mScripts.Clear ( );
1136  mActiveScript = -1;
1137 
1138  UpdateWindowMenu ( );
1139  UpdateScript ( );
1140  break;
1141  }
1142  }
1143 
1144  return 0;
1145 }
1146 
1147 /*
1148 ================
1149 rvDebuggerWindow::WndProc
1150 
1151 Window procedure for the deubgger window
1152 ================
1153 */
1154 LRESULT CALLBACK rvDebuggerWindow::WndProc ( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam )
1155 {
1156  rvDebuggerWindow* window = (rvDebuggerWindow*) GetWindowLong ( wnd, GWL_USERDATA );
1157 
1158  switch ( msg )
1159  {
1160  case WM_INITMENUPOPUP:
1161  window->HandleInitMenu ( wparam, lparam );
1162  break;
1163 
1164  case WM_DESTROY:
1165  {
1166  gDebuggerApp.GetOptions().SetColumnWidths ( "cw_callstack", window->mWndCallstack );
1167  gDebuggerApp.GetOptions().SetColumnWidths ( "cw_threads", window->mWndThreads );
1168  gDebuggerApp.GetOptions().SetColumnWidths ( "cw_watch", window->mWndWatch );
1169  gDebuggerApp.GetOptions().SetWindowPlacement ( "wp_main", wnd );
1170 
1171  int i;
1172  for ( i = 0; i < window->mWatches.Num ( ); i ++ )
1173  {
1174  gDebuggerApp.GetOptions().SetString ( va("watch%d", i ), window->mWatches[i]->mVariable );
1175  }
1176  gDebuggerApp.GetOptions().SetString ( va("watch%d", i ), "" );
1177 
1178  window->mWnd = NULL;
1179  SetWindowLong ( wnd, GWL_USERDATA, 0 );
1180  break;
1181  }
1182 
1183  case WM_ERASEBKGND:
1184  return 0;
1185 
1186  case WM_COMMAND:
1187  window->HandleCommand ( wparam, lparam );
1188  break;
1189 
1190  case WM_SETCURSOR:
1191  {
1192  POINT point;
1193  GetCursorPos ( &point );
1194  ScreenToClient ( wnd, &point );
1195  if ( PtInRect ( &window->mSplitterRect, point ) )
1196  {
1197  SetCursor ( LoadCursor ( NULL, IDC_SIZENS ) );
1198  return true;
1199  }
1200  break;
1201  }
1202 
1203  case WM_SIZE:
1204  {
1205  RECT rect;
1206  window->mMarginSize = window->mZoomScaleDem ? ((long)(18.0f * (float)window->mZoomScaleNum / (float)window->mZoomScaleDem)):18;
1207  window->mSplitterRect.left = 0;
1208  window->mSplitterRect.right = LOWORD(lparam);
1209 
1210  SendMessage ( window->mWndToolbar, TB_AUTOSIZE, 0, 0 );
1211  GetWindowRect ( window->mWndToolbar, &rect );
1212  MoveWindow ( window->mWndScript, 0, rect.bottom-rect.top, LOWORD(lparam), window->mSplitterRect.top-(rect.bottom-rect.top), TRUE );
1213  MoveWindow ( window->mWndMargin, 0, 0, window->mMarginSize, window->mSplitterRect.top-(rect.bottom-rect.top), TRUE );
1214 
1215  SetRect ( &rect, 0, window->mSplitterRect.bottom, LOWORD(lparam), HIWORD(lparam) );
1216  MoveWindow ( window->mWndTabs, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
1217  SendMessage ( window->mWndTabs, TCM_ADJUSTRECT, FALSE, (LPARAM)&rect );
1218  rect.bottom -= 4 ;
1219  MoveWindow ( window->mWndBorder, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
1220  InflateRect ( &rect, -1, -1 );
1221  MoveWindow ( window->mWndOutput, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
1222  MoveWindow ( window->mWndConsole, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
1223  MoveWindow ( window->mWndCallstack, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
1224  MoveWindow ( window->mWndWatch, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
1225  MoveWindow ( window->mWndThreads, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE );
1226  break;
1227  }
1228 
1229  case WM_PAINT:
1230  {
1231  PAINTSTRUCT ps;
1232  HDC dc = BeginPaint ( wnd, &ps );
1233  FillRect ( dc, &window->mSplitterRect, GetSysColorBrush ( COLOR_3DFACE ) );
1234 
1235  if ( !window->mScripts.Num() )
1236  {
1237  RECT rect;
1238  GetClientRect ( wnd, &rect );
1239  rect.bottom = window->mSplitterRect.top;
1240  FillRect ( dc, &rect, GetSysColorBrush ( COLOR_APPWORKSPACE ) );
1241  }
1242 
1243  EndPaint ( wnd, &ps );
1244  break;
1245  }
1246 
1247  case WM_LBUTTONDOWN:
1248  {
1249  POINT pt = { LOWORD(lparam),HIWORD(lparam) };
1250  if ( PtInRect ( &window->mSplitterRect, pt ) )
1251  {
1252  window->mSplitterDrag = true;
1253  SetCapture ( wnd );
1254 
1255  HDC dc = GetDC ( wnd );
1256  DrawFocusRect ( dc, &window->mSplitterRect );
1257  ReleaseDC ( wnd, dc );
1258  }
1259  break;
1260  }
1261 
1262  case WM_LBUTTONUP:
1263  if ( window->mSplitterDrag )
1264  {
1265  HDC dc = GetDC ( wnd );
1266  DrawFocusRect ( dc, &window->mSplitterRect );
1267  ReleaseDC ( wnd, dc );
1268 
1269  window->mSplitterDrag = false;
1270 
1271  RECT client;
1272  GetClientRect ( wnd, &client );
1273  SendMessage ( wnd, WM_SIZE, 0, MAKELPARAM(client.right-client.left,client.bottom-client.top) );
1274 
1275  InvalidateRect ( wnd, NULL, TRUE );
1276 
1277  ReleaseCapture ( );
1278  }
1279  break;
1280 
1281  case WM_MOUSEMOVE:
1282  {
1283  if ( window->mSplitterDrag )
1284  {
1285  HDC dc = GetDC ( wnd );
1286  DrawFocusRect ( dc, &window->mSplitterRect );
1287  ReleaseDC ( wnd, dc );
1288 
1289  if ( GetCapture ( ) != wnd )
1290  {
1291  break;
1292  }
1293 
1294  RECT client;
1295  GetClientRect ( wnd, &client );
1296 
1297  window->mSplitterRect.top = HIWORD(lparam);
1298  if ( window->mSplitterRect.top < client.top )
1299  {
1300  window->mSplitterRect.top = client.top;
1301  }
1302  else if ( window->mSplitterRect.top + 4 > client.bottom )
1303  {
1304  window->mSplitterRect.top = client.bottom - 4;
1305  }
1306  window->mSplitterRect.bottom = window->mSplitterRect.top + 4;
1307 
1308  dc = GetDC ( wnd );
1309  DrawFocusRect ( dc, &window->mSplitterRect );
1310  ReleaseDC ( wnd, dc );
1311  }
1312  break;
1313  }
1314 
1315  case WM_DRAWITEM:
1316  window->HandleDrawItem ( wparam, lparam );
1317  break;
1318 
1319  case WM_CREATE:
1320  {
1321  CREATESTRUCT* cs = (CREATESTRUCT*) lparam;
1322  window = (rvDebuggerWindow*) cs->lpCreateParams;
1323  SetWindowLong ( wnd, GWL_USERDATA, (LONG)cs->lpCreateParams );
1324 
1325  window->mWnd = wnd;
1326  window->HandleCreate ( wparam, lparam );
1327 
1328  break;
1329  }
1330 
1331  case WM_ACTIVATE:
1332  window->HandleActivate ( wparam, lparam );
1333  break;
1334 
1335  case WM_NOTIFY:
1336  {
1337  NMHDR* hdr;
1338  hdr = (NMHDR*)lparam;
1339 
1340  // Tool tips
1341  if ( hdr->code == TTN_GETDISPINFO )
1342  {
1343  window->HandleTooltipGetDispInfo ( wparam, lparam );
1344  break;
1345  }
1346 
1347  switch ( hdr->idFrom )
1348  {
1349  case IDC_DBG_WATCH:
1350  switch ( hdr->code )
1351  {
1352  case LVN_KEYDOWN:
1353  {
1354  NMLVKEYDOWN* key = (NMLVKEYDOWN*) hdr;
1355  switch ( key->wVKey )
1356  {
1357  case VK_DELETE:
1358  {
1359  int sel = ListView_GetNextItem ( hdr->hwndFrom, -1, LVNI_SELECTED );
1360  if ( sel != -1 && sel != ListView_GetItemCount ( hdr->hwndFrom ) - 1 )
1361  {
1362  LVITEM item;
1363  item.iItem = sel;
1364  item.mask = LVIF_PARAM;
1365  ListView_GetItem ( hdr->hwndFrom, &item );
1366  ListView_DeleteItem ( hdr->hwndFrom, sel );
1367 
1368  window->mWatches.Remove ( (rvDebuggerWatch*)item.lParam );
1369  delete (rvDebuggerWatch*)item.lParam;
1370 
1371  ListView_SetItemState ( hdr->hwndFrom,
1372  sel,
1373  LVIS_SELECTED, LVIS_SELECTED );
1374 
1375  if ( ListView_GetNextItem ( hdr->hwndFrom, -1, LVNI_SELECTED ) == -1 )
1376  {
1377  ListView_SetItemState ( hdr->hwndFrom,
1378  ListView_GetItemCount ( hdr->hwndFrom ) - 1,
1379  LVIS_SELECTED, LVIS_SELECTED );
1380  }
1381  }
1382  break;
1383  }
1384 
1385  case VK_RETURN:
1386  {
1387  int sel = ListView_GetNextItem ( hdr->hwndFrom, -1, LVNI_SELECTED );
1388  if ( sel != -1 )
1389  {
1390  ListView_EditLabel ( hdr->hwndFrom, sel );
1391  }
1392  break;
1393  }
1394  }
1395  break;
1396  }
1397 
1398  case LVN_BEGINLABELEDIT:
1399  {
1400  NMLVDISPINFO* di = (NMLVDISPINFO*)hdr;
1401 
1402  DWORD style = GetWindowLong ( ListView_GetEditControl ( hdr->hwndFrom ), GWL_STYLE );
1403  SetWindowLong ( ListView_GetEditControl ( hdr->hwndFrom ), GWL_STYLE, style & (~WS_BORDER) );
1404 
1405  rvDebuggerWatch* watch = (rvDebuggerWatch*)di->item.lParam;
1406  if ( watch )
1407  {
1408  SetWindowText ( ListView_GetEditControl ( hdr->hwndFrom ), watch->mVariable );
1409  }
1410 
1411  return FALSE;
1412  }
1413 
1414  case LVN_ENDLABELEDIT:
1415  {
1416  NMLVDISPINFO* di = (NMLVDISPINFO*)hdr;
1417 
1418  if ( di->item.iItem == ListView_GetItemCount ( hdr->hwndFrom ) - 1 )
1419  {
1420  if ( !di->item.pszText || !di->item.pszText[0] )
1421  {
1422  return FALSE;
1423  }
1424 
1425  window->AddWatch ( ((NMLVDISPINFO*)hdr)->item.pszText );
1426  window->UpdateWatch ( );
1427  return FALSE;
1428  }
1429  else
1430  {
1431  rvDebuggerWatch* watch = (rvDebuggerWatch*) di->item.lParam;
1432  if ( watch && di->item.pszText && di->item.pszText[0] )
1433  {
1434  watch->mVariable = di->item.pszText;
1435  }
1436  }
1437 
1438  return TRUE;
1439  }
1440  }
1441  break;
1442 
1443  case IDC_DBG_CALLSTACK:
1444  if ( hdr->code == NM_DBLCLK )
1445  {
1446  int sel = ListView_GetNextItem ( hdr->hwndFrom, -1, LVNI_SELECTED );
1447  if ( sel != -1 )
1448  {
1449  window->mCurrentStackDepth = sel;
1450  window->UpdateCallstack ( );
1451  window->UpdateWatch ( );
1452  window->OpenScript ( window->mClient->GetCallstack()[window->mCurrentStackDepth]->mFilename,
1453  window->mClient->GetCallstack()[window->mCurrentStackDepth]->mLineNumber );
1454  }
1455  }
1456  break;
1457  case IDC_DBG_TABS:
1458  if ( hdr->code == TCN_SELCHANGE )
1459  {
1460  ShowWindow ( window->mWndOutput, SW_HIDE );
1461  ShowWindow ( window->mWndConsole, SW_HIDE );
1462  ShowWindow ( window->mWndCallstack, SW_HIDE );
1463  ShowWindow ( window->mWndWatch, SW_HIDE );
1464  ShowWindow ( window->mWndThreads, SW_HIDE );
1465  switch ( TabCtrl_GetCurSel ( hdr->hwndFrom ) )
1466  {
1467  case 0:
1468  ShowWindow ( window->mWndOutput, SW_SHOW );
1469  break;
1470 
1471  case 1:
1472  ShowWindow ( window->mWndConsole, SW_SHOW );
1473  break;
1474 
1475  case 2:
1476  ShowWindow ( window->mWndCallstack, SW_SHOW );
1477  break;
1478 
1479  case 3:
1480  ShowWindow ( window->mWndWatch, SW_SHOW );
1481  break;
1482 
1483  case 4:
1484  ShowWindow ( window->mWndThreads, SW_SHOW );
1485  break;
1486  }
1487  }
1488  break;
1489  }
1490  break;
1491  }
1492 
1493  case WM_CLOSE:
1494  if ( window->mClient->IsConnected ( ) )
1495  {
1496  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 ) )
1497  {
1498  return 0;
1499  }
1500  }
1501  PostQuitMessage ( 0 );
1502  break;
1503  }
1504 
1505  return DefWindowProc ( wnd, msg, wparam, lparam );
1506 }
1507 
1508 /*
1509 ================
1510 rvDebuggerWindow::Activate
1511 
1512 Static method that will activate the currently running debugger. If one is found
1513 and activated then true will be returned.
1514 ================
1515 */
1517 {
1518  HWND find;
1519 
1520  find = FindWindow ( DEBUGGERWINDOWCLASS, NULL );
1521  if ( !find )
1522  {
1523  return false;
1524  }
1525 
1526  SetForegroundWindow ( find );
1527 
1528  return true;
1529 }
1530 
1531 /*
1532 ================
1533 rvDebuggerWindow::ProcessNetMessage
1534 
1535 Process an incoming network message
1536 ================
1537 */
1539 {
1540  unsigned short command;
1541 
1542  command = (unsigned short)MSG_ReadShort ( msg );
1543 
1544  switch ( command )
1545  {
1546  case DBMSG_RESUMED:
1547  UpdateTitle ( );
1548  UpdateToolbar ( );
1549  break;
1550 
1551  case DBMSG_INSPECTVARIABLE:
1552  {
1553  char temp[1024];
1554  char temp2[1024];
1555  int i;
1556 
1557  MSG_ReadShort ( msg );
1558  MSG_ReadString ( msg, temp, 1024 );
1559  MSG_ReadString ( msg, temp2, 1024 );
1560  if ( mTooltipVar.Icmp ( temp ) == 0 )
1561  {
1562  mTooltipValue = temp2;
1563 
1564  TOOLINFO info;
1565  info.cbSize = sizeof(info);
1566  info.hwnd = mWndScript;
1567  info.uId = 0;
1568  info.hinst = mInstance;
1569  info.lpszText = va("%s=%s", mTooltipVar.c_str(), mTooltipValue.c_str() );
1570  SendMessage ( mWndToolTips, TTM_UPDATETIPTEXT, 0, (LPARAM)&info );
1571  SendMessage ( mWndToolTips, TTM_UPDATE, 0, 0 );
1572  }
1573 
1574  // See if any of the watches were updated by this inspect
1575  for ( i = 0; i < mWatches.Num(); i ++ )
1576  {
1577  rvDebuggerWatch* watch = mWatches[i];
1578 
1579  // See if the name matches the variable
1580  if ( watch->mVariable.Cmp ( temp ) )
1581  {
1582  continue;
1583  }
1584 
1585  // Has the value changed?
1586  if ( !watch->mValue.Cmp ( temp2 ) )
1587  {
1588  continue;
1589  }
1590 
1591  watch->mModified = true;
1592  watch->mValue = temp2;
1593 
1594  // Find the list view item that is storing this watch info and redraw it
1595  for ( int l = 0; l < ListView_GetItemCount(mWndWatch); l ++ )
1596  {
1597  LVITEM item;
1598  item.mask = LVIF_PARAM;
1599  item.iItem = l;
1600  ListView_GetItem ( mWndWatch, &item );
1601  if ( item.lParam == (LPARAM) watch )
1602  {
1603  ListView_RedrawItems ( mWndWatch, l, l );
1604  UpdateWindow ( mWndWatch );
1605  break;
1606  }
1607  }
1608  }
1609 
1610  break;
1611  }
1612 
1613  case DBMSG_CONNECT:
1614  case DBMSG_CONNECTED:
1615  UpdateTitle ( );
1616  UpdateToolbar ( );
1617  Printf ( "Connected...\n" );
1618  break;
1619 
1620  case DBMSG_DISCONNECT:
1621  UpdateTitle ( );
1622  UpdateToolbar ( );
1623  Printf ( "Disconnected...\n" );
1624  break;
1625 
1626  case DBMSG_PRINT:
1627  SendMessage ( mWndConsole, EM_SETSEL, -1, -1 );
1628  SendMessage ( mWndConsole, EM_REPLACESEL, 0, (LPARAM)(const char*)(msg->data) + msg->readcount );
1629  SendMessage ( mWndConsole, EM_SCROLLCARET, 0, 0 );
1630  break;
1631 
1632  case DBMSG_BREAK:
1633  {
1634  Printf ( "Break: line=%d file='%s'\n", mClient->GetBreakLineNumber ( ), mClient->GetBreakFilename() );
1635 
1636  mCurrentStackDepth = 0;
1637  mClient->InspectVariable ( mTooltipVar, mCurrentStackDepth );
1638  UpdateWatch ( );
1639  EnableWindows ( TRUE );
1641  UpdateTitle ( );
1642  UpdateToolbar ( );
1643  SetForegroundWindow ( mWnd );
1644  break;
1645  }
1646 
1648  {
1649  UpdateCallstack ( );
1650  break;
1651  }
1652 
1653  case DBMSG_INSPECTTHREADS:
1654  {
1655  LVITEM item;
1656  ListView_DeleteAllItems ( mWndThreads );
1657  ZeroMemory ( &item, sizeof(item) );
1658  item.mask = LVIF_TEXT|LVIF_IMAGE;
1659 
1660  for ( int i = 0; i < mClient->GetThreads().Num(); i ++ )
1661  {
1662  rvDebuggerThread* entry = mClient->GetThreads()[i];
1663  item.iItem = ListView_GetItemCount ( mWndThreads );
1664  item.pszText = "";
1665  item.iImage = entry->mCurrent ? 1 : 0;
1666  ListView_InsertItem ( mWndThreads, &item );
1667 
1668  ListView_SetItemText ( mWndThreads, item.iItem, 1, (LPSTR)va("%d", entry->mID) );
1669  ListView_SetItemText ( mWndThreads, item.iItem, 2, (LPSTR)entry->mName.c_str() );
1670 
1671  if ( entry->mDying )
1672  {
1673  ListView_SetItemText ( mWndThreads, item.iItem, 3, "Dying" );
1674  }
1675  else if ( entry->mWaiting )
1676  {
1677  ListView_SetItemText ( mWndThreads, item.iItem, 3, "Waiting" );
1678  }
1679  else if ( entry->mDoneProcessing )
1680  {
1681  ListView_SetItemText ( mWndThreads, item.iItem, 3, "Stopped" );
1682  }
1683  else
1684  {
1685  ListView_SetItemText ( mWndThreads, item.iItem, 3, "Running" );
1686  }
1687  }
1688  break;
1689  }
1690  }
1691 }
1692 
1693 /*
1694 ================
1695 rvDebuggerWindow::Printf
1696 
1697 Sends a formatted message to the output window
1698 ================
1699 */
1700 void rvDebuggerWindow::Printf ( const char* fmt, ... )
1701 {
1702  va_list argptr;
1703  char msg[4096];
1704 
1705  va_start (argptr,fmt);
1706  vsprintf (msg,fmt,argptr);
1707  va_end (argptr);
1708 
1709  SendMessage ( mWndOutput, EM_SETSEL, -1, -1 );
1710  SendMessage ( mWndOutput, EM_REPLACESEL, 0, (LPARAM)msg );
1711  SendMessage ( mWndOutput, EM_SCROLLCARET, 0, 0 );
1712 }
1713 
1714 /*
1715 ================
1716 rvDebuggerWindow::OpenScript
1717 
1718 Opens the script with the given filename and will scroll to the given line
1719 number if one is specified
1720 ================
1721 */
1722 bool rvDebuggerWindow::OpenScript ( const char* filename, int lineNumber )
1723 {
1724  int i;
1725 
1726  SetCursor ( LoadCursor ( NULL, IDC_WAIT ) );
1727 
1728  mActiveScript = -1;
1729 
1730  // See if the script is already loaded
1731  for ( i = 0; i < mScripts.Num(); i ++ )
1732  {
1733  if ( !idStr::Icmp ( mScripts[i]->GetFilename ( ), filename ) )
1734  {
1735  if ( mActiveScript != i )
1736  {
1737  mActiveScript = i;
1738  break;
1739  }
1740  }
1741  }
1742 
1743  // If the script isnt open already then open it
1744  if ( mActiveScript == -1 )
1745  {
1746  rvDebuggerScript* script = new rvDebuggerScript;
1747 
1748  // Load the script
1749  if ( !script->Load ( filename ) )
1750  {
1751  delete script;
1752  SetCursor ( LoadCursor ( NULL, IDC_ARROW ) );
1753  return false;
1754  }
1755 
1756  gDebuggerApp.GetOptions().AddRecentFile ( filename );
1757  UpdateRecentFiles ( );
1758 
1759  mActiveScript = mScripts.Append ( script );
1760  }
1761 
1762  // Test code that will place a breakpoint on all valid lines of code
1763 #if 0
1764 
1765  for ( i = 0; i < mScripts[mActiveScript]->GetProgram().NumStatements(); i ++ )
1766  {
1767  dstatement_t* st = &mScripts[mActiveScript]->GetProgram().GetStatement ( i );
1768  rvDebuggerBreakpoint* bp = new rvDebuggerBreakpoint ( filename, st->linenumber );
1769  mBreakpoints.Append ( bp );
1770  }
1771 
1772 #endif
1773 
1774  UpdateScript ( );
1775  UpdateWindowMenu ( );
1776 
1777  // Move to a specific line number?
1778  if ( lineNumber != -1 )
1779  {
1780  int c;
1781 
1782  // Put the caret on the line number specified and scroll it into position.
1783  // This is a bit of a hack since we set the selection twice, but setting the
1784  // selection to (c,c) didnt work for scrolling the caret so we do (c,c+1)
1785  // and then scroll before going back to (c,c).
1786  // NOTE: We scroll to the line before the one we want so its more visible
1787  SetFocus ( mWndScript );
1788  c = SendMessage ( mWndScript, EM_LINEINDEX, lineNumber - 1, 0 );
1789  SendMessage ( mWndScript, EM_SETSEL, c, c + 1 );
1790  SendMessage ( mWndScript, EM_SCROLLCARET, 0, 0 );
1791  c = SendMessage ( mWndScript, EM_LINEINDEX, lineNumber, 0 );
1792  SendMessage ( mWndScript, EM_SETSEL, c, c );
1793  }
1794  else
1795  {
1796  SendMessage ( mWndScript, EM_SETSEL, 0, 0 );
1797  }
1798 
1799  // Make sure the script window is visible
1800  ShowWindow ( mWndScript, SW_SHOW );
1801  UpdateWindow ( mWndScript );
1802 
1803  SetCursor ( LoadCursor ( NULL, IDC_ARROW ) );
1804 
1805  return true;
1806 }
1807 
1808 /*
1809 ================
1810 rvDebuggerWindow::ToggleBreakpoint
1811 
1812 Toggles the breakpoint on the current script line
1813 ================
1814 */
1816 {
1818  DWORD sel;
1819  int line;
1820 
1821  // Find the currently selected line
1822  SendMessage ( mWndScript, EM_GETSEL, (WPARAM)&sel, 0 );
1823  line = SendMessage ( mWndScript, EM_LINEFROMCHAR, sel, 0 ) + 1;
1824 
1825  // If there is already a breakpoint there then just remove it, otherwise
1826  // we need to create a new breakpoint
1827  bp = mClient->FindBreakpoint ( mScripts[mActiveScript]->GetFilename ( ), line );
1828  if ( !bp )
1829  {
1830  // Make sure the line is code before letting them add a breakpoint there
1831  if ( !mScripts[mActiveScript]->IsLineCode ( line ) )
1832  {
1833  MessageBeep ( MB_ICONEXCLAMATION );
1834  return;
1835  }
1836 
1837  mClient->AddBreakpoint ( mScripts[mActiveScript]->GetFilename(), line );
1838  }
1839  else
1840  {
1841  mClient->RemoveBreakpoint ( bp->GetID ( ) );
1842  }
1843 
1844  // Force a repaint of the script window
1845  InvalidateRect ( mWndScript, NULL, FALSE );
1846 }
1847 
1848 /*
1849 ================
1850 rvDebuggerWindow::AboutDlgProc
1851 
1852 Dialog box procedure for the about box
1853 ================
1854 */
1855 INT_PTR CALLBACK rvDebuggerWindow::AboutDlgProc ( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam )
1856 {
1857  switch ( msg )
1858  {
1859  case WM_COMMAND:
1860  EndDialog ( wnd, 0 );
1861  break;
1862  }
1863 
1864  return FALSE;
1865 }
1866 
1867 /*
1868 ================
1869 rvDebuggerWindow::CreateToolbar
1870 
1871 Create the toolbar and and all of its buttons
1872 ================
1873 */
1875 {
1876  // Create the toolbar control
1877  mWndToolbar = CreateWindowEx ( 0, TOOLBARCLASSNAME, "", WS_CHILD|WS_VISIBLE,0,0,0,0, mWnd, (HMENU)IDC_DBG_TOOLBAR, mInstance, NULL );
1878 
1879  // Initialize the toolbar
1880  SendMessage ( mWndToolbar, TB_BUTTONSTRUCTSIZE, ( WPARAM )sizeof( TBBUTTON ), 0 );
1881  SendMessage ( mWndToolbar, TB_SETBUTTONSIZE, 0, MAKELONG(16,16) );
1882  SendMessage ( mWndToolbar, TB_SETSTYLE, 0, SendMessage ( mWndToolbar, TB_GETSTYLE, 0, 0 ) | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS );
1883 
1884  TBMETRICS tbmet;
1885  tbmet.cbSize = sizeof(TBMETRICS);
1886  SendMessage ( mWndToolbar, TB_GETMETRICS, 0, (LPARAM)&tbmet );
1887  tbmet.cyPad = 0;
1888  tbmet.cyBarPad = 0;
1889  SendMessage ( mWndToolbar, TB_SETMETRICS, 0, (LPARAM)&tbmet );
1890 
1891  // Add the bitmap containing button images to the toolbar.
1892  TBADDBITMAP tbab;
1893  tbab.hInst = mInstance;
1894  tbab.nID = IDB_DBG_TOOLBAR;
1895  SendMessage( mWndToolbar, TB_ADDBITMAP, (WPARAM)4, (LPARAM) &tbab );
1896 
1897  // Add the buttons to the toolbar
1898  TBBUTTON tbb[] = { { 0, 0, TBSTATE_ENABLED, BTNS_SEP, 0, 0, -1 },
1899  { 8, ID_DBG_FILE_OPEN, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
1900  { 0, 0, TBSTATE_ENABLED, BTNS_SEP, 0, 0, -1 },
1901  { 0, ID_DBG_DEBUG_RUN, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
1902  { 1, ID_DBG_DEBUG_BREAK, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
1903  { 0, 0, TBSTATE_ENABLED, BTNS_SEP, 0, 0, -1 },
1904  { 4, ID_DBG_DEBUG_STEPINTO, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
1905  { 5, ID_DBG_DEBUG_STEPOVER, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
1906  { 6, ID_DBG_DEBUG_STEPOUT, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, -1 },
1907  { 0, 0, TBSTATE_ENABLED, BTNS_SEP, 0, 0, -1 } };
1908 
1909  SendMessage( mWndToolbar, TB_ADDBUTTONS, (WPARAM)sizeof(tbb)/sizeof(TBBUTTON), (LPARAM) tbb );
1910 }
1911 
1912 /*
1913 ================
1914 rvDebuggerWindow::HandleTooltipGetDispInfo
1915 
1916 Handle the getdispinfo notification message for tooltips by responding with the
1917 tooptip text for the given toolbar button.
1918 ================
1919 */
1921 {
1922  NMTTDISPINFO* ttdi = (NMTTDISPINFO*) lparam;
1923  switch ( ttdi->hdr.idFrom )
1924  {
1925  case ID_DBG_FILE_OPEN:
1926  strcpy ( ttdi->szText, "Open Script" );
1927  break;
1928 
1929  case ID_DBG_DEBUG_STEPINTO:
1930  strcpy ( ttdi->szText, "Step Into" );
1931  break;
1932 
1933  case ID_DBG_DEBUG_STEPOVER:
1934  strcpy ( ttdi->szText, "Step Over" );
1935  break;
1936 
1937  case ID_DBG_DEBUG_STEPOUT:
1938  strcpy ( ttdi->szText, "Step Out" );
1939  break;
1940 
1941  case ID_DBG_DEBUG_BREAK:
1942  strcpy ( ttdi->szText, "Break" );
1943  break;
1944 
1945  case ID_DBG_DEBUG_RUN:
1946  if ( mClient->IsConnected() )
1947  {
1948  strcpy ( ttdi->szText, "Continue" );
1949  }
1950  else
1951  {
1952  strcpy ( ttdi->szText, "Run" );
1953  }
1954  break;
1955 
1956  default:
1957  strcpy ( ttdi->szText, "" );
1958  break;
1959  }
1960 }
1961 
1962 /*
1963 ================
1964 rvDebuggerWindow::HandleActivate
1965 
1966 When the main window is activated, check all the loaded scripts and see if any of them
1967 have been modified since the last time they were loaded. If they have then reload
1968 them and adjust all breakpoints that now fall on invalid lines.
1969 ================
1970 */
1971 int rvDebuggerWindow::HandleActivate ( WPARAM wparam, LPARAM lparam )
1972 {
1973  int i;
1974 
1975  // We are only interested in the activation, not deactivation
1976  if ( !LOWORD(wparam) )
1977  {
1978  return 0;
1979  }
1980 
1981  // Run through all of the loaded scripts and see if any of them have been modified
1982  for ( i = 0; i < mScripts.Num(); i ++ )
1983  {
1984  if ( mScripts[i]->IsFileModified ( true ) )
1985  {
1986  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 ) )
1987  {
1988  mScripts[i]->Reload ( );
1989 
1990  // Update the script if it was the active one
1991  if ( mActiveScript == i )
1992  {
1993  mLastActiveScript = -1;
1994  UpdateScript ( );
1995  }
1996 
1997  // Loop through the breakpoints and see if any of them have
1998  // moved to invalid lines within the script. If so then just remove
1999  // them.
2000  for ( int b = mClient->GetBreakpointCount() - 1; b >= 0 ; b-- )
2001  {
2003  assert ( bp );
2004 
2005  if ( !idStr::Icmp ( bp->GetFilename(), mScripts[i]->GetFilename() ) )
2006  {
2007  if ( !mScripts[i]->IsLineCode ( bp->GetLineNumber ( ) ) )
2008  {
2009  mClient->RemoveBreakpoint ( bp->GetID ( ) );
2010  }
2011  }
2012  }
2013  }
2014  }
2015  }
2016 
2017  return 1;
2018 }
2019 
2020 /*
2021 ================
2022 rvDebuggerWindow::EnableWindows
2023 
2024 Enables and disables all windows with a enable state dependent on the current
2025 connected and paused state of the debugger.
2026 ================
2027 */
2029 {
2030  EnableWindow ( mWndCallstack, state );
2031  EnableWindow ( mWndThreads, state );
2032  EnableWindow ( mWndWatch, state );
2033 }
2034 
2035 /*
2036 ================
2037 rvDebuggerWindow::AddWatch
2038 
2039 Add a variable to the watch window. If update is set to true then also query the
2040 debugger client for the value
2041 ================
2042 */
2043 void rvDebuggerWindow::AddWatch ( const char* varname, bool update )
2044 {
2045  rvDebuggerWatch* watch;
2046 
2047  watch = new rvDebuggerWatch;
2048  watch->mVariable = varname;
2049  watch->mModified = false;
2050  watch->mValue = "???";
2051  mWatches.Append ( watch );
2052 
2053  // Add the variable to the watch control
2054  LVITEM item;
2055  item.mask = LVIF_PARAM;
2056  item.iItem = ListView_GetItemCount ( mWndWatch ) - 1;
2057  item.iSubItem = 0;
2058  item.lParam = (LPARAM)watch;
2059  ListView_InsertItem ( mWndWatch, &item );
2060 
2061  // If update is set then request the value from the debugger client
2062  if ( update )
2063  {
2065  }
2066 }
2067 
2068 /*
2069 ================
2070 rvDebuggerWindow::InitRecentFiles
2071 
2072 Finds the file menu and the location within it where the MRU should
2073 be added.
2074 ================
2075 */
2077 {
2078  int i;
2079  int count;
2080 
2081  mRecentFileMenu = GetSubMenu ( GetMenu(mWnd), 0 );
2082  count = GetMenuItemCount ( mRecentFileMenu );
2083 
2084  for ( i = 0; i < count; i ++ )
2085  {
2086  if ( GetMenuItemID ( mRecentFileMenu, i ) == ID_DBG_FILE_MRU )
2087  {
2089  DeleteMenu ( mRecentFileMenu, mRecentFileInsertPos, MF_BYPOSITION );
2090  return true;
2091  }
2092  }
2093 
2094  return false;
2095 }
2096 
2097 /*
2098 ================
2099 rvDebuggerWindow::UpdateRecentFiles
2100 
2101 Updates the mru in the menu
2102 ================
2103 */
2105 {
2106  int i;
2107  int j;
2108 
2109  // Make sure everything is initialized
2110  if ( !mRecentFileMenu )
2111  {
2112  InitRecentFiles ( );
2113  }
2114 
2115  // Delete all the old recent files from the menu's
2116  for ( i = 0; i < rvRegistryOptions::MAX_MRU_SIZE; i ++ )
2117  {
2118  DeleteMenu ( mRecentFileMenu, ID_DBG_FILE_MRU1 + i, MF_BYCOMMAND );
2119  }
2120 
2121  // Make sure there is a separator after the recent files
2123  {
2124  MENUITEMINFO info;
2125  ZeroMemory ( &info, sizeof(info) );
2126  info.cbSize = sizeof(info);
2127  info.fMask = MIIM_FTYPE;
2128  GetMenuItemInfo ( mRecentFileMenu, mRecentFileInsertPos+1,TRUE, &info );
2129  if ( !(info.fType & MFT_SEPARATOR ) )
2130  {
2131  InsertMenu ( mRecentFileMenu, mRecentFileInsertPos, MF_BYPOSITION|MF_SEPARATOR|MF_ENABLED, 0, NULL );
2132  }
2133  }
2134 
2135  // Add the recent files to the menu now
2136  for ( j = 0, i = gDebuggerApp.GetOptions().GetRecentFileCount ( ) - 1; i >= 0; i --, j++ )
2137  {
2138  UINT id = ID_DBG_FILE_MRU1 + j;
2139  idStr str = va("&%d ", j+1);
2140  str.Append ( gDebuggerApp.GetOptions().GetRecentFile ( i ) );
2141  InsertMenu ( mRecentFileMenu, mRecentFileInsertPos+j+1, MF_BYPOSITION|MF_STRING|MF_ENABLED, id, str );
2142  }
2143 }
2144 
2145 /*
2146 ================
2147 rvDebuggerWindow::GetSelectedText
2148 
2149 Function to retrieve the text that is currently selected in the
2150 script control
2151 ================
2152 */
2154 {
2155  TEXTRANGE range;
2156  int start;
2157  int end;
2158  char* temp;
2159 
2160  text.Empty ( );
2161 
2162  if ( mScripts.Num ( ) )
2163  {
2164  SendMessage ( mWndScript, EM_GETSEL, (WPARAM)&start, (LPARAM)&end );
2165  if ( start == end )
2166  {
2167  end = SendMessage ( mWndScript, EM_FINDWORDBREAK, WB_RIGHT, start );
2168  start = SendMessage ( mWndScript, EM_FINDWORDBREAK, WB_LEFT, start );
2169  }
2170 
2171  temp = new char[end-start+10];
2172  range.chrg.cpMin = start;
2173  range.chrg.cpMax = end;
2174  range.lpstrText = temp;
2175  SendMessage ( mWndScript, EM_GETTEXTRANGE, 0, (LPARAM) &range );
2176  text = temp;
2177  delete[] temp;
2178 
2179  return start;
2180  }
2181 
2182  return -1;
2183 }
2184 
2185 /*
2186 ================
2187 rvDebuggerWindow::FindNext
2188 
2189 Finds the next match of the find text in the active script. The next is
2190 always relative to the current selection. If the text parameter is NULL
2191 then the last text used will be searched for.
2192 ================
2193 */
2194 bool rvDebuggerWindow::FindNext ( const char* text )
2195 {
2196  int start;
2197  FINDTEXT ft;
2198 
2199  if ( text )
2200  {
2201  mFind = text;
2202  }
2203 
2204  if ( !mFind.Length ( ) )
2205  {
2206  return false;
2207  }
2208 
2209  SendMessage ( mWndScript, EM_GETSEL, (WPARAM)&start, (LPARAM)0 );
2210  if ( start < 0 )
2211  {
2212  start = 0;
2213  }
2214 
2215  ft.chrg.cpMin = start + 1;
2216  ft.chrg.cpMax = -1;
2217  ft.lpstrText = mFind.c_str();
2218  start = SendMessage ( mWndScript, EM_FINDTEXT, FR_DOWN, (LPARAM)&ft );
2219 
2220  if ( start < 0 )
2221  {
2222  ft.chrg.cpMin = 0;
2223  ft.chrg.cpMax = -1;
2224  ft.lpstrText = mFind.c_str();
2225  start = SendMessage ( mWndScript, EM_FINDTEXT, FR_DOWN, (LPARAM)&ft );
2226 
2227  if ( start < 0 )
2228  {
2229  return false;
2230  }
2231  }
2232 
2233  SendMessage ( mWndScript, EM_SETSEL, start, start + mFind.Length() );
2234  SendMessage ( mWndScript, EM_SCROLLCARET, 0, 0 );
2235 
2236  return true;
2237 }
2238 
2239 /*
2240 ================
2241 rvDebuggerWindow::FindPrev
2242 
2243 Finds the previous match of the find text in the active script. The previous is
2244 always relative to the current selection. If the text parameter is NULL
2245 then the last text used will be searched for.
2246 ================
2247 */
2248 bool rvDebuggerWindow::FindPrev ( const char* text )
2249 {
2250  int start;
2251  FINDTEXT ft;
2252 
2253  if ( text )
2254  {
2255  mFind = text;
2256  }
2257 
2258  if ( !mFind.Length ( ) )
2259  {
2260  return false;
2261  }
2262 
2263  SendMessage ( mWndScript, EM_GETSEL, (WPARAM)&start, (LPARAM)0 );
2264  if ( start < 0 )
2265  {
2266  start = 0;
2267  }
2268 
2269  ft.chrg.cpMin = start;
2270  ft.chrg.cpMax = -1;
2271  ft.lpstrText = mFind.c_str();
2272  start = SendMessage ( mWndScript, EM_FINDTEXT, 0, (LPARAM)&ft );
2273 
2274  if ( start < 0 )
2275  {
2276  GETTEXTLENGTHEX gtl;
2277  gtl.flags = GTL_DEFAULT;
2278  gtl.codepage = CP_ACP;
2279  ft.chrg.cpMin = SendMessage ( mWndScript, EM_GETTEXTLENGTHEX, (WPARAM)&gtl, 0 );
2280  ft.chrg.cpMax = 0;
2281  ft.lpstrText = mFind.c_str();
2282  start = SendMessage ( mWndScript, EM_FINDTEXT, 0, (LPARAM)&ft );
2283 
2284  if ( start < 0 )
2285  {
2286  return false;
2287  }
2288  }
2289 
2290  SendMessage ( mWndScript, EM_SETSEL, start, start + mFind.Length() );
2291  SendMessage ( mWndScript, EM_SCROLLCARET, 0, 0 );
2292 
2293  return true;
2294 }
2295 
2296 /*
2297 ================
2298 rvDebuggerWindow::HandleDrawItem
2299 
2300 Handled the WM_DRAWITEM message. The watch window is custom drawn so a grid can be displayed.
2301 ================
2302 */
2303 int rvDebuggerWindow::HandleDrawItem ( WPARAM wparam, LPARAM lparam )
2304 {
2305  DRAWITEMSTRUCT* dis;
2306  LVCOLUMN col;
2307  int index;
2308  idStr widths;
2309  RECT rect;
2310  rvDebuggerWatch* watch;
2311  bool selected;
2312 
2313  dis = (DRAWITEMSTRUCT*) lparam;
2314  watch = (rvDebuggerWatch*)dis->itemData;
2315 
2316  col.mask = LVCF_WIDTH;
2317  rect = dis->rcItem;
2318  rect.left = rect.left - 1;
2319  rect.right = rect.left;
2320  rect.bottom++;
2321 
2322  selected = ((dis->itemState & ODS_SELECTED) && GetFocus()==mWndWatch);
2323 
2324  // Set the colors based on the selected state and draw the item background
2325  if ( selected )
2326  {
2327  FillRect ( dis->hDC, &dis->rcItem, GetSysColorBrush ( COLOR_HIGHLIGHT ) );
2328  }
2329  else
2330  {
2331  FillRect ( dis->hDC, &dis->rcItem, GetSysColorBrush ( IsWindowEnabled ( mWndWatch ) ? COLOR_WINDOW : COLOR_3DFACE ) );
2332  }
2333 
2334  // Run through the columns and draw each with a frame around it and the text
2335  // vertically centered in it
2336  for ( index = 0; ListView_GetColumn ( mWndWatch, index, &col ); index ++ )
2337  {
2338  rect.right = rect.left + col.cx;
2339  FrameRect ( dis->hDC, &rect, GetSysColorBrush ( COLOR_3DFACE ) );
2340 
2341  // Draw info on the watch if available
2342  if ( watch )
2343  {
2344  RECT textrect;
2345  textrect = rect;
2346  textrect.left += 5;
2347 
2348  switch ( index )
2349  {
2350  case 0:
2351  SetTextColor ( dis->hDC, GetSysColor ( selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT ) );
2352  DrawText ( dis->hDC, watch->mVariable, -1, &textrect, DT_LEFT|DT_VCENTER );
2353  break;
2354 
2355  case 1:
2356  if ( watch && watch->mModified && (IsWindowEnabled ( mWndWatch ) ) )
2357  {
2358  SetTextColor ( dis->hDC, RGB(255,50,50) );
2359  }
2360  else
2361  {
2362  SetTextColor ( dis->hDC, GetSysColor ( selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT ) );
2363  }
2364  DrawText ( dis->hDC, watch->mValue, -1, &textrect, DT_LEFT|DT_VCENTER );
2365  break;
2366  }
2367  }
2368 
2369  rect.left = rect.right - 1;
2370  }
2371 
2372  return 0;
2373 }
#define ID_DBG_FILE_MRU
void SetString(const char *name, const char *v)
#define IDR_DBG_MAIN
bool FindPrev(const char *text=NULL)
bool GetWindowPlacement(const char *name, HWND hwnd)
bool DoModal(rvDebuggerWindow *window)
#define IDC_DBG_CONSOLE
CPropTreeItem LPARAM
Definition: PropTree.h:70
#define ID_DBG_DEBUG_STEPINTO
assert(prefInfo.fullscreenBtn)
void SetColumnWidths(const char *name, HWND list)
int Cmp(const char *text) const
Definition: Str.h:652
idCVarSystem * cvarSystem
Definition: CVarSystem.cpp:487
#define OFD_MUSTEXIST
const char * GetFilename(void)
int HandleCreate(WPARAM wparam, LPARAM lparam)
#define ID_DBG_DEBUG_SHOWNEXTSTATEMENT
bool DoModal(rvDebuggerWindow *window, int callstackDepth, const char *variable=NULL)
CONST PIXELFORMATDESCRIPTOR UINT
Definition: win_qgl.cpp:47
#define ID_DBG_DEBUG_STEPOVER
#define IDC_DBG_CALLSTACK
#define ID_DBG_DEBUG_RUN
int Length(void) const
Definition: Str.h:702
#define ID_DBG_DEBUG_STEPOUT
#define IDR_DBG_SCRIPT_POPUP
void InspectVariable(const char *name, int callstackDepth)
bool OpenScript(const char *filename, int lineNumber=-1)
DWORD
Definition: win_qgl.cpp:61
#define IDI_DBG_BREAKPOINT
#define IDC_DBG_OUTPUT
#define IDC_DBG_TOOLBAR
void Printf(const char *format,...)
void UpdateWindowMenu(void)
#define ID_DBG_FILE_MRU1
case const float
Definition: Callbacks.cpp:62
void EnableWindows(bool state)
static LRESULT CALLBACK MarginWndProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
#define DEBUGGERWINDOWCLASS
bool RegisterClass(void)
rvDebuggerBreakpoint * FindBreakpoint(const char *filename, int linenumber)
GLdouble s
Definition: glext.h:2935
static LRESULT CALLBACK ScriptWndProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
rvDebuggerBreakpoint * GetBreakpoint(int index)
int i
Definition: process.py:33
GLuint GLuint num
Definition: glext.h:5390
Boolean result
rvRegistryOptions & GetOptions(void)
Definition: DebuggerApp.h:95
static bool Activate(void)
#define ID_DBG_DEBUG_BREAK
void AddWatch(const char *name, bool update=true)
int Icmp(const char *text) const
Definition: Str.h:667
GLsizei range
Definition: glext.h:4368
list l
Definition: prepare.py:17
int HandleActivate(WPARAM wparam, LPARAM lparam)
HIMAGELIST mImageList
idStr & StripPath(void)
Definition: Str.cpp:885
void CreateToolbar(void)
GLuint GLuint GLsizei count
Definition: glext.h:2845
bool Create(HINSTANCE hInstance)
bool InitRecentFiles(void)
#define ID_DBG_FILE_NEXT
#define ID_DBG_DEBUG_QUICKWATCH
GLuint index
Definition: glext.h:3476
const GLubyte * c
Definition: glext.h:4677
void Empty(void)
Definition: Str.h:714
#define ID_DBG_EDIT_FINDSELECTED
#define IDC_DBG_BORDER
#define ID_DBG_FILE_EXIT
#define ID_DBG_WINDOWMAX
GLuint GLuint end
Definition: glext.h:2845
const char * GetFindText(void)
#define NULL
Definition: Lib.h:88
virtual const char * GetCVarString(const char *name) const =0
idList< rvDebuggerScript * > mScripts
void GetColumnWidths(const char *name, HWND list)
void SetFlags(int flags)
void UpdateScript(void)
bool IsStopped(void)
#define IDC_DBG_TABS
int GetBreakLineNumber(void)
#define IDI_DBG_CURRENTLINE
void SetTitle(const char *title)
#define ID_DBG_FILE_CLOSE
const char * GetBreakFilename(void)
static INT_PTR CALLBACK AboutDlgProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
#define ID_DBG_HELP_ABOUT
void HandleTooltipGetDispInfo(WPARAM wparam, LPARAM lparam)
typedef HDC(WINAPI *PFNWGLGETCURRENTREADDCARBPROC)(void)
int GetSelectedText(idStr &text)
const char * GetFilename(void)
void SetWindowPlacement(const char *name, HWND hwnd)
void StepInto(void)
rvDebuggerClient * mClient
rvDebuggerWatchList mWatches
static const int MAX_MRU_SIZE
void UpdateToolbar(void)
GLubyte GLubyte b
Definition: glext.h:4662
rvDebuggerCallstackList & GetCallstack(void)
int HandleCommand(WPARAM wParam, LPARAM lParam)
prefInfo window
rvDebuggerClient & GetClient(void)
Definition: DebuggerApp.h:90
long LONG
int Append(const type &obj)
Definition: List.h:646
bool Load(const char *filename)
#define ID_DBG_WINDOW_CLOSEALL
int HandleDrawItem(WPARAM wparam, LPARAM lparam)
#define IDC_DBG_SPLITTER
GLuint id
Definition: glext.h:3103
void AddRecentFile(const char *filename)
#define IDB_DBG_TOOLBAR
#define ID_DBG_DEBUG_TOGGLEBREAKPOINT
#define ID_DBG_DEBUG_RUNTOCURSOR
#define ID_DBG_EDIT_FINDSELECTEDPREV
tuple f
Definition: idal.py:89
#define ID_DBG_EDIT_FIND
void Append(const char a)
Definition: Str.h:729
int Num(void) const
Definition: List.h:265
bool RemoveIndex(int index)
Definition: List.h:849
const GLcharARB * name
Definition: glext.h:3629
GLfloat * st
Definition: qgl.h:89
GLsizeiptr size
Definition: glext.h:3112
void UpdateTitle(void)
Definition: Str.h:116
#define IDI_DBG_EMPTY
int vsprintf(idStr &string, const char *fmt, va_list argptr)
Definition: Str.cpp:1549
const char * c_str(void) const
Definition: Str.h:487
bool RemoveBreakpoint(int bpID)
#define FALSE
Definition: mprintf.c:70
int HandleInitMenu(WPARAM wParam, LPARAM lParam)
bool FindNext(const char *text=NULL)
#define ID_DBG_EDIT_FINDPREV
int GetRecentFileCount(void)
void StepOver(void)
const char * GetRecentFile(int index)
bool IsConnected(void)
#define TRUE
Definition: mprintf.c:69
int GetBreakpointCount(void)
GLint j
Definition: qgl.h:264
void UpdateCallstack(void)
int AddBreakpoint(const char *filename, int lineNumber, bool onceOnly=false)
#define IDC_DBG_WATCH
#define IDC_DBG_THREADS
#define ID_DBG_EDIT_FINDNEXT
if(!ValidDisplayID(prefInfo.prefDisplayID)) prefInfo.prefDisplayID
char * va(const char *fmt,...)
Definition: Str.cpp:1568
rvDebuggerThreadList & GetThreads(void)
static int CALLBACK ScriptWordBreakProc(LPTSTR text, int current, int max, int action)
rvDebuggerApp gDebuggerApp
Definition: debugger.cpp:38
#define max(x, y)
Definition: os.h:70
#define ID_DBG_WINDOWMIN
#define IDC_DBG_SCRIPT
#define IDI_DBG_CURRENT
bool DoModal(HWND parent)
void ProcessNetMessage(msg_t *msg)
static LRESULT CALLBACK WndProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
void ToggleBreakpoint(void)
const char * GetString(const char *name)
GLuint start
Definition: glext.h:2845
bool Remove(const type &obj)
Definition: List.h:878
void SetFilter(const char *filter)
#define IDD_DBG_ABOUT
void UpdateWatch(void)
#define ID_DBG_FILE_OPEN
void UpdateRecentFiles(void)
GLdouble GLdouble t
Definition: glext.h:2943
void Clear(void)
Definition: List.h:184