aboutsummaryrefslogtreecommitdiff
path: root/src/samples/thmviewer/thmviewer.cpp
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-04-22 17:57:31 -0700
committerRob Mensching <rob@firegiant.com>2021-04-29 16:49:19 -0700
commit03d5c46cbbb94f73ac468709345fc6a0e50def8d (patch)
tree9460f73cc308ead863e7a28a68fdeb6d85a2e69e /src/samples/thmviewer/thmviewer.cpp
parent35606d2cd04a7b1bec1d669f9619501dff2bf9dc (diff)
downloadwix-03d5c46cbbb94f73ac468709345fc6a0e50def8d.tar.gz
wix-03d5c46cbbb94f73ac468709345fc6a0e50def8d.tar.bz2
wix-03d5c46cbbb94f73ac468709345fc6a0e50def8d.zip
Move Tools into wix
Diffstat (limited to 'src/samples/thmviewer/thmviewer.cpp')
-rw-r--r--src/samples/thmviewer/thmviewer.cpp543
1 files changed, 543 insertions, 0 deletions
diff --git a/src/samples/thmviewer/thmviewer.cpp b/src/samples/thmviewer/thmviewer.cpp
new file mode 100644
index 00000000..f83182d3
--- /dev/null
+++ b/src/samples/thmviewer/thmviewer.cpp
@@ -0,0 +1,543 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5static const LPCWSTR THMVWR_WINDOW_CLASS_MAIN = L"ThmViewerMain";
6
7static THEME* vpTheme = NULL;
8static DWORD vdwDisplayThreadId = 0;
9static LPWSTR vsczThemeLoadErrors = NULL;
10
11enum THMVWR_CONTROL
12{
13 // Non-paged controls
14 THMVWR_CONTROL_TREE = THEME_FIRST_ASSIGN_CONTROL_ID,
15};
16
17static THEME_ASSIGN_CONTROL_ID vrgInitControls[] = {
18 { THMVWR_CONTROL_TREE, L"Tree" },
19};
20
21// Internal functions
22
23static HRESULT ProcessCommandLine(
24 __in_z_opt LPCWSTR wzCommandLine,
25 __out_z LPWSTR* psczThemeFile,
26 __out_z LPWSTR* psczWxlFile
27 );
28static HRESULT CreateTheme(
29 __in HINSTANCE hInstance,
30 __out THEME** ppTheme
31 );
32static HRESULT CreateMainWindowClass(
33 __in HINSTANCE hInstance,
34 __in THEME* pTheme,
35 __out ATOM* pAtom
36 );
37static LRESULT CALLBACK MainWndProc(
38 __in HWND hWnd,
39 __in UINT uMsg,
40 __in WPARAM wParam,
41 __in LPARAM lParam
42 );
43static void OnThemeLoadBegin(
44 __in_z_opt LPWSTR sczThemeLoadErrors
45 );
46static void OnThemeLoadError(
47 __in THEME* pTheme,
48 __in HRESULT hrFailure
49 );
50static void OnNewTheme(
51 __in THEME* pTheme,
52 __in HWND hWnd,
53 __in HANDLE_THEME* pHandle
54 );
55static void CALLBACK ThmviewerTraceError(
56 __in_z LPCSTR szFile,
57 __in int iLine,
58 __in REPORT_LEVEL rl,
59 __in UINT source,
60 __in HRESULT hrError,
61 __in_z __format_string LPCSTR szFormat,
62 __in va_list args
63 );
64
65
66int WINAPI wWinMain(
67 __in HINSTANCE hInstance,
68 __in_opt HINSTANCE /* hPrevInstance */,
69 __in_z LPWSTR lpCmdLine,
70 __in int /*nCmdShow*/
71 )
72{
73 ::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
74
75 HRESULT hr = S_OK;
76 BOOL fComInitialized = FALSE;
77 LPWSTR sczThemeFile = NULL;
78 LPWSTR sczWxlFile = NULL;
79 ATOM atom = 0;
80 HWND hWnd = NULL;
81
82 HANDLE hDisplayThread = NULL;
83 HANDLE hLoadThread = NULL;
84
85 BOOL fRet = FALSE;
86 MSG msg = { };
87
88 hr = ::CoInitialize(NULL);
89 ExitOnFailure(hr, "Failed to initialize COM.");
90 fComInitialized = TRUE;
91
92 DutilInitialize(&ThmviewerTraceError);
93
94 hr = ProcessCommandLine(lpCmdLine, &sczThemeFile, &sczWxlFile);
95 ExitOnFailure(hr, "Failed to process command line.");
96
97 hr = CreateTheme(hInstance, &vpTheme);
98 ExitOnFailure(hr, "Failed to create theme.");
99
100 hr = CreateMainWindowClass(hInstance, vpTheme, &atom);
101 ExitOnFailure(hr, "Failed to create main window.");
102
103 hr = ThemeCreateParentWindow(vpTheme, 0, reinterpret_cast<LPCWSTR>(atom), vpTheme->sczCaption, vpTheme->dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, hInstance, NULL, THEME_WINDOW_INITIAL_POSITION_DEFAULT, &hWnd);
104 ExitOnFailure(hr, "Failed to create window.");
105
106 if (!sczThemeFile)
107 {
108 // Prompt for a path to the theme file.
109 OPENFILENAMEW ofn = { };
110 WCHAR wzFile[MAX_PATH] = { };
111
112 ofn.lStructSize = sizeof(ofn);
113 ofn.hwndOwner = hWnd;
114 ofn.lpstrFile = wzFile;
115 ofn.nMaxFile = countof(wzFile);
116 ofn.lpstrFilter = L"Theme Files (*.thm)\0*.thm\0XML Files (*.xml)\0*.xml\0All Files (*.*)\0*.*\0";
117 ofn.nFilterIndex = 1;
118 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
119 ofn.lpstrTitle = vpTheme->sczCaption;
120
121 if (::GetOpenFileNameW(&ofn))
122 {
123 hr = StrAllocString(&sczThemeFile, wzFile, 0);
124 ExitOnFailure(hr, "Failed to copy opened file to theme file.");
125 }
126 else
127 {
128 ::MessageBoxW(hWnd, L"Must specify a path to theme file.", vpTheme->sczCaption, MB_OK | MB_ICONERROR);
129 ExitFunction1(hr = E_INVALIDARG);
130 }
131 }
132
133 hr = DisplayStart(hInstance, hWnd, &hDisplayThread, &vdwDisplayThreadId);
134 ExitOnFailure(hr, "Failed to start display.");
135
136 hr = LoadStart(sczThemeFile, sczWxlFile, hWnd, &hLoadThread);
137 ExitOnFailure(hr, "Failed to start load.");
138
139 // message pump
140 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
141 {
142 if (-1 == fRet)
143 {
144 hr = E_UNEXPECTED;
145 ExitOnFailure(hr, "Unexpected return value from message pump.");
146 }
147 else if (!ThemeHandleKeyboardMessage(vpTheme, msg.hwnd, &msg))
148 {
149 ::TranslateMessage(&msg);
150 ::DispatchMessageW(&msg);
151 }
152 }
153
154LExit:
155 if (::IsWindow(hWnd))
156 {
157 ::DestroyWindow(hWnd);
158 }
159
160 if (hDisplayThread)
161 {
162 ::PostThreadMessageW(vdwDisplayThreadId, WM_QUIT, 0, 0);
163 ::WaitForSingleObject(hDisplayThread, 10000);
164 ::CloseHandle(hDisplayThread);
165 }
166
167 // TODO: come up with a good way to kill the load thread, probably need to switch
168 // the ReadDirectoryW() to overlapped mode.
169 ReleaseHandle(hLoadThread);
170
171 if (atom && !::UnregisterClassW(reinterpret_cast<LPCWSTR>(atom), hInstance))
172 {
173 DWORD er = ::GetLastError();
174 er = er;
175 }
176
177 ThemeFree(vpTheme);
178 ThemeUninitialize();
179 DutilUninitialize();
180
181 // uninitialize COM
182 if (fComInitialized)
183 {
184 ::CoUninitialize();
185 }
186
187 ReleaseNullStr(vsczThemeLoadErrors);
188 ReleaseStr(sczThemeFile);
189 ReleaseStr(sczWxlFile);
190 return hr;
191}
192
193static void CALLBACK ThmviewerTraceError(
194 __in_z LPCSTR /*szFile*/,
195 __in int /*iLine*/,
196 __in REPORT_LEVEL /*rl*/,
197 __in UINT source,
198 __in HRESULT hrError,
199 __in_z __format_string LPCSTR szFormat,
200 __in va_list args
201 )
202{
203 HRESULT hr = S_OK;
204 LPSTR sczFormattedAnsi = NULL;
205 LPWSTR sczMessage = NULL;
206
207 if (DUTIL_SOURCE_THMUTIL != source)
208 {
209 ExitFunction();
210 }
211
212 hr = StrAnsiAllocFormattedArgs(&sczFormattedAnsi, szFormat, args);
213 ExitOnFailure(hr, "Failed to format error log string.");
214
215 hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x: %S\r\n", hrError, sczFormattedAnsi);
216 ExitOnFailure(hr, "Failed to prepend error number to error log string.");
217
218 hr = StrAllocConcat(&vsczThemeLoadErrors, sczMessage, 0);
219 ExitOnFailure(hr, "Failed to append theme load error.");
220
221LExit:
222 ReleaseStr(sczFormattedAnsi);
223 ReleaseStr(sczMessage);
224}
225
226
227//
228// ProcessCommandLine - process the provided command line arguments.
229//
230static HRESULT ProcessCommandLine(
231 __in_z_opt LPCWSTR wzCommandLine,
232 __out_z LPWSTR* psczThemeFile,
233 __out_z LPWSTR* psczWxlFile
234 )
235{
236 HRESULT hr = S_OK;
237 int argc = 0;
238 LPWSTR* argv = NULL;
239
240 if (wzCommandLine && *wzCommandLine)
241 {
242 hr = AppParseCommandLine(wzCommandLine, &argc, &argv);
243 ExitOnFailure(hr, "Failed to parse command line.");
244
245 for (int i = 0; i < argc; ++i)
246 {
247 if (argv[i][0] == L'-' || argv[i][0] == L'/')
248 {
249 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1))
250 {
251 if (i + 1 >= argc)
252 {
253 ExitOnRootFailure(hr = E_INVALIDARG, "Must specify a language.");
254 }
255
256 ++i;
257 }
258 }
259 else
260 {
261 LPCWSTR wzExtension = PathExtension(argv[i]);
262 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzExtension, -1, L".wxl", -1))
263 {
264 hr = StrAllocString(psczWxlFile, argv[i], 0);
265 }
266 else
267 {
268 hr = StrAllocString(psczThemeFile, argv[i], 0);
269 }
270 ExitOnFailure(hr, "Failed to copy path to file.");
271 }
272 }
273 }
274
275LExit:
276 if (argv)
277 {
278 AppFreeCommandLineArgs(argv);
279 }
280
281 return hr;
282}
283
284static HRESULT CreateTheme(
285 __in HINSTANCE hInstance,
286 __out THEME** ppTheme
287 )
288{
289 HRESULT hr = S_OK;
290
291 hr = ThemeInitialize(hInstance);
292 ExitOnFailure(hr, "Failed to initialize theme manager.");
293
294 hr = ThemeLoadFromResource(hInstance, MAKEINTRESOURCEA(THMVWR_RES_THEME_FILE), ppTheme);
295 ExitOnFailure(hr, "Failed to load theme from thmviewer.thm.");
296
297LExit:
298 return hr;
299}
300
301static HRESULT CreateMainWindowClass(
302 __in HINSTANCE hInstance,
303 __in THEME* pTheme,
304 __out ATOM* pAtom
305 )
306{
307 HRESULT hr = S_OK;
308 ATOM atom = 0;
309 WNDCLASSW wc = { };
310
311 wc.lpfnWndProc = MainWndProc;
312 wc.hInstance = hInstance;
313 wc.hIcon = reinterpret_cast<HICON>(pTheme->hIcon);
314 wc.hCursor = ::LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
315 wc.hbrBackground = pTheme->rgFonts[pTheme->dwFontId].hBackground;
316 wc.lpszMenuName = NULL;
317 wc.lpszClassName = THMVWR_WINDOW_CLASS_MAIN;
318 atom = ::RegisterClassW(&wc);
319 if (!atom)
320 {
321 ExitWithLastError(hr, "Failed to register main windowclass .");
322 }
323
324 *pAtom = atom;
325
326LExit:
327 return hr;
328}
329
330static LRESULT CALLBACK MainWndProc(
331 __in HWND hWnd,
332 __in UINT uMsg,
333 __in WPARAM wParam,
334 __in LPARAM lParam
335 )
336{
337 HANDLE_THEME* pHandleTheme = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
338
339 switch (uMsg)
340 {
341 case WM_NCCREATE:
342 {
343 //LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
344 //pBA = reinterpret_cast<CWixStandardBootstrapperApplication*>(lpcs->lpCreateParams);
345 //::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pBA));
346 }
347 break;
348
349 case WM_NCDESTROY:
350 DecrementHandleTheme(pHandleTheme);
351 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
352 break;
353
354 case WM_CREATE:
355 {
356 HRESULT hr = ThemeLoadControls(vpTheme, vrgInitControls, countof(vrgInitControls));
357 if (FAILED(hr))
358 {
359 return -1;
360 }
361 }
362 break;
363
364 case WM_THMVWR_THEME_LOAD_BEGIN:
365 OnThemeLoadBegin(vsczThemeLoadErrors);
366 return 0;
367
368 case WM_THMVWR_THEME_LOAD_ERROR:
369 OnThemeLoadError(vpTheme, lParam);
370 return 0;
371
372 case WM_THMVWR_NEW_THEME:
373 OnNewTheme(vpTheme, hWnd, reinterpret_cast<HANDLE_THEME*>(lParam));
374 return 0;
375
376 case WM_DESTROY:
377 ::PostQuitMessage(0);
378 break;
379
380 case WM_NOTIFY:
381 {
382 NMHDR* pnmhdr = reinterpret_cast<NMHDR*>(lParam);
383 switch (pnmhdr->code)
384 {
385 case TVN_SELCHANGEDW:
386 {
387 NMTREEVIEWW* ptv = reinterpret_cast<NMTREEVIEWW*>(lParam);
388 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_HIDE, ptv->itemOld.lParam);
389 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_SHOW, ptv->itemNew.lParam);
390 }
391 break;
392
393 //case NM_DBLCLK:
394 // TVITEM item = { };
395 // item.mask = TVIF_PARAM;
396 // item.hItem = TreeView_GetSelection(pnmhdr->hwndFrom);
397 // TreeView_GetItem(pnmhdr->hwndFrom, &item);
398 // ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_SHOW, item.lParam);
399 // return 1;
400 }
401 }
402 break;
403 }
404
405 return ThemeDefWindowProc(vpTheme, hWnd, uMsg, wParam, lParam);
406}
407
408static void OnThemeLoadBegin(
409 __in_z_opt LPWSTR sczThemeLoadErrors
410 )
411{
412 ReleaseNullStr(sczThemeLoadErrors);
413}
414
415static void OnThemeLoadError(
416 __in THEME* pTheme,
417 __in HRESULT hrFailure
418 )
419{
420 HRESULT hr = S_OK;
421 LPWSTR sczMessage = NULL;
422 LPWSTR* psczErrors = NULL;
423 UINT cErrors = 0;
424 TVINSERTSTRUCTW tvi = { };
425
426 // Add the application node.
427 tvi.hParent = NULL;
428 tvi.hInsertAfter = TVI_ROOT;
429 tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
430 tvi.item.lParam = 0;
431 tvi.item.pszText = L"Failed to load theme.";
432 tvi.hParent = reinterpret_cast<HTREEITEM>(ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
433
434 if (!vsczThemeLoadErrors)
435 {
436 hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x.", hrFailure);
437 ExitOnFailure(hr, "Failed to format error message.");
438
439 tvi.item.pszText = sczMessage;
440 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
441
442 hr = StrAllocFromError(&sczMessage, hrFailure, NULL);
443 ExitOnFailure(hr, "Failed to format error message text.");
444
445 tvi.item.pszText = sczMessage;
446 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
447 }
448 else
449 {
450 hr = StrSplitAllocArray(&psczErrors, &cErrors, vsczThemeLoadErrors, L"\r\n");
451 ExitOnFailure(hr, "Failed to split theme load errors.");
452
453 for (DWORD i = 0; i < cErrors; ++i)
454 {
455 tvi.item.pszText = psczErrors[i];
456 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
457 }
458 }
459
460 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent));
461
462LExit:
463 ReleaseStr(sczMessage);
464 ReleaseMem(psczErrors);
465}
466
467
468static void OnNewTheme(
469 __in THEME* pTheme,
470 __in HWND hWnd,
471 __in HANDLE_THEME* pHandle
472 )
473{
474 HANDLE_THEME* pOldHandle = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
475 THEME* pNewTheme = pHandle->pTheme;
476
477 WCHAR wzSelectedPage[MAX_PATH] = { };
478 HTREEITEM htiSelected = NULL;
479 TVINSERTSTRUCTW tvi = { };
480 TVITEMW item = { };
481
482 if (pOldHandle)
483 {
484 DecrementHandleTheme(pOldHandle);
485 pOldHandle = NULL;
486 }
487
488 // Pass the new theme handle to the display thread so it can get the display window prepared
489 // to show the new theme.
490 IncrementHandleTheme(pHandle);
491 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_NEW_THEME, 0, reinterpret_cast<LPARAM>(pHandle));
492
493 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pHandle));
494
495 // Remember the currently selected item by name so we can try to automatically select it later.
496 // Otherwise, the user would see their window destroyed after every save of their theme file and
497 // have to click to get the window back.
498 item.mask = TVIF_TEXT;
499 item.pszText = wzSelectedPage;
500 item.cchTextMax = countof(wzSelectedPage);
501 item.hItem = reinterpret_cast<HTREEITEM>(ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_GETNEXTITEM, TVGN_CARET, NULL));
502 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_GETITEM, 0, reinterpret_cast<LPARAM>(&item));
503
504 // Remove the previous items in the tree.
505 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_DELETEITEM, 0, reinterpret_cast<LPARAM>(TVI_ROOT));
506
507 // Add the application node.
508 tvi.hParent = NULL;
509 tvi.hInsertAfter = TVI_ROOT;
510 tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
511 tvi.item.lParam = 0;
512 tvi.item.pszText = pHandle && pHandle->pTheme && pHandle->pTheme->sczCaption ? pHandle->pTheme->sczCaption : L"Window";
513
514 // Add the pages.
515 tvi.hParent = reinterpret_cast<HTREEITEM>(ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
516 tvi.hInsertAfter = TVI_SORT;
517 for (DWORD i = 0; i < pNewTheme->cPages; ++i)
518 {
519 THEME_PAGE* pPage = pNewTheme->rgPages + i;
520 if (pPage->sczName && *pPage->sczName)
521 {
522 tvi.item.pszText = pPage->sczName;
523 tvi.item.lParam = i + 1; //prgdwPageIds[i]; - TODO: do the right thing here by calling ThemeGetPageIds(), should not assume we know how the page ids will be calculated.
524
525 HTREEITEM hti = reinterpret_cast<HTREEITEM>(ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
526 if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pPage->sczName, -1, wzSelectedPage, -1))
527 {
528 htiSelected = hti;
529 }
530 }
531 }
532
533 if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, L"Application", -1, wzSelectedPage, -1))
534 {
535 htiSelected = tvi.hParent;
536 }
537
538 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent));
539 if (htiSelected)
540 {
541 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_SELECTITEM, TVGN_CARET, reinterpret_cast<LPARAM>(htiSelected));
542 }
543}