1 #ifndef __cxxtest__Win32Gui_h__
2 #define __cxxtest__Win32Gui_h__
3 
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 //
19 
20 #include <cxxtest/Gui.h>
21 
22 #include <windows.h>
23 #include <commctrl.h>
24 
25 namespace CxxTest
26 {
27     class Win32Gui : public GuiListener
28     {
29     public:
enterGui(int & argc,char ** argv)30         void enterGui( int &argc, char **argv )
31 	{
32 	    parseCommandLine( argc, argv );
33 	}
34 
enterWorld(const WorldDescription & wd)35         void enterWorld( const WorldDescription &wd )
36         {
37             getTotalTests( wd );
38             _testsDone = 0;
39             startGuiThread();
40         }
41 
guiEnterSuite(const char * suiteName)42 	void guiEnterSuite( const char *suiteName )
43 	{
44 	    showSuiteName( suiteName );
45             reset( _suiteStart );
46 	}
47 
guiEnterTest(const char * suiteName,const char * testName)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         }
57 
yellowBar()58         void yellowBar()
59         {
60 	    setColor( 255, 255, 0 );
61             setIcon( IDI_WARNING );
62             getTotalTests();
63         }
64 
redBar()65         void redBar()
66         {
67             if ( _startMinimized )
68                 showMainWindow( SW_SHOWNORMAL );
69 	    setColor( 255, 0, 0 );
70 	    setIcon( IDI_ERROR );
71             getTotalTests();
72         }
73 
leaveGui()74         void leaveGui()
75         {
76             if ( keep() )
77             {
78                 showSummary();
79                 WaitForSingleObject( _gui, INFINITE );
80             }
81             DestroyWindow( _mainWindow );
82         }
83 
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")];
105 
parseCommandLine(int argc,char ** argv)106         void parseCommandLine( int argc, char **argv )
107         {
108             _startMinimized = _keep = false;
109 	    _title = argv[0];
110 
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         }
121 
getTotalTests()122         void getTotalTests()
123         {
124             getTotalTests( tracker().world() );
125         }
126 
getTotalTests(const WorldDescription & wd)127         void getTotalTests( const WorldDescription &wd )
128         {
129             _numTotalTests = wd.numTotalTests();
130             wd.strTotalTests( _strTotalTests );
131         }
132 
startGuiThread()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         }
140 
guiThread(LPVOID parameter)141         static DWORD WINAPI guiThread( LPVOID parameter )
142         {
143             ((Win32Gui *)parameter)->gui();
144             return 0;
145         }
146 
gui()147         void gui()
148         {
149             registerWindowClass();
150             createMainWindow();
151             initCommonControls();
152             createProgressBar();
153             createStatusBar();
154             centerMainWindow();
155             showMainWindow();
156             startTimer();
157             startTests();
158 
159             messageLoop();
160         }
161 
registerWindowClass()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;
176 
177             RegisterClassEx( &_windowClass );
178         }
179 
createMainWindow()180         void createMainWindow()
181         {
182             _mainWindow = createWindow( _windowClass.lpszClassName, WS_OVERLAPPEDWINDOW );
183         }
184 
initCommonControls()185         void initCommonControls()
186         {
187             HMODULE dll = LoadLibraryA( "comctl32.dll" );
188             if ( !dll )
189 		return;
190 
191 	    typedef void (WINAPI *FUNC)( void );
192 	    FUNC func = (FUNC)GetProcAddress( dll, "InitCommonControls" );
193 	    if ( !func )
194                 return;
195 
196 	    func();
197         }
198 
createProgressBar()199         void createProgressBar()
200         {
201             _progressBar = createWindow( PROGRESS_CLASS, WS_CHILD | WS_VISIBLE | PBS_SMOOTH, _mainWindow );
202 
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         }
213 
createStatusBar()214         void createStatusBar()
215         {
216             _statusBar = createWindow( STATUSCLASSNAME, WS_CHILD | WS_VISIBLE, _mainWindow );
217             setRatios( 4, 1, 3, 1, 3, 1 );
218         }
219 
setRatios(unsigned suiteNameRatio,unsigned suiteTimeRatio,unsigned testNameRatio,unsigned testTimeRatio,unsigned testsDoneRatio,unsigned worldTimeRatio)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         }
232 
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         }
238 
239         void progressBarMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 )
240         {
241             SendMessage( _progressBar, message, wParam, lParam );
242         }
243 
centerMainWindow()244         void centerMainWindow()
245         {
246             RECT screen;
247             getScreenArea( screen );
248 
249             LONG screenWidth = screen.right - screen.left;
250             LONG screenHeight = screen.bottom - screen.top;
251 
252             LONG xCenter = (screen.right + screen.left) / 2;
253             LONG yCenter = (screen.bottom + screen.top) / 2;
254 
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;
260 
261             SetWindowPos( _mainWindow, HWND_TOP,
262                           xCenter - (windowWidth / 2), yCenter - (windowHeight / 2),
263                           windowWidth, windowHeight, 0 );
264         }
265 
getScreenArea(RECT & area)266         void getScreenArea( RECT &area )
267         {
268             if ( !getScreenAreaWithoutTaskbar( area ) )
269                 getWholeScreenArea( area );
270         }
271 
getScreenAreaWithoutTaskbar(RECT & area)272         bool getScreenAreaWithoutTaskbar( RECT &area )
273         {
274             return (SystemParametersInfo( SPI_GETWORKAREA, sizeof(RECT), &area, 0 ) != 0);
275         }
276 
getWholeScreenArea(RECT & area)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         }
283 
showMainWindow()284         void showMainWindow()
285         {
286             showMainWindow( _startMinimized ? SW_MINIMIZE : SW_SHOWNORMAL );
287             UpdateWindow( _mainWindow );
288         }
289 
showMainWindow(int mode)290         void showMainWindow( int mode )
291         {
292             ShowWindow( _mainWindow, mode );
293         }
294 
295         enum { TIMER_ID = 1, TIMER_DELAY = 1000 };
296 
startTimer()297         void startTimer()
298         {
299             reset( _worldStart );
300             reset( _suiteStart );
301             reset( _testStart );
302             SetTimer( _mainWindow, TIMER_ID, TIMER_DELAY, 0 );
303         }
304 
reset(DWORD & tick)305         void reset( DWORD &tick )
306         {
307             tick = GetTickCount();
308         }
309 
startTests()310         void startTests()
311         {
312             SetEvent( _canStartTests );
313         }
314 
messageLoop()315         void messageLoop()
316         {
317             MSG message;
318             while ( BOOL haveMessage = GetMessage( &message, NULL, 0, 0 ) )
319                 if ( haveMessage != -1 )
320                     DispatchMessage( &message );
321         }
322 
windowProcedure(HWND window,UINT message,WPARAM wParam,LPARAM lParam)323         static LRESULT CALLBACK windowProcedure( HWND window, UINT message, WPARAM wParam, LPARAM lParam )
324         {
325             if ( message == WM_CREATE )
326                 setUp( window, (LPCREATESTRUCT)lParam );
327 
328             Win32Gui *that = (Win32Gui *)GetWindowLong( window, GWL_USERDATA );
329             return that->handle( window, message, wParam, lParam );
330         }
331 
setUp(HWND window,LPCREATESTRUCT create)332         static void setUp( HWND window, LPCREATESTRUCT create )
333         {
334             SetWindowLong( window, GWL_USERDATA, (LONG)create->lpCreateParams );
335         }
336 
handle(HWND window,UINT message,WPARAM wParam,LPARAM lParam)337         LRESULT handle( HWND window, UINT message, WPARAM wParam, LPARAM lParam )
338         {
339             switch ( message )
340             {
341             case WM_SIZE: resizeControls(); break;
342 
343             case WM_TIMER: updateTime(); break;
344 
345             case WM_CLOSE:
346             case WM_DESTROY:
347             case WM_QUIT:
348                 ExitProcess( 0 );
349 
350             default: return DefWindowProc( window, message, wParam, lParam );
351             }
352             return 0;
353         }
354 
resizeControls()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;
361 
362             GetClientRect( _statusBar, &r );
363             LONG statusHeight = r.bottom - r.top;
364             LONG resizeGripWidth = statusHeight;
365             LONG progressHeight = height - statusHeight;
366 
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         }
371 
setStatusParts(LONG width)372         void setStatusParts( LONG width )
373         {
374             for ( unsigned i = 0; i < STATUS_TOTAL_PARTS; ++ i )
375                 _statusWidths[i] = (width * _statusOffsets[i]) / _statusTotal;
376 
377             statusBarMessage( SB_SETPARTS, STATUS_TOTAL_PARTS, _statusWidths );
378         }
379 
380         void statusBarMessage( UINT message, WPARAM wParam = 0, const void *lParam = 0 )
381         {
382             SendMessage( _statusBar, message, wParam, (LPARAM)lParam );
383         }
384 
greenBar()385         void greenBar()
386         {
387             setColor( 0, 255, 0 );
388             setIcon( IDI_INFORMATION );
389         }
390 
391 #ifdef PBM_SETBARCOLOR
setColor(BYTE red,BYTE green,BYTE blue)392         void setColor( BYTE red, BYTE green, BYTE blue )
393         {
394             progressBarMessage( PBM_SETBARCOLOR, 0, RGB( red, green, blue ) );
395         }
396 #else // !PBM_SETBARCOLOR
setColor(BYTE,BYTE,BYTE)397         void setColor( BYTE, BYTE, BYTE )
398         {
399         }
400 #endif // PBM_SETBARCOLOR
401 
setIcon(LPCTSTR icon)402         void setIcon( LPCTSTR icon )
403         {
404             SendMessage( _mainWindow, WM_SETICON, ICON_BIG, (LPARAM)loadStandardIcon( icon ) );
405         }
406 
loadStandardIcon(LPCTSTR icon)407         HICON loadStandardIcon( LPCTSTR icon )
408         {
409             return LoadIcon( (HINSTANCE)NULL, icon );
410         }
411 
setTestCaption(const char * suiteName,const char * testName)412         void setTestCaption( const char *suiteName, const char *testName )
413         {
414             setCaption( suiteName, "::", testName, "()" );
415         }
416 
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         }
431 
showSuiteName(const char * suiteName)432         void showSuiteName( const char *suiteName )
433         {
434             setStatusPart( STATUS_SUITE_NAME, suiteName );
435 	}
436 
showTestName(const char * testName)437 	void showTestName( const char *testName )
438 	{
439             setStatusPart( STATUS_TEST_NAME, testName );
440 	}
441 
showTestsDone()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         }
449 
updateTime()450         void updateTime()
451         {
452             setStatusTime( STATUS_WORLD_TIME, _worldStart );
453             setStatusTime( STATUS_SUITE_TIME, _suiteStart );
454             setStatusTime( STATUS_TEST_TIME, _testStart );
455         }
456 
setStatusTime(unsigned part,DWORD start)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;
463 
464             if ( hours )
465                 wsprintfA( _timeString, "%u:%02u:%02u", hours, minutes, seconds );
466             else
467                 wsprintfA( _timeString, "%02u:%02u", minutes, seconds );
468 
469             setStatusPart( part, _timeString );
470         }
471 
keep()472         bool keep()
473         {
474             if ( !_keep )
475                 return false;
476             if ( !_startMinimized )
477                 return true;
478             return (_mainWindow == GetForegroundWindow());
479         }
480 
showSummary()481         void showSummary()
482         {
483             stopTimer();
484             setSummaryStatusBar();
485             setSummaryCaption();
486         }
487 
setStatusPart(unsigned part,const char * text)488         void setStatusPart( unsigned part, const char *text )
489         {
490             statusBarMessage( SB_SETTEXTA, part, text );
491         }
492 
stopTimer()493         void stopTimer()
494         {
495             KillTimer( _mainWindow, TIMER_ID );
496             setStatusTime( STATUS_WORLD_TIME, _worldStart );
497         }
498 
setSummaryStatusBar()499         void setSummaryStatusBar()
500         {
501             setRatios( 0, 0, 0, 0, 1, 1 );
502             resizeControls();
503 
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 );
510 
511             setStatusPart( STATUS_TESTS_DONE, _statusTestsDone );
512         }
513 
setSummaryCaption()514         void setSummaryCaption()
515         {
516             setCaption( _statusTestsDone );
517         }
518 
allocate(unsigned length)519         char *allocate( unsigned length )
520         {
521             return (char *)HeapAlloc( GetProcessHeap(), 0, length );
522         }
523 
deallocate(char * data)524         void deallocate( char *data )
525         {
526             HeapFree( GetProcessHeap(), 0, data );
527         }
528     };
529 }
530 
531 #endif // __cxxtest__Win32Gui_h__
532