#ifndef __cxxtest__Win32Gui_h__ #define __cxxtest__Win32Gui_h__ // // The Win32Gui displays a simple progress bar using the Win32 API. // // It accepts the following command line options: // -minimized Start minimized, pop up on error // -keep Don't close the window at the end // -title TITLE Set the window caption // // If both -minimized and -keep are specified, GUI will only keep the // window if it's in focus. // // N.B. If you're wondering why this class doesn't use any standard // library or STL ( would have been nice) it's because it only // uses "straight" Win32 API. // #include #include #include namespace CxxTest { class Win32Gui : public GuiListener { public: void enterGui( int &argc, char **argv ) { parseCommandLine( argc, argv ); } void enterWorld( const WorldDescription &wd ) { getTotalTests( wd ); _testsDone = 0; startGuiThread(); } void guiEnterSuite( const char *suiteName ) { showSuiteName( suiteName ); reset( _suiteStart ); } void guiEnterTest( const char *suiteName, const char *testName ) { ++ _testsDone; setTestCaption( suiteName, testName ); showTestName( testName ); showTestsDone(); progressBarMessage( PBM_STEPIT ); reset( _testStart ); } void yellowBar() { setColor( 255, 255, 0 ); setIcon( IDI_WARNING ); getTotalTests(); } void redBar() { if ( _startMinimized ) showMainWindow( SW_SHOWNORMAL ); setColor( 255, 0, 0 ); setIcon( IDI_ERROR ); getTotalTests(); } void leaveGui() { if ( keep() ) { showSummary(); WaitForSingleObject( _gui, INFINITE ); } DestroyWindow( _mainWindow ); } private: const char *_title; bool _startMinimized, _keep; HANDLE _gui; WNDCLASSEX _windowClass; HWND _mainWindow, _progressBar, _statusBar; HANDLE _canStartTests; unsigned _numTotalTests, _testsDone; char _strTotalTests[WorldDescription::MAX_STRLEN_TOTAL_TESTS]; enum { STATUS_SUITE_NAME, STATUS_SUITE_TIME, STATUS_TEST_NAME, STATUS_TEST_TIME, STATUS_TESTS_DONE, STATUS_WORLD_TIME, STATUS_TOTAL_PARTS }; int _statusWidths[STATUS_TOTAL_PARTS]; unsigned _statusOffsets[STATUS_TOTAL_PARTS]; unsigned _statusTotal; char _statusTestsDone[sizeof("1000000000 of (100%)") + WorldDescription::MAX_STRLEN_TOTAL_TESTS]; DWORD _worldStart, _suiteStart, _testStart; char _timeString[sizeof("00:00:00")]; void parseCommandLine( int argc, char **argv ) { _startMinimized = _keep = false; _title = argv[0]; for ( int i = 1; i < argc; ++ i ) { if ( !lstrcmpA( argv[i], "-minimized" ) ) _startMinimized = true; else if ( !lstrcmpA( argv[i], "-keep" ) ) _keep = true; else if ( !lstrcmpA( argv[i], "-title" ) && (i + 1 < argc) ) _title = argv[++i]; } } void getTotalTests() { getTotalTests( tracker().world() ); } void getTotalTests( const WorldDescription &wd ) { _numTotalTests = wd.numTotalTests(); wd.strTotalTests( _strTotalTests ); } void startGuiThread() { _canStartTests = CreateEvent( NULL, TRUE, FALSE, NULL ); DWORD threadId; _gui = CreateThread( NULL, 0, &(Win32Gui::guiThread), (LPVOID)this, 0, &threadId ); WaitForSingleObject( _canStartTests, INFINITE ); } static DWORD WINAPI guiThread( LPVOID parameter ) { ((Win32Gui *)parameter)->gui(); return 0; } void gui() { registerWindowClass(); createMainWindow(); initCommonControls(); createProgressBar(); createStatusBar(); centerMainWindow(); showMainWindow(); startTimer(); startTests(); messageLoop(); } void registerWindowClass() { _windowClass.cbSize = sizeof(_windowClass); _windowClass.style = CS_HREDRAW | CS_VREDRAW; _windowClass.lpfnWndProc = &(Win32Gui::windowProcedure); _windowClass.cbClsExtra = 0; _windowClass.cbWndExtra = sizeof(LONG); _windowClass.hInstance = (HINSTANCE)NULL; _windowClass.hIcon = (HICON)NULL; _windowClass.hCursor = (HCURSOR)NULL; _windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); _windowClass.lpszMenuName = NULL; _windowClass.lpszClassName = TEXT("CxxTest Window Class"); _windowClass.hIconSm = (HICON)NULL; RegisterClassEx( &_windowClass ); } void createMainWindow() { _mainWindow = createWindow( _windowClass.lpszClassName, WS_OVERLAPPEDWINDOW ); } void initCommonControls() { HMODULE dll = LoadLibraryA( "comctl32.dll" ); if ( !dll ) return; typedef void (WINAPI *FUNC)( void ); FUNC func = (FUNC)GetProcAddress( dll, "InitCommonControls" ); if ( !func ) return; func(); } void createProgressBar() { _progressBar = createWindow( PROGRESS_CLASS, WS_CHILD | WS_VISIBLE | PBS_SMOOTH, _mainWindow ); #ifdef PBM_SETRANGE32 progressBarMessage( PBM_SETRANGE32, 0, _numTotalTests ); #else // No PBM_SETRANGE32, use PBM_SETRANGE progressBarMessage( PBM_SETRANGE, 0, MAKELPARAM( 0, (WORD)_numTotalTests ) ); #endif // PBM_SETRANGE32 progressBarMessage( PBM_SETPOS, 0 ); progressBarMessage( PBM_SETSTEP, 1 ); greenBar(); UpdateWindow( _progressBar ); } void createStatusBar() { _statusBar = createWindow( STATUSCLASSNAME, WS_CHILD | WS_VISIBLE, _mainWindow ); setRatios( 4, 1, 3, 1, 3, 1 ); } void setRatios( unsigned suiteNameRatio, unsigned suiteTimeRatio, unsigned testNameRatio, unsigned testTimeRatio, unsigned testsDoneRatio, unsigned worldTimeRatio ) { _statusTotal = 0; _statusOffsets[STATUS_SUITE_NAME] = (_statusTotal += suiteNameRatio); _statusOffsets[STATUS_SUITE_TIME] = (_statusTotal += suiteTimeRatio); _statusOffsets[STATUS_TEST_NAME] = (_statusTotal += testNameRatio); _statusOffsets[STATUS_TEST_TIME] = (_statusTotal += testTimeRatio); _statusOffsets[STATUS_TESTS_DONE] = (_statusTotal += testsDoneRatio); _statusOffsets[STATUS_WORLD_TIME] = (_statusTotal += worldTimeRatio); } HWND createWindow( LPCTSTR className, DWORD style, HWND parent = (HWND)NULL ) { return CreateWindow( className, NULL, style, 0, 0, 0, 0, parent, (HMENU)NULL, (HINSTANCE)NULL, (LPVOID)this ); } void progressBarMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 ) { SendMessage( _progressBar, message, wParam, lParam ); } void centerMainWindow() { RECT screen; getScreenArea( screen ); LONG screenWidth = screen.right - screen.left; LONG screenHeight = screen.bottom - screen.top; LONG xCenter = (screen.right + screen.left) / 2; LONG yCenter = (screen.bottom + screen.top) / 2; LONG windowWidth = (screenWidth * 4) / 5; LONG windowHeight = screenHeight / 10; LONG minimumHeight = 2 * (GetSystemMetrics( SM_CYCAPTION ) + GetSystemMetrics( SM_CYFRAME )); if ( windowHeight < minimumHeight ) windowHeight = minimumHeight; SetWindowPos( _mainWindow, HWND_TOP, xCenter - (windowWidth / 2), yCenter - (windowHeight / 2), windowWidth, windowHeight, 0 ); } void getScreenArea( RECT &area ) { if ( !getScreenAreaWithoutTaskbar( area ) ) getWholeScreenArea( area ); } bool getScreenAreaWithoutTaskbar( RECT &area ) { return (SystemParametersInfo( SPI_GETWORKAREA, sizeof(RECT), &area, 0 ) != 0); } void getWholeScreenArea( RECT &area ) { area.left = area.top = 0; area.right = GetSystemMetrics( SM_CXSCREEN ); area.bottom = GetSystemMetrics( SM_CYSCREEN ); } void showMainWindow() { showMainWindow( _startMinimized ? SW_MINIMIZE : SW_SHOWNORMAL ); UpdateWindow( _mainWindow ); } void showMainWindow( int mode ) { ShowWindow( _mainWindow, mode ); } enum { TIMER_ID = 1, TIMER_DELAY = 1000 }; void startTimer() { reset( _worldStart ); reset( _suiteStart ); reset( _testStart ); SetTimer( _mainWindow, TIMER_ID, TIMER_DELAY, 0 ); } void reset( DWORD &tick ) { tick = GetTickCount(); } void startTests() { SetEvent( _canStartTests ); } void messageLoop() { MSG message; while ( BOOL haveMessage = GetMessage( &message, NULL, 0, 0 ) ) if ( haveMessage != -1 ) DispatchMessage( &message ); } static LRESULT CALLBACK windowProcedure( HWND window, UINT message, WPARAM wParam, LPARAM lParam ) { if ( message == WM_CREATE ) setUp( window, (LPCREATESTRUCT)lParam ); Win32Gui *that = (Win32Gui *)GetWindowLong( window, GWL_USERDATA ); return that->handle( window, message, wParam, lParam ); } static void setUp( HWND window, LPCREATESTRUCT create ) { SetWindowLong( window, GWL_USERDATA, (LONG)create->lpCreateParams ); } LRESULT handle( HWND window, UINT message, WPARAM wParam, LPARAM lParam ) { switch ( message ) { case WM_SIZE: resizeControls(); break; case WM_TIMER: updateTime(); break; case WM_CLOSE: case WM_DESTROY: case WM_QUIT: ExitProcess( 0 ); default: return DefWindowProc( window, message, wParam, lParam ); } return 0; } void resizeControls() { RECT r; GetClientRect( _mainWindow, &r ); LONG width = r.right - r.left; LONG height = r.bottom - r.top; GetClientRect( _statusBar, &r ); LONG statusHeight = r.bottom - r.top; LONG resizeGripWidth = statusHeight; LONG progressHeight = height - statusHeight; SetWindowPos( _progressBar, HWND_TOP, 0, 0, width, progressHeight, 0 ); SetWindowPos( _statusBar, HWND_TOP, 0, progressHeight, width, statusHeight, 0 ); setStatusParts( width - resizeGripWidth ); } void setStatusParts( LONG width ) { for ( unsigned i = 0; i < STATUS_TOTAL_PARTS; ++ i ) _statusWidths[i] = (width * _statusOffsets[i]) / _statusTotal; statusBarMessage( SB_SETPARTS, STATUS_TOTAL_PARTS, _statusWidths ); } void statusBarMessage( UINT message, WPARAM wParam = 0, const void *lParam = 0 ) { SendMessage( _statusBar, message, wParam, (LPARAM)lParam ); } void greenBar() { setColor( 0, 255, 0 ); setIcon( IDI_INFORMATION ); } #ifdef PBM_SETBARCOLOR void setColor( BYTE red, BYTE green, BYTE blue ) { progressBarMessage( PBM_SETBARCOLOR, 0, RGB( red, green, blue ) ); } #else // !PBM_SETBARCOLOR void setColor( BYTE, BYTE, BYTE ) { } #endif // PBM_SETBARCOLOR void setIcon( LPCTSTR icon ) { SendMessage( _mainWindow, WM_SETICON, ICON_BIG, (LPARAM)loadStandardIcon( icon ) ); } HICON loadStandardIcon( LPCTSTR icon ) { return LoadIcon( (HINSTANCE)NULL, icon ); } void setTestCaption( const char *suiteName, const char *testName ) { setCaption( suiteName, "::", testName, "()" ); } void setCaption( const char *a = "", const char *b = "", const char *c = "", const char *d = "" ) { unsigned length = lstrlenA( _title ) + sizeof( " - " ) + lstrlenA( a ) + lstrlenA( b ) + lstrlenA( c ) + lstrlenA( d ); char *name = allocate( length ); lstrcpyA( name, _title ); lstrcatA( name, " - " ); lstrcatA( name, a ); lstrcatA( name, b ); lstrcatA( name, c ); lstrcatA( name, d ); SetWindowTextA( _mainWindow, name ); deallocate( name ); } void showSuiteName( const char *suiteName ) { setStatusPart( STATUS_SUITE_NAME, suiteName ); } void showTestName( const char *testName ) { setStatusPart( STATUS_TEST_NAME, testName ); } void showTestsDone() { wsprintfA( _statusTestsDone, "%u of %s (%u%%)", _testsDone, _strTotalTests, (_testsDone * 100) / _numTotalTests ); setStatusPart( STATUS_TESTS_DONE, _statusTestsDone ); } void updateTime() { setStatusTime( STATUS_WORLD_TIME, _worldStart ); setStatusTime( STATUS_SUITE_TIME, _suiteStart ); setStatusTime( STATUS_TEST_TIME, _testStart ); } void setStatusTime( unsigned part, DWORD start ) { unsigned total = (GetTickCount() - start) / 1000; unsigned hours = total / 3600; unsigned minutes = (total / 60) % 60; unsigned seconds = total % 60; if ( hours ) wsprintfA( _timeString, "%u:%02u:%02u", hours, minutes, seconds ); else wsprintfA( _timeString, "%02u:%02u", minutes, seconds ); setStatusPart( part, _timeString ); } bool keep() { if ( !_keep ) return false; if ( !_startMinimized ) return true; return (_mainWindow == GetForegroundWindow()); } void showSummary() { stopTimer(); setSummaryStatusBar(); setSummaryCaption(); } void setStatusPart( unsigned part, const char *text ) { statusBarMessage( SB_SETTEXTA, part, text ); } void stopTimer() { KillTimer( _mainWindow, TIMER_ID ); setStatusTime( STATUS_WORLD_TIME, _worldStart ); } void setSummaryStatusBar() { setRatios( 0, 0, 0, 0, 1, 1 ); resizeControls(); const char *tests = (_numTotalTests == 1) ? "test" : "tests"; if ( tracker().failedTests() ) wsprintfA( _statusTestsDone, "Failed %u of %s %s", tracker().failedTests(), _strTotalTests, tests ); else wsprintfA( _statusTestsDone, "%s %s passed", _strTotalTests, tests ); setStatusPart( STATUS_TESTS_DONE, _statusTestsDone ); } void setSummaryCaption() { setCaption( _statusTestsDone ); } char *allocate( unsigned length ) { return (char *)HeapAlloc( GetProcessHeap(), 0, length ); } void deallocate( char *data ) { HeapFree( GetProcessHeap(), 0, data ); } }; } #endif // __cxxtest__Win32Gui_h__