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