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