aboutsummaryrefslogtreecommitdiff
path: root/src/libs/dutil/WixToolset.DUtil/thmutil.cpp
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-04-22 06:38:23 -0700
committerRob Mensching <rob@firegiant.com>2021-04-29 16:21:09 -0700
commit7f642e51670bc38a4ef782a363936850bc2b0ba9 (patch)
tree19684b2d94979f130c0935328f0d44cf006e45ef /src/libs/dutil/WixToolset.DUtil/thmutil.cpp
parentf39e7a3e164d0736e45049e5726d0da2013da3c9 (diff)
downloadwix-7f642e51670bc38a4ef782a363936850bc2b0ba9.tar.gz
wix-7f642e51670bc38a4ef782a363936850bc2b0ba9.tar.bz2
wix-7f642e51670bc38a4ef782a363936850bc2b0ba9.zip
Move dutil into libs/dutil
Diffstat (limited to 'src/libs/dutil/WixToolset.DUtil/thmutil.cpp')
-rw-r--r--src/libs/dutil/WixToolset.DUtil/thmutil.cpp5709
1 files changed, 5709 insertions, 0 deletions
diff --git a/src/libs/dutil/WixToolset.DUtil/thmutil.cpp b/src/libs/dutil/WixToolset.DUtil/thmutil.cpp
new file mode 100644
index 00000000..d200a0ea
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/thmutil.cpp
@@ -0,0 +1,5709 @@
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
5
6// Exit macros
7#define ThmExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
8#define ThmExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
9#define ThmExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
10#define ThmExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
11#define ThmExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
12#define ThmExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__)
13#define ThmExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_THMUTIL, p, x, e, s, __VA_ARGS__)
14#define ThmExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_THMUTIL, p, x, s, __VA_ARGS__)
15#define ThmExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_THMUTIL, p, x, e, s, __VA_ARGS__)
16#define ThmExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_THMUTIL, p, x, s, __VA_ARGS__)
17#define ThmExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_THMUTIL, e, x, s, __VA_ARGS__)
18#define ThmExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_THMUTIL, g, x, s, __VA_ARGS__)
19
20// from CommCtrl.h
21#ifndef BS_COMMANDLINK
22#define BS_COMMANDLINK 0x0000000EL
23#endif
24
25#ifndef BCM_SETNOTE
26#define BCM_SETNOTE (BCM_FIRST + 0x0009)
27#endif
28
29#ifndef BCM_SETSHIELD
30#define BCM_SETSHIELD (BCM_FIRST + 0x000C)
31#endif
32
33#ifndef LWS_NOPREFIX
34#define LWS_NOPREFIX 0x0004
35#endif
36
37const DWORD THEME_INVALID_ID = 0xFFFFFFFF;
38const COLORREF THEME_INVISIBLE_COLORREF = 0xFFFFFFFF;
39const DWORD GROW_FONT_INSTANCES = 3;
40const DWORD GROW_WINDOW_TEXT = 250;
41const LPCWSTR THEME_WC_HYPERLINK = L"ThemeHyperLink";
42const LPCWSTR THEME_WC_PANEL = L"ThemePanel";
43const LPCWSTR THEME_WC_STATICOWNERDRAW = L"ThemeStaticOwnerDraw";
44
45static Gdiplus::GdiplusStartupInput vgsi;
46static Gdiplus::GdiplusStartupOutput vgso = { };
47static ULONG_PTR vgdiToken = 0;
48static ULONG_PTR vgdiHookToken = 0;
49static HMODULE vhHyperlinkRegisteredModule = NULL;
50static HMODULE vhPanelRegisteredModule = NULL;
51static HMODULE vhStaticOwnerDrawRegisteredModule = NULL;
52static WNDPROC vpfnStaticOwnerDrawBaseWndProc = NULL;
53static HMODULE vhModuleMsftEdit = NULL;
54static HMODULE vhModuleRichEd = NULL;
55static HCURSOR vhCursorHand = NULL;
56
57enum INTERNAL_CONTROL_STYLE
58{
59 INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED = 0x0001,
60 INTERNAL_CONTROL_STYLE_FILESYSTEM_AUTOCOMPLETE = 0x0002,
61 INTERNAL_CONTROL_STYLE_DISABLED = 0x0004,
62 INTERNAL_CONTROL_STYLE_HIDDEN = 0x0008,
63 INTERNAL_CONTROL_STYLE_OWNER_DRAW = 0x0010,
64};
65
66struct MEMBUFFER_FOR_RICHEDIT
67{
68 BYTE* rgbData;
69 DWORD cbData;
70
71 DWORD iData;
72};
73
74
75// prototypes
76static HRESULT RegisterWindowClasses(
77 __in_opt HMODULE hModule
78 );
79static HRESULT ParseTheme(
80 __in_opt HMODULE hModule,
81 __in_opt LPCWSTR wzRelativePath,
82 __in IXMLDOMDocument* pixd,
83 __out THEME** ppTheme
84 );
85static HRESULT ParseImage(
86 __in_opt HMODULE hModule,
87 __in_z_opt LPCWSTR wzRelativePath,
88 __in IXMLDOMNode* pElement,
89 __out HBITMAP* phImage
90 );
91static HRESULT ParseIcon(
92 __in_opt HMODULE hModule,
93 __in_z_opt LPCWSTR wzRelativePath,
94 __in IXMLDOMNode* pElement,
95 __out HICON* phIcon
96 );
97static HRESULT ParseWindow(
98 __in_opt HMODULE hModule,
99 __in_opt LPCWSTR wzRelativePath,
100 __in IXMLDOMElement* pElement,
101 __in THEME* pTheme
102 );
103static HRESULT GetFontColor(
104 __in IXMLDOMNode* pixn,
105 __in_z LPCWSTR wzAttributeName,
106 __out COLORREF* pColorRef,
107 __out DWORD* pdwSystemColor
108 );
109static HRESULT ParseFonts(
110 __in IXMLDOMElement* pElement,
111 __in THEME* pTheme
112 );
113static HRESULT ParsePages(
114 __in_opt HMODULE hModule,
115 __in_opt LPCWSTR wzRelativePath,
116 __in IXMLDOMNode* pElement,
117 __in THEME* pTheme
118 );
119static HRESULT ParseImageLists(
120 __in_opt HMODULE hModule,
121 __in_opt LPCWSTR wzRelativePath,
122 __in IXMLDOMNode* pElement,
123 __in THEME* pTheme
124 );
125static HRESULT ParseControls(
126 __in_opt HMODULE hModule,
127 __in_opt LPCWSTR wzRelativePath,
128 __in IXMLDOMNode* pElement,
129 __in THEME* pTheme,
130 __in_opt THEME_CONTROL* pParentControl,
131 __in_opt THEME_PAGE* pPage
132 );
133static HRESULT ParseControl(
134 __in_opt HMODULE hModule,
135 __in_opt LPCWSTR wzRelativePath,
136 __in IXMLDOMNode* pixn,
137 __in THEME* pTheme,
138 __in THEME_CONTROL* pControl,
139 __in BOOL fSkipDimensions,
140 __in_opt THEME_PAGE* pPage
141 );
142static HRESULT ParseActions(
143 __in IXMLDOMNode* pixn,
144 __in THEME_CONTROL* pControl
145 );
146static HRESULT ParseColumns(
147 __in IXMLDOMNode* pixn,
148 __in THEME_CONTROL* pControl
149 );
150static HRESULT ParseRadioButtons(
151 __in_opt HMODULE hModule,
152 __in_opt LPCWSTR wzRelativePath,
153 __in IXMLDOMNode* pixn,
154 __in THEME* pTheme,
155 __in_opt THEME_CONTROL* pParentControl,
156 __in THEME_PAGE* pPage
157 );
158static HRESULT ParseTabs(
159 __in IXMLDOMNode* pixn,
160 __in THEME_CONTROL* pControl
161 );
162static HRESULT ParseText(
163 __in IXMLDOMNode* pixn,
164 __in THEME_CONTROL* pControl,
165 __inout BOOL* pfAnyChildren
166);
167static HRESULT ParseTooltips(
168 __in IXMLDOMNode* pixn,
169 __in THEME_CONTROL* pControl,
170 __inout BOOL* pfAnyChildren
171 );
172static HRESULT ParseNotes(
173 __in IXMLDOMNode* pixn,
174 __in THEME_CONTROL* pControl,
175 __out BOOL* pfAnyChildren
176 );
177static HRESULT StopBillboard(
178 __in THEME* pTheme,
179 __in DWORD dwControl
180 );
181static HRESULT StartBillboard(
182 __in THEME* pTheme,
183 __in DWORD dwControl
184 );
185static HRESULT EnsureFontInstance(
186 __in THEME* pTheme,
187 __in THEME_FONT* pFont,
188 __out THEME_FONT_INSTANCE** ppFontInstance
189 );
190static HRESULT FindImageList(
191 __in THEME* pTheme,
192 __in_z LPCWSTR wzImageListName,
193 __out HIMAGELIST *phImageList
194 );
195static HRESULT LoadControls(
196 __in THEME* pTheme,
197 __in_opt THEME_CONTROL* pParentControl,
198 __in_ecount_opt(cAssignControlIds) const THEME_ASSIGN_CONTROL_ID* rgAssignControlIds,
199 __in DWORD cAssignControlIds
200 );
201static HRESULT ShowControl(
202 __in THEME* pTheme,
203 __in THEME_CONTROL* pControl,
204 __in int nCmdShow,
205 __in BOOL fSaveEditboxes,
206 __in THEME_SHOW_PAGE_REASON reason,
207 __in DWORD dwPageId,
208 __out_opt HWND* phwndFocus
209 );
210static HRESULT ShowControls(
211 __in THEME* pTheme,
212 __in_opt const THEME_CONTROL* pParentControl,
213 __in int nCmdShow,
214 __in BOOL fSaveEditboxes,
215 __in THEME_SHOW_PAGE_REASON reason,
216 __in DWORD dwPageId
217 );
218static HRESULT DrawButton(
219 __in THEME* pTheme,
220 __in DRAWITEMSTRUCT* pdis,
221 __in const THEME_CONTROL* pControl
222 );
223static void DrawControlText(
224 __in THEME* pTheme,
225 __in DRAWITEMSTRUCT* pdis,
226 __in const THEME_CONTROL* pControl,
227 __in BOOL fCentered,
228 __in BOOL fDrawFocusRect
229 );
230static HRESULT DrawHyperlink(
231 __in THEME* pTheme,
232 __in DRAWITEMSTRUCT* pdis,
233 __in const THEME_CONTROL* pControl
234 );
235static HRESULT DrawImage(
236 __in THEME* pTheme,
237 __in DRAWITEMSTRUCT* pdis,
238 __in const THEME_CONTROL* pControl
239 );
240static HRESULT DrawProgressBar(
241 __in THEME* pTheme,
242 __in DRAWITEMSTRUCT* pdis,
243 __in const THEME_CONTROL* pControl
244 );
245static BOOL DrawHoverControl(
246 __in THEME* pTheme,
247 __in BOOL fHover
248 );
249static DWORD CALLBACK RichEditStreamFromFileHandleCallback(
250 __in DWORD_PTR dwCookie,
251 __in_bcount(cb) LPBYTE pbBuff,
252 __in LONG cb,
253 __in LONG *pcb
254 );
255static DWORD CALLBACK RichEditStreamFromMemoryCallback(
256 __in DWORD_PTR dwCookie,
257 __in_bcount(cb) LPBYTE pbBuff,
258 __in LONG cb,
259 __in LONG *pcb
260 );
261static void FreeFontInstance(
262 __in THEME_FONT_INSTANCE* pFontInstance
263 );
264static void FreeFont(
265 __in THEME_FONT* pFont
266 );
267static void FreePage(
268 __in THEME_PAGE* pPage
269 );
270static void FreeControl(
271 __in THEME_CONTROL* pControl
272 );
273static void FreeConditionalText(
274 __in THEME_CONDITIONAL_TEXT* pConditionalText
275 );
276static void FreeImageList(
277 __in THEME_IMAGELIST* pImageList
278 );
279static void FreeAction(
280 __in THEME_ACTION* pAction
281 );
282static void FreeColumn(
283 __in THEME_COLUMN* pColumn
284 );
285static void FreeTab(
286 __in THEME_TAB* pTab
287 );
288static void CALLBACK OnBillboardTimer(
289 __in THEME* pTheme,
290 __in HWND hwnd,
291 __in UINT_PTR idEvent
292 );
293static void OnBrowseDirectory(
294 __in THEME* pTheme,
295 __in HWND hWnd,
296 __in const THEME_ACTION* pAction
297 );
298static BOOL OnButtonClicked(
299 __in THEME* pTheme,
300 __in HWND hWnd,
301 __in const THEME_CONTROL* pControl
302 );
303static BOOL OnDpiChanged(
304 __in THEME* pTheme,
305 __in WPARAM wParam,
306 __in LPARAM lParam
307 );
308static void OnNcCreate(
309 __in THEME* pTheme,
310 __in HWND hWnd,
311 __in LPARAM lParam
312 );
313static HRESULT OnRichEditEnLink(
314 __in LPARAM lParam,
315 __in HWND hWndRichEdit,
316 __in HWND hWnd
317 );
318static BOOL ControlIsType(
319 __in const THEME* pTheme,
320 __in DWORD dwControl,
321 __in THEME_CONTROL_TYPE type
322 );
323static const THEME_CONTROL* FindControlFromHWnd(
324 __in const THEME* pTheme,
325 __in HWND hWnd,
326 __in_opt const THEME_CONTROL* pParentControl = NULL
327 );
328static void GetControlDimensions(
329 __in const THEME_CONTROL* pControl,
330 __in const RECT* prcParent,
331 __out int* piWidth,
332 __out int* piHeight,
333 __out int* piX,
334 __out int* piY
335 );
336// Using iWidth as total width of listview, base width of columns, and "Expands" flag on columns
337// calculates final width of each column (storing result in each column's nWidth value)
338static HRESULT SizeListViewColumns(
339 __inout THEME_CONTROL* pControl
340 );
341static LRESULT CALLBACK ControlGroupDefWindowProc(
342 __in_opt THEME* pTheme,
343 __in HWND hWnd,
344 __in UINT uMsg,
345 __in WPARAM wParam,
346 __in LPARAM lParam
347 );
348static LRESULT CALLBACK PanelWndProc(
349 __in HWND hWnd,
350 __in UINT uMsg,
351 __in WPARAM wParam,
352 __in LPARAM lParam
353 );
354static LRESULT CALLBACK StaticOwnerDrawWndProc(
355 __in HWND hWnd,
356 __in UINT uMsg,
357 __in WPARAM wParam,
358 __in LPARAM lParam
359 );
360static HRESULT LocalizeControls(
361 __in DWORD cControls,
362 __in THEME_CONTROL* rgControls,
363 __in const WIX_LOCALIZATION *pWixLoc
364 );
365static HRESULT LocalizeControl(
366 __in THEME_CONTROL* pControl,
367 __in const WIX_LOCALIZATION *pWixLoc
368 );
369static HRESULT LoadControlsString(
370 __in DWORD cControls,
371 __in THEME_CONTROL* rgControls,
372 __in HMODULE hResModule
373 );
374static HRESULT LoadControlString(
375 __in THEME_CONTROL* pControl,
376 __in HMODULE hResModule
377 );
378static void ResizeControls(
379 __in DWORD cControls,
380 __in THEME_CONTROL* rgControls,
381 __in const RECT* prcParent
382 );
383static void ResizeControl(
384 __in THEME_CONTROL* pControl,
385 __in const RECT* prcParent
386 );
387static void ScaleThemeFromWindow(
388 __in THEME* pTheme,
389 __in UINT nDpi,
390 __in int x,
391 __in int y
392 );
393static void ScaleTheme(
394 __in THEME* pTheme,
395 __in UINT nDpi,
396 __in int x,
397 __in int y,
398 __in DWORD dwStyle,
399 __in BOOL fMenu,
400 __in DWORD dwExStyle
401 );
402static void ScaleControls(
403 __in THEME* pTheme,
404 __in DWORD cControls,
405 __in THEME_CONTROL* rgControls,
406 __in UINT nDpi
407 );
408static void ScaleControl(
409 __in THEME* pTheme,
410 __in THEME_CONTROL* pControl,
411 __in UINT nDpi
412 );
413static void GetControls(
414 __in THEME* pTheme,
415 __in_opt THEME_CONTROL* pParentControl,
416 __out DWORD** ppcControls,
417 __out THEME_CONTROL*** pprgControls
418 );
419static void GetControls(
420 __in const THEME* pTheme,
421 __in_opt const THEME_CONTROL* pParentControl,
422 __out DWORD& cControls,
423 __out THEME_CONTROL*& rgControls
424 );
425static void UnloadControls(
426 __in DWORD cControls,
427 __in THEME_CONTROL* rgControls
428 );
429
430
431// Public functions.
432
433DAPI_(HRESULT) ThemeInitialize(
434 __in_opt HMODULE hModule
435 )
436{
437 HRESULT hr = S_OK;
438 INITCOMMONCONTROLSEX icex = { };
439
440 DpiuInitialize();
441
442 hr = XmlInitialize();
443 ThmExitOnFailure(hr, "Failed to initialize XML.");
444
445 hr = RegisterWindowClasses(hModule);
446 ThmExitOnFailure(hr, "Failed to register theme window classes.");
447
448 // Initialize GDI+ and common controls.
449 vgsi.SuppressBackgroundThread = TRUE;
450
451 hr = GdipInitialize(&vgsi, &vgdiToken, &vgso);
452 ThmExitOnFailure(hr, "Failed to initialize GDI+.");
453
454 icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
455 icex.dwICC = ICC_STANDARD_CLASSES | ICC_PROGRESS_CLASS | ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES | ICC_TAB_CLASSES | ICC_LINK_CLASS;
456 ::InitCommonControlsEx(&icex);
457
458 (*vgso.NotificationHook)(&vgdiHookToken);
459
460LExit:
461 return hr;
462}
463
464
465DAPI_(void) ThemeUninitialize()
466{
467 if (vhModuleMsftEdit)
468 {
469 ::FreeLibrary(vhModuleMsftEdit);
470 vhModuleMsftEdit = NULL;
471 }
472
473 if (vhModuleRichEd)
474 {
475 ::FreeLibrary(vhModuleRichEd);
476 vhModuleRichEd = NULL;
477 }
478
479 if (vhHyperlinkRegisteredModule)
480 {
481 ::UnregisterClassW(THEME_WC_HYPERLINK, vhHyperlinkRegisteredModule);
482 vhHyperlinkRegisteredModule = NULL;
483 }
484
485 if (vhPanelRegisteredModule)
486 {
487 ::UnregisterClassW(THEME_WC_PANEL, vhPanelRegisteredModule);
488 vhPanelRegisteredModule = NULL;
489 }
490
491 if (vhStaticOwnerDrawRegisteredModule)
492 {
493 ::UnregisterClassW(THEME_WC_STATICOWNERDRAW, vhStaticOwnerDrawRegisteredModule);
494 vhStaticOwnerDrawRegisteredModule = NULL;
495 vpfnStaticOwnerDrawBaseWndProc = NULL;
496 }
497
498 if (vgdiToken)
499 {
500 GdipUninitialize(vgdiToken);
501 vgdiToken = 0;
502 }
503
504 XmlUninitialize();
505 DpiuUninitialize();
506}
507
508
509DAPI_(HRESULT) ThemeLoadFromFile(
510 __in_z LPCWSTR wzThemeFile,
511 __out THEME** ppTheme
512 )
513{
514 HRESULT hr = S_OK;
515 IXMLDOMDocument* pixd = NULL;
516 LPWSTR sczRelativePath = NULL;
517
518 hr = XmlLoadDocumentFromFile(wzThemeFile, &pixd);
519 ThmExitOnFailure(hr, "Failed to load theme resource as XML document.");
520
521 hr = PathGetDirectory(wzThemeFile, &sczRelativePath);
522 ThmExitOnFailure(hr, "Failed to get relative path from theme file.");
523
524 hr = ParseTheme(NULL, sczRelativePath, pixd, ppTheme);
525 ThmExitOnFailure(hr, "Failed to parse theme.");
526
527LExit:
528 ReleaseStr(sczRelativePath);
529 ReleaseObject(pixd);
530
531 return hr;
532}
533
534
535DAPI_(HRESULT) ThemeLoadFromResource(
536 __in_opt HMODULE hModule,
537 __in_z LPCSTR szResource,
538 __out THEME** ppTheme
539 )
540{
541 HRESULT hr = S_OK;
542 LPVOID pvResource = NULL;
543 DWORD cbResource = 0;
544 LPWSTR sczXml = NULL;
545 IXMLDOMDocument* pixd = NULL;
546
547 hr = ResReadData(hModule, szResource, &pvResource, &cbResource);
548 ThmExitOnFailure(hr, "Failed to read theme from resource.");
549
550 hr = StrAllocStringAnsi(&sczXml, reinterpret_cast<LPCSTR>(pvResource), cbResource, CP_UTF8);
551 ThmExitOnFailure(hr, "Failed to convert XML document data from UTF-8 to unicode string.");
552
553 hr = XmlLoadDocument(sczXml, &pixd);
554 ThmExitOnFailure(hr, "Failed to load theme resource as XML document.");
555
556 hr = ParseTheme(hModule, NULL, pixd, ppTheme);
557 ThmExitOnFailure(hr, "Failed to parse theme.");
558
559LExit:
560 ReleaseObject(pixd);
561 ReleaseStr(sczXml);
562
563 return hr;
564}
565
566
567DAPI_(void) ThemeFree(
568 __in THEME* pTheme
569 )
570{
571 if (pTheme)
572 {
573 for (DWORD i = 0; i < pTheme->cFonts; ++i)
574 {
575 FreeFont(pTheme->rgFonts + i);
576 }
577
578 for (DWORD i = 0; i < pTheme->cPages; ++i)
579 {
580 FreePage(pTheme->rgPages + i);
581 }
582
583 for (DWORD i = 0; i < pTheme->cImageLists; ++i)
584 {
585 FreeImageList(pTheme->rgImageLists + i);
586 }
587
588 for (DWORD i = 0; i < pTheme->cControls; ++i)
589 {
590 FreeControl(pTheme->rgControls + i);
591 }
592
593 ReleaseMem(pTheme->rgControls);
594 ReleaseMem(pTheme->rgPages);
595 ReleaseMem(pTheme->rgFonts);
596
597 if (pTheme->hImage)
598 {
599 ::DeleteBitmap(pTheme->hImage);
600 }
601
602 ReleaseStr(pTheme->sczCaption);
603 ReleaseMem(pTheme);
604 }
605}
606
607DAPI_(HRESULT) ThemeRegisterVariableCallbacks(
608 __in THEME* pTheme,
609 __in_opt PFNTHM_EVALUATE_VARIABLE_CONDITION pfnEvaluateCondition,
610 __in_opt PFNTHM_FORMAT_VARIABLE_STRING pfnFormatString,
611 __in_opt PFNTHM_GET_VARIABLE_NUMERIC pfnGetNumericVariable,
612 __in_opt PFNTHM_SET_VARIABLE_NUMERIC pfnSetNumericVariable,
613 __in_opt PFNTHM_GET_VARIABLE_STRING pfnGetStringVariable,
614 __in_opt PFNTHM_SET_VARIABLE_STRING pfnSetStringVariable,
615 __in_opt LPVOID pvContext
616 )
617{
618 HRESULT hr = S_OK;
619 ThmExitOnNull(pTheme, hr, S_FALSE, "Theme must be loaded first.");
620
621 pTheme->pfnEvaluateCondition = pfnEvaluateCondition;
622 pTheme->pfnFormatString = pfnFormatString;
623 pTheme->pfnGetNumericVariable = pfnGetNumericVariable;
624 pTheme->pfnSetNumericVariable = pfnSetNumericVariable;
625 pTheme->pfnGetStringVariable = pfnGetStringVariable;
626 pTheme->pfnSetStringVariable = pfnSetStringVariable;
627 pTheme->pvVariableContext = pvContext;
628
629LExit:
630 return hr;
631}
632
633
634DAPI_(HRESULT) ThemeCreateParentWindow(
635 __in THEME* pTheme,
636 __in DWORD dwExStyle,
637 __in LPCWSTR szClassName,
638 __in LPCWSTR szWindowName,
639 __in DWORD dwStyle,
640 __in int x,
641 __in int y,
642 __in_opt HWND hwndParent,
643 __in_opt HINSTANCE hInstance,
644 __in_opt LPVOID lpParam,
645 __in THEME_WINDOW_INITIAL_POSITION initialPosition,
646 __out_opt HWND* phWnd
647 )
648{
649 HRESULT hr = S_OK;
650 DPIU_MONITOR_CONTEXT* pMonitorContext = NULL;
651 POINT pt = { };
652 RECT* pMonitorRect = NULL;
653 HMENU hMenu = NULL;
654 HWND hWnd = NULL;
655
656 if (pTheme->hwndParent)
657 {
658 ThmExitOnFailure(hr = E_INVALIDSTATE, "ThemeCreateParentWindow called after the theme was loaded.");
659 }
660
661 if (THEME_WINDOW_INITIAL_POSITION_CENTER_MONITOR_FROM_COORDINATES == initialPosition)
662 {
663 pt.x = x;
664 pt.y = y;
665 hr = DpiuGetMonitorContextFromPoint(&pt, &pMonitorContext);
666 if (SUCCEEDED(hr))
667 {
668 pMonitorRect = &pMonitorContext->mi.rcWork;
669 if (pMonitorContext->nDpi != pTheme->nDpi)
670 {
671 ScaleTheme(pTheme, pMonitorContext->nDpi, pMonitorRect->left, pMonitorRect->top, dwStyle, NULL != hMenu, dwExStyle);
672 }
673
674 x = pMonitorRect->left + (pMonitorRect->right - pMonitorRect->left - pTheme->nWindowWidth) / 2;
675 y = pMonitorRect->top + (pMonitorRect->bottom - pMonitorRect->top - pTheme->nWindowHeight) / 2;
676 }
677 else
678 {
679 hr = S_OK;
680 x = CW_USEDEFAULT;
681 y = CW_USEDEFAULT;
682 }
683 }
684
685 hWnd = ::CreateWindowExW(dwExStyle, szClassName, szWindowName, dwStyle, x, y, pTheme->nWindowWidth, pTheme->nWindowHeight, hwndParent, hMenu, hInstance, lpParam);
686 ThmExitOnNullWithLastError(hWnd, hr, "Failed to create theme parent window.");
687 ThmExitOnNull(pTheme->hwndParent, hr, E_INVALIDSTATE, "Theme parent window is not set, make sure ThemeDefWindowProc is called for WM_NCCREATE.");
688 AssertSz(hWnd == pTheme->hwndParent, "Theme parent window does not equal newly created window.");
689
690 if (phWnd)
691 {
692 *phWnd = hWnd;
693 }
694
695LExit:
696 ReleaseMem(pMonitorContext);
697
698 return hr;
699}
700
701
702DAPI_(HRESULT) ThemeLoadControls(
703 __in THEME* pTheme,
704 __in_ecount_opt(cAssignControlIds) const THEME_ASSIGN_CONTROL_ID* rgAssignControlIds,
705 __in DWORD cAssignControlIds
706 )
707{
708 HRESULT hr = S_OK;
709
710 if (!pTheme->hwndParent)
711 {
712 ThmExitOnFailure(hr = E_INVALIDSTATE, "ThemeLoadControls called before theme parent window created.");
713 }
714
715 hr = LoadControls(pTheme, NULL, rgAssignControlIds, cAssignControlIds);
716
717LExit:
718 return hr;
719}
720
721
722DAPI_(void) ThemeUnloadControls(
723 __in THEME* pTheme
724 )
725{
726 UnloadControls(pTheme->cControls, pTheme->rgControls);
727
728 pTheme->hwndHover = NULL;
729 pTheme->hwndParent = NULL;
730}
731
732DAPI_(HRESULT) ThemeLocalize(
733 __in THEME *pTheme,
734 __in const WIX_LOCALIZATION *pWixLoc
735 )
736{
737 HRESULT hr = S_OK;
738 LPWSTR sczCaption = NULL;
739
740 hr = LocLocalizeString(pWixLoc, &pTheme->sczCaption);
741 ThmExitOnFailure(hr, "Failed to localize theme caption.");
742
743 if (pTheme->pfnFormatString)
744 {
745 hr = pTheme->pfnFormatString(pTheme->sczCaption, &sczCaption, pTheme->pvVariableContext);
746 if (SUCCEEDED(hr))
747 {
748 hr = ThemeUpdateCaption(pTheme, sczCaption);
749 }
750 }
751
752 hr = LocalizeControls(pTheme->cControls, pTheme->rgControls, pWixLoc);
753
754LExit:
755 ReleaseStr(sczCaption);
756
757 return hr;
758}
759
760/********************************************************************
761 ThemeLoadStrings - Loads string resources.
762 Must be called after loading a theme and before calling
763 ThemeLoadControls.
764*******************************************************************/
765DAPI_(HRESULT) ThemeLoadStrings(
766 __in THEME* pTheme,
767 __in HMODULE hResModule
768 )
769{
770 HRESULT hr = S_OK;
771 ThmExitOnNull(pTheme, hr, S_FALSE, "Theme must be loaded first.");
772
773 if (UINT_MAX != pTheme->uStringId)
774 {
775 hr = ResReadString(hResModule, pTheme->uStringId, &pTheme->sczCaption);
776 ThmExitOnFailure(hr, "Failed to load theme caption.");
777 }
778
779 hr = LoadControlsString(pTheme->cControls, pTheme->rgControls, hResModule);
780
781LExit:
782 return hr;
783}
784
785
786DAPI_(HRESULT) ThemeLoadRichEditFromFile(
787 __in THEME* pTheme,
788 __in DWORD dwControl,
789 __in_z LPCWSTR wzFileName,
790 __in HMODULE hModule
791 )
792{
793 HRESULT hr = S_OK;
794 LPWSTR sczFile = NULL;
795 HANDLE hFile = INVALID_HANDLE_VALUE;
796 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
797
798 hr = PathRelativeToModule(&sczFile, wzFileName, hModule);
799 ThmExitOnFailure(hr, "Failed to read resource data.");
800
801 hFile = ::CreateFileW(sczFile, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
802 if (INVALID_HANDLE_VALUE == hFile)
803 {
804 ThmExitWithLastError(hr, "Failed to open RTF file.");
805 }
806 else
807 {
808 LONGLONG llRtfSize;
809 hr = FileSizeByHandle(hFile, &llRtfSize);
810 if (SUCCEEDED(hr))
811 {
812 ::SendMessageW(hWnd, EM_EXLIMITTEXT, 0, static_cast<LPARAM>(llRtfSize));
813 }
814
815 EDITSTREAM es = { };
816 es.pfnCallback = RichEditStreamFromFileHandleCallback;
817 es.dwCookie = reinterpret_cast<DWORD_PTR>(hFile);
818
819 ::SendMessageW(hWnd, EM_STREAMIN, SF_RTF, reinterpret_cast<LPARAM>(&es));
820 hr = es.dwError;
821 ThmExitOnFailure(hr, "Failed to update RTF stream.");
822 }
823
824LExit:
825 ReleaseStr(sczFile);
826 ReleaseFile(hFile);
827
828 return hr;
829}
830
831
832DAPI_(HRESULT) ThemeLoadRichEditFromResource(
833 __in THEME* pTheme,
834 __in DWORD dwControl,
835 __in_z LPCSTR szResourceName,
836 __in HMODULE hModule
837 )
838{
839 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
840 return ThemeLoadRichEditFromResourceToHWnd(hWnd, szResourceName, hModule);
841}
842
843DAPI_(HRESULT) ThemeLoadRichEditFromResourceToHWnd(
844 __in HWND hWnd,
845 __in_z LPCSTR szResourceName,
846 __in HMODULE hModule
847 )
848{
849 HRESULT hr = S_OK;
850 MEMBUFFER_FOR_RICHEDIT buffer = { };
851 EDITSTREAM es = { };
852
853 hr = ResReadData(hModule, szResourceName, reinterpret_cast<LPVOID*>(&buffer.rgbData), &buffer.cbData);
854 ThmExitOnFailure(hr, "Failed to read resource data.");
855
856 es.pfnCallback = RichEditStreamFromMemoryCallback;
857 es.dwCookie = reinterpret_cast<DWORD_PTR>(&buffer);
858
859 ::SendMessageW(hWnd, EM_STREAMIN, SF_RTF, reinterpret_cast<LPARAM>(&es));
860 hr = es.dwError;
861 ThmExitOnFailure(hr, "Failed to update RTF stream.");
862
863LExit:
864 return hr;
865}
866
867
868DAPI_(BOOL) ThemeHandleKeyboardMessage(
869 __in_opt THEME* pTheme,
870 __in HWND /*hWnd*/,
871 __in MSG* pMsg
872 )
873{
874 return pTheme ? ::IsDialogMessageW(pTheme->hwndParent, pMsg) : FALSE;
875}
876
877
878extern "C" LRESULT CALLBACK ThemeDefWindowProc(
879 __in_opt THEME* pTheme,
880 __in HWND hWnd,
881 __in UINT uMsg,
882 __in WPARAM wParam,
883 __in LPARAM lParam
884 )
885{
886 RECT rcParent = { };
887 RECT *pRect = NULL;
888
889 if (pTheme)
890 {
891 switch (uMsg)
892 {
893 case WM_NCCREATE:
894 if (pTheme->hwndParent)
895 {
896 AssertSz(FALSE, "WM_NCCREATE called multiple times");
897 }
898 else
899 {
900 OnNcCreate(pTheme, hWnd, lParam);
901 }
902 break;
903
904 case WM_NCHITTEST:
905 if (pTheme->dwStyle & WS_POPUP)
906 {
907 return HTCAPTION; // allow pop-up windows to be moved by grabbing any non-control.
908 }
909 break;
910
911 case WM_DPICHANGED:
912 if (OnDpiChanged(pTheme, wParam, lParam))
913 {
914 return 0;
915 }
916 break;
917
918 case WM_SIZING:
919 if (pTheme->fAutoResize)
920 {
921 pRect = reinterpret_cast<RECT *>(lParam);
922 if (pRect->right - pRect->left < pTheme->nMinimumWidth)
923 {
924 if (wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT)
925 {
926 pRect->left = pRect->right - pTheme->nMinimumWidth;
927 }
928 else
929 {
930 pRect->right = pRect->left + pTheme->nMinimumWidth;
931 }
932 }
933 if (pRect->bottom - pRect->top < pTheme->nMinimumHeight)
934 {
935 if (wParam == WMSZ_BOTTOM || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOMRIGHT)
936 {
937 pRect->bottom = pRect->top + pTheme->nMinimumHeight;
938 }
939 else
940 {
941 pRect->top = pRect->bottom - pTheme->nMinimumHeight;
942 }
943 }
944
945 return TRUE;
946 }
947 break;
948
949 case WM_SIZE:
950 if (pTheme->fAutoResize || pTheme->fForceResize)
951 {
952 pTheme->fForceResize = FALSE;
953 ::GetClientRect(pTheme->hwndParent, &rcParent);
954 ResizeControls(pTheme->cControls, pTheme->rgControls, &rcParent);
955 return 0;
956 }
957 break;
958 }
959 }
960
961 return ControlGroupDefWindowProc(pTheme, hWnd, uMsg, wParam, lParam);
962}
963
964
965DAPI_(void) ThemeGetPageIds(
966 __in const THEME* pTheme,
967 __in_ecount(cGetPages) LPCWSTR* rgwzFindNames,
968 __inout_ecount(cGetPages) DWORD* rgdwPageIds,
969 __in DWORD cGetPages
970 )
971{
972 for (DWORD i = 0; i < cGetPages; ++i)
973 {
974 LPCWSTR wzFindName = rgwzFindNames[i];
975 for (DWORD j = 0; j < pTheme->cPages; ++j)
976 {
977 LPCWSTR wzPageName = pTheme->rgPages[j].sczName;
978 if (wzPageName && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPageName, -1, wzFindName, -1))
979 {
980 rgdwPageIds[i] = j + 1; // add one to make the page ids 1-based (so zero is invalid).
981 break;
982 }
983 }
984 }
985}
986
987
988DAPI_(THEME_PAGE*) ThemeGetPage(
989 __in const THEME* pTheme,
990 __in DWORD dwPage
991 )
992{
993 DWORD iPage = dwPage - 1;
994 THEME_PAGE* pPage = NULL;
995
996 if (iPage < pTheme->cPages)
997 {
998 pPage = pTheme->rgPages + iPage;
999 }
1000
1001 return pPage;
1002}
1003
1004
1005DAPI_(HRESULT) ThemeShowPage(
1006 __in THEME* pTheme,
1007 __in DWORD dwPage,
1008 __in int nCmdShow
1009 )
1010{
1011 return ThemeShowPageEx(pTheme, dwPage, nCmdShow, THEME_SHOW_PAGE_REASON_DEFAULT);
1012}
1013
1014
1015DAPI_(HRESULT) ThemeShowPageEx(
1016 __in THEME* pTheme,
1017 __in DWORD dwPage,
1018 __in int nCmdShow,
1019 __in THEME_SHOW_PAGE_REASON reason
1020 )
1021{
1022 HRESULT hr = S_OK;
1023 BOOL fHide = SW_HIDE == nCmdShow;
1024 BOOL fSaveEditboxes = FALSE;
1025 THEME_SAVEDVARIABLE* pSavedVariable = NULL;
1026 THEME_PAGE* pPage = ThemeGetPage(pTheme, dwPage);
1027
1028 if (pPage)
1029 {
1030 if (fHide)
1031 {
1032 switch (reason)
1033 {
1034 case THEME_SHOW_PAGE_REASON_DEFAULT:
1035 // Set the variables in the loop below.
1036 fSaveEditboxes = TRUE;
1037 break;
1038 case THEME_SHOW_PAGE_REASON_CANCEL:
1039 if (pPage->cSavedVariables && pTheme->pfnSetStringVariable)
1040 {
1041 // Best effort to cancel any changes to the variables.
1042 for (DWORD v = 0; v < pPage->cSavedVariables; ++v)
1043 {
1044 pSavedVariable = pPage->rgSavedVariables + v;
1045 if (pSavedVariable->wzName)
1046 {
1047 pTheme->pfnSetStringVariable(pSavedVariable->wzName, pSavedVariable->sczValue, FALSE, pTheme->pvVariableContext);
1048 }
1049 }
1050 }
1051 break;
1052 }
1053
1054 if (THEME_SHOW_PAGE_REASON_REFRESH != reason)
1055 {
1056 pPage->cSavedVariables = 0;
1057 if (pPage->rgSavedVariables)
1058 {
1059 SecureZeroMemory(pPage->rgSavedVariables, MemSize(pPage->rgSavedVariables));
1060 }
1061 }
1062
1063 pTheme->dwCurrentPageId = 0;
1064 }
1065 else
1066 {
1067 if (THEME_SHOW_PAGE_REASON_REFRESH == reason)
1068 {
1069 fSaveEditboxes = TRUE;
1070 }
1071 else
1072 {
1073 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pPage->rgSavedVariables), pPage->cControlIndices, sizeof(THEME_SAVEDVARIABLE), pPage->cControlIndices);
1074 ThmExitOnFailure(hr, "Failed to allocate memory for saved variables.");
1075
1076 SecureZeroMemory(pPage->rgSavedVariables, MemSize(pPage->rgSavedVariables));
1077 pPage->cSavedVariables = pPage->cControlIndices;
1078
1079 // Save the variables in the loop below.
1080 }
1081
1082 pTheme->dwCurrentPageId = dwPage;
1083 }
1084 }
1085
1086 hr = ShowControls(pTheme, NULL, nCmdShow, fSaveEditboxes, reason, dwPage);
1087 ThmExitOnFailure(hr, "Failed to show page controls.");
1088
1089LExit:
1090 return hr;
1091}
1092
1093
1094DAPI_(BOOL) ThemeControlExists(
1095 __in const THEME* pTheme,
1096 __in DWORD dwControl
1097 )
1098{
1099 BOOL fExists = FALSE;
1100 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1101 if (hWnd)
1102 {
1103 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd);
1104 fExists = (pControl && hWnd == pControl->hWnd);
1105 }
1106
1107 return fExists;
1108}
1109
1110
1111DAPI_(void) ThemeControlEnable(
1112 __in THEME* pTheme,
1113 __in DWORD dwControl,
1114 __in BOOL fEnable
1115 )
1116{
1117 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1118 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
1119 if (pControl)
1120 {
1121 pControl->dwInternalStyle = fEnable ? (pControl->dwInternalStyle & ~INTERNAL_CONTROL_STYLE_DISABLED) : (pControl->dwInternalStyle | INTERNAL_CONTROL_STYLE_DISABLED);
1122 ::EnableWindow(hWnd, fEnable);
1123
1124 if (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED)
1125 {
1126 ::ShowWindow(hWnd, fEnable ? SW_SHOW : SW_HIDE);
1127 }
1128 }
1129}
1130
1131
1132DAPI_(BOOL) ThemeControlEnabled(
1133 __in THEME* pTheme,
1134 __in DWORD dwControl
1135 )
1136{
1137 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1138 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd);
1139 return pControl && !(pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_DISABLED);
1140}
1141
1142
1143DAPI_(void) ThemeControlElevates(
1144 __in THEME* pTheme,
1145 __in DWORD dwControl,
1146 __in BOOL fElevates
1147 )
1148{
1149 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1150 ::SendMessageW(hWnd, BCM_SETSHIELD, 0, fElevates);
1151}
1152
1153
1154DAPI_(void) ThemeShowControl(
1155 __in THEME* pTheme,
1156 __in DWORD dwControl,
1157 __in int nCmdShow
1158 )
1159{
1160 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1161 ::ShowWindow(hWnd, nCmdShow);
1162
1163 // Save the control's visible state.
1164 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
1165 if (pControl)
1166 {
1167 pControl->dwInternalStyle = (SW_HIDE == nCmdShow) ? (pControl->dwInternalStyle | INTERNAL_CONTROL_STYLE_HIDDEN) : (pControl->dwInternalStyle & ~INTERNAL_CONTROL_STYLE_HIDDEN);
1168 }
1169}
1170
1171
1172DAPI_(void) ThemeShowControlEx(
1173 __in THEME* pTheme,
1174 __in DWORD dwControl,
1175 __in int nCmdShow
1176 )
1177{
1178 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1179 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
1180 if (pControl)
1181 {
1182 ShowControl(pTheme, pControl, nCmdShow, THEME_CONTROL_TYPE_EDITBOX == pControl->type, THEME_SHOW_PAGE_REASON_REFRESH, 0, NULL);
1183 }
1184}
1185
1186
1187DAPI_(BOOL) ThemeControlVisible(
1188 __in THEME* pTheme,
1189 __in DWORD dwControl
1190 )
1191{
1192 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1193 return ::IsWindowVisible(hWnd);
1194}
1195
1196
1197DAPI_(BOOL) ThemePostControlMessage(
1198 __in THEME* pTheme,
1199 __in DWORD dwControl,
1200 __in UINT Msg,
1201 __in WPARAM wParam,
1202 __in LPARAM lParam
1203 )
1204{
1205 HRESULT hr = S_OK;
1206 UINT er = ERROR_SUCCESS;
1207 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1208
1209 if (!::PostMessageW(hWnd, Msg, wParam, lParam))
1210 {
1211 er = ::GetLastError();
1212 hr = HRESULT_FROM_WIN32(er);
1213 }
1214
1215 return SUCCEEDED(hr);
1216}
1217
1218
1219DAPI_(LRESULT) ThemeSendControlMessage(
1220 __in const THEME* pTheme,
1221 __in DWORD dwControl,
1222 __in UINT Msg,
1223 __in WPARAM wParam,
1224 __in LPARAM lParam
1225 )
1226{
1227 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1228 return ::SendMessageW(hWnd, Msg, wParam, lParam);
1229}
1230
1231
1232DAPI_(HRESULT) ThemeDrawBackground(
1233 __in THEME* pTheme,
1234 __in PAINTSTRUCT* pps
1235 )
1236{
1237 HRESULT hr = S_FALSE;
1238
1239 if (pTheme->hImage && 0 <= pTheme->nSourceX && 0 <= pTheme->nSourceY && pps->fErase)
1240 {
1241 HDC hdcMem = ::CreateCompatibleDC(pps->hdc);
1242 HBITMAP hDefaultBitmap = static_cast<HBITMAP>(::SelectObject(hdcMem, pTheme->hImage));
1243 DWORD dwSourceWidth = pTheme->nDefaultDpiWidth;
1244 DWORD dwSourceHeight = pTheme->nDefaultDpiHeight;
1245
1246 ::StretchBlt(pps->hdc, 0, 0, pTheme->nWidth, pTheme->nHeight, hdcMem, pTheme->nSourceX, pTheme->nSourceY, dwSourceWidth, dwSourceHeight, SRCCOPY);
1247
1248 ::SelectObject(hdcMem, hDefaultBitmap);
1249 ::DeleteDC(hdcMem);
1250
1251 hr = S_OK;
1252 }
1253
1254 return hr;
1255}
1256
1257
1258DAPI_(HRESULT) ThemeDrawControl(
1259 __in THEME* pTheme,
1260 __in DRAWITEMSTRUCT* pdis
1261 )
1262{
1263 HRESULT hr = S_OK;
1264 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, pdis->hwndItem);
1265
1266 AssertSz(pControl, "Expected control window from owner draw window.");
1267 AssertSz(pControl->hWnd == pdis->hwndItem, "Expected control window to match owner draw window.");
1268 AssertSz(pControl->nWidth < 1 || pControl->nWidth == pdis->rcItem.right - pdis->rcItem.left, "Expected control window width to match owner draw window width.");
1269 AssertSz(pControl->nHeight < 1 || pControl->nHeight == pdis->rcItem.bottom - pdis->rcItem.top, "Expected control window height to match owner draw window height.");
1270
1271 switch (pControl->type)
1272 {
1273 case THEME_CONTROL_TYPE_BUTTON:
1274 hr = DrawButton(pTheme, pdis, pControl);
1275 ThmExitOnFailure(hr, "Failed to draw button.");
1276 break;
1277
1278 case THEME_CONTROL_TYPE_HYPERLINK:
1279 hr = DrawHyperlink(pTheme, pdis, pControl);
1280 ThmExitOnFailure(hr, "Failed to draw hyperlink.");
1281 break;
1282
1283 case THEME_CONTROL_TYPE_IMAGE:
1284 hr = DrawImage(pTheme, pdis, pControl);
1285 ThmExitOnFailure(hr, "Failed to draw image.");
1286 break;
1287
1288 case THEME_CONTROL_TYPE_PROGRESSBAR:
1289 hr = DrawProgressBar(pTheme, pdis, pControl);
1290 ThmExitOnFailure(hr, "Failed to draw progress bar.");
1291 break;
1292
1293 default:
1294 hr = E_UNEXPECTED;
1295 ThmExitOnRootFailure(hr, "Did not specify an owner draw control to draw.");
1296 }
1297
1298LExit:
1299 return hr;
1300}
1301
1302
1303DAPI_(BOOL) ThemeHoverControl(
1304 __in THEME* pTheme,
1305 __in HWND hwndParent,
1306 __in HWND hwndControl
1307 )
1308{
1309 BOOL fHovered = FALSE;
1310 if (hwndControl != pTheme->hwndHover)
1311 {
1312 if (pTheme->hwndHover && pTheme->hwndHover != hwndParent)
1313 {
1314 DrawHoverControl(pTheme, FALSE);
1315 }
1316
1317 pTheme->hwndHover = hwndControl;
1318
1319 if (pTheme->hwndHover && pTheme->hwndHover != hwndParent)
1320 {
1321 fHovered = DrawHoverControl(pTheme, TRUE);
1322 }
1323 }
1324
1325 return fHovered;
1326}
1327
1328
1329DAPI_(BOOL) ThemeIsControlChecked(
1330 __in THEME* pTheme,
1331 __in DWORD dwControl
1332 )
1333{
1334 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1335 return BST_CHECKED == ::SendMessageW(hWnd, BM_GETCHECK, 0, 0);
1336}
1337
1338
1339DAPI_(BOOL) ThemeSetControlColor(
1340 __in THEME* pTheme,
1341 __in HDC hdc,
1342 __in HWND hWnd,
1343 __out HBRUSH* phBackgroundBrush
1344 )
1345{
1346 THEME_FONT* pFont = NULL;
1347 BOOL fHasBackground = FALSE;
1348
1349 *phBackgroundBrush = NULL;
1350
1351 if (hWnd == pTheme->hwndParent)
1352 {
1353 pFont = (THEME_INVALID_ID == pTheme->dwFontId) ? NULL : pTheme->rgFonts + pTheme->dwFontId;
1354 }
1355 else
1356 {
1357 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd);
1358 pFont = (!pControl || THEME_INVALID_ID == pControl->dwFontId) ? NULL : pTheme->rgFonts + pControl->dwFontId;
1359 }
1360
1361 if (pFont)
1362 {
1363 if (pFont->hForeground)
1364 {
1365 ::SetTextColor(hdc, pFont->crForeground);
1366 }
1367
1368 if (pFont->hBackground)
1369 {
1370 ::SetBkColor(hdc, pFont->crBackground);
1371
1372 *phBackgroundBrush = pFont->hBackground;
1373 fHasBackground = TRUE;
1374 }
1375 else
1376 {
1377 ::SetBkMode(hdc, TRANSPARENT);
1378 *phBackgroundBrush = static_cast<HBRUSH>(::GetStockObject(NULL_BRUSH));
1379 fHasBackground = TRUE;
1380 }
1381 }
1382
1383 return fHasBackground;
1384}
1385
1386
1387DAPI_(HRESULT) ThemeSetProgressControl(
1388 __in THEME* pTheme,
1389 __in DWORD dwControl,
1390 __in DWORD dwProgressPercentage
1391 )
1392{
1393 HRESULT hr = E_NOTFOUND;
1394 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1395
1396 if (hWnd)
1397 {
1398 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
1399 if (pControl && THEME_CONTROL_TYPE_PROGRESSBAR == pControl->type)
1400 {
1401 DWORD dwCurrentProgress = LOWORD(pControl->dwData);
1402
1403 if (dwCurrentProgress != dwProgressPercentage)
1404 {
1405 DWORD dwColor = HIWORD(pControl->dwData);
1406 pControl->dwData = MAKEDWORD(dwProgressPercentage, dwColor);
1407
1408 if (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_OWNER_DRAW)
1409 {
1410 if (!::InvalidateRect(hWnd, NULL, FALSE))
1411 {
1412 ThmExitWithLastError(hr, "Failed to invalidate progress bar window.");
1413 }
1414 }
1415 else
1416 {
1417 ::SendMessageW(hWnd, PBM_SETPOS, dwProgressPercentage, 0);
1418 }
1419
1420 hr = S_OK;
1421 }
1422 else
1423 {
1424 hr = S_FALSE;
1425 }
1426 }
1427 }
1428
1429LExit:
1430 return hr;
1431}
1432
1433
1434DAPI_(HRESULT) ThemeSetProgressControlColor(
1435 __in THEME* pTheme,
1436 __in DWORD dwControl,
1437 __in DWORD dwColorIndex
1438 )
1439{
1440 HRESULT hr = S_FALSE;
1441 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1442 if (hWnd)
1443 {
1444 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
1445
1446 // Only set color on owner draw progress bars.
1447 if (pControl && (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_OWNER_DRAW))
1448 {
1449 DWORD dwCurrentColor = HIWORD(pControl->dwData);
1450
1451 if (dwCurrentColor != dwColorIndex)
1452 {
1453 DWORD dwCurrentProgress = LOWORD(pControl->dwData);
1454 pControl->dwData = MAKEDWORD(dwCurrentProgress, dwColorIndex);
1455
1456 if (!::InvalidateRect(hWnd, NULL, FALSE))
1457 {
1458 ThmExitWithLastError(hr, "Failed to invalidate progress bar window.");
1459 }
1460
1461 hr = S_OK;
1462 }
1463 }
1464 }
1465
1466LExit:
1467 return hr;
1468}
1469
1470
1471DAPI_(HRESULT) ThemeSetTextControl(
1472 __in const THEME* pTheme,
1473 __in DWORD dwControl,
1474 __in_z_opt LPCWSTR wzText
1475 )
1476{
1477 return ThemeSetTextControlEx(pTheme, dwControl, FALSE, wzText);
1478}
1479
1480
1481DAPI_(HRESULT) ThemeSetTextControlEx(
1482 __in const THEME* pTheme,
1483 __in DWORD dwControl,
1484 __in BOOL fUpdate,
1485 __in_z_opt LPCWSTR wzText
1486 )
1487{
1488 HRESULT hr = S_OK;
1489 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1490
1491 if (hWnd)
1492 {
1493 if (fUpdate)
1494 {
1495 ::ShowWindow(hWnd, SW_HIDE);
1496 }
1497
1498 if (!::SetWindowTextW(hWnd, wzText))
1499 {
1500 ThmExitWithLastError(hr, "Failed to set control text.");
1501 }
1502
1503 if (fUpdate)
1504 {
1505 ::ShowWindow(hWnd, SW_SHOW);
1506 }
1507 }
1508
1509LExit:
1510 return hr;
1511}
1512
1513
1514DAPI_(HRESULT) ThemeGetTextControl(
1515 __in const THEME* pTheme,
1516 __in DWORD dwControl,
1517 __inout_z LPWSTR* psczText
1518 )
1519{
1520 HRESULT hr = S_OK;
1521 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
1522 SIZE_T cbSize = 0;
1523 DWORD cchText = 0;
1524 DWORD cchTextRead = 0;
1525
1526 // Ensure the string has room for at least one character.
1527 hr = StrMaxLength(*psczText, &cbSize);
1528 ThmExitOnFailure(hr, "Failed to get text buffer length.");
1529
1530 cchText = (DWORD)min(DWORD_MAX, cbSize);
1531
1532 if (!cchText)
1533 {
1534 cchText = GROW_WINDOW_TEXT;
1535
1536 hr = StrAlloc(psczText, cchText);
1537 ThmExitOnFailure(hr, "Failed to grow text buffer.");
1538 }
1539
1540 // Read (and keep growing buffer) until we finally read less than there
1541 // is room in the buffer.
1542 for (;;)
1543 {
1544 cchTextRead = ::GetWindowTextW(hWnd, *psczText, cchText);
1545 if (cchTextRead + 1 < cchText)
1546 {
1547 break;
1548 }
1549 else
1550 {
1551 cchText = cchTextRead + GROW_WINDOW_TEXT;
1552
1553 hr = StrAlloc(psczText, cchText);
1554 ThmExitOnFailure(hr, "Failed to grow text buffer again.");
1555 }
1556 }
1557
1558LExit:
1559 return hr;
1560}
1561
1562
1563DAPI_(HRESULT) ThemeUpdateCaption(
1564 __in THEME* pTheme,
1565 __in_z LPCWSTR wzCaption
1566 )
1567{
1568 HRESULT hr = S_OK;
1569
1570 hr = StrAllocString(&pTheme->sczCaption, wzCaption, 0);
1571 ThmExitOnFailure(hr, "Failed to update theme caption.");
1572
1573LExit:
1574 return hr;
1575}
1576
1577
1578DAPI_(void) ThemeSetFocus(
1579 __in THEME* pTheme,
1580 __in DWORD dwControl
1581 )
1582{
1583 HWND hwndFocus = ::GetDlgItem(pTheme->hwndParent, dwControl);
1584 if (hwndFocus && !ThemeControlEnabled(pTheme, dwControl))
1585 {
1586 hwndFocus = ::GetNextDlgTabItem(pTheme->hwndParent, hwndFocus, FALSE);
1587 }
1588
1589 if (hwndFocus)
1590 {
1591 ::SetFocus(hwndFocus);
1592 }
1593}
1594
1595
1596DAPI_(void) ThemeShowChild(
1597 __in THEME* pTheme,
1598 __in THEME_CONTROL* pParentControl,
1599 __in DWORD dwIndex
1600 )
1601{
1602 // show one child, hide the rest
1603 for (DWORD i = 0; i < pParentControl->cControls; ++i)
1604 {
1605 THEME_CONTROL* pControl = pParentControl->rgControls + i;
1606 ShowControl(pTheme, pControl, dwIndex == i ? SW_SHOW : SW_HIDE, FALSE, THEME_SHOW_PAGE_REASON_DEFAULT, 0, NULL);
1607 }
1608}
1609
1610
1611// Internal functions.
1612
1613static HRESULT RegisterWindowClasses(
1614 __in_opt HMODULE hModule
1615 )
1616{
1617 HRESULT hr = S_OK;
1618 WNDCLASSW wcHyperlink = { };
1619 WNDCLASSW wcPanel = { };
1620 WNDCLASSW wcStaticOwnerDraw = { };
1621 WNDPROC pfnStaticOwnerDrawBaseWndProc = NULL;
1622
1623 vhCursorHand = ::LoadCursorA(NULL, IDC_HAND);
1624
1625 // Base the theme hyperlink class on a button but give it the "hand" icon.
1626 if (!::GetClassInfoW(NULL, WC_BUTTONW, &wcHyperlink))
1627 {
1628 ThmExitWithLastError(hr, "Failed to get button window class.");
1629 }
1630
1631 wcHyperlink.lpszClassName = THEME_WC_HYPERLINK;
1632#pragma prefast(push)
1633#pragma prefast(disable:25068)
1634 wcHyperlink.hCursor = vhCursorHand;
1635#pragma prefast(pop)
1636
1637 if (!::RegisterClassW(&wcHyperlink))
1638 {
1639 ThmExitWithLastError(hr, "Failed to get button window class.");
1640 }
1641 vhHyperlinkRegisteredModule = hModule;
1642
1643 // Panel is its own do-nothing class.
1644 wcPanel.lpfnWndProc = PanelWndProc;
1645 wcPanel.hInstance = hModule;
1646 wcPanel.hCursor = ::LoadCursorW(NULL, (LPCWSTR) IDC_ARROW);
1647 wcPanel.lpszClassName = THEME_WC_PANEL;
1648 if (!::RegisterClassW(&wcPanel))
1649 {
1650 ThmExitWithLastError(hr, "Failed to register window.");
1651 }
1652 vhPanelRegisteredModule = hModule;
1653
1654 if (!::GetClassInfoW(NULL, WC_STATICW, &wcStaticOwnerDraw))
1655 {
1656 ThmExitWithLastError(hr, "Failed to get static window class.");
1657 }
1658
1659 pfnStaticOwnerDrawBaseWndProc = wcStaticOwnerDraw.lpfnWndProc;
1660 wcStaticOwnerDraw.lpfnWndProc = StaticOwnerDrawWndProc;
1661 wcStaticOwnerDraw.hInstance = hModule;
1662 wcStaticOwnerDraw.lpszClassName = THEME_WC_STATICOWNERDRAW;
1663 if (!::RegisterClassW(&wcStaticOwnerDraw))
1664 {
1665 ThmExitWithLastError(hr, "Failed to register OwnerDraw window class.");
1666 }
1667 vhStaticOwnerDrawRegisteredModule = hModule;
1668 vpfnStaticOwnerDrawBaseWndProc = pfnStaticOwnerDrawBaseWndProc;
1669
1670
1671LExit:
1672 return hr;
1673}
1674
1675static HRESULT ParseTheme(
1676 __in_opt HMODULE hModule,
1677 __in_opt LPCWSTR wzRelativePath,
1678 __in IXMLDOMDocument* pixd,
1679 __out THEME** ppTheme
1680 )
1681{
1682 static WORD wThemeId = 0;
1683
1684 HRESULT hr = S_OK;
1685 THEME* pTheme = NULL;
1686 IXMLDOMElement *pThemeElement = NULL;
1687
1688 hr = pixd->get_documentElement(&pThemeElement);
1689 ThmExitOnFailure(hr, "Failed to get theme element.");
1690
1691 pTheme = static_cast<THEME*>(MemAlloc(sizeof(THEME), TRUE));
1692 ThmExitOnNull(pTheme, hr, E_OUTOFMEMORY, "Failed to allocate memory for theme.");
1693
1694 pTheme->wId = ++wThemeId;
1695 pTheme->nDpi = USER_DEFAULT_SCREEN_DPI;
1696
1697 // Parse the optional background resource image.
1698 hr = ParseImage(hModule, wzRelativePath, pThemeElement, &pTheme->hImage);
1699 ThmExitOnFailure(hr, "Failed while parsing theme image.");
1700
1701 // Parse the fonts.
1702 hr = ParseFonts(pThemeElement, pTheme);
1703 ThmExitOnFailure(hr, "Failed to parse theme fonts.");
1704
1705 // Parse the window element.
1706 hr = ParseWindow(hModule, wzRelativePath, pThemeElement, pTheme);
1707 ThmExitOnFailure(hr, "Failed to parse theme window element.");
1708
1709 *ppTheme = pTheme;
1710 pTheme = NULL;
1711
1712LExit:
1713 ReleaseObject(pThemeElement);
1714
1715 if (pTheme)
1716 {
1717 ThemeFree(pTheme);
1718 }
1719
1720 return hr;
1721}
1722
1723static HRESULT ParseImage(
1724 __in_opt HMODULE hModule,
1725 __in_z_opt LPCWSTR wzRelativePath,
1726 __in IXMLDOMNode* pElement,
1727 __out HBITMAP* phImage
1728 )
1729{
1730 HRESULT hr = S_OK;
1731 BSTR bstr = NULL;
1732 LPWSTR sczImageFile = NULL;
1733 int iResourceId = 0;
1734 Gdiplus::Bitmap* pBitmap = NULL;
1735 *phImage = NULL;
1736
1737 hr = XmlGetAttribute(pElement, L"ImageResource", &bstr);
1738 ThmExitOnFailure(hr, "Failed to get image resource attribute.");
1739
1740 if (S_OK == hr)
1741 {
1742 iResourceId = wcstol(bstr, NULL, 10);
1743
1744 hr = GdipBitmapFromResource(hModule, MAKEINTRESOURCE(iResourceId), &pBitmap);
1745 // Don't fail.
1746 }
1747
1748 ReleaseNullBSTR(bstr);
1749
1750 // Parse the optional background image from a given file.
1751 if (!pBitmap)
1752 {
1753 hr = XmlGetAttribute(pElement, L"ImageFile", &bstr);
1754 ThmExitOnFailure(hr, "Failed to get image file attribute.");
1755
1756 if (S_OK == hr)
1757 {
1758 if (wzRelativePath)
1759 {
1760 hr = PathConcat(wzRelativePath, bstr, &sczImageFile);
1761 ThmExitOnFailure(hr, "Failed to combine image file path.");
1762 }
1763 else
1764 {
1765 hr = PathRelativeToModule(&sczImageFile, bstr, hModule);
1766 ThmExitOnFailure(hr, "Failed to get image filename.");
1767 }
1768
1769 hr = GdipBitmapFromFile(sczImageFile, &pBitmap);
1770 // Don't fail.
1771 }
1772 }
1773
1774 // If there is an image, convert it into a bitmap handle.
1775 if (pBitmap)
1776 {
1777 Gdiplus::Color black;
1778 Gdiplus::Status gs = pBitmap->GetHBITMAP(black, phImage);
1779 ThmExitOnGdipFailure(gs, hr, "Failed to convert GDI+ bitmap into HBITMAP.");
1780 }
1781
1782 hr = S_OK;
1783
1784LExit:
1785 if (pBitmap)
1786 {
1787 delete pBitmap;
1788 }
1789
1790 ReleaseStr(sczImageFile);
1791 ReleaseBSTR(bstr);
1792
1793 return hr;
1794}
1795
1796
1797static HRESULT ParseIcon(
1798 __in_opt HMODULE hModule,
1799 __in_z_opt LPCWSTR wzRelativePath,
1800 __in IXMLDOMNode* pElement,
1801 __out HICON* phIcon
1802 )
1803{
1804 HRESULT hr = S_OK;
1805 BSTR bstr = NULL;
1806 LPWSTR sczImageFile = NULL;
1807 int iResourceId = 0;
1808 *phIcon = NULL;
1809
1810 hr = XmlGetAttribute(pElement, L"IconResource", &bstr);
1811 ThmExitOnFailure(hr, "Failed to get icon resource attribute.");
1812
1813 if (S_OK == hr)
1814 {
1815 iResourceId = wcstol(bstr, NULL, 10);
1816
1817 *phIcon = reinterpret_cast<HICON>(::LoadImageW(hModule, MAKEINTRESOURCEW(iResourceId), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
1818 ThmExitOnNullWithLastError(*phIcon, hr, "Failed to load icon.");
1819 }
1820 else
1821 {
1822 ReleaseNullBSTR(bstr);
1823
1824 hr = XmlGetAttribute(pElement, L"IconFile", &bstr);
1825 ThmExitOnFailure(hr, "Failed to get icon file attribute.");
1826
1827 if (S_OK == hr)
1828 {
1829 if (wzRelativePath)
1830 {
1831 hr = PathConcat(wzRelativePath, bstr, &sczImageFile);
1832 ThmExitOnFailure(hr, "Failed to combine image file path.");
1833 }
1834 else
1835 {
1836 hr = PathRelativeToModule(&sczImageFile, bstr, hModule);
1837 ThmExitOnFailure(hr, "Failed to get image filename.");
1838 }
1839
1840 *phIcon = reinterpret_cast<HICON>(::LoadImageW(NULL, sczImageFile, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE));
1841 ThmExitOnNullWithLastError(*phIcon, hr, "Failed to load icon: %ls.", sczImageFile);
1842 }
1843 }
1844
1845LExit:
1846 ReleaseStr(sczImageFile);
1847 ReleaseBSTR(bstr);
1848
1849 return hr;
1850}
1851
1852
1853static HRESULT ParseWindow(
1854 __in_opt HMODULE hModule,
1855 __in_opt LPCWSTR wzRelativePath,
1856 __in IXMLDOMElement* pElement,
1857 __in THEME* pTheme
1858 )
1859{
1860 HRESULT hr = S_OK;
1861 IXMLDOMNode* pixn = NULL;
1862 DWORD dwValue = 0;
1863 BSTR bstr = NULL;
1864 LPWSTR sczIconFile = NULL;
1865
1866 hr = XmlSelectSingleNode(pElement, L"Window", &pixn);
1867 if (S_FALSE == hr)
1868 {
1869 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
1870 }
1871 ThmExitOnFailure(hr, "Failed to find window element.");
1872
1873 hr = XmlGetYesNoAttribute(pixn, L"AutoResize", &pTheme->fAutoResize);
1874 if (E_NOTFOUND == hr)
1875 {
1876 hr = S_OK;
1877 }
1878 ThmExitOnFailure(hr, "Failed to get window AutoResize attribute.");
1879
1880 hr = XmlGetAttributeNumber(pixn, L"Width", &dwValue);
1881 if (S_FALSE == hr)
1882 {
1883 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
1884 ThmExitOnRootFailure(hr, "Failed to find window Width attribute.");
1885 }
1886 ThmExitOnFailure(hr, "Failed to get window Width attribute.");
1887
1888 pTheme->nWidth = pTheme->nDefaultDpiWidth = pTheme->nWindowWidth = dwValue;
1889
1890 hr = XmlGetAttributeNumber(pixn, L"Height", &dwValue);
1891 if (S_FALSE == hr)
1892 {
1893 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
1894 ThmExitOnRootFailure(hr, "Failed to find window Height attribute.");
1895 }
1896 ThmExitOnFailure(hr, "Failed to get window Height attribute.");
1897
1898 pTheme->nHeight = pTheme->nDefaultDpiHeight = pTheme->nWindowHeight = dwValue;
1899
1900 hr = XmlGetAttributeNumber(pixn, L"MinimumWidth", &dwValue);
1901 if (S_FALSE == hr)
1902 {
1903 dwValue = 0;
1904 hr = S_OK;
1905 }
1906 ThmExitOnFailure(hr, "Failed to get window MinimumWidth attribute.");
1907
1908 pTheme->nMinimumWidth = pTheme->nDefaultDpiMinimumWidth = dwValue;
1909
1910 hr = XmlGetAttributeNumber(pixn, L"MinimumHeight", &dwValue);
1911 if (S_FALSE == hr)
1912 {
1913 dwValue = 0;
1914 hr = S_OK;
1915 }
1916 ThmExitOnFailure(hr, "Failed to get window MinimumHeight attribute.");
1917
1918 pTheme->nMinimumHeight = pTheme->nDefaultDpiMinimumHeight = dwValue;
1919
1920 hr = XmlGetAttributeNumber(pixn, L"FontId", &pTheme->dwFontId);
1921 if (S_FALSE == hr)
1922 {
1923 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
1924 ThmExitOnRootFailure(hr, "Failed to find window FontId attribute.");
1925 }
1926 ThmExitOnFailure(hr, "Failed to get window FontId attribute.");
1927
1928 // Get the optional window icon from a resource.
1929 hr = XmlGetAttribute(pixn, L"IconResource", &bstr);
1930 ThmExitOnFailure(hr, "Failed to get window IconResource attribute.");
1931
1932 if (S_OK == hr)
1933 {
1934 pTheme->hIcon = ::LoadIconW(hModule, bstr);
1935 ThmExitOnNullWithLastError(pTheme->hIcon, hr, "Failed to load window icon from IconResource.");
1936
1937 ReleaseNullBSTR(bstr);
1938 }
1939
1940 // Get the optional window icon from a file.
1941 hr = XmlGetAttribute(pixn, L"IconFile", &bstr);
1942 ThmExitOnFailure(hr, "Failed to get window IconFile attribute.");
1943
1944 if (S_OK == hr)
1945 {
1946 if (wzRelativePath)
1947 {
1948 hr = PathConcat(wzRelativePath, bstr, &sczIconFile);
1949 ThmExitOnFailure(hr, "Failed to combine icon file path.");
1950 }
1951 else
1952 {
1953 hr = PathRelativeToModule(&sczIconFile, bstr, hModule);
1954 ThmExitOnFailure(hr, "Failed to get icon filename.");
1955 }
1956
1957 pTheme->hIcon = ::LoadImageW(NULL, sczIconFile, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
1958 ThmExitOnNullWithLastError(pTheme->hIcon, hr, "Failed to load window icon from IconFile: %ls.", bstr);
1959
1960 ReleaseNullBSTR(bstr);
1961 }
1962
1963 hr = XmlGetAttributeNumber(pixn, L"SourceX", reinterpret_cast<DWORD*>(&pTheme->nSourceX));
1964 if (S_FALSE == hr)
1965 {
1966 pTheme->nSourceX = -1;
1967 }
1968 ThmExitOnFailure(hr, "Failed to get window SourceX attribute.");
1969
1970 hr = XmlGetAttributeNumber(pixn, L"SourceY", reinterpret_cast<DWORD*>(&pTheme->nSourceY));
1971 if (S_FALSE == hr)
1972 {
1973 pTheme->nSourceY = -1;
1974 }
1975 ThmExitOnFailure(hr, "Failed to get window SourceY attribute.");
1976
1977 // Parse the optional window style.
1978 hr = XmlGetAttributeNumberBase(pixn, L"HexStyle", 16, &pTheme->dwStyle);
1979 ThmExitOnFailure(hr, "Failed to get theme window style (Window@HexStyle) attribute.");
1980
1981 if (S_FALSE == hr)
1982 {
1983 pTheme->dwStyle = WS_VISIBLE | WS_MINIMIZEBOX | WS_SYSMENU;
1984 pTheme->dwStyle |= (0 <= pTheme->nSourceX && 0 <= pTheme->nSourceY) ? WS_POPUP : WS_OVERLAPPED;
1985 }
1986
1987 hr = XmlGetAttributeNumber(pixn, L"StringId", reinterpret_cast<DWORD*>(&pTheme->uStringId));
1988 ThmExitOnFailure(hr, "Failed to get window StringId attribute.");
1989
1990 if (S_FALSE == hr)
1991 {
1992 pTheme->uStringId = UINT_MAX;
1993
1994 hr = XmlGetAttribute(pixn, L"Caption", &bstr);
1995 ThmExitOnFailure(hr, "Failed to get window Caption attribute.");
1996
1997 if (S_FALSE == hr)
1998 {
1999 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2000 ThmExitOnRootFailure(hr, "Window elements must contain the Caption or StringId attribute.");
2001 }
2002
2003 hr = StrAllocString(&pTheme->sczCaption, bstr, 0);
2004 ThmExitOnFailure(hr, "Failed to copy window Caption attribute.");
2005 }
2006
2007 // Parse any image lists.
2008 hr = ParseImageLists(hModule, wzRelativePath, pixn, pTheme);
2009 ThmExitOnFailure(hr, "Failed to parse image lists.");
2010
2011 // Parse the pages.
2012 hr = ParsePages(hModule, wzRelativePath, pixn, pTheme);
2013 ThmExitOnFailure(hr, "Failed to parse theme pages.");
2014
2015 // Parse the non-paged controls.
2016 hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, NULL, NULL);
2017 ThmExitOnFailure(hr, "Failed to parse theme controls.");
2018
2019LExit:
2020 ReleaseStr(sczIconFile);
2021 ReleaseBSTR(bstr);
2022 ReleaseObject(pixn);
2023
2024 return hr;
2025}
2026
2027
2028static HRESULT ParseFonts(
2029 __in IXMLDOMElement* pElement,
2030 __in THEME* pTheme
2031 )
2032{
2033 HRESULT hr = S_OK;
2034 IXMLDOMNodeList* pixnl = NULL;
2035 IXMLDOMNode* pixn = NULL;
2036 BSTR bstrName = NULL;
2037 DWORD dwId = 0;
2038 COLORREF crForeground = THEME_INVISIBLE_COLORREF;
2039 COLORREF crBackground = THEME_INVISIBLE_COLORREF;
2040 DWORD dwSystemForegroundColor = FALSE;
2041 DWORD dwSystemBackgroundColor = FALSE;
2042
2043 hr = XmlSelectNodes(pElement, L"Font", &pixnl);
2044 ThmExitOnFailure(hr, "Failed to find font elements.");
2045
2046 hr = pixnl->get_length(reinterpret_cast<long*>(&pTheme->cFonts));
2047 ThmExitOnFailure(hr, "Failed to count the number of theme fonts.");
2048
2049 if (!pTheme->cFonts)
2050 {
2051 ExitFunction1(hr = S_OK);
2052 }
2053
2054 pTheme->rgFonts = static_cast<THEME_FONT*>(MemAlloc(sizeof(THEME_FONT) * pTheme->cFonts, TRUE));
2055 ThmExitOnNull(pTheme->rgFonts, hr, E_OUTOFMEMORY, "Failed to allocate theme fonts.");
2056
2057 while (S_OK == (hr = XmlNextElement(pixnl, &pixn, NULL)))
2058 {
2059 hr = XmlGetAttributeNumber(pixn, L"Id", &dwId);
2060 if (S_FALSE == hr)
2061 {
2062 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2063 }
2064 ThmExitOnFailure(hr, "Failed to find font id.");
2065
2066 if (pTheme->cFonts <= dwId)
2067 {
2068 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2069 ThmExitOnRootFailure(hr, "Invalid theme font id.");
2070 }
2071
2072 THEME_FONT* pFont = pTheme->rgFonts + dwId;
2073 if (pFont->cFontInstances)
2074 {
2075 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2076 ThmExitOnRootFailure(hr, "Theme font id duplicated.");
2077 }
2078
2079 pFont->lfQuality = CLEARTYPE_QUALITY;
2080
2081 hr = XmlGetText(pixn, &bstrName);
2082 if (S_FALSE == hr)
2083 {
2084 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2085 }
2086 ThmExitOnFailure(hr, "Failed to get font name.");
2087
2088 hr = StrAllocString(&pFont->sczFaceName, bstrName, 0);
2089 ThmExitOnFailure(hr, "Failed to copy font name.");
2090
2091 hr = XmlGetAttributeNumber(pixn, L"Height", reinterpret_cast<DWORD*>(&pFont->lfHeight));
2092 if (S_FALSE == hr)
2093 {
2094 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2095 }
2096 ThmExitOnFailure(hr, "Failed to find font height attribute.");
2097
2098 hr = XmlGetAttributeNumber(pixn, L"Weight", reinterpret_cast<DWORD*>(&pFont->lfWeight));
2099 if (S_FALSE == hr)
2100 {
2101 pFont->lfWeight = FW_DONTCARE;
2102 hr = S_OK;
2103 }
2104 ThmExitOnFailure(hr, "Failed to find font weight attribute.");
2105
2106 hr = XmlGetYesNoAttribute(pixn, L"Underline", reinterpret_cast<BOOL*>(&pFont->lfUnderline));
2107 if (E_NOTFOUND == hr)
2108 {
2109 pFont->lfUnderline = FALSE;
2110 hr = S_OK;
2111 }
2112 ThmExitOnFailure(hr, "Failed to find font underline attribute.");
2113
2114 hr = GetFontColor(pixn, L"Foreground", &crForeground, &dwSystemForegroundColor);
2115 ThmExitOnFailure(hr, "Failed to find font foreground color.");
2116
2117 hr = GetFontColor(pixn, L"Background", &crBackground, &dwSystemBackgroundColor);
2118 ThmExitOnFailure(hr, "Failed to find font background color.");
2119
2120 pFont->crForeground = crForeground;
2121 if (THEME_INVISIBLE_COLORREF != pFont->crForeground)
2122 {
2123 pFont->hForeground = dwSystemForegroundColor ? ::GetSysColorBrush(dwSystemForegroundColor) : ::CreateSolidBrush(pFont->crForeground);
2124 ThmExitOnNull(pFont->hForeground, hr, E_OUTOFMEMORY, "Failed to create text foreground brush.");
2125 }
2126
2127 pFont->crBackground = crBackground;
2128 if (THEME_INVISIBLE_COLORREF != pFont->crBackground)
2129 {
2130 pFont->hBackground = dwSystemBackgroundColor ? ::GetSysColorBrush(dwSystemBackgroundColor) : ::CreateSolidBrush(pFont->crBackground);
2131 ThmExitOnNull(pFont->hBackground, hr, E_OUTOFMEMORY, "Failed to create text background brush.");
2132 }
2133
2134 ReleaseNullBSTR(bstrName);
2135 ReleaseNullObject(pixn);
2136 }
2137 ThmExitOnFailure(hr, "Failed to enumerate all fonts.");
2138
2139 if (S_FALSE == hr)
2140 {
2141 hr = S_OK;
2142 }
2143
2144LExit:
2145 ReleaseBSTR(bstrName);
2146 ReleaseObject(pixn);
2147 ReleaseObject(pixnl);
2148
2149 return hr;
2150}
2151
2152
2153static HRESULT GetFontColor(
2154 __in IXMLDOMNode* pixn,
2155 __in_z LPCWSTR wzAttributeName,
2156 __out COLORREF* pColorRef,
2157 __out DWORD* pdwSystemColor
2158 )
2159{
2160 HRESULT hr = S_OK;
2161 BSTR bstr = NULL;
2162
2163 *pdwSystemColor = 0;
2164
2165 hr = XmlGetAttribute(pixn, wzAttributeName, &bstr);
2166 if (S_FALSE == hr)
2167 {
2168 *pColorRef = THEME_INVISIBLE_COLORREF;
2169 ExitFunction1(hr = S_OK);
2170 }
2171 ThmExitOnFailure(hr, "Failed to find font %ls color.", wzAttributeName);
2172
2173 if (pdwSystemColor)
2174 {
2175 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"btnface", -1))
2176 {
2177 *pdwSystemColor = COLOR_BTNFACE;
2178 }
2179 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"btntext", -1))
2180 {
2181 *pdwSystemColor = COLOR_BTNTEXT;
2182 }
2183 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"graytext", -1))
2184 {
2185 *pdwSystemColor = COLOR_GRAYTEXT;
2186 }
2187 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"highlight", -1))
2188 {
2189 *pdwSystemColor = COLOR_HIGHLIGHT;
2190 }
2191 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"highlighttext", -1))
2192 {
2193 *pdwSystemColor = COLOR_HIGHLIGHTTEXT;
2194 }
2195 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"hotlight", -1))
2196 {
2197 *pdwSystemColor = COLOR_HOTLIGHT;
2198 }
2199 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"window", -1))
2200 {
2201 *pdwSystemColor = COLOR_WINDOW;
2202 }
2203 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"windowtext", -1))
2204 {
2205 *pdwSystemColor = COLOR_WINDOWTEXT;
2206 }
2207 else
2208 {
2209 *pColorRef = wcstoul(bstr, NULL, 16);
2210 }
2211
2212 if (*pdwSystemColor)
2213 {
2214 *pColorRef = ::GetSysColor(*pdwSystemColor);
2215 }
2216 }
2217
2218LExit:
2219 ReleaseBSTR(bstr);
2220
2221 return hr;
2222}
2223
2224static HRESULT ParsePages(
2225 __in_opt HMODULE hModule,
2226 __in_opt LPCWSTR wzRelativePath,
2227 __in IXMLDOMNode* pElement,
2228 __in THEME* pTheme
2229 )
2230{
2231 HRESULT hr = S_OK;
2232 IXMLDOMNodeList* pixnl = NULL;
2233 IXMLDOMNode* pixn = NULL;
2234 BSTR bstrType = NULL;
2235 THEME_PAGE* pPage = NULL;
2236 DWORD iPage = 0;
2237
2238 hr = XmlSelectNodes(pElement, L"Page", &pixnl);
2239 ThmExitOnFailure(hr, "Failed to find page elements.");
2240
2241 hr = pixnl->get_length(reinterpret_cast<long*>(&pTheme->cPages));
2242 ThmExitOnFailure(hr, "Failed to count the number of theme pages.");
2243
2244 if (!pTheme->cPages)
2245 {
2246 ExitFunction1(hr = S_OK);
2247 }
2248
2249 pTheme->rgPages = static_cast<THEME_PAGE*>(MemAlloc(sizeof(THEME_PAGE) * pTheme->cPages, TRUE));
2250 ThmExitOnNull(pTheme->rgPages, hr, E_OUTOFMEMORY, "Failed to allocate theme pages.");
2251
2252 while (S_OK == (hr = XmlNextElement(pixnl, &pixn, &bstrType)))
2253 {
2254 pPage = pTheme->rgPages + iPage;
2255
2256 pPage->wId = static_cast<WORD>(iPage + 1);
2257
2258 hr = XmlGetAttributeEx(pixn, L"Name", &pPage->sczName);
2259 if (E_NOTFOUND == hr)
2260 {
2261 hr = S_OK;
2262 }
2263 ThmExitOnFailure(hr, "Failed when querying page Name.");
2264
2265 hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, NULL, pPage);
2266 ThmExitOnFailure(hr, "Failed to parse page controls.");
2267
2268 ++iPage;
2269
2270 ReleaseNullBSTR(bstrType);
2271 ReleaseNullObject(pixn);
2272 }
2273 ThmExitOnFailure(hr, "Failed to enumerate all pages.");
2274
2275 if (S_FALSE == hr)
2276 {
2277 hr = S_OK;
2278 }
2279
2280LExit:
2281 ReleaseBSTR(bstrType);
2282 ReleaseObject(pixn);
2283 ReleaseObject(pixnl);
2284
2285 return hr;
2286}
2287
2288
2289static HRESULT ParseImageLists(
2290 __in_opt HMODULE hModule,
2291 __in_opt LPCWSTR wzRelativePath,
2292 __in IXMLDOMNode* pElement,
2293 __in THEME* pTheme
2294 )
2295{
2296 HRESULT hr = S_OK;
2297 IXMLDOMNodeList* pixnlImageLists = NULL;
2298 IXMLDOMNode* pixnImageList = NULL;
2299 IXMLDOMNodeList* pixnlImages = NULL;
2300 IXMLDOMNode* pixnImage = NULL;
2301 DWORD dwImageListIndex = 0;
2302 DWORD dwImageCount = 0;
2303 HBITMAP hBitmap = NULL;
2304 BITMAP bm = { };
2305 BSTR bstr = NULL;
2306 DWORD i = 0;
2307 int iRetVal = 0;
2308
2309 hr = XmlSelectNodes(pElement, L"ImageList", &pixnlImageLists);
2310 ThmExitOnFailure(hr, "Failed to find ImageList elements.");
2311
2312 hr = pixnlImageLists->get_length(reinterpret_cast<long*>(&pTheme->cImageLists));
2313 ThmExitOnFailure(hr, "Failed to count the number of image lists.");
2314
2315 if (!pTheme->cImageLists)
2316 {
2317 ExitFunction1(hr = S_OK);
2318 }
2319
2320 pTheme->rgImageLists = static_cast<THEME_IMAGELIST*>(MemAlloc(sizeof(THEME_IMAGELIST) * pTheme->cImageLists, TRUE));
2321 ThmExitOnNull(pTheme->rgImageLists, hr, E_OUTOFMEMORY, "Failed to allocate theme image lists.");
2322
2323 while (S_OK == (hr = XmlNextElement(pixnlImageLists, &pixnImageList, NULL)))
2324 {
2325 hr = XmlGetAttribute(pixnImageList, L"Name", &bstr);
2326 if (S_FALSE == hr)
2327 {
2328 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2329 }
2330 ThmExitOnFailure(hr, "Failed to find ImageList/@Name attribute.");
2331
2332 hr = StrAllocString(&pTheme->rgImageLists[dwImageListIndex].sczName, bstr, 0);
2333 ThmExitOnFailure(hr, "Failed to make copy of ImageList name.");
2334
2335 hr = XmlSelectNodes(pixnImageList, L"Image", &pixnlImages);
2336 ThmExitOnFailure(hr, "Failed to select child Image nodes.");
2337
2338 hr = pixnlImages->get_length(reinterpret_cast<long*>(&dwImageCount));
2339 ThmExitOnFailure(hr, "Failed to count the number of images in list.");
2340
2341 if (0 < dwImageCount)
2342 {
2343 i = 0;
2344 while (S_OK == (hr = XmlNextElement(pixnlImages, &pixnImage, NULL)))
2345 {
2346 if (hBitmap)
2347 {
2348 ::DeleteObject(hBitmap);
2349 hBitmap = NULL;
2350 }
2351 hr = ParseImage(hModule, wzRelativePath, pixnImage, &hBitmap);
2352 ThmExitOnFailure(hr, "Failed to parse image: %u", i);
2353
2354 if (0 == i)
2355 {
2356 ::GetObjectW(hBitmap, sizeof(BITMAP), &bm);
2357
2358 pTheme->rgImageLists[dwImageListIndex].hImageList = ImageList_Create(bm.bmWidth, bm.bmHeight, ILC_COLOR24, dwImageCount, 0);
2359 ThmExitOnNullWithLastError(pTheme->rgImageLists[dwImageListIndex].hImageList, hr, "Failed to create image list.");
2360 }
2361
2362 iRetVal = ImageList_Add(pTheme->rgImageLists[dwImageListIndex].hImageList, hBitmap, NULL);
2363 if (-1 == iRetVal)
2364 {
2365 ThmExitWithLastError(hr, "Failed to add image %u to image list.", i);
2366 }
2367
2368 ++i;
2369 }
2370 }
2371 ++dwImageListIndex;
2372 }
2373
2374LExit:
2375 if (hBitmap)
2376 {
2377 ::DeleteObject(hBitmap);
2378 }
2379 ReleaseBSTR(bstr);
2380 ReleaseObject(pixnlImageLists);
2381 ReleaseObject(pixnImageList);
2382 ReleaseObject(pixnlImages);
2383 ReleaseObject(pixnImage);
2384
2385 return hr;
2386}
2387
2388static void GetControls(
2389 __in THEME* pTheme,
2390 __in_opt THEME_CONTROL* pParentControl,
2391 __out DWORD** ppcControls,
2392 __out THEME_CONTROL*** pprgControls
2393 )
2394{
2395 if (pParentControl)
2396 {
2397 *ppcControls = &pParentControl->cControls;
2398 *pprgControls = &pParentControl->rgControls;
2399 }
2400 else
2401 {
2402 *ppcControls = &pTheme->cControls;
2403 *pprgControls = &pTheme->rgControls;
2404 }
2405}
2406
2407static void GetControls(
2408 __in const THEME* pTheme,
2409 __in_opt const THEME_CONTROL* pParentControl,
2410 __out DWORD& cControls,
2411 __out THEME_CONTROL*& rgControls
2412 )
2413{
2414 if (pParentControl)
2415 {
2416 cControls = pParentControl->cControls;
2417 rgControls = pParentControl->rgControls;
2418 }
2419 else
2420 {
2421 cControls = pTheme->cControls;
2422 rgControls = pTheme->rgControls;
2423 }
2424}
2425
2426static HRESULT ParseControls(
2427 __in_opt HMODULE hModule,
2428 __in_opt LPCWSTR wzRelativePath,
2429 __in IXMLDOMNode* pElement,
2430 __in THEME* pTheme,
2431 __in_opt THEME_CONTROL* pParentControl,
2432 __in_opt THEME_PAGE* pPage
2433 )
2434{
2435 HRESULT hr = S_OK;
2436 IXMLDOMNodeList* pixnl = NULL;
2437 IXMLDOMNode* pixn = NULL;
2438 BSTR bstrType = NULL;
2439 DWORD cNewControls = 0;
2440 DWORD iControl = 0;
2441 DWORD iPageControl = 0;
2442 DWORD* pcControls = NULL;
2443 THEME_CONTROL** prgControls = NULL;
2444
2445 GetControls(pTheme, pParentControl, &pcControls, &prgControls);
2446
2447 hr = ParseRadioButtons(hModule, wzRelativePath, pElement, pTheme, pParentControl, pPage);
2448 ThmExitOnFailure(hr, "Failed to parse radio buttons.");
2449
2450 hr = XmlSelectNodes(pElement, L"Billboard|Button|Checkbox|Combobox|CommandLink|Editbox|Hyperlink|Hypertext|ImageControl|Label|ListView|Panel|Progressbar|Richedit|Static|Tabs|TreeView", &pixnl);
2451 ThmExitOnFailure(hr, "Failed to find control elements.");
2452
2453 hr = pixnl->get_length(reinterpret_cast<long*>(&cNewControls));
2454 ThmExitOnFailure(hr, "Failed to count the number of theme controls.");
2455
2456 if (!cNewControls)
2457 {
2458 ExitFunction1(hr = S_OK);
2459 }
2460
2461 hr = MemReAllocArray(reinterpret_cast<LPVOID*>(prgControls), *pcControls, sizeof(THEME_CONTROL), cNewControls);
2462 ThmExitOnFailure(hr, "Failed to reallocate theme controls.");
2463
2464 cNewControls += *pcControls;
2465
2466 if (pPage)
2467 {
2468 iPageControl = pPage->cControlIndices;
2469 pPage->cControlIndices += cNewControls;
2470 }
2471
2472 iControl = *pcControls;
2473 *pcControls = cNewControls;
2474
2475 while (S_OK == (hr = XmlNextElement(pixnl, &pixn, &bstrType)))
2476 {
2477 THEME_CONTROL_TYPE type = THEME_CONTROL_TYPE_UNKNOWN;
2478
2479 if (!bstrType)
2480 {
2481 hr = E_UNEXPECTED;
2482 ThmExitOnFailure(hr, "Null element encountered!");
2483 }
2484
2485 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Billboard", -1))
2486 {
2487 type = THEME_CONTROL_TYPE_BILLBOARD;
2488 }
2489 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Button", -1))
2490 {
2491 type = THEME_CONTROL_TYPE_BUTTON;
2492 }
2493 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Checkbox", -1))
2494 {
2495 type = THEME_CONTROL_TYPE_CHECKBOX;
2496 }
2497 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Combobox", -1))
2498 {
2499 type = THEME_CONTROL_TYPE_COMBOBOX;
2500 }
2501 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"CommandLink", -1))
2502 {
2503 type = THEME_CONTROL_TYPE_COMMANDLINK;
2504 }
2505 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Editbox", -1))
2506 {
2507 type = THEME_CONTROL_TYPE_EDITBOX;
2508 }
2509 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Hyperlink", -1))
2510 {
2511 type = THEME_CONTROL_TYPE_HYPERLINK;
2512 }
2513 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Hypertext", -1))
2514 {
2515 type = THEME_CONTROL_TYPE_HYPERTEXT;
2516 }
2517 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"ImageControl", -1))
2518 {
2519 type = THEME_CONTROL_TYPE_IMAGE;
2520 }
2521 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Label", -1))
2522 {
2523 type = THEME_CONTROL_TYPE_LABEL;
2524 }
2525 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"ListView", -1))
2526 {
2527 type = THEME_CONTROL_TYPE_LISTVIEW;
2528 }
2529 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Panel", -1))
2530 {
2531 type = THEME_CONTROL_TYPE_PANEL;
2532 }
2533 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Progressbar", -1))
2534 {
2535 type = THEME_CONTROL_TYPE_PROGRESSBAR;
2536 }
2537 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Richedit", -1))
2538 {
2539 type = THEME_CONTROL_TYPE_RICHEDIT;
2540 }
2541 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Static", -1))
2542 {
2543 type = THEME_CONTROL_TYPE_STATIC;
2544 }
2545 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Tabs", -1))
2546 {
2547 type = THEME_CONTROL_TYPE_TAB;
2548 }
2549 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"TreeView", -1))
2550 {
2551 type = THEME_CONTROL_TYPE_TREEVIEW;
2552 }
2553
2554 if (THEME_CONTROL_TYPE_UNKNOWN != type)
2555 {
2556 THEME_CONTROL* pControl = *prgControls + iControl;
2557 pControl->type = type;
2558
2559 // billboard children are always the size of the billboard
2560 BOOL fBillboardSizing = pParentControl && THEME_CONTROL_TYPE_BILLBOARD == pParentControl->type;
2561
2562 hr = ParseControl(hModule, wzRelativePath, pixn, pTheme, pControl, fBillboardSizing, pPage);
2563 ThmExitOnFailure(hr, "Failed to parse control.");
2564
2565 if (fBillboardSizing)
2566 {
2567 pControl->nX = pControl->nDefaultDpiX = 0;
2568 pControl->nY = pControl->nDefaultDpiY = 0;
2569 pControl->nWidth = pControl->nDefaultDpiWidth = 0;
2570 pControl->nHeight = pControl->nDefaultDpiHeight = 0;
2571 }
2572
2573 if (pPage)
2574 {
2575 pControl->wPageId = pPage->wId;
2576 ++iPageControl;
2577 }
2578
2579 ++iControl;
2580 }
2581
2582 ReleaseNullBSTR(bstrType);
2583 ReleaseNullObject(pixn);
2584 }
2585 ThmExitOnFailure(hr, "Failed to enumerate all controls.");
2586
2587 if (S_FALSE == hr)
2588 {
2589 hr = S_OK;
2590 }
2591
2592 AssertSz(iControl == cNewControls, "The number of parsed controls didn't match the number of expected controls.");
2593
2594LExit:
2595 ReleaseBSTR(bstrType);
2596 ReleaseObject(pixn);
2597 ReleaseObject(pixnl);
2598
2599 return hr;
2600}
2601
2602
2603static HRESULT ParseControl(
2604 __in_opt HMODULE hModule,
2605 __in_opt LPCWSTR wzRelativePath,
2606 __in IXMLDOMNode* pixn,
2607 __in THEME* pTheme,
2608 __in THEME_CONTROL* pControl,
2609 __in BOOL fSkipDimensions,
2610 __in_opt THEME_PAGE* pPage
2611 )
2612{
2613 HRESULT hr = S_OK;
2614 DWORD dwValue = 0;
2615 BOOL fValue = FALSE;
2616 BSTR bstrText = NULL;
2617 BOOL fAnyTextChildren = FALSE;
2618 BOOL fAnyNoteChildren = FALSE;
2619
2620 hr = XmlGetAttributeEx(pixn, L"Name", &pControl->sczName);
2621 if (E_NOTFOUND == hr)
2622 {
2623 hr = S_OK;
2624 }
2625 ThmExitOnFailure(hr, "Failed when querying control Name attribute.");
2626
2627 hr = XmlGetAttributeEx(pixn, L"EnableCondition", &pControl->sczEnableCondition);
2628 if (E_NOTFOUND == hr)
2629 {
2630 hr = S_OK;
2631 }
2632 ThmExitOnFailure(hr, "Failed when querying control EnableCondition attribute.");
2633
2634 hr = XmlGetAttributeEx(pixn, L"VisibleCondition", &pControl->sczVisibleCondition);
2635 if (E_NOTFOUND == hr)
2636 {
2637 hr = S_OK;
2638 }
2639 ThmExitOnFailure(hr, "Failed when querying control VisibleCondition attribute.");
2640
2641 if (!fSkipDimensions)
2642 {
2643 hr = XmlGetAttributeNumber(pixn, L"X", &dwValue);
2644 if (S_FALSE == hr)
2645 {
2646 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2647 }
2648 ThmExitOnFailure(hr, "Failed to find control X attribute.");
2649
2650 pControl->nX = pControl->nDefaultDpiX = dwValue;
2651
2652 hr = XmlGetAttributeNumber(pixn, L"Y", &dwValue);
2653 if (S_FALSE == hr)
2654 {
2655 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2656 }
2657 ThmExitOnFailure(hr, "Failed to find control Y attribute.");
2658
2659 pControl->nY = pControl->nDefaultDpiY = dwValue;
2660
2661 hr = XmlGetAttributeNumber(pixn, L"Height", &dwValue);
2662 if (S_FALSE == hr)
2663 {
2664 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2665 }
2666 ThmExitOnFailure(hr, "Failed to find control Height attribute.");
2667
2668 pControl->nHeight = pControl->nDefaultDpiHeight = dwValue;
2669
2670 hr = XmlGetAttributeNumber(pixn, L"Width", &dwValue);
2671 if (S_FALSE == hr)
2672 {
2673 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
2674 }
2675 ThmExitOnFailure(hr, "Failed to find control Width attribute.");
2676
2677 pControl->nWidth = pControl->nDefaultDpiWidth = dwValue;
2678 }
2679
2680 // Parse the optional background resource image.
2681 hr = ParseImage(hModule, wzRelativePath, pixn, &pControl->hImage);
2682 ThmExitOnFailure(hr, "Failed while parsing control image.");
2683
2684 hr = XmlGetAttributeNumber(pixn, L"SourceX", reinterpret_cast<DWORD*>(&pControl->nSourceX));
2685 if (S_FALSE == hr)
2686 {
2687 pControl->nSourceX = -1;
2688 }
2689 ThmExitOnFailure(hr, "Failed when querying control SourceX attribute.");
2690
2691 hr = XmlGetAttributeNumber(pixn, L"SourceY", reinterpret_cast<DWORD*>(&pControl->nSourceY));
2692 if (S_FALSE == hr)
2693 {
2694 pControl->nSourceY = -1;
2695 }
2696 ThmExitOnFailure(hr, "Failed when querying control SourceY attribute.");
2697
2698 hr = XmlGetAttributeNumber(pixn, L"FontId", &pControl->dwFontId);
2699 if (S_FALSE == hr)
2700 {
2701 pControl->dwFontId = THEME_INVALID_ID;
2702 }
2703 ThmExitOnFailure(hr, "Failed when querying control FontId attribute.");
2704
2705 // Parse the optional window style.
2706 hr = XmlGetAttributeNumberBase(pixn, L"HexStyle", 16, &pControl->dwStyle);
2707 ThmExitOnFailure(hr, "Failed when querying control HexStyle attribute.");
2708
2709 // Parse the tabstop bit "shortcut nomenclature", this could have been set with the style above.
2710 hr = XmlGetYesNoAttribute(pixn, L"TabStop", &fValue);
2711 if (E_NOTFOUND == hr)
2712 {
2713 hr = S_OK;
2714 }
2715 else
2716 {
2717 ThmExitOnFailure(hr, "Failed when querying control TabStop attribute.");
2718
2719 if (fValue)
2720 {
2721 pControl->dwStyle |= WS_TABSTOP;
2722 }
2723 }
2724
2725 hr = XmlGetYesNoAttribute(pixn, L"Visible", &fValue);
2726 if (E_NOTFOUND == hr)
2727 {
2728 hr = S_OK;
2729 }
2730 else
2731 {
2732 ThmExitOnFailure(hr, "Failed when querying control Visible attribute.");
2733
2734 if (fValue)
2735 {
2736 pControl->dwStyle |= WS_VISIBLE;
2737 }
2738 }
2739
2740 hr = XmlGetYesNoAttribute(pixn, L"HideWhenDisabled", &fValue);
2741 if (E_NOTFOUND == hr)
2742 {
2743 hr = S_OK;
2744 }
2745 else
2746 {
2747 ThmExitOnFailure(hr, "Failed when querying control HideWhenDisabled attribute.");
2748
2749 if (fValue)
2750 {
2751 pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED;
2752 }
2753 }
2754
2755 hr = XmlGetYesNoAttribute(pixn, L"DisableAutomaticBehavior", &pControl->fDisableVariableFunctionality);
2756 if (E_NOTFOUND == hr)
2757 {
2758 hr = S_OK;
2759 }
2760 else
2761 {
2762 ThmExitOnFailure(hr, "Failed when querying control DisableAutomaticBehavior attribute.");
2763 }
2764
2765 hr = ParseActions(pixn, pControl);
2766 ThmExitOnFailure(hr, "Failed to parse action nodes of the control.");
2767
2768 hr = ParseText(pixn, pControl, &fAnyTextChildren);
2769 ThmExitOnFailure(hr, "Failed to parse text nodes of the control.");
2770
2771 hr = ParseTooltips(pixn, pControl, &fAnyTextChildren);
2772 ThmExitOnFailure(hr, "Failed to parse control Tooltip.");
2773
2774 if (THEME_CONTROL_TYPE_COMMANDLINK == pControl->type)
2775 {
2776 hr = ParseNotes(pixn, pControl, &fAnyNoteChildren);
2777 ThmExitOnFailure(hr, "Failed to parse note text nodes of the control.");
2778 }
2779
2780 if (fAnyTextChildren || fAnyNoteChildren)
2781 {
2782 pControl->uStringId = UINT_MAX;
2783 }
2784 else
2785 {
2786 hr = XmlGetAttributeNumber(pixn, L"StringId", reinterpret_cast<DWORD*>(&pControl->uStringId));
2787 ThmExitOnFailure(hr, "Failed when querying control StringId attribute.");
2788
2789 if (S_FALSE == hr)
2790 {
2791 pControl->uStringId = UINT_MAX;
2792
2793 if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type || THEME_CONTROL_TYPE_PANEL == pControl->type)
2794 {
2795 // Billboards and panels have child elements and we don't want to pick up child element text in the parents.
2796 hr = S_OK;
2797 }
2798 else
2799 {
2800 hr = XmlGetText(pixn, &bstrText);
2801 ThmExitOnFailure(hr, "Failed to get control inner text.");
2802
2803 if (S_OK == hr)
2804 {
2805 hr = StrAllocString(&pControl->sczText, bstrText, 0);
2806 ThmExitOnFailure(hr, "Failed to copy control text.");
2807
2808 ReleaseNullBSTR(bstrText);
2809 }
2810 else if (S_FALSE == hr)
2811 {
2812 hr = S_OK;
2813 }
2814 }
2815 }
2816 }
2817
2818 if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type)
2819 {
2820 hr = XmlGetYesNoAttribute(pixn, L"Loop", &pControl->fBillboardLoops);
2821 if (E_NOTFOUND == hr)
2822 {
2823 hr = S_OK;
2824 }
2825 ThmExitOnFailure(hr, "Failed when querying Billboard/@Loop attribute.");
2826
2827 pControl->wBillboardInterval = 5000;
2828 hr = XmlGetAttributeNumber(pixn, L"Interval", &dwValue);
2829 if (S_OK == hr && dwValue)
2830 {
2831 pControl->wBillboardInterval = static_cast<WORD>(dwValue & 0xFFFF);
2832 }
2833 ThmExitOnFailure(hr, "Failed when querying Billboard/@Interval attribute.");
2834
2835 hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, pControl, pPage);
2836 ThmExitOnFailure(hr, "Failed to parse billboard children.");
2837 }
2838 else if (THEME_CONTROL_TYPE_COMMANDLINK == pControl->type)
2839 {
2840 hr = ParseIcon(hModule, wzRelativePath, pixn, &pControl->hIcon);
2841 ThmExitOnFailure(hr, "Failed while parsing control icon.");
2842 }
2843 else if (THEME_CONTROL_TYPE_EDITBOX == pControl->type)
2844 {
2845 hr = XmlGetYesNoAttribute(pixn, L"FileSystemAutoComplete", &fValue);
2846 if (E_NOTFOUND == hr)
2847 {
2848 hr = S_OK;
2849 }
2850 else
2851 {
2852 ThmExitOnFailure(hr, "Failed when querying Editbox/@FileSystemAutoComplete attribute.");
2853
2854 if (fValue)
2855 {
2856 pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_FILESYSTEM_AUTOCOMPLETE;
2857 }
2858 }
2859 }
2860 else if (THEME_CONTROL_TYPE_HYPERLINK == pControl->type || THEME_CONTROL_TYPE_BUTTON == pControl->type)
2861 {
2862 hr = XmlGetAttributeNumber(pixn, L"HoverFontId", &pControl->dwFontHoverId);
2863 if (S_FALSE == hr)
2864 {
2865 pControl->dwFontHoverId = THEME_INVALID_ID;
2866 }
2867 ThmExitOnFailure(hr, "Failed when querying control HoverFontId attribute.");
2868
2869 hr = XmlGetAttributeNumber(pixn, L"SelectedFontId", &pControl->dwFontSelectedId);
2870 if (S_FALSE == hr)
2871 {
2872 pControl->dwFontSelectedId = THEME_INVALID_ID;
2873 }
2874 ThmExitOnFailure(hr, "Failed when querying control SelectedFontId attribute.");
2875 }
2876 else if (THEME_CONTROL_TYPE_LABEL == pControl->type)
2877 {
2878 hr = XmlGetYesNoAttribute(pixn, L"Center", &fValue);
2879 if (E_NOTFOUND == hr)
2880 {
2881 hr = S_OK;
2882 }
2883 else if (fValue)
2884 {
2885 pControl->dwStyle |= SS_CENTER;
2886 }
2887 ThmExitOnFailure(hr, "Failed when querying Label/@Center attribute.");
2888
2889 hr = XmlGetYesNoAttribute(pixn, L"DisablePrefix", &fValue);
2890 if (E_NOTFOUND == hr)
2891 {
2892 hr = S_OK;
2893 }
2894 else if (fValue)
2895 {
2896 pControl->dwStyle |= SS_NOPREFIX;
2897 }
2898 ThmExitOnFailure(hr, "Failed when querying Label/@DisablePrefix attribute.");
2899 }
2900 else if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type)
2901 {
2902 // Parse the optional extended window style.
2903 hr = XmlGetAttributeNumberBase(pixn, L"HexExtendedStyle", 16, &pControl->dwExtendedStyle);
2904 ThmExitOnFailure(hr, "Failed when querying ListView/@HexExtendedStyle attribute.");
2905
2906 hr = XmlGetAttribute(pixn, L"ImageList", &bstrText);
2907 if (S_FALSE != hr)
2908 {
2909 ThmExitOnFailure(hr, "Failed when querying ListView/@ImageList attribute.");
2910
2911 hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[0]);
2912 ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageList for ListView.", bstrText);
2913 }
2914
2915 hr = XmlGetAttribute(pixn, L"ImageListSmall", &bstrText);
2916 if (S_FALSE != hr)
2917 {
2918 ThmExitOnFailure(hr, "Failed when querying ListView/@ImageListSmall attribute.");
2919
2920 hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[1]);
2921 ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageListSmall for ListView.", bstrText);
2922 }
2923
2924 hr = XmlGetAttribute(pixn, L"ImageListState", &bstrText);
2925 if (S_FALSE != hr)
2926 {
2927 ThmExitOnFailure(hr, "Failed when querying ListView/@ImageListState attribute.");
2928
2929 hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[2]);
2930 ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageListState for ListView.", bstrText);
2931 }
2932
2933 hr = XmlGetAttribute(pixn, L"ImageListGroupHeader", &bstrText);
2934 if (S_FALSE != hr)
2935 {
2936 ThmExitOnFailure(hr, "Failed when querying ListView/@ImageListGroupHeader attribute.");
2937
2938 hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[3]);
2939 ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageListGroupHeader for ListView.", bstrText);
2940 }
2941
2942 hr = ParseColumns(pixn, pControl);
2943 ThmExitOnFailure(hr, "Failed to parse columns.");
2944 }
2945 else if (THEME_CONTROL_TYPE_PANEL == pControl->type)
2946 {
2947 hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, pControl, pPage);
2948 ThmExitOnFailure(hr, "Failed to parse panel children.");
2949 }
2950 else if (THEME_CONTROL_TYPE_RADIOBUTTON == pControl->type)
2951 {
2952 hr = XmlGetAttributeEx(pixn, L"Value", &pControl->sczValue);
2953 if (E_NOTFOUND == hr)
2954 {
2955 hr = S_OK;
2956 }
2957 ThmExitOnFailure(hr, "Failed when querying RadioButton/@Value attribute.");
2958 }
2959 else if (THEME_CONTROL_TYPE_TAB == pControl->type)
2960 {
2961 hr = ParseTabs(pixn, pControl);
2962 ThmExitOnFailure(hr, "Failed to parse tabs");
2963 }
2964 else if (THEME_CONTROL_TYPE_TREEVIEW == pControl->type)
2965 {
2966 pControl->dwStyle |= TVS_DISABLEDRAGDROP;
2967
2968 hr = XmlGetYesNoAttribute(pixn, L"EnableDragDrop", &fValue);
2969 if (E_NOTFOUND == hr)
2970 {
2971 hr = S_OK;
2972 }
2973 else if (fValue)
2974 {
2975 pControl->dwStyle &= ~TVS_DISABLEDRAGDROP;
2976 }
2977 ThmExitOnFailure(hr, "Failed when querying TreeView/@EnableDragDrop attribute.");
2978
2979 hr = XmlGetYesNoAttribute(pixn, L"FullRowSelect", &fValue);
2980 if (E_NOTFOUND == hr)
2981 {
2982 hr = S_OK;
2983 }
2984 else if (fValue)
2985 {
2986 pControl->dwStyle |= TVS_FULLROWSELECT;
2987 }
2988 ThmExitOnFailure(hr, "Failed when querying TreeView/@FullRowSelect attribute.");
2989
2990 hr = XmlGetYesNoAttribute(pixn, L"HasButtons", &fValue);
2991 if (E_NOTFOUND == hr)
2992 {
2993 hr = S_OK;
2994 }
2995 else if (fValue)
2996 {
2997 pControl->dwStyle |= TVS_HASBUTTONS;
2998 }
2999 ThmExitOnFailure(hr, "Failed when querying TreeView/@HasButtons attribute.");
3000
3001 hr = XmlGetYesNoAttribute(pixn, L"AlwaysShowSelect", &fValue);
3002 if (E_NOTFOUND == hr)
3003 {
3004 hr = S_OK;
3005 }
3006 else if (fValue)
3007 {
3008 pControl->dwStyle |= TVS_SHOWSELALWAYS;
3009 }
3010 ThmExitOnFailure(hr, "Failed when querying TreeView/@AlwaysShowSelect attribute.");
3011
3012 hr = XmlGetYesNoAttribute(pixn, L"LinesAtRoot", &fValue);
3013 if (E_NOTFOUND == hr)
3014 {
3015 hr = S_OK;
3016 }
3017 else if (fValue)
3018 {
3019 pControl->dwStyle |= TVS_LINESATROOT;
3020 }
3021 ThmExitOnFailure(hr, "Failed when querying TreeView/@LinesAtRoot attribute.");
3022
3023 hr = XmlGetYesNoAttribute(pixn, L"HasLines", &fValue);
3024 if (E_NOTFOUND == hr)
3025 {
3026 hr = S_OK;
3027 }
3028 else if (fValue)
3029 {
3030 pControl->dwStyle |= TVS_HASLINES;
3031 }
3032 ThmExitOnFailure(hr, "Failed when querying TreeView/@HasLines attribute.");
3033 }
3034
3035LExit:
3036 ReleaseBSTR(bstrText);
3037
3038 return hr;
3039}
3040
3041
3042static HRESULT ParseActions(
3043 __in IXMLDOMNode* pixn,
3044 __in THEME_CONTROL* pControl
3045 )
3046{
3047 HRESULT hr = S_OK;
3048 DWORD i = 0;
3049 IXMLDOMNodeList* pixnl = NULL;
3050 IXMLDOMNode* pixnChild = NULL;
3051 BSTR bstrType = NULL;
3052
3053 hr = XmlSelectNodes(pixn, L"BrowseDirectoryAction|ChangePageAction|CloseWindowAction", &pixnl);
3054 ThmExitOnFailure(hr, "Failed to select child action nodes.");
3055
3056 hr = pixnl->get_length(reinterpret_cast<long*>(&pControl->cActions));
3057 ThmExitOnFailure(hr, "Failed to count the number of action nodes.");
3058
3059 if (0 < pControl->cActions)
3060 {
3061 MemAllocArray(reinterpret_cast<LPVOID*>(&pControl->rgActions), sizeof(THEME_ACTION), pControl->cActions);
3062 ThmExitOnNull(pControl->rgActions, hr, E_OUTOFMEMORY, "Failed to allocate THEME_ACTION structs.");
3063
3064 i = 0;
3065 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, &bstrType)))
3066 {
3067 if (!bstrType)
3068 {
3069 hr = E_UNEXPECTED;
3070 ThmExitOnFailure(hr, "Null element encountered!");
3071 }
3072
3073 THEME_ACTION* pAction = pControl->rgActions + i;
3074
3075 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"BrowseDirectoryAction", -1))
3076 {
3077 pAction->type = THEME_ACTION_TYPE_BROWSE_DIRECTORY;
3078
3079 hr = XmlGetAttributeEx(pixnChild, L"VariableName", &pAction->BrowseDirectory.sczVariableName);
3080 ThmExitOnFailure(hr, "Failed when querying BrowseDirectoryAction/@VariableName attribute.");
3081 }
3082 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"ChangePageAction", -1))
3083 {
3084 pAction->type = THEME_ACTION_TYPE_CHANGE_PAGE;
3085
3086 hr = XmlGetAttributeEx(pixnChild, L"Page", &pAction->ChangePage.sczPageName);
3087 ThmExitOnFailure(hr, "Failed when querying ChangePageAction/@Page attribute.");
3088
3089 hr = XmlGetYesNoAttribute(pixnChild, L"Cancel", &pAction->ChangePage.fCancel);
3090 if (E_NOTFOUND != hr)
3091 {
3092 ThmExitOnFailure(hr, "Failed when querying ChangePageAction/@Cancel attribute.");
3093 }
3094 }
3095 else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"CloseWindowAction", -1))
3096 {
3097 pAction->type = THEME_ACTION_TYPE_CLOSE_WINDOW;
3098 }
3099 else
3100 {
3101 hr = E_UNEXPECTED;
3102 ThmExitOnFailure(hr, "Unexpected element encountered: %ls", bstrType);
3103 }
3104
3105 hr = XmlGetAttributeEx(pixnChild, L"Condition", &pAction->sczCondition);
3106 if (E_NOTFOUND != hr)
3107 {
3108 ThmExitOnFailure(hr, "Failed when querying %ls/@Condition attribute.", bstrType);
3109 }
3110
3111 if (!pAction->sczCondition)
3112 {
3113 if (pControl->pDefaultAction)
3114 {
3115 hr = E_INVALIDDATA;
3116 ThmExitOnFailure(hr, "Control '%ls' has multiple actions without a condition.", pControl->sczName);
3117 }
3118
3119 pControl->pDefaultAction = pAction;
3120 }
3121
3122 ++i;
3123 ReleaseNullBSTR(bstrType);
3124 ReleaseObject(pixnChild);
3125 }
3126 }
3127
3128LExit:
3129 ReleaseObject(pixnl);
3130 ReleaseObject(pixnChild);
3131 ReleaseBSTR(bstrType);
3132
3133 return hr;
3134}
3135
3136
3137static HRESULT ParseColumns(
3138 __in IXMLDOMNode* pixn,
3139 __in THEME_CONTROL* pControl
3140 )
3141{
3142 HRESULT hr = S_OK;
3143 DWORD i = 0;
3144 IXMLDOMNodeList* pixnl = NULL;
3145 IXMLDOMNode* pixnChild = NULL;
3146 BSTR bstrText = NULL;
3147 DWORD dwValue = 0;
3148
3149 hr = XmlSelectNodes(pixn, L"Column", &pixnl);
3150 ThmExitOnFailure(hr, "Failed to select child column nodes.");
3151
3152 hr = pixnl->get_length(reinterpret_cast<long*>(&pControl->cColumns));
3153 ThmExitOnFailure(hr, "Failed to count the number of control columns.");
3154
3155 if (0 < pControl->cColumns)
3156 {
3157 hr = MemAllocArray(reinterpret_cast<LPVOID*>(&pControl->ptcColumns), sizeof(THEME_COLUMN), pControl->cColumns);
3158 ThmExitOnFailure(hr, "Failed to allocate column structs.");
3159
3160 i = 0;
3161 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL)))
3162 {
3163 THEME_COLUMN* pColumn = pControl->ptcColumns + i;
3164
3165 hr = XmlGetText(pixnChild, &bstrText);
3166 ThmExitOnFailure(hr, "Failed to get inner text of column element.");
3167
3168 hr = XmlGetAttributeNumber(pixnChild, L"Width", &dwValue);
3169 if (S_FALSE == hr)
3170 {
3171 dwValue = 100;
3172 }
3173 ThmExitOnFailure(hr, "Failed to get column width attribute.");
3174
3175 pColumn->nBaseWidth = pColumn->nDefaultDpiBaseWidth = dwValue;
3176
3177 hr = XmlGetYesNoAttribute(pixnChild, L"Expands", reinterpret_cast<BOOL*>(&pColumn->fExpands));
3178 if (E_NOTFOUND == hr)
3179 {
3180 hr = S_OK;
3181 }
3182 ThmExitOnFailure(hr, "Failed to get expands attribute.");
3183
3184 hr = StrAllocString(&pColumn->pszName, bstrText, 0);
3185 ThmExitOnFailure(hr, "Failed to copy column name.");
3186
3187 ++i;
3188 ReleaseNullBSTR(bstrText);
3189 }
3190 }
3191
3192LExit:
3193 ReleaseObject(pixnl);
3194 ReleaseObject(pixnChild);
3195 ReleaseBSTR(bstrText);
3196
3197 return hr;
3198}
3199
3200
3201static HRESULT ParseRadioButtons(
3202 __in_opt HMODULE hModule,
3203 __in_opt LPCWSTR wzRelativePath,
3204 __in IXMLDOMNode* pixn,
3205 __in THEME* pTheme,
3206 __in_opt THEME_CONTROL* pParentControl,
3207 __in THEME_PAGE* pPage
3208 )
3209{
3210 HRESULT hr = S_OK;
3211 DWORD cRadioButtons = 0;
3212 DWORD iControl = 0;
3213 DWORD iPageControl = 0;
3214 IXMLDOMNodeList* pixnlRadioButtons = NULL;
3215 IXMLDOMNodeList* pixnl = NULL;
3216 IXMLDOMNode* pixnRadioButtons = NULL;
3217 IXMLDOMNode* pixnChild = NULL;
3218 LPWSTR sczName = NULL;
3219 THEME_CONTROL* pControl = NULL;
3220 BOOL fFirst = FALSE;
3221 DWORD* pcControls = NULL;
3222 THEME_CONTROL** prgControls = NULL;
3223
3224 GetControls(pTheme, pParentControl, &pcControls, &prgControls);
3225
3226 hr = XmlSelectNodes(pixn, L"RadioButtons", &pixnlRadioButtons);
3227 ThmExitOnFailure(hr, "Failed to select RadioButtons nodes.");
3228
3229 while (S_OK == (hr = XmlNextElement(pixnlRadioButtons, &pixnRadioButtons, NULL)))
3230 {
3231 hr = XmlGetAttributeEx(pixnRadioButtons, L"Name", &sczName);
3232 if (E_NOTFOUND == hr)
3233 {
3234 hr = S_OK;
3235 }
3236 ThmExitOnFailure(hr, "Failed when querying RadioButtons Name.");
3237
3238 hr = XmlSelectNodes(pixnRadioButtons, L"RadioButton", &pixnl);
3239 ThmExitOnFailure(hr, "Failed to select RadioButton nodes.");
3240
3241 hr = pixnl->get_length(reinterpret_cast<long*>(&cRadioButtons));
3242 ThmExitOnFailure(hr, "Failed to count the number of RadioButton nodes.");
3243
3244 if (cRadioButtons)
3245 {
3246 if (pPage)
3247 {
3248 iPageControl = pPage->cControlIndices;
3249 pPage->cControlIndices += cRadioButtons;
3250 }
3251
3252 hr = MemReAllocArray(reinterpret_cast<LPVOID*>(prgControls), *pcControls, sizeof(THEME_CONTROL), cRadioButtons);
3253 ThmExitOnFailure(hr, "Failed to reallocate theme controls.");
3254
3255 iControl = *pcControls;
3256 *pcControls += cRadioButtons;
3257
3258 fFirst = TRUE;
3259
3260 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL)))
3261 {
3262 pControl = *prgControls + iControl;
3263 pControl->type = THEME_CONTROL_TYPE_RADIOBUTTON;
3264
3265 hr = ParseControl(hModule, wzRelativePath, pixnChild, pTheme, pControl, FALSE, pPage);
3266 ThmExitOnFailure(hr, "Failed to parse control.");
3267
3268 if (fFirst)
3269 {
3270 pControl->dwStyle |= WS_GROUP;
3271 fFirst = FALSE;
3272 }
3273
3274 hr = StrAllocString(&pControl->sczVariable, sczName, 0);
3275 ThmExitOnFailure(hr, "Failed to copy radio button variable.");
3276
3277 if (pPage)
3278 {
3279 pControl->wPageId = pPage->wId;
3280 ++iPageControl;
3281 }
3282
3283 ++iControl;
3284 }
3285
3286 if (!fFirst)
3287 {
3288 pControl->fLastRadioButton = TRUE;
3289 }
3290 }
3291 }
3292
3293LExit:
3294 ReleaseStr(sczName);
3295 ReleaseObject(pixnl);
3296 ReleaseObject(pixnChild);
3297 ReleaseObject(pixnlRadioButtons);
3298 ReleaseObject(pixnRadioButtons);
3299
3300 return hr;
3301}
3302
3303
3304static HRESULT ParseTabs(
3305 __in IXMLDOMNode* pixn,
3306 __in THEME_CONTROL* pControl
3307 )
3308{
3309 HRESULT hr = S_OK;
3310 DWORD i = 0;
3311 IXMLDOMNodeList* pixnl = NULL;
3312 IXMLDOMNode* pixnChild = NULL;
3313 BSTR bstrText = NULL;
3314
3315 hr = XmlSelectNodes(pixn, L"Tab", &pixnl);
3316 ThmExitOnFailure(hr, "Failed to select child tab nodes.");
3317
3318 hr = pixnl->get_length(reinterpret_cast<long*>(&pControl->cTabs));
3319 ThmExitOnFailure(hr, "Failed to count the number of tabs.");
3320
3321 if (0 < pControl->cTabs)
3322 {
3323 hr = MemAllocArray(reinterpret_cast<LPVOID*>(&pControl->pttTabs), sizeof(THEME_TAB), pControl->cTabs);
3324 ThmExitOnFailure(hr, "Failed to allocate tab structs.");
3325
3326 i = 0;
3327 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL)))
3328 {
3329 hr = XmlGetText(pixnChild, &bstrText);
3330 ThmExitOnFailure(hr, "Failed to get inner text of tab element.");
3331
3332 hr = StrAllocString(&(pControl->pttTabs[i].pszName), bstrText, 0);
3333 ThmExitOnFailure(hr, "Failed to copy tab name.");
3334
3335 ++i;
3336 ReleaseNullBSTR(bstrText);
3337 }
3338 }
3339
3340LExit:
3341 ReleaseObject(pixnl);
3342 ReleaseObject(pixnChild);
3343 ReleaseBSTR(bstrText);
3344
3345 return hr;
3346}
3347
3348
3349static HRESULT ParseText(
3350 __in IXMLDOMNode* pixn,
3351 __in THEME_CONTROL* pControl,
3352 __inout BOOL* pfAnyChildren
3353 )
3354{
3355 HRESULT hr = S_OK;
3356 DWORD i = 0;
3357 IXMLDOMNodeList* pixnl = NULL;
3358 IXMLDOMNode* pixnChild = NULL;
3359 BSTR bstrText = NULL;
3360
3361 hr = XmlSelectNodes(pixn, L"Text", &pixnl);
3362 ThmExitOnFailure(hr, "Failed to select child Text nodes.");
3363
3364 hr = pixnl->get_length(reinterpret_cast<long*>(&pControl->cConditionalText));
3365 ThmExitOnFailure(hr, "Failed to count the number of Text nodes.");
3366
3367 *pfAnyChildren |= 0 < pControl->cConditionalText;
3368
3369 if (0 < pControl->cConditionalText)
3370 {
3371 MemAllocArray(reinterpret_cast<LPVOID*>(&pControl->rgConditionalText), sizeof(THEME_CONDITIONAL_TEXT), pControl->cConditionalText);
3372 ThmExitOnNull(pControl->rgConditionalText, hr, E_OUTOFMEMORY, "Failed to allocate THEME_CONDITIONAL_TEXT structs.");
3373
3374 i = 0;
3375 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL)))
3376 {
3377 THEME_CONDITIONAL_TEXT* pConditionalText = pControl->rgConditionalText + i;
3378
3379 hr = XmlGetAttributeEx(pixnChild, L"Condition", &pConditionalText->sczCondition);
3380 if (E_NOTFOUND == hr)
3381 {
3382 hr = S_OK;
3383 }
3384 ThmExitOnFailure(hr, "Failed when querying Text/@Condition attribute.");
3385
3386 hr = XmlGetText(pixnChild, &bstrText);
3387 ThmExitOnFailure(hr, "Failed to get inner text of Text element.");
3388
3389 if (S_OK == hr)
3390 {
3391 if (pConditionalText->sczCondition)
3392 {
3393 hr = StrAllocString(&pConditionalText->sczText, bstrText, 0);
3394 ThmExitOnFailure(hr, "Failed to copy text to conditional text.");
3395
3396 ++i;
3397 }
3398 else
3399 {
3400 if (pControl->sczText)
3401 {
3402 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
3403 ThmExitOnFailure(hr, "Unconditional text for the '%ls' control is specified multiple times.", pControl->sczName);
3404 }
3405
3406 hr = StrAllocString(&pControl->sczText, bstrText, 0);
3407 ThmExitOnFailure(hr, "Failed to copy text to control.");
3408
3409 // Unconditional text entries aren't stored in the conditional text list.
3410 --pControl->cConditionalText;
3411 }
3412 }
3413
3414 ReleaseNullBSTR(bstrText);
3415 }
3416 }
3417
3418LExit:
3419 ReleaseObject(pixnl);
3420 ReleaseObject(pixnChild);
3421 ReleaseBSTR(bstrText);
3422
3423 return hr;
3424}
3425
3426
3427static HRESULT ParseTooltips(
3428 __in IXMLDOMNode* pixn,
3429 __in THEME_CONTROL* pControl,
3430 __inout BOOL* pfAnyChildren
3431)
3432{
3433 HRESULT hr = S_OK;
3434 IXMLDOMNode* pixnChild = NULL;
3435 BSTR bstrText = NULL;
3436
3437 hr = XmlSelectSingleNode(pixn, L"Tooltip", &pixnChild);
3438 ThmExitOnFailure(hr, "Failed to select child Tooltip node.");
3439
3440 if (S_OK == hr)
3441 {
3442 *pfAnyChildren |= TRUE;
3443
3444 hr = XmlGetText(pixnChild, &bstrText);
3445 ThmExitOnFailure(hr, "Failed to get inner text of Tooltip element.");
3446
3447 if (S_OK == hr)
3448 {
3449 hr = StrAllocString(&pControl->sczTooltip, bstrText, 0);
3450 ThmExitOnFailure(hr, "Failed to copy tooltip text to control.");
3451 }
3452 }
3453
3454LExit:
3455 ReleaseObject(pixnChild);
3456 ReleaseBSTR(bstrText);
3457
3458 return hr;
3459}
3460
3461
3462static HRESULT ParseNotes(
3463 __in IXMLDOMNode* pixn,
3464 __in THEME_CONTROL* pControl,
3465 __out BOOL* pfAnyChildren
3466 )
3467{
3468 HRESULT hr = S_OK;
3469 DWORD i = 0;
3470 IXMLDOMNodeList* pixnl = NULL;
3471 IXMLDOMNode* pixnChild = NULL;
3472 BSTR bstrText = NULL;
3473
3474 hr = XmlSelectNodes(pixn, L"Note", &pixnl);
3475 ThmExitOnFailure(hr, "Failed to select child Note nodes.");
3476
3477 hr = pixnl->get_length(reinterpret_cast<long*>(&pControl->cConditionalNotes));
3478 ThmExitOnFailure(hr, "Failed to count the number of Note nodes.");
3479
3480 if (pfAnyChildren)
3481 {
3482 *pfAnyChildren = 0 < pControl->cConditionalNotes;
3483 }
3484
3485 if (0 < pControl->cConditionalNotes)
3486 {
3487 MemAllocArray(reinterpret_cast<LPVOID*>(&pControl->rgConditionalNotes), sizeof(THEME_CONDITIONAL_TEXT), pControl->cConditionalNotes);
3488 ThmExitOnNull(pControl->rgConditionalNotes, hr, E_OUTOFMEMORY, "Failed to allocate note THEME_CONDITIONAL_TEXT structs.");
3489
3490 i = 0;
3491 while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL)))
3492 {
3493 THEME_CONDITIONAL_TEXT* pConditionalNote = pControl->rgConditionalNotes + i;
3494
3495 hr = XmlGetAttributeEx(pixnChild, L"Condition", &pConditionalNote->sczCondition);
3496 if (E_NOTFOUND == hr)
3497 {
3498 hr = S_OK;
3499 }
3500 ThmExitOnFailure(hr, "Failed when querying Note/@Condition attribute.");
3501
3502 hr = XmlGetText(pixnChild, &bstrText);
3503 ThmExitOnFailure(hr, "Failed to get inner text of Note element.");
3504
3505 if (S_OK == hr)
3506 {
3507 if (pConditionalNote->sczCondition)
3508 {
3509 hr = StrAllocString(&pConditionalNote->sczText, bstrText, 0);
3510 ThmExitOnFailure(hr, "Failed to copy text to conditional note text.");
3511
3512 ++i;
3513 }
3514 else
3515 {
3516 if (pControl->sczNote)
3517 {
3518 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
3519 ThmExitOnFailure(hr, "Unconditional note text for the '%ls' control is specified multiple times.", pControl->sczName);
3520 }
3521
3522 hr = StrAllocString(&pControl->sczNote, bstrText, 0);
3523 ThmExitOnFailure(hr, "Failed to copy text to command link control.");
3524
3525 // Unconditional note entries aren't stored in the conditional notes list.
3526 --pControl->cConditionalNotes;
3527 }
3528 }
3529
3530 ReleaseNullBSTR(bstrText);
3531 }
3532 }
3533
3534LExit:
3535 ReleaseObject(pixnl);
3536 ReleaseObject(pixnChild);
3537 ReleaseBSTR(bstrText);
3538
3539 return hr;
3540}
3541
3542
3543static HRESULT StartBillboard(
3544 __in THEME* pTheme,
3545 __in DWORD dwControl
3546 )
3547{
3548 HRESULT hr = E_NOTFOUND;
3549 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
3550
3551 if (hWnd)
3552 {
3553 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hWnd));
3554 if (pControl && THEME_CONTROL_TYPE_BILLBOARD == pControl->type)
3555 {
3556 // kick off
3557 pControl->dwData = 0;
3558 OnBillboardTimer(pTheme, pTheme->hwndParent, dwControl);
3559
3560 if (!::SetTimer(pTheme->hwndParent, pControl->wId, pControl->wBillboardInterval, NULL))
3561 {
3562 ThmExitWithLastError(hr, "Failed to start billboard.");
3563 }
3564
3565 hr = S_OK;
3566 }
3567 }
3568
3569LExit:
3570 return hr;
3571}
3572
3573
3574static HRESULT StopBillboard(
3575 __in THEME* pTheme,
3576 __in DWORD dwControl
3577 )
3578{
3579 HRESULT hr = E_NOTFOUND;
3580 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
3581
3582 if (hWnd)
3583 {
3584 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd);
3585 if (pControl && THEME_CONTROL_TYPE_BILLBOARD == pControl->type)
3586 {
3587 ThemeControlEnable(pTheme, dwControl, FALSE);
3588
3589 if (::KillTimer(pTheme->hwndParent, pControl->wId))
3590 {
3591 hr = S_OK;
3592 }
3593 }
3594 }
3595
3596 return hr;
3597}
3598
3599static HRESULT EnsureFontInstance(
3600 __in THEME* pTheme,
3601 __in THEME_FONT* pFont,
3602 __out THEME_FONT_INSTANCE** ppFontInstance
3603 )
3604{
3605 HRESULT hr = S_OK;
3606 THEME_FONT_INSTANCE* pFontInstance = NULL;
3607 LOGFONTW lf = { };
3608
3609 for (DWORD i = 0; i < pFont->cFontInstances; ++i)
3610 {
3611 pFontInstance = pFont->rgFontInstances + i;
3612 if (pTheme->nDpi == pFontInstance->nDpi)
3613 {
3614 *ppFontInstance = pFontInstance;
3615 ExitFunction();
3616 }
3617 }
3618
3619 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pFont->rgFontInstances), pFont->cFontInstances, sizeof(THEME_FONT_INSTANCE), GROW_FONT_INSTANCES);
3620 ThmExitOnFailure(hr, "Failed to allocate memory for font instances.");
3621
3622 pFontInstance = pFont->rgFontInstances + pFont->cFontInstances;
3623 pFontInstance->nDpi = pTheme->nDpi;
3624
3625 lf.lfHeight = DpiuScaleValue(pFont->lfHeight, pFontInstance->nDpi);
3626 lf.lfWeight = pFont->lfWeight;
3627 lf.lfUnderline = pFont->lfUnderline;
3628 lf.lfQuality = pFont->lfQuality;
3629
3630 hr = ::StringCchCopyW(lf.lfFaceName, countof(lf.lfFaceName), pFont->sczFaceName);
3631 ThmExitOnFailure(hr, "Failed to copy font name to create font.");
3632
3633 pFontInstance->hFont = ::CreateFontIndirectW(&lf);
3634 ThmExitOnNull(pFontInstance->hFont, hr, E_OUTOFMEMORY, "Failed to create DPI specific font.");
3635
3636 ++pFont->cFontInstances;
3637 *ppFontInstance = pFontInstance;
3638
3639LExit:
3640 return hr;
3641}
3642
3643
3644static HRESULT FindImageList(
3645 __in THEME* pTheme,
3646 __in_z LPCWSTR wzImageListName,
3647 __out HIMAGELIST *phImageList
3648 )
3649{
3650 HRESULT hr = S_OK;
3651
3652 for (DWORD i = 0; i < pTheme->cImageLists; ++i)
3653 {
3654 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pTheme->rgImageLists[i].sczName, -1, wzImageListName, -1))
3655 {
3656 *phImageList = pTheme->rgImageLists[i].hImageList;
3657 ExitFunction1(hr = S_OK);
3658 }
3659 }
3660
3661 hr = E_NOTFOUND;
3662
3663LExit:
3664 return hr;
3665}
3666
3667
3668static HRESULT DrawButton(
3669 __in THEME* pTheme,
3670 __in DRAWITEMSTRUCT* pdis,
3671 __in const THEME_CONTROL* pControl
3672 )
3673{
3674 int nSourceX = pControl->hImage ? 0 : pControl->nSourceX;
3675 int nSourceY = pControl->hImage ? 0 : pControl->nSourceY;
3676 DWORD dwSourceWidth = pControl->nDefaultDpiWidth;
3677 DWORD dwSourceHeight = pControl->nDefaultDpiHeight;
3678
3679 HDC hdcMem = ::CreateCompatibleDC(pdis->hDC);
3680 HBITMAP hDefaultBitmap = static_cast<HBITMAP>(::SelectObject(hdcMem, pControl->hImage ? pControl->hImage : pTheme->hImage));
3681
3682 DWORD_PTR dwStyle = ::GetWindowLongPtrW(pdis->hwndItem, GWL_STYLE);
3683 // "clicked" gets priority
3684 if (ODS_SELECTED & pdis->itemState)
3685 {
3686 nSourceY += pControl->nDefaultDpiHeight * 2;
3687 }
3688 // then hover
3689 else if (pControl->dwData & THEME_CONTROL_DATA_HOVER)
3690 {
3691 nSourceY += pControl->nDefaultDpiHeight;
3692 }
3693 // then focused
3694 else if (WS_TABSTOP & dwStyle && ODS_FOCUS & pdis->itemState)
3695 {
3696 nSourceY += pControl->nDefaultDpiHeight * 3;
3697 }
3698
3699 ::StretchBlt(pdis->hDC, 0, 0, pControl->nWidth, pControl->nHeight, hdcMem, nSourceX, nSourceY, dwSourceWidth, dwSourceHeight, SRCCOPY);
3700
3701 ::SelectObject(hdcMem, hDefaultBitmap);
3702 ::DeleteDC(hdcMem);
3703
3704 DrawControlText(pTheme, pdis, pControl, TRUE, FALSE);
3705
3706 return S_OK;
3707}
3708
3709
3710static HRESULT DrawHyperlink(
3711 __in THEME* pTheme,
3712 __in DRAWITEMSTRUCT* pdis,
3713 __in const THEME_CONTROL* pControl
3714 )
3715{
3716 DrawControlText(pTheme, pdis, pControl, FALSE, TRUE);
3717 return S_OK;
3718}
3719
3720
3721static void DrawControlText(
3722 __in THEME* pTheme,
3723 __in DRAWITEMSTRUCT* pdis,
3724 __in const THEME_CONTROL* pControl,
3725 __in BOOL fCentered,
3726 __in BOOL fDrawFocusRect
3727 )
3728{
3729 HRESULT hr = S_OK;
3730 WCHAR wzText[256] = { };
3731 DWORD cchText = 0;
3732 THEME_FONT* pFont = NULL;
3733 THEME_FONT_INSTANCE* pFontInstance = NULL;
3734 HFONT hfPrev = NULL;
3735
3736 if (0 == (cchText = ::GetWindowTextW(pdis->hwndItem, wzText, countof(wzText))))
3737 {
3738 // nothing to do
3739 return;
3740 }
3741
3742 if (ODS_SELECTED & pdis->itemState)
3743 {
3744 pFont = pTheme->rgFonts + (THEME_INVALID_ID != pControl->dwFontSelectedId ? pControl->dwFontSelectedId : pControl->dwFontId);
3745 }
3746 else if (pControl->dwData & THEME_CONTROL_DATA_HOVER)
3747 {
3748 pFont = pTheme->rgFonts + (THEME_INVALID_ID != pControl->dwFontHoverId ? pControl->dwFontHoverId : pControl->dwFontId);
3749 }
3750 else
3751 {
3752 pFont = pTheme->rgFonts + pControl->dwFontId;
3753 }
3754
3755 hr = EnsureFontInstance(pTheme, pFont, &pFontInstance);
3756 if (SUCCEEDED(hr))
3757 {
3758 hfPrev = SelectFont(pdis->hDC, pFontInstance->hFont);
3759 }
3760
3761 ::DrawTextExW(pdis->hDC, wzText, cchText, &pdis->rcItem, DT_SINGLELINE | (fCentered ? (DT_CENTER | DT_VCENTER) : 0), NULL);
3762
3763 if (fDrawFocusRect && (WS_TABSTOP & ::GetWindowLongPtrW(pdis->hwndItem, GWL_STYLE)) && (ODS_FOCUS & pdis->itemState))
3764 {
3765 ::DrawFocusRect(pdis->hDC, &pdis->rcItem);
3766 }
3767
3768 if (hfPrev)
3769 {
3770 SelectFont(pdis->hDC, hfPrev);
3771 }
3772}
3773
3774
3775static HRESULT DrawImage(
3776 __in THEME* pTheme,
3777 __in DRAWITEMSTRUCT* pdis,
3778 __in const THEME_CONTROL* pControl
3779 )
3780{
3781 DWORD dwHeight = pdis->rcItem.bottom - pdis->rcItem.top;
3782 DWORD dwWidth = pdis->rcItem.right - pdis->rcItem.left;
3783 int nSourceX = pControl->hImage ? 0 : pControl->nSourceX;
3784 int nSourceY = pControl->hImage ? 0 : pControl->nSourceY;
3785 DWORD dwSourceHeight = pControl->nDefaultDpiHeight;
3786 DWORD dwSourceWidth = pControl->nDefaultDpiWidth;
3787
3788 BLENDFUNCTION bf = { };
3789 bf.BlendOp = AC_SRC_OVER;
3790 bf.SourceConstantAlpha = 255;
3791 bf.AlphaFormat = AC_SRC_ALPHA;
3792
3793 HDC hdcMem = ::CreateCompatibleDC(pdis->hDC);
3794 HBITMAP hDefaultBitmap = static_cast<HBITMAP>(::SelectObject(hdcMem, pControl->hImage ? pControl->hImage : pTheme->hImage));
3795
3796 // Try to draw the image with transparency and if that fails (usually because the image has no
3797 // alpha channel) then draw the image as is.
3798 if (!::AlphaBlend(pdis->hDC, 0, 0, dwWidth, dwHeight, hdcMem, nSourceX, nSourceY, dwSourceWidth, dwSourceHeight, bf))
3799 {
3800 ::StretchBlt(pdis->hDC, 0, 0, dwWidth, dwHeight, hdcMem, nSourceX, nSourceY, dwSourceWidth, dwSourceHeight, SRCCOPY);
3801 }
3802
3803 ::SelectObject(hdcMem, hDefaultBitmap);
3804 ::DeleteDC(hdcMem);
3805 return S_OK;
3806}
3807
3808
3809static HRESULT DrawProgressBar(
3810 __in THEME* pTheme,
3811 __in DRAWITEMSTRUCT* pdis,
3812 __in const THEME_CONTROL* pControl
3813 )
3814{
3815 DWORD dwProgressColor = HIWORD(pControl->dwData);
3816 DWORD dwProgressPercentage = LOWORD(pControl->dwData);
3817 DWORD dwHeight = pdis->rcItem.bottom - pdis->rcItem.top;
3818 DWORD dwCenter = (pdis->rcItem.right - 2) * dwProgressPercentage / 100;
3819 DWORD dwSourceHeight = pControl->nDefaultDpiHeight;
3820 int nSourceX = pControl->hImage ? 0 : pControl->nSourceX;
3821 int nSourceY = (pControl->hImage ? 0 : pControl->nSourceY) + (dwProgressColor * dwSourceHeight);
3822
3823 HDC hdcMem = ::CreateCompatibleDC(pdis->hDC);
3824 HBITMAP hDefaultBitmap = static_cast<HBITMAP>(::SelectObject(hdcMem, pControl->hImage ? pControl->hImage : pTheme->hImage));
3825
3826 // Draw the left side of the progress bar.
3827 ::StretchBlt(pdis->hDC, 0, 0, 1, dwHeight, hdcMem, nSourceX, nSourceY, 1, dwSourceHeight, SRCCOPY);
3828
3829 // Draw the filled side of the progress bar, if there is any.
3830 if (0 < dwCenter)
3831 {
3832 ::StretchBlt(pdis->hDC, 1, 0, dwCenter, dwHeight, hdcMem, nSourceX + 1, nSourceY, 1, dwSourceHeight, SRCCOPY);
3833 }
3834
3835 // Draw the unfilled side of the progress bar, if there is any.
3836 if (dwCenter < static_cast<DWORD>(pdis->rcItem.right - 2))
3837 {
3838 ::StretchBlt(pdis->hDC, 1 + dwCenter, 0, pdis->rcItem.right - dwCenter - 1, dwHeight, hdcMem, nSourceX + 2, nSourceY, 1, dwSourceHeight, SRCCOPY);
3839 }
3840
3841 // Draw the right side of the progress bar.
3842 ::StretchBlt(pdis->hDC, pdis->rcItem.right - 1, 0, 1, dwHeight, hdcMem, nSourceX + 3, nSourceY, 1, dwSourceHeight, SRCCOPY);
3843
3844 ::SelectObject(hdcMem, hDefaultBitmap);
3845 ::DeleteDC(hdcMem);
3846 return S_OK;
3847}
3848
3849
3850static BOOL DrawHoverControl(
3851 __in THEME* pTheme,
3852 __in BOOL fHover
3853 )
3854{
3855 BOOL fChangedHover = FALSE;
3856 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, pTheme->hwndHover));
3857
3858 // Only hyperlinks and owner-drawn buttons have hover states.
3859 if (pControl && (THEME_CONTROL_TYPE_HYPERLINK == pControl->type ||
3860 (THEME_CONTROL_TYPE_BUTTON == pControl->type && (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_OWNER_DRAW))))
3861 {
3862 if (fHover)
3863 {
3864 pControl->dwData |= THEME_CONTROL_DATA_HOVER;
3865 }
3866 else
3867 {
3868 pControl->dwData &= ~THEME_CONTROL_DATA_HOVER;
3869 }
3870
3871 ::InvalidateRect(pControl->hWnd, NULL, FALSE);
3872 fChangedHover = TRUE;
3873 }
3874
3875 return fChangedHover;
3876}
3877
3878
3879static void FreePage(
3880 __in THEME_PAGE* pPage
3881 )
3882{
3883 if (pPage)
3884 {
3885 ReleaseStr(pPage->sczName);
3886
3887 if (pPage->cSavedVariables)
3888 {
3889 for (DWORD i = 0; i < pPage->cSavedVariables; ++i)
3890 {
3891 ReleaseStr(pPage->rgSavedVariables[i].sczValue);
3892 }
3893 }
3894
3895 ReleaseMem(pPage->rgSavedVariables);
3896 }
3897}
3898
3899
3900static void FreeImageList(
3901 __in THEME_IMAGELIST* pImageList
3902 )
3903{
3904 if (pImageList)
3905 {
3906 ReleaseStr(pImageList->sczName);
3907 ImageList_Destroy(pImageList->hImageList);
3908 }
3909}
3910
3911static void FreeControl(
3912 __in THEME_CONTROL* pControl
3913 )
3914{
3915 if (pControl)
3916 {
3917 if (::IsWindow(pControl->hWnd))
3918 {
3919 ::CloseWindow(pControl->hWnd);
3920 pControl->hWnd = NULL;
3921 }
3922
3923 ReleaseStr(pControl->sczName);
3924 ReleaseStr(pControl->sczText);
3925 ReleaseStr(pControl->sczTooltip);
3926 ReleaseStr(pControl->sczNote);
3927 ReleaseStr(pControl->sczEnableCondition);
3928 ReleaseStr(pControl->sczVisibleCondition);
3929 ReleaseStr(pControl->sczValue);
3930 ReleaseStr(pControl->sczVariable);
3931
3932 if (pControl->hImage)
3933 {
3934 ::DeleteBitmap(pControl->hImage);
3935 }
3936
3937 for (DWORD i = 0; i < pControl->cControls; ++i)
3938 {
3939 FreeControl(pControl->rgControls + i);
3940 }
3941
3942 for (DWORD i = 0; i < pControl->cActions; ++i)
3943 {
3944 FreeAction(&(pControl->rgActions[i]));
3945 }
3946
3947 for (DWORD i = 0; i < pControl->cColumns; ++i)
3948 {
3949 FreeColumn(&(pControl->ptcColumns[i]));
3950 }
3951
3952 for (DWORD i = 0; i < pControl->cConditionalText; ++i)
3953 {
3954 FreeConditionalText(&(pControl->rgConditionalText[i]));
3955 }
3956
3957 for (DWORD i = 0; i < pControl->cConditionalNotes; ++i)
3958 {
3959 FreeConditionalText(&(pControl->rgConditionalNotes[i]));
3960 }
3961
3962 for (DWORD i = 0; i < pControl->cTabs; ++i)
3963 {
3964 FreeTab(&(pControl->pttTabs[i]));
3965 }
3966
3967 ReleaseMem(pControl->rgActions)
3968 ReleaseMem(pControl->ptcColumns);
3969 ReleaseMem(pControl->rgConditionalText);
3970 ReleaseMem(pControl->rgConditionalNotes);
3971 ReleaseMem(pControl->pttTabs);
3972 }
3973}
3974
3975
3976static void FreeAction(
3977 __in THEME_ACTION* pAction
3978 )
3979{
3980 switch (pAction->type)
3981 {
3982 case THEME_ACTION_TYPE_BROWSE_DIRECTORY:
3983 ReleaseStr(pAction->BrowseDirectory.sczVariableName);
3984 break;
3985 case THEME_ACTION_TYPE_CHANGE_PAGE:
3986 ReleaseStr(pAction->ChangePage.sczPageName);
3987 break;
3988 }
3989
3990 ReleaseStr(pAction->sczCondition);
3991}
3992
3993
3994static void FreeColumn(
3995 __in THEME_COLUMN* pColumn
3996 )
3997{
3998 ReleaseStr(pColumn->pszName);
3999}
4000
4001
4002static void FreeConditionalText(
4003 __in THEME_CONDITIONAL_TEXT* pConditionalText
4004 )
4005{
4006 ReleaseStr(pConditionalText->sczCondition);
4007 ReleaseStr(pConditionalText->sczText);
4008}
4009
4010
4011static void FreeTab(
4012 __in THEME_TAB* pTab
4013 )
4014{
4015 ReleaseStr(pTab->pszName);
4016}
4017
4018
4019static void FreeFontInstance(
4020 __in THEME_FONT_INSTANCE* pFontInstance
4021 )
4022{
4023 if (pFontInstance->hFont)
4024 {
4025 ::DeleteObject(pFontInstance->hFont);
4026 pFontInstance->hFont = NULL;
4027 }
4028}
4029
4030
4031static void FreeFont(
4032 __in THEME_FONT* pFont
4033 )
4034{
4035 if (pFont)
4036 {
4037 if (pFont->hBackground)
4038 {
4039 ::DeleteObject(pFont->hBackground);
4040 pFont->hBackground = NULL;
4041 }
4042
4043 if (pFont->hForeground)
4044 {
4045 ::DeleteObject(pFont->hForeground);
4046 pFont->hForeground = NULL;
4047 }
4048
4049 for (DWORD i = 0; i < pFont->cFontInstances; ++i)
4050 {
4051 FreeFontInstance(&(pFont->rgFontInstances[i]));
4052 }
4053
4054 ReleaseMem(pFont->rgFontInstances);
4055 ReleaseStr(pFont->sczFaceName);
4056 }
4057}
4058
4059
4060static DWORD CALLBACK RichEditStreamFromFileHandleCallback(
4061 __in DWORD_PTR dwCookie,
4062 __in_bcount(cb) LPBYTE pbBuff,
4063 __in LONG cb,
4064 __in LONG* pcb
4065 )
4066{
4067 HRESULT hr = S_OK;
4068 HANDLE hFile = reinterpret_cast<HANDLE>(dwCookie);
4069
4070 if (!::ReadFile(hFile, pbBuff, cb, reinterpret_cast<DWORD*>(pcb), NULL))
4071 {
4072 ThmExitWithLastError(hr, "Failed to read file");
4073 }
4074
4075LExit:
4076 return hr;
4077}
4078
4079
4080static DWORD CALLBACK RichEditStreamFromMemoryCallback(
4081 __in DWORD_PTR dwCookie,
4082 __in_bcount(cb) LPBYTE pbBuff,
4083 __in LONG cb,
4084 __in LONG* pcb
4085 )
4086{
4087 HRESULT hr = S_OK;
4088 MEMBUFFER_FOR_RICHEDIT* pBuffer = reinterpret_cast<MEMBUFFER_FOR_RICHEDIT*>(dwCookie);
4089 DWORD cbCopy = 0;
4090
4091 if (pBuffer->iData < pBuffer->cbData)
4092 {
4093 cbCopy = min(static_cast<DWORD>(cb), pBuffer->cbData - pBuffer->iData);
4094 memcpy(pbBuff, pBuffer->rgbData + pBuffer->iData, cbCopy);
4095
4096 pBuffer->iData += cbCopy;
4097 Assert(pBuffer->iData <= pBuffer->cbData);
4098 }
4099
4100 *pcb = cbCopy;
4101 return hr;
4102}
4103
4104
4105static void CALLBACK OnBillboardTimer(
4106 __in THEME* pTheme,
4107 __in HWND hwnd,
4108 __in UINT_PTR idEvent
4109 )
4110{
4111 HWND hwndControl = ::GetDlgItem(hwnd, static_cast<int>(idEvent));
4112 if (hwndControl)
4113 {
4114 THEME_CONTROL* pControl = const_cast<THEME_CONTROL*>(FindControlFromHWnd(pTheme, hwndControl));
4115 AssertSz(pControl && THEME_CONTROL_TYPE_BILLBOARD == pControl->type, "Only billboard controls should get billboard timer messages.");
4116
4117 if (pControl)
4118 {
4119 if (pControl->dwData < pControl->cControls)
4120 {
4121 ThemeShowChild(pTheme, pControl, pControl->dwData);
4122 }
4123 else if (pControl->fBillboardLoops)
4124 {
4125 pControl->dwData = 0;
4126 ThemeShowChild(pTheme, pControl, pControl->dwData);
4127 }
4128 else // no more looping
4129 {
4130 ::KillTimer(hwnd, idEvent);
4131 }
4132
4133 ++pControl->dwData;
4134 }
4135 }
4136}
4137
4138static void OnBrowseDirectory(
4139 __in THEME* pTheme,
4140 __in HWND hWnd,
4141 __in const THEME_ACTION* pAction
4142 )
4143{
4144 HRESULT hr = S_OK;
4145 WCHAR wzPath[MAX_PATH] = { };
4146 BROWSEINFOW browseInfo = { };
4147 PIDLIST_ABSOLUTE pidl = NULL;
4148
4149 browseInfo.hwndOwner = hWnd;
4150 browseInfo.pszDisplayName = wzPath;
4151 browseInfo.lpszTitle = pTheme->sczCaption;
4152 browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
4153 pidl = ::SHBrowseForFolderW(&browseInfo);
4154 if (pidl && ::SHGetPathFromIDListW(pidl, wzPath))
4155 {
4156 // Since editbox changes aren't immediately saved off, we have to treat them differently.
4157 THEME_CONTROL* pTargetControl = NULL;
4158
4159 for (DWORD i = 0; i < pTheme->cControls; ++i)
4160 {
4161 THEME_CONTROL* pControl = pTheme->rgControls + i;
4162
4163 if ((!pControl->wPageId || pControl->wPageId == pTheme->dwCurrentPageId) &&
4164 CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pControl->sczName, -1, pAction->BrowseDirectory.sczVariableName, -1))
4165 {
4166 pTargetControl = pControl;
4167 break;
4168 }
4169 }
4170
4171 if (pTargetControl && THEME_CONTROL_TYPE_EDITBOX == pTargetControl->type && !pTargetControl->fDisableVariableFunctionality)
4172 {
4173 hr = ThemeSetTextControl(pTheme, pTargetControl->wId, wzPath);
4174 ThmExitOnFailure(hr, "Failed to set text on editbox: %ls", pTargetControl->sczName);
4175 }
4176 else if (pTheme->pfnSetStringVariable)
4177 {
4178 hr = pTheme->pfnSetStringVariable(pAction->BrowseDirectory.sczVariableName, wzPath, FALSE, pTheme->pvVariableContext);
4179 ThmExitOnFailure(hr, "Failed to set variable: %ls", pAction->BrowseDirectory.sczVariableName);
4180 }
4181 else if (pTargetControl)
4182 {
4183 hr = ThemeSetTextControl(pTheme, pTargetControl->wId, wzPath);
4184 ThmExitOnFailure(hr, "Failed to set text on control: %ls", pTargetControl->sczName);
4185 }
4186
4187 ThemeShowPageEx(pTheme, pTheme->dwCurrentPageId, SW_SHOW, THEME_SHOW_PAGE_REASON_REFRESH);
4188 }
4189
4190LExit:
4191 if (pidl)
4192 {
4193 ::CoTaskMemFree(pidl);
4194 }
4195}
4196
4197static BOOL OnButtonClicked(
4198 __in THEME* pTheme,
4199 __in HWND hWnd,
4200 __in const THEME_CONTROL* pControl
4201 )
4202{
4203 HRESULT hr = S_OK;
4204 BOOL fHandled = FALSE;
4205
4206 if (THEME_CONTROL_TYPE_BUTTON == pControl->type || THEME_CONTROL_TYPE_COMMANDLINK == pControl->type)
4207 {
4208 if (pControl->cActions)
4209 {
4210 fHandled = TRUE;
4211 THEME_ACTION* pChosenAction = pControl->pDefaultAction;
4212
4213 if (pTheme->pfnEvaluateCondition)
4214 {
4215 // As documented in the xsd, if there are multiple conditions that are true at the same time then the behavior is undefined.
4216 // This is the current implementation and can change at any time.
4217 for (DWORD j = 0; j < pControl->cActions; ++j)
4218 {
4219 THEME_ACTION* pAction = pControl->rgActions + j;
4220
4221 if (pAction->sczCondition)
4222 {
4223 BOOL fCondition = FALSE;
4224
4225 hr = pTheme->pfnEvaluateCondition(pAction->sczCondition, &fCondition, pTheme->pvVariableContext);
4226 ThmExitOnFailure(hr, "Failed to evaluate condition: %ls", pAction->sczCondition);
4227
4228 if (fCondition)
4229 {
4230 pChosenAction = pAction;
4231 break;
4232 }
4233 }
4234 }
4235 }
4236
4237 if (pChosenAction)
4238 {
4239 switch (pChosenAction->type)
4240 {
4241 case THEME_ACTION_TYPE_BROWSE_DIRECTORY:
4242 OnBrowseDirectory(pTheme, hWnd, pChosenAction);
4243 break;
4244
4245 case THEME_ACTION_TYPE_CLOSE_WINDOW:
4246 ::SendMessageW(hWnd, WM_CLOSE, 0, 0);
4247 break;
4248
4249 case THEME_ACTION_TYPE_CHANGE_PAGE:
4250 DWORD dwPageId = 0;
4251 LPCWSTR pPageNames = pChosenAction->ChangePage.sczPageName;
4252 ThemeGetPageIds(pTheme, &pPageNames, &dwPageId, 1);
4253
4254 if (!dwPageId)
4255 {
4256 ThmExitOnFailure(E_INVALIDDATA, "Unknown page: %ls", pChosenAction->ChangePage.sczPageName);
4257 }
4258
4259 ThemeShowPageEx(pTheme, pTheme->dwCurrentPageId, SW_HIDE, pChosenAction->ChangePage.fCancel ? THEME_SHOW_PAGE_REASON_CANCEL : THEME_SHOW_PAGE_REASON_DEFAULT);
4260 ThemeShowPage(pTheme, dwPageId, SW_SHOW);
4261 break;
4262 }
4263 }
4264 }
4265 }
4266 else if (!pControl->fDisableVariableFunctionality && (pTheme->pfnSetNumericVariable || pTheme->pfnSetStringVariable))
4267 {
4268 BOOL fRefresh = FALSE;
4269
4270 switch (pControl->type)
4271 {
4272 case THEME_CONTROL_TYPE_CHECKBOX:
4273 if (pTheme->pfnSetNumericVariable && pControl->sczName && *pControl->sczName)
4274 {
4275 BOOL fChecked = ThemeIsControlChecked(pTheme, pControl->wId);
4276 pTheme->pfnSetNumericVariable(pControl->sczName, fChecked ? 1 : 0, pTheme->pvVariableContext);
4277 fRefresh = TRUE;
4278 }
4279 break;
4280 case THEME_CONTROL_TYPE_RADIOBUTTON:
4281 if (pTheme->pfnSetStringVariable && pControl->sczVariable && *pControl->sczVariable && ThemeIsControlChecked(pTheme, pControl->wId))
4282 {
4283 pTheme->pfnSetStringVariable(pControl->sczVariable, pControl->sczValue, FALSE, pTheme->pvVariableContext);
4284 fRefresh = TRUE;
4285 }
4286 break;
4287 }
4288
4289 if (fRefresh)
4290 {
4291 ThemeShowPageEx(pTheme, pTheme->dwCurrentPageId, SW_SHOW, THEME_SHOW_PAGE_REASON_REFRESH);
4292 fHandled = TRUE;
4293 }
4294 }
4295
4296LExit:
4297 return fHandled;
4298}
4299
4300static BOOL OnDpiChanged(
4301 __in THEME* pTheme,
4302 __in WPARAM wParam,
4303 __in LPARAM lParam
4304 )
4305{
4306 UINT nDpi = HIWORD(wParam);
4307 RECT* pRect = reinterpret_cast<RECT*>(lParam);
4308 BOOL fIgnored = pTheme->nDpi == nDpi;
4309
4310 if (fIgnored)
4311 {
4312 ExitFunction();
4313 }
4314
4315
4316 pTheme->fForceResize = !pTheme->fAutoResize;
4317 ScaleThemeFromWindow(pTheme, nDpi, pRect->left, pRect->top);
4318
4319LExit:
4320 return !fIgnored;
4321}
4322
4323static void OnNcCreate(
4324 __in THEME* pTheme,
4325 __in HWND hWnd,
4326 __in LPARAM lParam
4327 )
4328{
4329 DPIU_WINDOW_CONTEXT windowContext = { };
4330 CREATESTRUCTW* pCreateStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
4331
4332 pTheme->hwndParent = hWnd;
4333
4334 DpiuGetWindowContext(pTheme->hwndParent, &windowContext);
4335
4336 if (windowContext.nDpi != pTheme->nDpi)
4337 {
4338 ScaleTheme(pTheme, windowContext.nDpi, pCreateStruct->x, pCreateStruct->y, pCreateStruct->style, NULL != pCreateStruct->hMenu, pCreateStruct->dwExStyle);
4339 }
4340}
4341
4342static HRESULT OnRichEditEnLink(
4343 __in LPARAM lParam,
4344 __in HWND hWndRichEdit,
4345 __in HWND hWnd
4346 )
4347{
4348 HRESULT hr = S_OK;
4349 LPWSTR sczLink = NULL;
4350 ENLINK* link = reinterpret_cast<ENLINK*>(lParam);
4351
4352 switch (link->msg)
4353 {
4354 case WM_LBUTTONDOWN:
4355 {
4356 hr = StrAlloc(&sczLink, link->chrg.cpMax - link->chrg.cpMin + 2);
4357 ThmExitOnFailure(hr, "Failed to allocate string for link.");
4358
4359 TEXTRANGEW tr;
4360 tr.chrg.cpMin = link->chrg.cpMin;
4361 tr.chrg.cpMax = link->chrg.cpMax;
4362 tr.lpstrText = sczLink;
4363
4364 if (0 < ::SendMessageW(hWndRichEdit, EM_GETTEXTRANGE, 0, reinterpret_cast<LPARAM>(&tr)))
4365 {
4366 hr = ShelExec(sczLink, NULL, L"open", NULL, SW_SHOWDEFAULT, hWnd, NULL);
4367 ThmExitOnFailure(hr, "Failed to launch link: %ls", sczLink);
4368 }
4369
4370 break;
4371 }
4372
4373 case WM_SETCURSOR:
4374 ::SetCursor(vhCursorHand);
4375 break;
4376 }
4377
4378LExit:
4379 ReleaseStr(sczLink);
4380
4381 return hr;
4382}
4383
4384static BOOL ControlIsType(
4385 __in const THEME* pTheme,
4386 __in DWORD dwControl,
4387 __in const THEME_CONTROL_TYPE type
4388 )
4389{
4390 BOOL fIsType = FALSE;
4391 HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl);
4392 if (hWnd)
4393 {
4394 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd);
4395 fIsType = (pControl && type == pControl->type);
4396 }
4397
4398 return fIsType;
4399}
4400
4401static const THEME_CONTROL* FindControlFromHWnd(
4402 __in const THEME* pTheme,
4403 __in HWND hWnd,
4404 __in_opt const THEME_CONTROL* pParentControl
4405 )
4406{
4407 DWORD cControls = 0;
4408 THEME_CONTROL* rgControls = NULL;
4409
4410 GetControls(pTheme, pParentControl, cControls, rgControls);
4411
4412 // As we can't use GWLP_USERDATA (SysLink controls on Windows XP uses it too)...
4413 for (DWORD i = 0; i < cControls; ++i)
4414 {
4415 if (hWnd == rgControls[i].hWnd)
4416 {
4417 return rgControls + i;
4418 }
4419 else if (0 < rgControls[i].cControls)
4420 {
4421 const THEME_CONTROL* pChildControl = FindControlFromHWnd(pTheme, hWnd, rgControls + i);
4422 if (pChildControl)
4423 {
4424 return pChildControl;
4425 }
4426 }
4427 }
4428
4429 return NULL;
4430}
4431
4432static void GetControlDimensions(
4433 __in const THEME_CONTROL* pControl,
4434 __in const RECT* prcParent,
4435 __out int* piWidth,
4436 __out int* piHeight,
4437 __out int* piX,
4438 __out int* piY
4439 )
4440{
4441 *piWidth = pControl->nWidth + (0 < pControl->nWidth ? 0 : prcParent->right - max(0, pControl->nX));
4442 *piHeight = pControl->nHeight + (0 < pControl->nHeight ? 0 : prcParent->bottom - max(0, pControl->nY));
4443 *piX = pControl->nX + (-1 < pControl->nX ? 0 : prcParent->right - *piWidth);
4444 *piY = pControl->nY + (-1 < pControl->nY ? 0 : prcParent->bottom - *piHeight);
4445}
4446
4447static HRESULT SizeListViewColumns(
4448 __inout THEME_CONTROL* pControl
4449 )
4450{
4451 HRESULT hr = S_OK;
4452 RECT rcParent = { };
4453 int cNumExpandingColumns = 0;
4454 int iExtraAvailableSize;
4455
4456 if (!::GetWindowRect(pControl->hWnd, &rcParent))
4457 {
4458 ThmExitWithLastError(hr, "Failed to get window rect of listview control.");
4459 }
4460
4461 iExtraAvailableSize = rcParent.right - rcParent.left;
4462
4463 for (DWORD i = 0; i < pControl->cColumns; ++i)
4464 {
4465 if (pControl->ptcColumns[i].fExpands)
4466 {
4467 ++cNumExpandingColumns;
4468 }
4469
4470 iExtraAvailableSize -= pControl->ptcColumns[i].nBaseWidth;
4471 }
4472
4473 // Leave room for a vertical scroll bar just in case.
4474 iExtraAvailableSize -= ::GetSystemMetrics(SM_CXVSCROLL);
4475
4476 for (DWORD i = 0; i < pControl->cColumns; ++i)
4477 {
4478 if (pControl->ptcColumns[i].fExpands)
4479 {
4480 pControl->ptcColumns[i].nWidth = pControl->ptcColumns[i].nBaseWidth + (iExtraAvailableSize / cNumExpandingColumns);
4481 // In case there is any remainder, use it up the first chance we get.
4482 pControl->ptcColumns[i].nWidth += iExtraAvailableSize % cNumExpandingColumns;
4483 iExtraAvailableSize -= iExtraAvailableSize % cNumExpandingColumns;
4484 }
4485 else
4486 {
4487 pControl->ptcColumns[i].nWidth = pControl->ptcColumns[i].nBaseWidth;
4488 }
4489 }
4490
4491LExit:
4492 return hr;
4493}
4494
4495
4496static HRESULT ShowControl(
4497 __in THEME* pTheme,
4498 __in THEME_CONTROL* pControl,
4499 __in int nCmdShow,
4500 __in BOOL fSaveEditboxes,
4501 __in THEME_SHOW_PAGE_REASON reason,
4502 __in DWORD dwPageId,
4503 __out_opt HWND* phwndFocus
4504 )
4505{
4506 HRESULT hr = S_OK;
4507 DWORD iPageControl = 0;
4508 HWND hwndFocus = NULL;
4509 LPWSTR sczFormatString = NULL;
4510 LPWSTR sczText = NULL;
4511 THEME_SAVEDVARIABLE* pSavedVariable = NULL;
4512 BOOL fHide = SW_HIDE == nCmdShow;
4513 THEME_PAGE* pPage = ThemeGetPage(pTheme, dwPageId);
4514
4515 // Save the editbox value if necessary (other control types save their values immediately).
4516 if (pTheme->pfnSetStringVariable && !pControl->fDisableVariableFunctionality &&
4517 fSaveEditboxes && THEME_CONTROL_TYPE_EDITBOX == pControl->type && pControl->sczName && *pControl->sczName)
4518 {
4519 hr = ThemeGetTextControl(pTheme, pControl->wId, &sczText);
4520 ThmExitOnFailure(hr, "Failed to get the text for control: %ls", pControl->sczName);
4521
4522 hr = pTheme->pfnSetStringVariable(pControl->sczName, sczText, FALSE, pTheme->pvVariableContext);
4523 ThmExitOnFailure(hr, "Failed to set the variable '%ls' to '%ls'", pControl->sczName, sczText);
4524 }
4525
4526 HWND hWnd = pControl->hWnd;
4527
4528 if (fHide && pControl->wPageId)
4529 {
4530 ::ShowWindow(hWnd, SW_HIDE);
4531
4532 if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type)
4533 {
4534 StopBillboard(pTheme, pControl->wId);
4535 }
4536
4537 ExitFunction();
4538 }
4539
4540 BOOL fEnabled = !(pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_DISABLED);
4541 BOOL fVisible = !(pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDDEN);
4542
4543 if (!pControl->fDisableVariableFunctionality)
4544 {
4545 if (pTheme->pfnEvaluateCondition)
4546 {
4547 // If the control has a VisibleCondition, check if it's true.
4548 if (pControl->sczVisibleCondition)
4549 {
4550 hr = pTheme->pfnEvaluateCondition(pControl->sczVisibleCondition, &fVisible, pTheme->pvVariableContext);
4551 ThmExitOnFailure(hr, "Failed to evaluate VisibleCondition: %ls", pControl->sczVisibleCondition);
4552 }
4553
4554 // If the control has an EnableCondition, check if it's true.
4555 if (pControl->sczEnableCondition)
4556 {
4557 hr = pTheme->pfnEvaluateCondition(pControl->sczEnableCondition, &fEnabled, pTheme->pvVariableContext);
4558 ThmExitOnFailure(hr, "Failed to evaluate EnableCondition: %ls", pControl->sczEnableCondition);
4559 }
4560 }
4561
4562 // Try to format each control's text based on context, except for editboxes since their text comes from the user.
4563 if (pTheme->pfnFormatString && ((pControl->sczText && *pControl->sczText) || pControl->cConditionalText) && THEME_CONTROL_TYPE_EDITBOX != pControl->type)
4564 {
4565 LPWSTR wzText = pControl->sczText;
4566 LPWSTR wzNote = pControl->sczNote;
4567
4568 if (pTheme->pfnEvaluateCondition)
4569 {
4570 // As documented in the xsd, if there are multiple conditions that are true at the same time then the behavior is undefined.
4571 // This is the current implementation and can change at any time.
4572 for (DWORD j = 0; j < pControl->cConditionalText; ++j)
4573 {
4574 THEME_CONDITIONAL_TEXT* pConditionalText = pControl->rgConditionalText + j;
4575 wzText = pConditionalText->sczText;
4576
4577 if (pConditionalText->sczCondition)
4578 {
4579 BOOL fCondition = FALSE;
4580
4581 hr = pTheme->pfnEvaluateCondition(pConditionalText->sczCondition, &fCondition, pTheme->pvVariableContext);
4582 ThmExitOnFailure(hr, "Failed to evaluate condition: %ls", pConditionalText->sczCondition);
4583
4584 if (fCondition)
4585 {
4586 wzText = pConditionalText->sczText;
4587 break;
4588 }
4589 }
4590 }
4591
4592 for (DWORD j = 0; j < pControl->cConditionalNotes; ++j)
4593 {
4594 THEME_CONDITIONAL_TEXT* pConditionalNote = pControl->rgConditionalNotes + j;
4595 wzNote = pConditionalNote->sczText;
4596
4597 if (pConditionalNote->sczCondition)
4598 {
4599 BOOL fCondition = FALSE;
4600
4601 hr = pTheme->pfnEvaluateCondition(pConditionalNote->sczCondition, &fCondition, pTheme->pvVariableContext);
4602 ThmExitOnFailure(hr, "Failed to evaluate note condition: %ls", pConditionalNote->sczCondition);
4603
4604 if (fCondition)
4605 {
4606 wzNote = pConditionalNote->sczText;
4607 break;
4608 }
4609 }
4610 }
4611 }
4612
4613 if (wzText && *wzText)
4614 {
4615 hr = pTheme->pfnFormatString(wzText, &sczText, pTheme->pvVariableContext);
4616 ThmExitOnFailure(hr, "Failed to format string: %ls", wzText);
4617 }
4618 else
4619 {
4620 ReleaseNullStr(sczText);
4621 }
4622
4623 ThemeSetTextControl(pTheme, pControl->wId, sczText);
4624
4625 if (wzNote && *wzNote)
4626 {
4627 hr = pTheme->pfnFormatString(wzNote, &sczText, pTheme->pvVariableContext);
4628 ThmExitOnFailure(hr, "Failed to format note: %ls", wzNote);
4629 }
4630 else
4631 {
4632 ReleaseNullStr(sczText);
4633 }
4634
4635 ::SendMessageW(pControl->hWnd, BCM_SETNOTE, 0, reinterpret_cast<WPARAM>(sczText));
4636 }
4637
4638 // If this is a named control, do variable magic.
4639 if (pControl->sczName && *pControl->sczName)
4640 {
4641 // If this is a checkbox control,
4642 // try to set its default state to the state of a matching named variable.
4643 if (pTheme->pfnGetNumericVariable && THEME_CONTROL_TYPE_CHECKBOX == pControl->type)
4644 {
4645 LONGLONG llValue = 0;
4646 hr = pTheme->pfnGetNumericVariable(pControl->sczName, &llValue, pTheme->pvVariableContext);
4647 if (E_NOTFOUND == hr)
4648 {
4649 hr = S_OK;
4650 }
4651 ThmExitOnFailure(hr, "Failed to get numeric variable: %ls", pControl->sczName);
4652
4653 if (THEME_SHOW_PAGE_REASON_REFRESH != reason && pPage && pControl->wPageId)
4654 {
4655 pSavedVariable = pPage->rgSavedVariables + iPageControl;
4656 pSavedVariable->wzName = pControl->sczName;
4657
4658 if (SUCCEEDED(hr))
4659 {
4660 hr = StrAllocFormattedSecure(&pSavedVariable->sczValue, L"%lld", llValue);
4661 ThmExitOnFailure(hr, "Failed to save variable: %ls", pControl->sczName);
4662 }
4663
4664 ++iPageControl;
4665 }
4666
4667 ThemeSendControlMessage(pTheme, pControl->wId, BM_SETCHECK, SUCCEEDED(hr) && llValue ? BST_CHECKED : BST_UNCHECKED, 0);
4668 }
4669
4670 // If this is an editbox control,
4671 // try to set its default state to the state of a matching named variable.
4672 if (pTheme->pfnFormatString && THEME_CONTROL_TYPE_EDITBOX == pControl->type)
4673 {
4674 hr = StrAllocFormatted(&sczFormatString, L"[%ls]", pControl->sczName);
4675 ThmExitOnFailure(hr, "Failed to create format string: '%ls'", pControl->sczName);
4676
4677 hr = pTheme->pfnFormatString(sczFormatString, &sczText, pTheme->pvVariableContext);
4678 ThmExitOnFailure(hr, "Failed to format string: '%ls'", sczFormatString);
4679
4680 if (THEME_SHOW_PAGE_REASON_REFRESH != reason && pPage && pControl->wPageId)
4681 {
4682 pSavedVariable = pPage->rgSavedVariables + iPageControl;
4683 pSavedVariable->wzName = pControl->sczName;
4684
4685 if (SUCCEEDED(hr))
4686 {
4687 hr = StrAllocStringSecure(&pSavedVariable->sczValue, sczText, 0);
4688 ThmExitOnFailure(hr, "Failed to save variable: %ls", pControl->sczName);
4689 }
4690
4691 ++iPageControl;
4692 }
4693
4694 ThemeSetTextControl(pTheme, pControl->wId, sczText);
4695 }
4696 }
4697
4698 // If this is a radio button associated with a variable,
4699 // try to set its default state to the state of the variable.
4700 if (pTheme->pfnGetStringVariable && THEME_CONTROL_TYPE_RADIOBUTTON == pControl->type && pControl->sczVariable && *pControl->sczVariable)
4701 {
4702 hr = pTheme->pfnGetStringVariable(pControl->sczVariable, &sczText, pTheme->pvVariableContext);
4703 if (E_NOTFOUND == hr)
4704 {
4705 ReleaseNullStr(sczText);
4706 }
4707 else
4708 {
4709 ThmExitOnFailure(hr, "Failed to get string variable: %ls", pControl->sczVariable);
4710 }
4711
4712 if (THEME_SHOW_PAGE_REASON_REFRESH != reason && pPage && pControl->wPageId && pControl->fLastRadioButton)
4713 {
4714 pSavedVariable = pPage->rgSavedVariables + iPageControl;
4715 pSavedVariable->wzName = pControl->sczVariable;
4716
4717 if (SUCCEEDED(hr))
4718 {
4719 hr = StrAllocStringSecure(&pSavedVariable->sczValue, sczText, 0);
4720 ThmExitOnFailure(hr, "Failed to save variable: %ls", pControl->sczVariable);
4721 }
4722
4723 ++iPageControl;
4724 }
4725
4726 hr = S_OK;
4727
4728 Button_SetCheck(hWnd, (!sczText && !pControl->sczValue) || (sczText && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczText, -1, pControl->sczValue, -1)));
4729 }
4730 }
4731
4732 if (!fVisible || (!fEnabled && (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED)))
4733 {
4734 ::ShowWindow(hWnd, SW_HIDE);
4735 }
4736 else
4737 {
4738 ::EnableWindow(hWnd, !fHide && fEnabled);
4739
4740 if (!hwndFocus && pControl->wPageId && (pControl->dwStyle & WS_TABSTOP))
4741 {
4742 hwndFocus = hWnd;
4743 }
4744
4745 ::ShowWindow(hWnd, nCmdShow);
4746 }
4747
4748 if (0 < pControl->cControls)
4749 {
4750 ShowControls(pTheme, pControl, nCmdShow, fSaveEditboxes, reason, dwPageId);
4751 }
4752
4753 if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type && pControl->wPageId)
4754 {
4755 if (fEnabled)
4756 {
4757 StartBillboard(pTheme, pControl->wId);
4758 }
4759 else
4760 {
4761 StopBillboard(pTheme, pControl->wId);
4762 }
4763 }
4764
4765 if (phwndFocus)
4766 {
4767 *phwndFocus = hwndFocus;
4768 }
4769
4770LExit:
4771 ReleaseStr(sczFormatString);
4772 ReleaseStr(sczText);
4773
4774 return hr;
4775}
4776
4777static HRESULT ShowControls(
4778 __in THEME* pTheme,
4779 __in_opt const THEME_CONTROL* pParentControl,
4780 __in int nCmdShow,
4781 __in BOOL fSaveEditboxes,
4782 __in THEME_SHOW_PAGE_REASON reason,
4783 __in DWORD dwPageId
4784 )
4785{
4786 HRESULT hr = S_OK;
4787 HWND hwndFocus = NULL;
4788 DWORD cControls = 0;
4789 THEME_CONTROL* rgControls = NULL;
4790
4791 GetControls(pTheme, pParentControl, cControls, rgControls);
4792
4793 for (DWORD i = 0; i < cControls; ++i)
4794 {
4795 THEME_CONTROL* pControl = rgControls + i;
4796
4797 // Only look at non-page controls and the specified page's controls.
4798 if (!pControl->wPageId || pControl->wPageId == dwPageId)
4799 {
4800 hr = ShowControl(pTheme, pControl, nCmdShow, fSaveEditboxes, reason, dwPageId, &hwndFocus);
4801 ThmExitOnFailure(hr, "Failed to show control '%ls' at index %d.", pControl->sczName, i);
4802 }
4803 }
4804
4805 if (hwndFocus)
4806 {
4807 ::SetFocus(hwndFocus);
4808 }
4809
4810LExit:
4811 return hr;
4812}
4813
4814
4815static LRESULT CALLBACK ControlGroupDefWindowProc(
4816 __in_opt THEME* pTheme,
4817 __in HWND hWnd,
4818 __in UINT uMsg,
4819 __in WPARAM wParam,
4820 __in LPARAM lParam
4821 )
4822{
4823 if (pTheme)
4824 {
4825 switch (uMsg)
4826 {
4827 case WM_DRAWITEM:
4828 ThemeDrawControl(pTheme, reinterpret_cast<LPDRAWITEMSTRUCT>(lParam));
4829 return TRUE;
4830
4831 case WM_CTLCOLORBTN: __fallthrough;
4832 case WM_CTLCOLORSTATIC:
4833 {
4834 HBRUSH hBrush = NULL;
4835 if (ThemeSetControlColor(pTheme, reinterpret_cast<HDC>(wParam), reinterpret_cast<HWND>(lParam), &hBrush))
4836 {
4837 return reinterpret_cast<LRESULT>(hBrush);
4838 }
4839 }
4840 break;
4841
4842 case WM_SETCURSOR:
4843 if (ThemeHoverControl(pTheme, hWnd, reinterpret_cast<HWND>(wParam)))
4844 {
4845 return TRUE;
4846 }
4847 break;
4848
4849 case WM_PAINT:
4850 if (::GetUpdateRect(hWnd, NULL, FALSE))
4851 {
4852 PAINTSTRUCT ps;
4853 ::BeginPaint(hWnd, &ps);
4854 if (hWnd == pTheme->hwndParent)
4855 {
4856 ThemeDrawBackground(pTheme, &ps);
4857 }
4858 ::EndPaint(hWnd, &ps);
4859 }
4860 return 0;
4861
4862 case WM_TIMER:
4863 OnBillboardTimer(pTheme, hWnd, wParam);
4864 break;
4865
4866 case WM_NOTIFY:
4867 if (lParam)
4868 {
4869 LPNMHDR pnmhdr = reinterpret_cast<LPNMHDR>(lParam);
4870 switch (pnmhdr->code)
4871 {
4872 // Tab/Shift+Tab support for rich-edit control.
4873 case EN_MSGFILTER:
4874 {
4875 MSGFILTER* msgFilter = reinterpret_cast<MSGFILTER*>(lParam);
4876 if (WM_KEYDOWN == msgFilter->msg && VK_TAB == msgFilter->wParam)
4877 {
4878 BOOL fShift = 0x8000 & ::GetKeyState(VK_SHIFT);
4879 HWND hwndFocus = ::GetNextDlgTabItem(hWnd, msgFilter->nmhdr.hwndFrom, fShift);
4880 ::SetFocus(hwndFocus);
4881 return 1;
4882 }
4883 break;
4884 }
4885
4886 // Hyperlink clicks from rich-edit control.
4887 case EN_LINK:
4888 return SUCCEEDED(OnRichEditEnLink(lParam, pnmhdr->hwndFrom, hWnd));
4889
4890 // Clicks on a hypertext/syslink control.
4891 case NM_CLICK: __fallthrough;
4892 case NM_RETURN:
4893 if (ControlIsType(pTheme, static_cast<DWORD>(pnmhdr->idFrom), THEME_CONTROL_TYPE_HYPERTEXT))
4894 {
4895 PNMLINK pnmlink = reinterpret_cast<PNMLINK>(lParam);
4896 LITEM litem = pnmlink->item;
4897 ShelExec(litem.szUrl, NULL, L"open", NULL, SW_SHOWDEFAULT, hWnd, NULL);
4898 return 1;
4899 }
4900
4901 return 0;
4902 }
4903 }
4904 break;
4905
4906 case WM_COMMAND:
4907 switch (HIWORD(wParam))
4908 {
4909 case BN_CLICKED:
4910 if (lParam)
4911 {
4912 const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, (HWND)lParam);
4913 if (pControl && OnButtonClicked(pTheme, hWnd, pControl))
4914 {
4915 return 0;
4916 }
4917 }
4918 break;
4919 }
4920 break;
4921 }
4922 }
4923
4924 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
4925}
4926
4927
4928static LRESULT CALLBACK PanelWndProc(
4929 __in HWND hWnd,
4930 __in UINT uMsg,
4931 __in WPARAM wParam,
4932 __in LPARAM lParam
4933 )
4934{
4935 LRESULT lres = 0;
4936 THEME* pTheme = reinterpret_cast<THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
4937
4938 switch (uMsg)
4939 {
4940 case WM_NCCREATE:
4941 {
4942 LPCREATESTRUCTW lpcs = reinterpret_cast<LPCREATESTRUCTW>(lParam);
4943 pTheme = reinterpret_cast<THEME*>(lpcs->lpCreateParams);
4944 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pTheme));
4945 }
4946 break;
4947
4948 case WM_NCDESTROY:
4949 lres = ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
4950 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
4951 return lres;
4952
4953 case WM_NCHITTEST:
4954 return HTCLIENT;
4955 break;
4956 }
4957
4958 return ControlGroupDefWindowProc(pTheme, hWnd, uMsg, wParam, lParam);
4959}
4960
4961static LRESULT CALLBACK StaticOwnerDrawWndProc(
4962 __in HWND hWnd,
4963 __in UINT uMsg,
4964 __in WPARAM wParam,
4965 __in LPARAM lParam
4966 )
4967{
4968 switch (uMsg)
4969 {
4970 case WM_UPDATEUISTATE:
4971 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
4972 default:
4973 return (*vpfnStaticOwnerDrawBaseWndProc)(hWnd, uMsg, wParam, lParam);
4974 }
4975}
4976
4977static HRESULT LoadControls(
4978 __in THEME* pTheme,
4979 __in_opt THEME_CONTROL* pParentControl,
4980 __in_ecount_opt(cAssignControlIds) const THEME_ASSIGN_CONTROL_ID* rgAssignControlIds,
4981 __in DWORD cAssignControlIds
4982 )
4983{
4984 HRESULT hr = S_OK;
4985 RECT rcParent = { };
4986 LPWSTR sczText = NULL;
4987 BOOL fStartNewGroup = FALSE;
4988 DWORD cControls = 0;
4989 THEME_CONTROL* rgControls = NULL;
4990 HWND hwndParent = pParentControl ? pParentControl->hWnd : pTheme->hwndParent;
4991 int w = 0;
4992 int h = 0;
4993 int x = 0;
4994 int y = 0;
4995
4996 GetControls(pTheme, pParentControl, cControls, rgControls);
4997 ::GetClientRect(hwndParent, &rcParent);
4998
4999 for (DWORD i = 0; i < cControls; ++i)
5000 {
5001 THEME_CONTROL* pControl = rgControls + i;
5002 THEME_FONT* pControlFont = (pTheme->cFonts > pControl->dwFontId) ? pTheme->rgFonts + pControl->dwFontId : NULL;
5003 THEME_FONT_INSTANCE* pControlFontInstance = NULL;
5004 LPCWSTR wzWindowClass = NULL;
5005 DWORD dwWindowBits = WS_CHILD;
5006 DWORD dwWindowExBits = 0;
5007
5008 if (fStartNewGroup)
5009 {
5010 dwWindowBits |= WS_GROUP;
5011 fStartNewGroup = FALSE;
5012 }
5013
5014 switch (pControl->type)
5015 {
5016 case THEME_CONTROL_TYPE_BILLBOARD:
5017 __fallthrough;
5018 case THEME_CONTROL_TYPE_PANEL:
5019 wzWindowClass = THEME_WC_PANEL;
5020 dwWindowBits |= WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
5021 dwWindowExBits |= WS_EX_TRANSPARENT | WS_EX_CONTROLPARENT;
5022#ifdef DEBUG
5023 StrAllocFormatted(&pControl->sczText, L"Panel '%ls', id: %d", pControl->sczName, pControl->wId);
5024#endif
5025 break;
5026
5027 case THEME_CONTROL_TYPE_CHECKBOX:
5028 dwWindowBits |= BS_AUTOCHECKBOX | BS_MULTILINE; // checkboxes are basically buttons with an extra bit tossed in.
5029 __fallthrough;
5030 case THEME_CONTROL_TYPE_BUTTON:
5031 wzWindowClass = WC_BUTTONW;
5032 if (pControl->hImage || (pTheme->hImage && 0 <= pControl->nSourceX && 0 <= pControl->nSourceY))
5033 {
5034 dwWindowBits |= BS_OWNERDRAW;
5035 pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_OWNER_DRAW;
5036 }
5037 break;
5038
5039 case THEME_CONTROL_TYPE_COMBOBOX:
5040 wzWindowClass = WC_COMBOBOXW;
5041 dwWindowBits |= CBS_DROPDOWNLIST | CBS_HASSTRINGS;
5042 break;
5043
5044 case THEME_CONTROL_TYPE_COMMANDLINK:
5045 wzWindowClass = WC_BUTTONW;
5046 dwWindowBits |= BS_COMMANDLINK;
5047 break;
5048
5049 case THEME_CONTROL_TYPE_EDITBOX:
5050 wzWindowClass = WC_EDITW;
5051 dwWindowBits |= ES_LEFT | ES_AUTOHSCROLL;
5052 dwWindowExBits = WS_EX_CLIENTEDGE;
5053 break;
5054
5055 case THEME_CONTROL_TYPE_HYPERLINK: // hyperlinks are basically just owner drawn buttons.
5056 wzWindowClass = THEME_WC_HYPERLINK;
5057 dwWindowBits |= BS_OWNERDRAW | BTNS_NOPREFIX;
5058 break;
5059
5060 case THEME_CONTROL_TYPE_HYPERTEXT:
5061 wzWindowClass = WC_LINK;
5062 dwWindowBits |= LWS_NOPREFIX;
5063 break;
5064
5065 case THEME_CONTROL_TYPE_IMAGE: // images are basically just owner drawn static controls (so we can draw .jpgs and .pngs instead of just bitmaps).
5066 if (pControl->hImage || (pTheme->hImage && 0 <= pControl->nSourceX && 0 <= pControl->nSourceY))
5067 {
5068 wzWindowClass = THEME_WC_STATICOWNERDRAW;
5069 dwWindowBits |= SS_OWNERDRAW;
5070 pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_OWNER_DRAW;
5071 }
5072 else
5073 {
5074 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
5075 ThmExitOnRootFailure(hr, "Invalid image or image list coordinates.");
5076 }
5077 break;
5078
5079 case THEME_CONTROL_TYPE_LABEL:
5080 wzWindowClass = WC_STATICW;
5081 break;
5082
5083 case THEME_CONTROL_TYPE_LISTVIEW:
5084 // If thmutil is handling the image list for this listview, tell Windows not to free it when the control is destroyed.
5085 if (pControl->rghImageList[0] || pControl->rghImageList[1] || pControl->rghImageList[2] || pControl->rghImageList[3])
5086 {
5087 pControl->dwStyle |= LVS_SHAREIMAGELISTS;
5088 }
5089 wzWindowClass = WC_LISTVIEWW;
5090 break;
5091
5092 case THEME_CONTROL_TYPE_PROGRESSBAR:
5093 if (pControl->hImage || (pTheme->hImage && 0 <= pControl->nSourceX && 0 <= pControl->nSourceY))
5094 {
5095 wzWindowClass = THEME_WC_STATICOWNERDRAW; // no such thing as an owner drawn progress bar so we'll make our own out of a static control.
5096 dwWindowBits |= SS_OWNERDRAW;
5097 pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_OWNER_DRAW;
5098 }
5099 else
5100 {
5101 wzWindowClass = PROGRESS_CLASSW;
5102 }
5103 break;
5104
5105 case THEME_CONTROL_TYPE_RADIOBUTTON:
5106 dwWindowBits |= BS_AUTORADIOBUTTON | BS_MULTILINE;
5107 wzWindowClass = WC_BUTTONW;
5108
5109 if (pControl->fLastRadioButton)
5110 {
5111 fStartNewGroup = TRUE;
5112 }
5113 break;
5114
5115 case THEME_CONTROL_TYPE_RICHEDIT:
5116 if (!vhModuleMsftEdit && !vhModuleRichEd)
5117 {
5118 hr = LoadSystemLibrary(L"Msftedit.dll", &vhModuleMsftEdit);
5119 if (FAILED(hr))
5120 {
5121 hr = LoadSystemLibrary(L"Riched20.dll", &vhModuleRichEd);
5122 ThmExitOnFailure(hr, "Failed to load Rich Edit control library.");
5123 }
5124 }
5125
5126 wzWindowClass = vhModuleMsftEdit ? MSFTEDIT_CLASS : RICHEDIT_CLASSW;
5127 dwWindowBits |= ES_AUTOVSCROLL | ES_MULTILINE | WS_VSCROLL | ES_READONLY;
5128 break;
5129
5130 case THEME_CONTROL_TYPE_STATIC:
5131 wzWindowClass = WC_STATICW;
5132 dwWindowBits |= SS_ETCHEDHORZ;
5133 break;
5134
5135 case THEME_CONTROL_TYPE_TAB:
5136 wzWindowClass = WC_TABCONTROLW;
5137 break;
5138
5139 case THEME_CONTROL_TYPE_TREEVIEW:
5140 wzWindowClass = WC_TREEVIEWW;
5141 break;
5142 }
5143 ThmExitOnNull(wzWindowClass, hr, E_INVALIDDATA, "Failed to configure control %u because of unknown type: %u", i, pControl->type);
5144
5145 // Default control ids to the theme id and its index in the control array, unless there
5146 // is a specific id to assign to a named control.
5147 WORD wControlId = MAKEWORD(i, pTheme->wId);
5148 for (DWORD iAssignControl = 0; pControl->sczName && iAssignControl < cAssignControlIds; ++iAssignControl)
5149 {
5150 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pControl->sczName, -1, rgAssignControlIds[iAssignControl].wzName, -1))
5151 {
5152 wControlId = rgAssignControlIds[iAssignControl].wId;
5153 break;
5154 }
5155 }
5156
5157 pControl->wId = wControlId;
5158
5159 GetControlDimensions(pControl, &rcParent, &w, &h, &x, &y);
5160
5161 BOOL fVisible = pControl->dwStyle & WS_VISIBLE;
5162 BOOL fDisabled = pControl->dwStyle & WS_DISABLED;
5163
5164 // If the control is supposed to be initially visible and it has a VisibleCondition, check if it's true.
5165 if (fVisible && pControl->sczVisibleCondition && pTheme->pfnEvaluateCondition && !pControl->fDisableVariableFunctionality)
5166 {
5167 hr = pTheme->pfnEvaluateCondition(pControl->sczVisibleCondition, &fVisible, pTheme->pvVariableContext);
5168 ThmExitOnFailure(hr, "Failed to evaluate VisibleCondition: %ls", pControl->sczVisibleCondition);
5169
5170 if (!fVisible)
5171 {
5172 pControl->dwStyle &= ~WS_VISIBLE;
5173 }
5174 }
5175
5176 // Disable controls that aren't visible so their shortcut keys don't trigger.
5177 if (!fVisible)
5178 {
5179 dwWindowBits |= WS_DISABLED;
5180 fDisabled = TRUE;
5181 }
5182
5183 // If the control is supposed to be initially enabled and it has an EnableCondition, check if it's true.
5184 if (!fDisabled && pControl->sczEnableCondition && pTheme->pfnEvaluateCondition && !pControl->fDisableVariableFunctionality)
5185 {
5186 BOOL fEnable = TRUE;
5187
5188 hr = pTheme->pfnEvaluateCondition(pControl->sczEnableCondition, &fEnable, pTheme->pvVariableContext);
5189 ThmExitOnFailure(hr, "Failed to evaluate EnableCondition: %ls", pControl->sczEnableCondition);
5190
5191 fDisabled = !fEnable;
5192 dwWindowBits |= fDisabled ? WS_DISABLED : 0;
5193 }
5194
5195 // Honor the HideWhenDisabled option.
5196 if ((pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED) && fVisible && fDisabled)
5197 {
5198 fVisible = FALSE;
5199 pControl->dwStyle &= ~WS_VISIBLE;
5200 }
5201
5202 pControl->hWnd = ::CreateWindowExW(dwWindowExBits, wzWindowClass, pControl->sczText, pControl->dwStyle | dwWindowBits, x, y, w, h, hwndParent, reinterpret_cast<HMENU>(wControlId), NULL, pTheme);
5203 ThmExitOnNullWithLastError(pControl->hWnd, hr, "Failed to create window.");
5204
5205 if (pControl->sczTooltip)
5206 {
5207 if (!pTheme->hwndTooltip)
5208 {
5209 pTheme->hwndTooltip = ::CreateWindowExW(WS_EX_TOOLWINDOW, TOOLTIPS_CLASSW, NULL, WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON | TTS_NOPREFIX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, NULL, NULL, NULL);
5210 }
5211
5212 if (pTheme->hwndTooltip)
5213 {
5214 TOOLINFOW toolinfo = {};
5215 toolinfo.cbSize = sizeof(toolinfo);
5216 toolinfo.hwnd = hwndParent;
5217 toolinfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
5218 toolinfo.uId = reinterpret_cast<UINT_PTR>(pControl->hWnd);
5219 toolinfo.lpszText = pControl->sczTooltip;
5220 ::SendMessageW(pTheme->hwndTooltip, TTM_ADDTOOLW, 0, reinterpret_cast<LPARAM>(&toolinfo));
5221 }
5222 }
5223
5224 if (THEME_CONTROL_TYPE_COMMANDLINK == pControl->type)
5225 {
5226 if (pControl->sczNote)
5227 {
5228 ::SendMessageW(pControl->hWnd, BCM_SETNOTE, 0, reinterpret_cast<WPARAM>(pControl->sczNote));
5229 }
5230
5231 if (pControl->hImage)
5232 {
5233 ::SendMessageW(pControl->hWnd, BM_SETIMAGE, IMAGE_BITMAP, reinterpret_cast<LPARAM>(pControl->hImage));
5234 }
5235 else if (pControl->hIcon)
5236 {
5237 ::SendMessageW(pControl->hWnd, BM_SETIMAGE, IMAGE_ICON, reinterpret_cast<LPARAM>(pControl->hIcon));
5238 }
5239 }
5240 else if (THEME_CONTROL_TYPE_EDITBOX == pControl->type)
5241 {
5242 if (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_FILESYSTEM_AUTOCOMPLETE)
5243 {
5244 hr = ::SHAutoComplete(pControl->hWnd, SHACF_FILESYS_ONLY);
5245 }
5246 }
5247 else if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type)
5248 {
5249 ::SendMessageW(pControl->hWnd, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, pControl->dwExtendedStyle);
5250
5251 hr = SizeListViewColumns(pControl);
5252 ThmExitOnFailure(hr, "Failed to get size of list view columns.");
5253
5254 for (DWORD j = 0; j < pControl->cColumns; ++j)
5255 {
5256 LVCOLUMNW lvc = { };
5257 lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
5258 lvc.cx = pControl->ptcColumns[j].nWidth;
5259 lvc.iSubItem = j;
5260 lvc.pszText = pControl->ptcColumns[j].pszName;
5261 lvc.fmt = LVCFMT_LEFT;
5262 lvc.cchTextMax = 4;
5263
5264 if (-1 == ::SendMessageW(pControl->hWnd, LVM_INSERTCOLUMNW, (WPARAM) (int) (j), (LPARAM) (const LV_COLUMNW *) (&lvc)))
5265 {
5266 ThmExitWithLastError(hr, "Failed to insert listview column %u into tab control.", j);
5267 }
5268
5269 // Return value tells us the old image list, we don't care.
5270 if (pControl->rghImageList[0])
5271 {
5272 ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast<WPARAM>(LVSIL_NORMAL), reinterpret_cast<LPARAM>(pControl->rghImageList[0]));
5273 }
5274 else if (pControl->rghImageList[1])
5275 {
5276 ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast<WPARAM>(LVSIL_SMALL), reinterpret_cast<LPARAM>(pControl->rghImageList[1]));
5277 }
5278 else if (pControl->rghImageList[2])
5279 {
5280 ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast<WPARAM>(LVSIL_STATE), reinterpret_cast<LPARAM>(pControl->rghImageList[2]));
5281 }
5282 else if (pControl->rghImageList[3])
5283 {
5284 ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast<WPARAM>(LVSIL_GROUPHEADER), reinterpret_cast<LPARAM>(pControl->rghImageList[3]));
5285 }
5286 }
5287 }
5288 else if (THEME_CONTROL_TYPE_RICHEDIT == pControl->type)
5289 {
5290 ::SendMessageW(pControl->hWnd, EM_AUTOURLDETECT, static_cast<WPARAM>(TRUE), 0);
5291 ::SendMessageW(pControl->hWnd, EM_SETEVENTMASK, 0, ENM_KEYEVENTS | ENM_LINK);
5292 }
5293 else if (THEME_CONTROL_TYPE_TAB == pControl->type)
5294 {
5295 ULONG_PTR hbrBackground = 0;
5296 if (THEME_INVALID_ID != pControl->dwFontId)
5297 {
5298 hbrBackground = reinterpret_cast<ULONG_PTR>(pTheme->rgFonts[pControl->dwFontId].hBackground);
5299 }
5300 else
5301 {
5302 hbrBackground = ::GetClassLongPtr(pTheme->hwndParent, GCLP_HBRBACKGROUND);
5303 }
5304 ::SetClassLongPtr(pControl->hWnd, GCLP_HBRBACKGROUND, hbrBackground);
5305
5306 for (DWORD j = 0; j < pControl->cTabs; ++j)
5307 {
5308 TCITEMW tci = { };
5309 tci.mask = TCIF_TEXT | TCIF_IMAGE;
5310 tci.iImage = -1;
5311 tci.pszText = pControl->pttTabs[j].pszName;
5312
5313 if (-1 == ::SendMessageW(pControl->hWnd, TCM_INSERTITEMW, (WPARAM) (int) (j), (LPARAM) (const TC_ITEMW *) (&tci)))
5314 {
5315 ThmExitWithLastError(hr, "Failed to insert tab %u into tab control.", j);
5316 }
5317 }
5318 }
5319
5320 if (pControlFont)
5321 {
5322 hr = EnsureFontInstance(pTheme, pControlFont, &pControlFontInstance);
5323 ThmExitOnFailure(hr, "Failed to get DPI specific font.");
5324
5325 ::SendMessageW(pControl->hWnd, WM_SETFONT, (WPARAM) pControlFontInstance->hFont, FALSE);
5326 }
5327
5328 // Initialize the text on all "application" (non-page) controls, best effort only.
5329 if (pTheme->pfnFormatString && !pControl->wPageId && pControl->sczText && *pControl->sczText)
5330 {
5331 HRESULT hrFormat = pTheme->pfnFormatString(pControl->sczText, &sczText, pTheme->pvVariableContext);
5332 if (SUCCEEDED(hrFormat))
5333 {
5334 ThemeSetTextControl(pTheme, pControl->wId, sczText);
5335 }
5336 }
5337
5338 if (pControl->cControls)
5339 {
5340 hr = LoadControls(pTheme, pControl, rgAssignControlIds, cAssignControlIds);
5341 ThmExitOnFailure(hr, "Failed to load child controls.");
5342 }
5343 }
5344
5345LExit:
5346 ReleaseStr(sczText);
5347
5348 return hr;
5349}
5350
5351static HRESULT LocalizeControls(
5352 __in DWORD cControls,
5353 __in THEME_CONTROL* rgControls,
5354 __in const WIX_LOCALIZATION *pWixLoc
5355 )
5356{
5357 HRESULT hr = S_OK;
5358
5359 for (DWORD i = 0; i < cControls; ++i)
5360 {
5361 THEME_CONTROL* pControl = rgControls + i;
5362 hr = LocalizeControl(pControl, pWixLoc);
5363 ThmExitOnFailure(hr, "Failed to localize control: %ls", pControl->sczName);
5364 }
5365
5366LExit:
5367 return hr;
5368}
5369
5370static HRESULT LocalizeControl(
5371 __in THEME_CONTROL* pControl,
5372 __in const WIX_LOCALIZATION *pWixLoc
5373 )
5374{
5375 HRESULT hr = S_OK;
5376 LOC_CONTROL* pLocControl = NULL;
5377 LPWSTR sczLocStringId = NULL;
5378
5379 if (pControl->sczText && *pControl->sczText)
5380 {
5381 hr = LocLocalizeString(pWixLoc, &pControl->sczText);
5382 ThmExitOnFailure(hr, "Failed to localize control text.");
5383 }
5384 else if (pControl->sczName)
5385 {
5386 LOC_STRING* plocString = NULL;
5387
5388 hr = StrAllocFormatted(&sczLocStringId, L"#(loc.%ls)", pControl->sczName);
5389 ThmExitOnFailure(hr, "Failed to format loc string id: %ls", pControl->sczName);
5390
5391 hr = LocGetString(pWixLoc, sczLocStringId, &plocString);
5392 if (E_NOTFOUND != hr)
5393 {
5394 ThmExitOnFailure(hr, "Failed to get loc string: %ls", pControl->sczName);
5395
5396 hr = StrAllocString(&pControl->sczText, plocString->wzText, 0);
5397 ThmExitOnFailure(hr, "Failed to copy loc string to control: %ls", plocString->wzText);
5398 }
5399 }
5400
5401 if (pControl->sczTooltip && *pControl->sczTooltip)
5402 {
5403 hr = LocLocalizeString(pWixLoc, &pControl->sczTooltip);
5404 ThmExitOnFailure(hr, "Failed to localize control tooltip text.");
5405 }
5406
5407 if (pControl->sczNote && *pControl->sczNote)
5408 {
5409 hr = LocLocalizeString(pWixLoc, &pControl->sczNote);
5410 ThmExitOnFailure(hr, "Failed to localize control note text.");
5411 }
5412
5413 for (DWORD j = 0; j < pControl->cConditionalText; ++j)
5414 {
5415 hr = LocLocalizeString(pWixLoc, &pControl->rgConditionalText[j].sczText);
5416 ThmExitOnFailure(hr, "Failed to localize conditional text.");
5417 }
5418
5419 for (DWORD j = 0; j < pControl->cConditionalNotes; ++j)
5420 {
5421 hr = LocLocalizeString(pWixLoc, &pControl->rgConditionalNotes[j].sczText);
5422 ThmExitOnFailure(hr, "Failed to localize conditional note.");
5423 }
5424
5425 for (DWORD j = 0; j < pControl->cColumns; ++j)
5426 {
5427 hr = LocLocalizeString(pWixLoc, &pControl->ptcColumns[j].pszName);
5428 ThmExitOnFailure(hr, "Failed to localize column text.");
5429 }
5430
5431 for (DWORD j = 0; j < pControl->cTabs; ++j)
5432 {
5433 hr = LocLocalizeString(pWixLoc, &pControl->pttTabs[j].pszName);
5434 ThmExitOnFailure(hr, "Failed to localize tab text.");
5435 }
5436
5437 // Localize control's size, location, and text.
5438 if (pControl->sczName)
5439 {
5440 hr = LocGetControl(pWixLoc, pControl->sczName, &pLocControl);
5441 if (E_NOTFOUND == hr)
5442 {
5443 ExitFunction1(hr = S_OK);
5444 }
5445 ThmExitOnFailure(hr, "Failed to localize control.");
5446
5447 if (LOC_CONTROL_NOT_SET != pLocControl->nX)
5448 {
5449 pControl->nDefaultDpiX = pLocControl->nX;
5450 }
5451
5452 if (LOC_CONTROL_NOT_SET != pLocControl->nY)
5453 {
5454 pControl->nDefaultDpiY = pLocControl->nY;
5455 }
5456
5457 if (LOC_CONTROL_NOT_SET != pLocControl->nWidth)
5458 {
5459 pControl->nDefaultDpiWidth = pLocControl->nWidth;
5460 }
5461
5462 if (LOC_CONTROL_NOT_SET != pLocControl->nHeight)
5463 {
5464 pControl->nDefaultDpiHeight = pLocControl->nHeight;
5465 }
5466
5467 if (pLocControl->wzText && *pLocControl->wzText)
5468 {
5469 hr = StrAllocString(&pControl->sczText, pLocControl->wzText, 0);
5470 ThmExitOnFailure(hr, "Failed to localize control text.");
5471 }
5472 }
5473
5474 hr = LocalizeControls(pControl->cControls, pControl->rgControls, pWixLoc);
5475
5476LExit:
5477 ReleaseStr(sczLocStringId);
5478
5479 return hr;
5480}
5481
5482static HRESULT LoadControlsString(
5483 __in DWORD cControls,
5484 __in THEME_CONTROL* rgControls,
5485 __in HMODULE hResModule
5486 )
5487{
5488 HRESULT hr = S_OK;
5489
5490 for (DWORD i = 0; i < cControls; ++i)
5491 {
5492 THEME_CONTROL* pControl = rgControls + i;
5493 hr = LoadControlString(pControl, hResModule);
5494 ThmExitOnFailure(hr, "Failed to load string for control: %ls", pControl->sczName);
5495 }
5496
5497LExit:
5498 return hr;
5499}
5500
5501static HRESULT LoadControlString(
5502 __in THEME_CONTROL* pControl,
5503 __in HMODULE hResModule
5504 )
5505{
5506 HRESULT hr = S_OK;
5507 if (UINT_MAX != pControl->uStringId)
5508 {
5509 hr = ResReadString(hResModule, pControl->uStringId, &pControl->sczText);
5510 ThmExitOnFailure(hr, "Failed to load control text.");
5511
5512 for (DWORD j = 0; j < pControl->cColumns; ++j)
5513 {
5514 if (UINT_MAX != pControl->ptcColumns[j].uStringId)
5515 {
5516 hr = ResReadString(hResModule, pControl->ptcColumns[j].uStringId, &pControl->ptcColumns[j].pszName);
5517 ThmExitOnFailure(hr, "Failed to load column text.");
5518 }
5519 }
5520
5521 for (DWORD j = 0; j < pControl->cTabs; ++j)
5522 {
5523 if (UINT_MAX != pControl->pttTabs[j].uStringId)
5524 {
5525 hr = ResReadString(hResModule, pControl->pttTabs[j].uStringId, &pControl->pttTabs[j].pszName);
5526 ThmExitOnFailure(hr, "Failed to load tab text.");
5527 }
5528 }
5529 }
5530
5531 hr = LoadControlsString(pControl->cControls, pControl->rgControls, hResModule);
5532
5533LExit:
5534 return hr;
5535}
5536
5537static void ResizeControls(
5538 __in DWORD cControls,
5539 __in THEME_CONTROL* rgControls,
5540 __in const RECT* prcParent
5541 )
5542{
5543 for (DWORD i = 0; i < cControls; ++i)
5544 {
5545 THEME_CONTROL* pControl = rgControls + i;
5546 ResizeControl(pControl, prcParent);
5547 }
5548}
5549
5550static void ResizeControl(
5551 __in THEME_CONTROL* pControl,
5552 __in const RECT* prcParent
5553 )
5554{
5555 int w = 0;
5556 int h = 0;
5557 int x = 0;
5558 int y = 0;
5559 RECT rcControl = { };
5560
5561 GetControlDimensions(pControl, prcParent, &w, &h, &x, &y);
5562 ::SetWindowPos(pControl->hWnd, NULL, x, y, w, h, SWP_NOACTIVATE | SWP_NOZORDER);
5563
5564#ifdef DEBUG
5565 if (THEME_CONTROL_TYPE_BUTTON == pControl->type)
5566 {
5567 Trace(REPORT_STANDARD, "Resizing button (%ls/%ls) to (%d,%d)+(%d,%d) for parent (%d,%d)-(%d,%d)",
5568 pControl->sczName, pControl->sczText, x, y, w, h, prcParent->left, prcParent->top, prcParent->right, prcParent->bottom);
5569 }
5570#endif
5571
5572 if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type)
5573 {
5574 SizeListViewColumns(pControl);
5575
5576 for (DWORD j = 0; j < pControl->cColumns; ++j)
5577 {
5578 if (-1 == ::SendMessageW(pControl->hWnd, LVM_SETCOLUMNWIDTH, (WPARAM) (int) (j), (LPARAM) (pControl->ptcColumns[j].nWidth)))
5579 {
5580 Trace(REPORT_DEBUG, "Failed to resize listview column %u with error %u", j, ::GetLastError());
5581 return;
5582 }
5583 }
5584 }
5585
5586 if (pControl->cControls)
5587 {
5588 ::GetClientRect(pControl->hWnd, &rcControl);
5589 ResizeControls(pControl->cControls, pControl->rgControls, &rcControl);
5590 }
5591}
5592
5593static void ScaleThemeFromWindow(
5594 __in THEME* pTheme,
5595 __in UINT nDpi,
5596 __in int x,
5597 __in int y
5598 )
5599{
5600 DWORD dwStyle = GetWindowStyle(pTheme->hwndParent);
5601 BOOL fMenu = NULL != ::GetMenu(pTheme->hwndParent);
5602 DWORD dwExStyle = GetWindowExStyle(pTheme->hwndParent);
5603
5604 ScaleTheme(pTheme, nDpi, x, y, dwStyle, fMenu, dwExStyle);
5605}
5606
5607static void ScaleTheme(
5608 __in THEME* pTheme,
5609 __in UINT nDpi,
5610 __in int x,
5611 __in int y,
5612 __in DWORD dwStyle,
5613 __in BOOL fMenu,
5614 __in DWORD dwExStyle
5615 )
5616{
5617 RECT rect = { };
5618
5619 pTheme->nDpi = nDpi;
5620
5621 pTheme->nHeight = DpiuScaleValue(pTheme->nDefaultDpiHeight, pTheme->nDpi);
5622 pTheme->nWidth = DpiuScaleValue(pTheme->nDefaultDpiWidth, pTheme->nDpi);
5623 pTheme->nMinimumHeight = DpiuScaleValue(pTheme->nDefaultDpiMinimumHeight, pTheme->nDpi);
5624 pTheme->nMinimumWidth = DpiuScaleValue(pTheme->nDefaultDpiMinimumWidth, pTheme->nDpi);
5625
5626 rect.left = x;
5627 rect.top = y;
5628 rect.right = x + pTheme->nWidth;
5629 rect.bottom = y + pTheme->nHeight;
5630 DpiuAdjustWindowRect(&rect, dwStyle, fMenu, dwExStyle, pTheme->nDpi);
5631 pTheme->nWindowWidth = rect.right - rect.left;
5632 pTheme->nWindowHeight = rect.bottom - rect.top;
5633
5634 ScaleControls(pTheme, pTheme->cControls, pTheme->rgControls, pTheme->nDpi);
5635
5636 if (pTheme->hwndParent)
5637 {
5638 ::SetWindowPos(pTheme->hwndParent, NULL, x, y, pTheme->nWindowWidth, pTheme->nWindowHeight, SWP_NOACTIVATE | SWP_NOZORDER);
5639 }
5640}
5641
5642static void ScaleControls(
5643 __in THEME* pTheme,
5644 __in DWORD cControls,
5645 __in THEME_CONTROL* rgControls,
5646 __in UINT nDpi
5647 )
5648{
5649 for (DWORD i = 0; i < cControls; ++i)
5650 {
5651 THEME_CONTROL* pControl = rgControls + i;
5652 ScaleControl(pTheme, pControl, nDpi);
5653 }
5654}
5655
5656static void ScaleControl(
5657 __in THEME* pTheme,
5658 __in THEME_CONTROL* pControl,
5659 __in UINT nDpi
5660 )
5661{
5662 HRESULT hr = S_OK;
5663 THEME_FONT* pControlFont = (pTheme->cFonts > pControl->dwFontId) ? pTheme->rgFonts + pControl->dwFontId : NULL;
5664 THEME_FONT_INSTANCE* pControlFontInstance = NULL;
5665
5666 if (pControlFont)
5667 {
5668 hr = EnsureFontInstance(pTheme, pControlFont, &pControlFontInstance);
5669 if (SUCCEEDED(hr) && pControl->hWnd)
5670 {
5671 ::SendMessageW(pControl->hWnd, WM_SETFONT, (WPARAM)pControlFontInstance->hFont, FALSE);
5672 }
5673 }
5674
5675 if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type)
5676 {
5677 for (DWORD i = 0; i < pControl->cColumns; ++i)
5678 {
5679 THEME_COLUMN* pColumn = pControl->ptcColumns + i;
5680
5681 pColumn->nBaseWidth = DpiuScaleValue(pColumn->nDefaultDpiBaseWidth, nDpi);
5682 }
5683 }
5684
5685 pControl->nWidth = DpiuScaleValue(pControl->nDefaultDpiWidth, nDpi);
5686 pControl->nHeight = DpiuScaleValue(pControl->nDefaultDpiHeight, nDpi);
5687 pControl->nX = DpiuScaleValue(pControl->nDefaultDpiX, nDpi);
5688 pControl->nY = DpiuScaleValue(pControl->nDefaultDpiY, nDpi);
5689
5690 if (pControl->cControls)
5691 {
5692 ScaleControls(pTheme, pControl->cControls, pControl->rgControls, nDpi);
5693 }
5694}
5695
5696static void UnloadControls(
5697 __in DWORD cControls,
5698 __in THEME_CONTROL* rgControls
5699 )
5700{
5701 for (DWORD i = 0; i < cControls; ++i)
5702 {
5703 THEME_CONTROL* pControl = rgControls + i;
5704 pControl->hWnd = NULL;
5705
5706 UnloadControls(pControl->cControls, pControl->rgControls);
5707 }
5708}
5709