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