1 #ifndef __cxxtest__Win32Gui_h__
2 #define __cxxtest__Win32Gui_h__
4 //
5 // The Win32Gui displays a simple progress bar using the Win32 API.
6 //
7 // It accepts the following command line options:
8 // -minimized Start minimized, pop up on error
9 // -keep Don't close the window at the end
10 // -title TITLE Set the window caption
11 //
12 // If both -minimized and -keep are specified, GUI will only keep the
13 // window if it's in focus.
14 //
15 // N.B. If you're wondering why this class doesn't use any standard
16 // library or STL (<string> would have been nice) it's because it only
17 // uses "straight" Win32 API.
18 //
20 #include <cxxtest/Gui.h>
22 #include <windows.h>
23 #include <commctrl.h>
25 namespace CxxTest
26 {
27 class Win32Gui : public GuiListener
28 {
29 public:
30 void enterGui( int &argc, char **argv )
31 {
32 parseCommandLine( argc, argv );
33 }
35 void enterWorld( const WorldDescription &wd )
36 {
37 getTotalTests( wd );
38 _testsDone = 0;
39 startGuiThread();
40 }
42 void guiEnterSuite( const char *suiteName )
43 {
44 showSuiteName( suiteName );
45 reset( _suiteStart );
46 }
48 void guiEnterTest( const char *suiteName, const char *testName )
49 {
50 ++ _testsDone;
51 setTestCaption( suiteName, testName );
52 showTestName( testName );
53 showTestsDone();
54 progressBarMessage( PBM_STEPIT );
55 reset( _testStart );
56 }
58 void yellowBar()
59 {
60 setColor( 255, 255, 0 );
61 setIcon( IDI_WARNING );
62 getTotalTests();
63 }
65 void redBar()
66 {
67 if ( _startMinimized )
68 showMainWindow( SW_SHOWNORMAL );
69 setColor( 255, 0, 0 );
70 setIcon( IDI_ERROR );
71 getTotalTests();
72 }
74 void leaveGui()
75 {
76 if ( keep() )
77 {
78 showSummary();
79 WaitForSingleObject( _gui, INFINITE );
80 }
81 DestroyWindow( _mainWindow );
82 }
84 private:
85 const char *_title;
86 bool _startMinimized, _keep;
87 HANDLE _gui;
88 WNDCLASSEX _windowClass;
89 HWND _mainWindow, _progressBar, _statusBar;
90 HANDLE _canStartTests;
91 unsigned _numTotalTests, _testsDone;
92 char _strTotalTests[WorldDescription::MAX_STRLEN_TOTAL_TESTS];
93 enum {
94 STATUS_SUITE_NAME, STATUS_SUITE_TIME,
95 STATUS_TEST_NAME, STATUS_TEST_TIME,
96 STATUS_TESTS_DONE, STATUS_WORLD_TIME,
97 STATUS_TOTAL_PARTS
98 };
99 int _statusWidths[STATUS_TOTAL_PARTS];
100 unsigned _statusOffsets[STATUS_TOTAL_PARTS];
101 unsigned _statusTotal;
102 char _statusTestsDone[sizeof("1000000000 of (100%)") + WorldDescription::MAX_STRLEN_TOTAL_TESTS];
103 DWORD _worldStart, _suiteStart, _testStart;
104 char _timeString[sizeof("00:00:00")];
106 void parseCommandLine( int argc, char **argv )
107 {
108 _startMinimized = _keep = false;
109 _title = argv[0];
111 for ( int i = 1; i < argc; ++ i )
112 {
113 if ( !lstrcmpA( argv[i], "-minimized" ) )
114 _startMinimized = true;
115 else if ( !lstrcmpA( argv[i], "-keep" ) )
116 _keep = true;
117 else if ( !lstrcmpA( argv[i], "-title" ) && (i + 1 < argc) )
118 _title = argv[++i];
119 }
120 }
122 void getTotalTests()
123 {
124 getTotalTests( tracker().world() );
125 }
127 void getTotalTests( const WorldDescription &wd )
128 {
129 _numTotalTests = wd.numTotalTests();
130 wd.strTotalTests( _strTotalTests );
131 }
133 void startGuiThread()
134 {
135 _canStartTests = CreateEvent( NULL, TRUE, FALSE, NULL );
136 DWORD threadId;
137 _gui = CreateThread( NULL, 0, &(Win32Gui::guiThread), (LPVOID)this, 0, &threadId );
138 WaitForSingleObject( _canStartTests, INFINITE );
139 }
141 static DWORD WINAPI guiThread( LPVOID parameter )
142 {
143 ((Win32Gui *)parameter)->gui();
144 return 0;
145 }
147 void gui()
148 {
149 registerWindowClass();
150 createMainWindow();
151 initCommonControls();
152 createProgressBar();
153 createStatusBar();
154 centerMainWindow();
155 showMainWindow();
156 startTimer();
157 startTests();
159 messageLoop();
160 }
162 void registerWindowClass()
163 {
164 _windowClass.cbSize = sizeof(_windowClass);
165 _windowClass.style = CS_HREDRAW | CS_VREDRAW;
166 _windowClass.lpfnWndProc = &(Win32Gui::windowProcedure);
167 _windowClass.cbClsExtra = 0;
168 _windowClass.cbWndExtra = sizeof(LONG);
169 _windowClass.hInstance = (HINSTANCE)NULL;
170 _windowClass.hIcon = (HICON)NULL;
171 _windowClass.hCursor = (HCURSOR)NULL;
172 _windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
173 _windowClass.lpszMenuName = NULL;
174 _windowClass.lpszClassName = TEXT("CxxTest Window Class");
175 _windowClass.hIconSm = (HICON)NULL;
177 RegisterClassEx( &_windowClass );
178 }
180 void createMainWindow()
181 {
182 _mainWindow = createWindow( _windowClass.lpszClassName, WS_OVERLAPPEDWINDOW );
183 }
185 void initCommonControls()
186 {
187 HMODULE dll = LoadLibraryA( "comctl32.dll" );
188 if ( !dll )
189 return;
191 typedef void (WINAPI *FUNC)( void );
192 FUNC func = (FUNC)GetProcAddress( dll, "InitCommonControls" );
193 if ( !func )
194 return;
196 func();
197 }
199 void createProgressBar()
200 {
201 _progressBar = createWindow( PROGRESS_CLASS, WS_CHILD | WS_VISIBLE | PBS_SMOOTH, _mainWindow );
203 #ifdef PBM_SETRANGE32
204 progressBarMessage( PBM_SETRANGE32, 0, _numTotalTests );
205 #else // No PBM_SETRANGE32, use PBM_SETRANGE
206 progressBarMessage( PBM_SETRANGE, 0, MAKELPARAM( 0, (WORD)_numTotalTests ) );
207 #endif // PBM_SETRANGE32
208 progressBarMessage( PBM_SETPOS, 0 );
209 progressBarMessage( PBM_SETSTEP, 1 );
210 greenBar();
211 UpdateWindow( _progressBar );
212 }
214 void createStatusBar()
215 {
216 _statusBar = createWindow( STATUSCLASSNAME, WS_CHILD | WS_VISIBLE, _mainWindow );
217 setRatios( 4, 1, 3, 1, 3, 1 );
218 }
220 void setRatios( unsigned suiteNameRatio, unsigned suiteTimeRatio,
221 unsigned testNameRatio, unsigned testTimeRatio,
222 unsigned testsDoneRatio, unsigned worldTimeRatio )
223 {
224 _statusTotal = 0;
225 _statusOffsets[STATUS_SUITE_NAME] = (_statusTotal += suiteNameRatio);
226 _statusOffsets[STATUS_SUITE_TIME] = (_statusTotal += suiteTimeRatio);
227 _statusOffsets[STATUS_TEST_NAME] = (_statusTotal += testNameRatio);
228 _statusOffsets[STATUS_TEST_TIME] = (_statusTotal += testTimeRatio);
229 _statusOffsets[STATUS_TESTS_DONE] = (_statusTotal += testsDoneRatio);
230 _statusOffsets[STATUS_WORLD_TIME] = (_statusTotal += worldTimeRatio);
231 }
233 HWND createWindow( LPCTSTR className, DWORD style, HWND parent = (HWND)NULL )
234 {
235 return CreateWindow( className, NULL, style, 0, 0, 0, 0, parent,
236 (HMENU)NULL, (HINSTANCE)NULL, (LPVOID)this );
237 }
239 void progressBarMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 )
240 {
241 SendMessage( _progressBar, message, wParam, lParam );
242 }
244 void centerMainWindow()
245 {
246 RECT screen;
247 getScreenArea( screen );
249 LONG screenWidth = screen.right - screen.left;
250 LONG screenHeight = screen.bottom - screen.top;
252 LONG xCenter = (screen.right + screen.left) / 2;
253 LONG yCenter = (screen.bottom + screen.top) / 2;
255 LONG windowWidth = (screenWidth * 4) / 5;
256 LONG windowHeight = screenHeight / 10;
257 LONG minimumHeight = 2 * (GetSystemMetrics( SM_CYCAPTION ) + GetSystemMetrics( SM_CYFRAME ));
258 if ( windowHeight < minimumHeight )
259 windowHeight = minimumHeight;
261 SetWindowPos( _mainWindow, HWND_TOP,
262 xCenter - (windowWidth / 2), yCenter - (windowHeight / 2),
263 windowWidth, windowHeight, 0 );
264 }
266 void getScreenArea( RECT &area )
267 {
268 if ( !getScreenAreaWithoutTaskbar( area ) )
269 getWholeScreenArea( area );
270 }
272 bool getScreenAreaWithoutTaskbar( RECT &area )
273 {
274 return (SystemParametersInfo( SPI_GETWORKAREA, sizeof(RECT), &area, 0 ) != 0);
275 }
277 void getWholeScreenArea( RECT &area )
278 {
279 area.left = area.top = 0;
280 area.right = GetSystemMetrics( SM_CXSCREEN );
281 area.bottom = GetSystemMetrics( SM_CYSCREEN );
282 }
284 void showMainWindow()
285 {
286 showMainWindow( _startMinimized ? SW_MINIMIZE : SW_SHOWNORMAL );
287 UpdateWindow( _mainWindow );
288 }
290 void showMainWindow( int mode )
291 {
292 ShowWindow( _mainWindow, mode );
293 }
295 enum { TIMER_ID = 1, TIMER_DELAY = 1000 };
297 void startTimer()
298 {
299 reset( _worldStart );
300 reset( _suiteStart );
301 reset( _testStart );
302 SetTimer( _mainWindow, TIMER_ID, TIMER_DELAY, 0 );
303 }
305 void reset( DWORD &tick )
306 {
307 tick = GetTickCount();
308 }
310 void startTests()
311 {
312 SetEvent( _canStartTests );
313 }
315 void messageLoop()
316 {
317 MSG message;
318 while ( BOOL haveMessage = GetMessage( &message, NULL, 0, 0 ) )
319 if ( haveMessage != -1 )
320 DispatchMessage( &message );
321 }
323 static LRESULT CALLBACK windowProcedure( HWND window, UINT message, WPARAM wParam, LPARAM lParam )
324 {
325 if ( message == WM_CREATE )
326 setUp( window, (LPCREATESTRUCT)lParam );
328 Win32Gui *that = (Win32Gui *)GetWindowLong( window, GWL_USERDATA );
329 return that->handle( window, message, wParam, lParam );
330 }
332 static void setUp( HWND window, LPCREATESTRUCT create )
333 {
334 SetWindowLong( window, GWL_USERDATA, (LONG)create->lpCreateParams );
335 }
337 LRESULT handle( HWND window, UINT message, WPARAM wParam, LPARAM lParam )
338 {
339 switch ( message )
340 {
341 case WM_SIZE: resizeControls(); break;
343 case WM_TIMER: updateTime(); break;
345 case WM_CLOSE:
346 case WM_DESTROY:
347 case WM_QUIT:
348 ExitProcess( 0 );
350 default: return DefWindowProc( window, message, wParam, lParam );
351 }
352 return 0;
353 }
355 void resizeControls()
356 {
357 RECT r;
358 GetClientRect( _mainWindow, &r );
359 LONG width = r.right - r.left;
360 LONG height = r.bottom - r.top;
362 GetClientRect( _statusBar, &r );
363 LONG statusHeight = r.bottom - r.top;
364 LONG resizeGripWidth = statusHeight;
365 LONG progressHeight = height - statusHeight;
367 SetWindowPos( _progressBar, HWND_TOP, 0, 0, width, progressHeight, 0 );
368 SetWindowPos( _statusBar, HWND_TOP, 0, progressHeight, width, statusHeight, 0 );
369 setStatusParts( width - resizeGripWidth );
370 }
372 void setStatusParts( LONG width )
373 {
374 for ( unsigned i = 0; i < STATUS_TOTAL_PARTS; ++ i )
375 _statusWidths[i] = (width * _statusOffsets[i]) / _statusTotal;
377 statusBarMessage( SB_SETPARTS, STATUS_TOTAL_PARTS, _statusWidths );
378 }
380 void statusBarMessage( UINT message, WPARAM wParam = 0, const void *lParam = 0 )
381 {
382 SendMessage( _statusBar, message, wParam, (LPARAM)lParam );
383 }
385 void greenBar()
386 {
387 setColor( 0, 255, 0 );
388 setIcon( IDI_INFORMATION );
389 }
391 #ifdef PBM_SETBARCOLOR
392 void setColor( BYTE red, BYTE green, BYTE blue )
393 {
394 progressBarMessage( PBM_SETBARCOLOR, 0, RGB( red, green, blue ) );
395 }
396 #else // !PBM_SETBARCOLOR
397 void setColor( BYTE, BYTE, BYTE )
398 {
399 }
400 #endif // PBM_SETBARCOLOR
402 void setIcon( LPCTSTR icon )
403 {
404 SendMessage( _mainWindow, WM_SETICON, ICON_BIG, (LPARAM)loadStandardIcon( icon ) );
405 }
407 HICON loadStandardIcon( LPCTSTR icon )
408 {
409 return LoadIcon( (HINSTANCE)NULL, icon );
410 }
412 void setTestCaption( const char *suiteName, const char *testName )
413 {
414 setCaption( suiteName, "::", testName, "()" );
415 }
417 void setCaption( const char *a = "", const char *b = "", const char *c = "", const char *d = "" )
418 {
419 unsigned length = lstrlenA( _title ) + sizeof( " - " ) +
420 lstrlenA( a ) + lstrlenA( b ) + lstrlenA( c ) + lstrlenA( d );
421 char *name = allocate( length );
422 lstrcpyA( name, _title );
423 lstrcatA( name, " - " );
424 lstrcatA( name, a );
425 lstrcatA( name, b );
426 lstrcatA( name, c );
427 lstrcatA( name, d );
428 SetWindowTextA( _mainWindow, name );
429 deallocate( name );
430 }
432 void showSuiteName( const char *suiteName )
433 {
434 setStatusPart( STATUS_SUITE_NAME, suiteName );
435 }
437 void showTestName( const char *testName )
438 {
439 setStatusPart( STATUS_TEST_NAME, testName );
440 }
442 void showTestsDone()
443 {
444 wsprintfA( _statusTestsDone, "%u of %s (%u%%)",
445 _testsDone, _strTotalTests,
446 (_testsDone * 100) / _numTotalTests );
447 setStatusPart( STATUS_TESTS_DONE, _statusTestsDone );
448 }
450 void updateTime()
451 {
452 setStatusTime( STATUS_WORLD_TIME, _worldStart );
453 setStatusTime( STATUS_SUITE_TIME, _suiteStart );
454 setStatusTime( STATUS_TEST_TIME, _testStart );
455 }
457 void setStatusTime( unsigned part, DWORD start )
458 {
459 unsigned total = (GetTickCount() - start) / 1000;
460 unsigned hours = total / 3600;
461 unsigned minutes = (total / 60) % 60;
462 unsigned seconds = total % 60;
464 if ( hours )
465 wsprintfA( _timeString, "%u:%02u:%02u", hours, minutes, seconds );
466 else
467 wsprintfA( _timeString, "%02u:%02u", minutes, seconds );
469 setStatusPart( part, _timeString );
470 }
472 bool keep()
473 {
474 if ( !_keep )
475 return false;
476 if ( !_startMinimized )
477 return true;
478 return (_mainWindow == GetForegroundWindow());
479 }
481 void showSummary()
482 {
483 stopTimer();
484 setSummaryStatusBar();
485 setSummaryCaption();
486 }
488 void setStatusPart( unsigned part, const char *text )
489 {
490 statusBarMessage( SB_SETTEXTA, part, text );
491 }
493 void stopTimer()
494 {
495 KillTimer( _mainWindow, TIMER_ID );
496 setStatusTime( STATUS_WORLD_TIME, _worldStart );
497 }
499 void setSummaryStatusBar()
500 {
501 setRatios( 0, 0, 0, 0, 1, 1 );
502 resizeControls();
504 const char *tests = (_numTotalTests == 1) ? "test" : "tests";
505 if ( tracker().failedTests() )
506 wsprintfA( _statusTestsDone, "Failed %u of %s %s",
507 tracker().failedTests(), _strTotalTests, tests );
508 else
509 wsprintfA( _statusTestsDone, "%s %s passed", _strTotalTests, tests );
511 setStatusPart( STATUS_TESTS_DONE, _statusTestsDone );
512 }
514 void setSummaryCaption()
515 {
516 setCaption( _statusTestsDone );
517 }
519 char *allocate( unsigned length )
520 {
521 return (char *)HeapAlloc( GetProcessHeap(), 0, length );
522 }
524 void deallocate( char *data )
525 {
526 HeapFree( GetProcessHeap(), 0, data );
527 }
528 };
529 };
531 #endif // __cxxtest__Win32Gui_h__