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