From 7f642e51670bc38a4ef782a363936850bc2b0ba9 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 22 Apr 2021 06:38:23 -0700 Subject: Move dutil into libs/dutil --- src/libs/dutil/WixToolset.DUtil/thmutil.cpp | 5709 +++++++++++++++++++++++++++ 1 file changed, 5709 insertions(+) create mode 100644 src/libs/dutil/WixToolset.DUtil/thmutil.cpp (limited to 'src/libs/dutil/WixToolset.DUtil/thmutil.cpp') diff --git a/src/libs/dutil/WixToolset.DUtil/thmutil.cpp b/src/libs/dutil/WixToolset.DUtil/thmutil.cpp new file mode 100644 index 00000000..d200a0ea --- /dev/null +++ b/src/libs/dutil/WixToolset.DUtil/thmutil.cpp @@ -0,0 +1,5709 @@ +// 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. + +#include "precomp.h" + + +// Exit macros +#define ThmExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__) +#define ThmExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__) +#define ThmExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__) +#define ThmExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__) +#define ThmExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__) +#define ThmExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_THMUTIL, x, s, __VA_ARGS__) +#define ThmExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_THMUTIL, p, x, e, s, __VA_ARGS__) +#define ThmExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_THMUTIL, p, x, s, __VA_ARGS__) +#define ThmExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_THMUTIL, p, x, e, s, __VA_ARGS__) +#define ThmExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_THMUTIL, p, x, s, __VA_ARGS__) +#define ThmExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_THMUTIL, e, x, s, __VA_ARGS__) +#define ThmExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_THMUTIL, g, x, s, __VA_ARGS__) + +// from CommCtrl.h +#ifndef BS_COMMANDLINK +#define BS_COMMANDLINK 0x0000000EL +#endif + +#ifndef BCM_SETNOTE +#define BCM_SETNOTE (BCM_FIRST + 0x0009) +#endif + +#ifndef BCM_SETSHIELD +#define BCM_SETSHIELD (BCM_FIRST + 0x000C) +#endif + +#ifndef LWS_NOPREFIX +#define LWS_NOPREFIX 0x0004 +#endif + +const DWORD THEME_INVALID_ID = 0xFFFFFFFF; +const COLORREF THEME_INVISIBLE_COLORREF = 0xFFFFFFFF; +const DWORD GROW_FONT_INSTANCES = 3; +const DWORD GROW_WINDOW_TEXT = 250; +const LPCWSTR THEME_WC_HYPERLINK = L"ThemeHyperLink"; +const LPCWSTR THEME_WC_PANEL = L"ThemePanel"; +const LPCWSTR THEME_WC_STATICOWNERDRAW = L"ThemeStaticOwnerDraw"; + +static Gdiplus::GdiplusStartupInput vgsi; +static Gdiplus::GdiplusStartupOutput vgso = { }; +static ULONG_PTR vgdiToken = 0; +static ULONG_PTR vgdiHookToken = 0; +static HMODULE vhHyperlinkRegisteredModule = NULL; +static HMODULE vhPanelRegisteredModule = NULL; +static HMODULE vhStaticOwnerDrawRegisteredModule = NULL; +static WNDPROC vpfnStaticOwnerDrawBaseWndProc = NULL; +static HMODULE vhModuleMsftEdit = NULL; +static HMODULE vhModuleRichEd = NULL; +static HCURSOR vhCursorHand = NULL; + +enum INTERNAL_CONTROL_STYLE +{ + INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED = 0x0001, + INTERNAL_CONTROL_STYLE_FILESYSTEM_AUTOCOMPLETE = 0x0002, + INTERNAL_CONTROL_STYLE_DISABLED = 0x0004, + INTERNAL_CONTROL_STYLE_HIDDEN = 0x0008, + INTERNAL_CONTROL_STYLE_OWNER_DRAW = 0x0010, +}; + +struct MEMBUFFER_FOR_RICHEDIT +{ + BYTE* rgbData; + DWORD cbData; + + DWORD iData; +}; + + +// prototypes +static HRESULT RegisterWindowClasses( + __in_opt HMODULE hModule + ); +static HRESULT ParseTheme( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMDocument* pixd, + __out THEME** ppTheme + ); +static HRESULT ParseImage( + __in_opt HMODULE hModule, + __in_z_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pElement, + __out HBITMAP* phImage + ); +static HRESULT ParseIcon( + __in_opt HMODULE hModule, + __in_z_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pElement, + __out HICON* phIcon + ); +static HRESULT ParseWindow( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMElement* pElement, + __in THEME* pTheme + ); +static HRESULT GetFontColor( + __in IXMLDOMNode* pixn, + __in_z LPCWSTR wzAttributeName, + __out COLORREF* pColorRef, + __out DWORD* pdwSystemColor + ); +static HRESULT ParseFonts( + __in IXMLDOMElement* pElement, + __in THEME* pTheme + ); +static HRESULT ParsePages( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pElement, + __in THEME* pTheme + ); +static HRESULT ParseImageLists( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pElement, + __in THEME* pTheme + ); +static HRESULT ParseControls( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pElement, + __in THEME* pTheme, + __in_opt THEME_CONTROL* pParentControl, + __in_opt THEME_PAGE* pPage + ); +static HRESULT ParseControl( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pixn, + __in THEME* pTheme, + __in THEME_CONTROL* pControl, + __in BOOL fSkipDimensions, + __in_opt THEME_PAGE* pPage + ); +static HRESULT ParseActions( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl + ); +static HRESULT ParseColumns( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl + ); +static HRESULT ParseRadioButtons( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pixn, + __in THEME* pTheme, + __in_opt THEME_CONTROL* pParentControl, + __in THEME_PAGE* pPage + ); +static HRESULT ParseTabs( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl + ); +static HRESULT ParseText( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl, + __inout BOOL* pfAnyChildren +); +static HRESULT ParseTooltips( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl, + __inout BOOL* pfAnyChildren + ); +static HRESULT ParseNotes( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl, + __out BOOL* pfAnyChildren + ); +static HRESULT StopBillboard( + __in THEME* pTheme, + __in DWORD dwControl + ); +static HRESULT StartBillboard( + __in THEME* pTheme, + __in DWORD dwControl + ); +static HRESULT EnsureFontInstance( + __in THEME* pTheme, + __in THEME_FONT* pFont, + __out THEME_FONT_INSTANCE** ppFontInstance + ); +static HRESULT FindImageList( + __in THEME* pTheme, + __in_z LPCWSTR wzImageListName, + __out HIMAGELIST *phImageList + ); +static HRESULT LoadControls( + __in THEME* pTheme, + __in_opt THEME_CONTROL* pParentControl, + __in_ecount_opt(cAssignControlIds) const THEME_ASSIGN_CONTROL_ID* rgAssignControlIds, + __in DWORD cAssignControlIds + ); +static HRESULT ShowControl( + __in THEME* pTheme, + __in THEME_CONTROL* pControl, + __in int nCmdShow, + __in BOOL fSaveEditboxes, + __in THEME_SHOW_PAGE_REASON reason, + __in DWORD dwPageId, + __out_opt HWND* phwndFocus + ); +static HRESULT ShowControls( + __in THEME* pTheme, + __in_opt const THEME_CONTROL* pParentControl, + __in int nCmdShow, + __in BOOL fSaveEditboxes, + __in THEME_SHOW_PAGE_REASON reason, + __in DWORD dwPageId + ); +static HRESULT DrawButton( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis, + __in const THEME_CONTROL* pControl + ); +static void DrawControlText( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis, + __in const THEME_CONTROL* pControl, + __in BOOL fCentered, + __in BOOL fDrawFocusRect + ); +static HRESULT DrawHyperlink( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis, + __in const THEME_CONTROL* pControl + ); +static HRESULT DrawImage( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis, + __in const THEME_CONTROL* pControl + ); +static HRESULT DrawProgressBar( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis, + __in const THEME_CONTROL* pControl + ); +static BOOL DrawHoverControl( + __in THEME* pTheme, + __in BOOL fHover + ); +static DWORD CALLBACK RichEditStreamFromFileHandleCallback( + __in DWORD_PTR dwCookie, + __in_bcount(cb) LPBYTE pbBuff, + __in LONG cb, + __in LONG *pcb + ); +static DWORD CALLBACK RichEditStreamFromMemoryCallback( + __in DWORD_PTR dwCookie, + __in_bcount(cb) LPBYTE pbBuff, + __in LONG cb, + __in LONG *pcb + ); +static void FreeFontInstance( + __in THEME_FONT_INSTANCE* pFontInstance + ); +static void FreeFont( + __in THEME_FONT* pFont + ); +static void FreePage( + __in THEME_PAGE* pPage + ); +static void FreeControl( + __in THEME_CONTROL* pControl + ); +static void FreeConditionalText( + __in THEME_CONDITIONAL_TEXT* pConditionalText + ); +static void FreeImageList( + __in THEME_IMAGELIST* pImageList + ); +static void FreeAction( + __in THEME_ACTION* pAction + ); +static void FreeColumn( + __in THEME_COLUMN* pColumn + ); +static void FreeTab( + __in THEME_TAB* pTab + ); +static void CALLBACK OnBillboardTimer( + __in THEME* pTheme, + __in HWND hwnd, + __in UINT_PTR idEvent + ); +static void OnBrowseDirectory( + __in THEME* pTheme, + __in HWND hWnd, + __in const THEME_ACTION* pAction + ); +static BOOL OnButtonClicked( + __in THEME* pTheme, + __in HWND hWnd, + __in const THEME_CONTROL* pControl + ); +static BOOL OnDpiChanged( + __in THEME* pTheme, + __in WPARAM wParam, + __in LPARAM lParam + ); +static void OnNcCreate( + __in THEME* pTheme, + __in HWND hWnd, + __in LPARAM lParam + ); +static HRESULT OnRichEditEnLink( + __in LPARAM lParam, + __in HWND hWndRichEdit, + __in HWND hWnd + ); +static BOOL ControlIsType( + __in const THEME* pTheme, + __in DWORD dwControl, + __in THEME_CONTROL_TYPE type + ); +static const THEME_CONTROL* FindControlFromHWnd( + __in const THEME* pTheme, + __in HWND hWnd, + __in_opt const THEME_CONTROL* pParentControl = NULL + ); +static void GetControlDimensions( + __in const THEME_CONTROL* pControl, + __in const RECT* prcParent, + __out int* piWidth, + __out int* piHeight, + __out int* piX, + __out int* piY + ); +// Using iWidth as total width of listview, base width of columns, and "Expands" flag on columns +// calculates final width of each column (storing result in each column's nWidth value) +static HRESULT SizeListViewColumns( + __inout THEME_CONTROL* pControl + ); +static LRESULT CALLBACK ControlGroupDefWindowProc( + __in_opt THEME* pTheme, + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ); +static LRESULT CALLBACK PanelWndProc( + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ); +static LRESULT CALLBACK StaticOwnerDrawWndProc( + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ); +static HRESULT LocalizeControls( + __in DWORD cControls, + __in THEME_CONTROL* rgControls, + __in const WIX_LOCALIZATION *pWixLoc + ); +static HRESULT LocalizeControl( + __in THEME_CONTROL* pControl, + __in const WIX_LOCALIZATION *pWixLoc + ); +static HRESULT LoadControlsString( + __in DWORD cControls, + __in THEME_CONTROL* rgControls, + __in HMODULE hResModule + ); +static HRESULT LoadControlString( + __in THEME_CONTROL* pControl, + __in HMODULE hResModule + ); +static void ResizeControls( + __in DWORD cControls, + __in THEME_CONTROL* rgControls, + __in const RECT* prcParent + ); +static void ResizeControl( + __in THEME_CONTROL* pControl, + __in const RECT* prcParent + ); +static void ScaleThemeFromWindow( + __in THEME* pTheme, + __in UINT nDpi, + __in int x, + __in int y + ); +static void ScaleTheme( + __in THEME* pTheme, + __in UINT nDpi, + __in int x, + __in int y, + __in DWORD dwStyle, + __in BOOL fMenu, + __in DWORD dwExStyle + ); +static void ScaleControls( + __in THEME* pTheme, + __in DWORD cControls, + __in THEME_CONTROL* rgControls, + __in UINT nDpi + ); +static void ScaleControl( + __in THEME* pTheme, + __in THEME_CONTROL* pControl, + __in UINT nDpi + ); +static void GetControls( + __in THEME* pTheme, + __in_opt THEME_CONTROL* pParentControl, + __out DWORD** ppcControls, + __out THEME_CONTROL*** pprgControls + ); +static void GetControls( + __in const THEME* pTheme, + __in_opt const THEME_CONTROL* pParentControl, + __out DWORD& cControls, + __out THEME_CONTROL*& rgControls + ); +static void UnloadControls( + __in DWORD cControls, + __in THEME_CONTROL* rgControls + ); + + +// Public functions. + +DAPI_(HRESULT) ThemeInitialize( + __in_opt HMODULE hModule + ) +{ + HRESULT hr = S_OK; + INITCOMMONCONTROLSEX icex = { }; + + DpiuInitialize(); + + hr = XmlInitialize(); + ThmExitOnFailure(hr, "Failed to initialize XML."); + + hr = RegisterWindowClasses(hModule); + ThmExitOnFailure(hr, "Failed to register theme window classes."); + + // Initialize GDI+ and common controls. + vgsi.SuppressBackgroundThread = TRUE; + + hr = GdipInitialize(&vgsi, &vgdiToken, &vgso); + ThmExitOnFailure(hr, "Failed to initialize GDI+."); + + icex.dwSize = sizeof(INITCOMMONCONTROLSEX); + icex.dwICC = ICC_STANDARD_CLASSES | ICC_PROGRESS_CLASS | ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES | ICC_TAB_CLASSES | ICC_LINK_CLASS; + ::InitCommonControlsEx(&icex); + + (*vgso.NotificationHook)(&vgdiHookToken); + +LExit: + return hr; +} + + +DAPI_(void) ThemeUninitialize() +{ + if (vhModuleMsftEdit) + { + ::FreeLibrary(vhModuleMsftEdit); + vhModuleMsftEdit = NULL; + } + + if (vhModuleRichEd) + { + ::FreeLibrary(vhModuleRichEd); + vhModuleRichEd = NULL; + } + + if (vhHyperlinkRegisteredModule) + { + ::UnregisterClassW(THEME_WC_HYPERLINK, vhHyperlinkRegisteredModule); + vhHyperlinkRegisteredModule = NULL; + } + + if (vhPanelRegisteredModule) + { + ::UnregisterClassW(THEME_WC_PANEL, vhPanelRegisteredModule); + vhPanelRegisteredModule = NULL; + } + + if (vhStaticOwnerDrawRegisteredModule) + { + ::UnregisterClassW(THEME_WC_STATICOWNERDRAW, vhStaticOwnerDrawRegisteredModule); + vhStaticOwnerDrawRegisteredModule = NULL; + vpfnStaticOwnerDrawBaseWndProc = NULL; + } + + if (vgdiToken) + { + GdipUninitialize(vgdiToken); + vgdiToken = 0; + } + + XmlUninitialize(); + DpiuUninitialize(); +} + + +DAPI_(HRESULT) ThemeLoadFromFile( + __in_z LPCWSTR wzThemeFile, + __out THEME** ppTheme + ) +{ + HRESULT hr = S_OK; + IXMLDOMDocument* pixd = NULL; + LPWSTR sczRelativePath = NULL; + + hr = XmlLoadDocumentFromFile(wzThemeFile, &pixd); + ThmExitOnFailure(hr, "Failed to load theme resource as XML document."); + + hr = PathGetDirectory(wzThemeFile, &sczRelativePath); + ThmExitOnFailure(hr, "Failed to get relative path from theme file."); + + hr = ParseTheme(NULL, sczRelativePath, pixd, ppTheme); + ThmExitOnFailure(hr, "Failed to parse theme."); + +LExit: + ReleaseStr(sczRelativePath); + ReleaseObject(pixd); + + return hr; +} + + +DAPI_(HRESULT) ThemeLoadFromResource( + __in_opt HMODULE hModule, + __in_z LPCSTR szResource, + __out THEME** ppTheme + ) +{ + HRESULT hr = S_OK; + LPVOID pvResource = NULL; + DWORD cbResource = 0; + LPWSTR sczXml = NULL; + IXMLDOMDocument* pixd = NULL; + + hr = ResReadData(hModule, szResource, &pvResource, &cbResource); + ThmExitOnFailure(hr, "Failed to read theme from resource."); + + hr = StrAllocStringAnsi(&sczXml, reinterpret_cast(pvResource), cbResource, CP_UTF8); + ThmExitOnFailure(hr, "Failed to convert XML document data from UTF-8 to unicode string."); + + hr = XmlLoadDocument(sczXml, &pixd); + ThmExitOnFailure(hr, "Failed to load theme resource as XML document."); + + hr = ParseTheme(hModule, NULL, pixd, ppTheme); + ThmExitOnFailure(hr, "Failed to parse theme."); + +LExit: + ReleaseObject(pixd); + ReleaseStr(sczXml); + + return hr; +} + + +DAPI_(void) ThemeFree( + __in THEME* pTheme + ) +{ + if (pTheme) + { + for (DWORD i = 0; i < pTheme->cFonts; ++i) + { + FreeFont(pTheme->rgFonts + i); + } + + for (DWORD i = 0; i < pTheme->cPages; ++i) + { + FreePage(pTheme->rgPages + i); + } + + for (DWORD i = 0; i < pTheme->cImageLists; ++i) + { + FreeImageList(pTheme->rgImageLists + i); + } + + for (DWORD i = 0; i < pTheme->cControls; ++i) + { + FreeControl(pTheme->rgControls + i); + } + + ReleaseMem(pTheme->rgControls); + ReleaseMem(pTheme->rgPages); + ReleaseMem(pTheme->rgFonts); + + if (pTheme->hImage) + { + ::DeleteBitmap(pTheme->hImage); + } + + ReleaseStr(pTheme->sczCaption); + ReleaseMem(pTheme); + } +} + +DAPI_(HRESULT) ThemeRegisterVariableCallbacks( + __in THEME* pTheme, + __in_opt PFNTHM_EVALUATE_VARIABLE_CONDITION pfnEvaluateCondition, + __in_opt PFNTHM_FORMAT_VARIABLE_STRING pfnFormatString, + __in_opt PFNTHM_GET_VARIABLE_NUMERIC pfnGetNumericVariable, + __in_opt PFNTHM_SET_VARIABLE_NUMERIC pfnSetNumericVariable, + __in_opt PFNTHM_GET_VARIABLE_STRING pfnGetStringVariable, + __in_opt PFNTHM_SET_VARIABLE_STRING pfnSetStringVariable, + __in_opt LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + ThmExitOnNull(pTheme, hr, S_FALSE, "Theme must be loaded first."); + + pTheme->pfnEvaluateCondition = pfnEvaluateCondition; + pTheme->pfnFormatString = pfnFormatString; + pTheme->pfnGetNumericVariable = pfnGetNumericVariable; + pTheme->pfnSetNumericVariable = pfnSetNumericVariable; + pTheme->pfnGetStringVariable = pfnGetStringVariable; + pTheme->pfnSetStringVariable = pfnSetStringVariable; + pTheme->pvVariableContext = pvContext; + +LExit: + return hr; +} + + +DAPI_(HRESULT) ThemeCreateParentWindow( + __in THEME* pTheme, + __in DWORD dwExStyle, + __in LPCWSTR szClassName, + __in LPCWSTR szWindowName, + __in DWORD dwStyle, + __in int x, + __in int y, + __in_opt HWND hwndParent, + __in_opt HINSTANCE hInstance, + __in_opt LPVOID lpParam, + __in THEME_WINDOW_INITIAL_POSITION initialPosition, + __out_opt HWND* phWnd + ) +{ + HRESULT hr = S_OK; + DPIU_MONITOR_CONTEXT* pMonitorContext = NULL; + POINT pt = { }; + RECT* pMonitorRect = NULL; + HMENU hMenu = NULL; + HWND hWnd = NULL; + + if (pTheme->hwndParent) + { + ThmExitOnFailure(hr = E_INVALIDSTATE, "ThemeCreateParentWindow called after the theme was loaded."); + } + + if (THEME_WINDOW_INITIAL_POSITION_CENTER_MONITOR_FROM_COORDINATES == initialPosition) + { + pt.x = x; + pt.y = y; + hr = DpiuGetMonitorContextFromPoint(&pt, &pMonitorContext); + if (SUCCEEDED(hr)) + { + pMonitorRect = &pMonitorContext->mi.rcWork; + if (pMonitorContext->nDpi != pTheme->nDpi) + { + ScaleTheme(pTheme, pMonitorContext->nDpi, pMonitorRect->left, pMonitorRect->top, dwStyle, NULL != hMenu, dwExStyle); + } + + x = pMonitorRect->left + (pMonitorRect->right - pMonitorRect->left - pTheme->nWindowWidth) / 2; + y = pMonitorRect->top + (pMonitorRect->bottom - pMonitorRect->top - pTheme->nWindowHeight) / 2; + } + else + { + hr = S_OK; + x = CW_USEDEFAULT; + y = CW_USEDEFAULT; + } + } + + hWnd = ::CreateWindowExW(dwExStyle, szClassName, szWindowName, dwStyle, x, y, pTheme->nWindowWidth, pTheme->nWindowHeight, hwndParent, hMenu, hInstance, lpParam); + ThmExitOnNullWithLastError(hWnd, hr, "Failed to create theme parent window."); + ThmExitOnNull(pTheme->hwndParent, hr, E_INVALIDSTATE, "Theme parent window is not set, make sure ThemeDefWindowProc is called for WM_NCCREATE."); + AssertSz(hWnd == pTheme->hwndParent, "Theme parent window does not equal newly created window."); + + if (phWnd) + { + *phWnd = hWnd; + } + +LExit: + ReleaseMem(pMonitorContext); + + return hr; +} + + +DAPI_(HRESULT) ThemeLoadControls( + __in THEME* pTheme, + __in_ecount_opt(cAssignControlIds) const THEME_ASSIGN_CONTROL_ID* rgAssignControlIds, + __in DWORD cAssignControlIds + ) +{ + HRESULT hr = S_OK; + + if (!pTheme->hwndParent) + { + ThmExitOnFailure(hr = E_INVALIDSTATE, "ThemeLoadControls called before theme parent window created."); + } + + hr = LoadControls(pTheme, NULL, rgAssignControlIds, cAssignControlIds); + +LExit: + return hr; +} + + +DAPI_(void) ThemeUnloadControls( + __in THEME* pTheme + ) +{ + UnloadControls(pTheme->cControls, pTheme->rgControls); + + pTheme->hwndHover = NULL; + pTheme->hwndParent = NULL; +} + +DAPI_(HRESULT) ThemeLocalize( + __in THEME *pTheme, + __in const WIX_LOCALIZATION *pWixLoc + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCaption = NULL; + + hr = LocLocalizeString(pWixLoc, &pTheme->sczCaption); + ThmExitOnFailure(hr, "Failed to localize theme caption."); + + if (pTheme->pfnFormatString) + { + hr = pTheme->pfnFormatString(pTheme->sczCaption, &sczCaption, pTheme->pvVariableContext); + if (SUCCEEDED(hr)) + { + hr = ThemeUpdateCaption(pTheme, sczCaption); + } + } + + hr = LocalizeControls(pTheme->cControls, pTheme->rgControls, pWixLoc); + +LExit: + ReleaseStr(sczCaption); + + return hr; +} + +/******************************************************************** + ThemeLoadStrings - Loads string resources. + Must be called after loading a theme and before calling + ThemeLoadControls. +*******************************************************************/ +DAPI_(HRESULT) ThemeLoadStrings( + __in THEME* pTheme, + __in HMODULE hResModule + ) +{ + HRESULT hr = S_OK; + ThmExitOnNull(pTheme, hr, S_FALSE, "Theme must be loaded first."); + + if (UINT_MAX != pTheme->uStringId) + { + hr = ResReadString(hResModule, pTheme->uStringId, &pTheme->sczCaption); + ThmExitOnFailure(hr, "Failed to load theme caption."); + } + + hr = LoadControlsString(pTheme->cControls, pTheme->rgControls, hResModule); + +LExit: + return hr; +} + + +DAPI_(HRESULT) ThemeLoadRichEditFromFile( + __in THEME* pTheme, + __in DWORD dwControl, + __in_z LPCWSTR wzFileName, + __in HMODULE hModule + ) +{ + HRESULT hr = S_OK; + LPWSTR sczFile = NULL; + HANDLE hFile = INVALID_HANDLE_VALUE; + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + + hr = PathRelativeToModule(&sczFile, wzFileName, hModule); + ThmExitOnFailure(hr, "Failed to read resource data."); + + hFile = ::CreateFileW(sczFile, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (INVALID_HANDLE_VALUE == hFile) + { + ThmExitWithLastError(hr, "Failed to open RTF file."); + } + else + { + LONGLONG llRtfSize; + hr = FileSizeByHandle(hFile, &llRtfSize); + if (SUCCEEDED(hr)) + { + ::SendMessageW(hWnd, EM_EXLIMITTEXT, 0, static_cast(llRtfSize)); + } + + EDITSTREAM es = { }; + es.pfnCallback = RichEditStreamFromFileHandleCallback; + es.dwCookie = reinterpret_cast(hFile); + + ::SendMessageW(hWnd, EM_STREAMIN, SF_RTF, reinterpret_cast(&es)); + hr = es.dwError; + ThmExitOnFailure(hr, "Failed to update RTF stream."); + } + +LExit: + ReleaseStr(sczFile); + ReleaseFile(hFile); + + return hr; +} + + +DAPI_(HRESULT) ThemeLoadRichEditFromResource( + __in THEME* pTheme, + __in DWORD dwControl, + __in_z LPCSTR szResourceName, + __in HMODULE hModule + ) +{ + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + return ThemeLoadRichEditFromResourceToHWnd(hWnd, szResourceName, hModule); +} + +DAPI_(HRESULT) ThemeLoadRichEditFromResourceToHWnd( + __in HWND hWnd, + __in_z LPCSTR szResourceName, + __in HMODULE hModule + ) +{ + HRESULT hr = S_OK; + MEMBUFFER_FOR_RICHEDIT buffer = { }; + EDITSTREAM es = { }; + + hr = ResReadData(hModule, szResourceName, reinterpret_cast(&buffer.rgbData), &buffer.cbData); + ThmExitOnFailure(hr, "Failed to read resource data."); + + es.pfnCallback = RichEditStreamFromMemoryCallback; + es.dwCookie = reinterpret_cast(&buffer); + + ::SendMessageW(hWnd, EM_STREAMIN, SF_RTF, reinterpret_cast(&es)); + hr = es.dwError; + ThmExitOnFailure(hr, "Failed to update RTF stream."); + +LExit: + return hr; +} + + +DAPI_(BOOL) ThemeHandleKeyboardMessage( + __in_opt THEME* pTheme, + __in HWND /*hWnd*/, + __in MSG* pMsg + ) +{ + return pTheme ? ::IsDialogMessageW(pTheme->hwndParent, pMsg) : FALSE; +} + + +extern "C" LRESULT CALLBACK ThemeDefWindowProc( + __in_opt THEME* pTheme, + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ) +{ + RECT rcParent = { }; + RECT *pRect = NULL; + + if (pTheme) + { + switch (uMsg) + { + case WM_NCCREATE: + if (pTheme->hwndParent) + { + AssertSz(FALSE, "WM_NCCREATE called multiple times"); + } + else + { + OnNcCreate(pTheme, hWnd, lParam); + } + break; + + case WM_NCHITTEST: + if (pTheme->dwStyle & WS_POPUP) + { + return HTCAPTION; // allow pop-up windows to be moved by grabbing any non-control. + } + break; + + case WM_DPICHANGED: + if (OnDpiChanged(pTheme, wParam, lParam)) + { + return 0; + } + break; + + case WM_SIZING: + if (pTheme->fAutoResize) + { + pRect = reinterpret_cast(lParam); + if (pRect->right - pRect->left < pTheme->nMinimumWidth) + { + if (wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT) + { + pRect->left = pRect->right - pTheme->nMinimumWidth; + } + else + { + pRect->right = pRect->left + pTheme->nMinimumWidth; + } + } + if (pRect->bottom - pRect->top < pTheme->nMinimumHeight) + { + if (wParam == WMSZ_BOTTOM || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOMRIGHT) + { + pRect->bottom = pRect->top + pTheme->nMinimumHeight; + } + else + { + pRect->top = pRect->bottom - pTheme->nMinimumHeight; + } + } + + return TRUE; + } + break; + + case WM_SIZE: + if (pTheme->fAutoResize || pTheme->fForceResize) + { + pTheme->fForceResize = FALSE; + ::GetClientRect(pTheme->hwndParent, &rcParent); + ResizeControls(pTheme->cControls, pTheme->rgControls, &rcParent); + return 0; + } + break; + } + } + + return ControlGroupDefWindowProc(pTheme, hWnd, uMsg, wParam, lParam); +} + + +DAPI_(void) ThemeGetPageIds( + __in const THEME* pTheme, + __in_ecount(cGetPages) LPCWSTR* rgwzFindNames, + __inout_ecount(cGetPages) DWORD* rgdwPageIds, + __in DWORD cGetPages + ) +{ + for (DWORD i = 0; i < cGetPages; ++i) + { + LPCWSTR wzFindName = rgwzFindNames[i]; + for (DWORD j = 0; j < pTheme->cPages; ++j) + { + LPCWSTR wzPageName = pTheme->rgPages[j].sczName; + if (wzPageName && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPageName, -1, wzFindName, -1)) + { + rgdwPageIds[i] = j + 1; // add one to make the page ids 1-based (so zero is invalid). + break; + } + } + } +} + + +DAPI_(THEME_PAGE*) ThemeGetPage( + __in const THEME* pTheme, + __in DWORD dwPage + ) +{ + DWORD iPage = dwPage - 1; + THEME_PAGE* pPage = NULL; + + if (iPage < pTheme->cPages) + { + pPage = pTheme->rgPages + iPage; + } + + return pPage; +} + + +DAPI_(HRESULT) ThemeShowPage( + __in THEME* pTheme, + __in DWORD dwPage, + __in int nCmdShow + ) +{ + return ThemeShowPageEx(pTheme, dwPage, nCmdShow, THEME_SHOW_PAGE_REASON_DEFAULT); +} + + +DAPI_(HRESULT) ThemeShowPageEx( + __in THEME* pTheme, + __in DWORD dwPage, + __in int nCmdShow, + __in THEME_SHOW_PAGE_REASON reason + ) +{ + HRESULT hr = S_OK; + BOOL fHide = SW_HIDE == nCmdShow; + BOOL fSaveEditboxes = FALSE; + THEME_SAVEDVARIABLE* pSavedVariable = NULL; + THEME_PAGE* pPage = ThemeGetPage(pTheme, dwPage); + + if (pPage) + { + if (fHide) + { + switch (reason) + { + case THEME_SHOW_PAGE_REASON_DEFAULT: + // Set the variables in the loop below. + fSaveEditboxes = TRUE; + break; + case THEME_SHOW_PAGE_REASON_CANCEL: + if (pPage->cSavedVariables && pTheme->pfnSetStringVariable) + { + // Best effort to cancel any changes to the variables. + for (DWORD v = 0; v < pPage->cSavedVariables; ++v) + { + pSavedVariable = pPage->rgSavedVariables + v; + if (pSavedVariable->wzName) + { + pTheme->pfnSetStringVariable(pSavedVariable->wzName, pSavedVariable->sczValue, FALSE, pTheme->pvVariableContext); + } + } + } + break; + } + + if (THEME_SHOW_PAGE_REASON_REFRESH != reason) + { + pPage->cSavedVariables = 0; + if (pPage->rgSavedVariables) + { + SecureZeroMemory(pPage->rgSavedVariables, MemSize(pPage->rgSavedVariables)); + } + } + + pTheme->dwCurrentPageId = 0; + } + else + { + if (THEME_SHOW_PAGE_REASON_REFRESH == reason) + { + fSaveEditboxes = TRUE; + } + else + { + hr = MemEnsureArraySize(reinterpret_cast(&pPage->rgSavedVariables), pPage->cControlIndices, sizeof(THEME_SAVEDVARIABLE), pPage->cControlIndices); + ThmExitOnFailure(hr, "Failed to allocate memory for saved variables."); + + SecureZeroMemory(pPage->rgSavedVariables, MemSize(pPage->rgSavedVariables)); + pPage->cSavedVariables = pPage->cControlIndices; + + // Save the variables in the loop below. + } + + pTheme->dwCurrentPageId = dwPage; + } + } + + hr = ShowControls(pTheme, NULL, nCmdShow, fSaveEditboxes, reason, dwPage); + ThmExitOnFailure(hr, "Failed to show page controls."); + +LExit: + return hr; +} + + +DAPI_(BOOL) ThemeControlExists( + __in const THEME* pTheme, + __in DWORD dwControl + ) +{ + BOOL fExists = FALSE; + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + if (hWnd) + { + const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd); + fExists = (pControl && hWnd == pControl->hWnd); + } + + return fExists; +} + + +DAPI_(void) ThemeControlEnable( + __in THEME* pTheme, + __in DWORD dwControl, + __in BOOL fEnable + ) +{ + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + THEME_CONTROL* pControl = const_cast(FindControlFromHWnd(pTheme, hWnd)); + if (pControl) + { + pControl->dwInternalStyle = fEnable ? (pControl->dwInternalStyle & ~INTERNAL_CONTROL_STYLE_DISABLED) : (pControl->dwInternalStyle | INTERNAL_CONTROL_STYLE_DISABLED); + ::EnableWindow(hWnd, fEnable); + + if (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED) + { + ::ShowWindow(hWnd, fEnable ? SW_SHOW : SW_HIDE); + } + } +} + + +DAPI_(BOOL) ThemeControlEnabled( + __in THEME* pTheme, + __in DWORD dwControl + ) +{ + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd); + return pControl && !(pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_DISABLED); +} + + +DAPI_(void) ThemeControlElevates( + __in THEME* pTheme, + __in DWORD dwControl, + __in BOOL fElevates + ) +{ + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + ::SendMessageW(hWnd, BCM_SETSHIELD, 0, fElevates); +} + + +DAPI_(void) ThemeShowControl( + __in THEME* pTheme, + __in DWORD dwControl, + __in int nCmdShow + ) +{ + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + ::ShowWindow(hWnd, nCmdShow); + + // Save the control's visible state. + THEME_CONTROL* pControl = const_cast(FindControlFromHWnd(pTheme, hWnd)); + if (pControl) + { + pControl->dwInternalStyle = (SW_HIDE == nCmdShow) ? (pControl->dwInternalStyle | INTERNAL_CONTROL_STYLE_HIDDEN) : (pControl->dwInternalStyle & ~INTERNAL_CONTROL_STYLE_HIDDEN); + } +} + + +DAPI_(void) ThemeShowControlEx( + __in THEME* pTheme, + __in DWORD dwControl, + __in int nCmdShow + ) +{ + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + THEME_CONTROL* pControl = const_cast(FindControlFromHWnd(pTheme, hWnd)); + if (pControl) + { + ShowControl(pTheme, pControl, nCmdShow, THEME_CONTROL_TYPE_EDITBOX == pControl->type, THEME_SHOW_PAGE_REASON_REFRESH, 0, NULL); + } +} + + +DAPI_(BOOL) ThemeControlVisible( + __in THEME* pTheme, + __in DWORD dwControl + ) +{ + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + return ::IsWindowVisible(hWnd); +} + + +DAPI_(BOOL) ThemePostControlMessage( + __in THEME* pTheme, + __in DWORD dwControl, + __in UINT Msg, + __in WPARAM wParam, + __in LPARAM lParam + ) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + + if (!::PostMessageW(hWnd, Msg, wParam, lParam)) + { + er = ::GetLastError(); + hr = HRESULT_FROM_WIN32(er); + } + + return SUCCEEDED(hr); +} + + +DAPI_(LRESULT) ThemeSendControlMessage( + __in const THEME* pTheme, + __in DWORD dwControl, + __in UINT Msg, + __in WPARAM wParam, + __in LPARAM lParam + ) +{ + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + return ::SendMessageW(hWnd, Msg, wParam, lParam); +} + + +DAPI_(HRESULT) ThemeDrawBackground( + __in THEME* pTheme, + __in PAINTSTRUCT* pps + ) +{ + HRESULT hr = S_FALSE; + + if (pTheme->hImage && 0 <= pTheme->nSourceX && 0 <= pTheme->nSourceY && pps->fErase) + { + HDC hdcMem = ::CreateCompatibleDC(pps->hdc); + HBITMAP hDefaultBitmap = static_cast(::SelectObject(hdcMem, pTheme->hImage)); + DWORD dwSourceWidth = pTheme->nDefaultDpiWidth; + DWORD dwSourceHeight = pTheme->nDefaultDpiHeight; + + ::StretchBlt(pps->hdc, 0, 0, pTheme->nWidth, pTheme->nHeight, hdcMem, pTheme->nSourceX, pTheme->nSourceY, dwSourceWidth, dwSourceHeight, SRCCOPY); + + ::SelectObject(hdcMem, hDefaultBitmap); + ::DeleteDC(hdcMem); + + hr = S_OK; + } + + return hr; +} + + +DAPI_(HRESULT) ThemeDrawControl( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis + ) +{ + HRESULT hr = S_OK; + const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, pdis->hwndItem); + + AssertSz(pControl, "Expected control window from owner draw window."); + AssertSz(pControl->hWnd == pdis->hwndItem, "Expected control window to match owner draw window."); + AssertSz(pControl->nWidth < 1 || pControl->nWidth == pdis->rcItem.right - pdis->rcItem.left, "Expected control window width to match owner draw window width."); + AssertSz(pControl->nHeight < 1 || pControl->nHeight == pdis->rcItem.bottom - pdis->rcItem.top, "Expected control window height to match owner draw window height."); + + switch (pControl->type) + { + case THEME_CONTROL_TYPE_BUTTON: + hr = DrawButton(pTheme, pdis, pControl); + ThmExitOnFailure(hr, "Failed to draw button."); + break; + + case THEME_CONTROL_TYPE_HYPERLINK: + hr = DrawHyperlink(pTheme, pdis, pControl); + ThmExitOnFailure(hr, "Failed to draw hyperlink."); + break; + + case THEME_CONTROL_TYPE_IMAGE: + hr = DrawImage(pTheme, pdis, pControl); + ThmExitOnFailure(hr, "Failed to draw image."); + break; + + case THEME_CONTROL_TYPE_PROGRESSBAR: + hr = DrawProgressBar(pTheme, pdis, pControl); + ThmExitOnFailure(hr, "Failed to draw progress bar."); + break; + + default: + hr = E_UNEXPECTED; + ThmExitOnRootFailure(hr, "Did not specify an owner draw control to draw."); + } + +LExit: + return hr; +} + + +DAPI_(BOOL) ThemeHoverControl( + __in THEME* pTheme, + __in HWND hwndParent, + __in HWND hwndControl + ) +{ + BOOL fHovered = FALSE; + if (hwndControl != pTheme->hwndHover) + { + if (pTheme->hwndHover && pTheme->hwndHover != hwndParent) + { + DrawHoverControl(pTheme, FALSE); + } + + pTheme->hwndHover = hwndControl; + + if (pTheme->hwndHover && pTheme->hwndHover != hwndParent) + { + fHovered = DrawHoverControl(pTheme, TRUE); + } + } + + return fHovered; +} + + +DAPI_(BOOL) ThemeIsControlChecked( + __in THEME* pTheme, + __in DWORD dwControl + ) +{ + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + return BST_CHECKED == ::SendMessageW(hWnd, BM_GETCHECK, 0, 0); +} + + +DAPI_(BOOL) ThemeSetControlColor( + __in THEME* pTheme, + __in HDC hdc, + __in HWND hWnd, + __out HBRUSH* phBackgroundBrush + ) +{ + THEME_FONT* pFont = NULL; + BOOL fHasBackground = FALSE; + + *phBackgroundBrush = NULL; + + if (hWnd == pTheme->hwndParent) + { + pFont = (THEME_INVALID_ID == pTheme->dwFontId) ? NULL : pTheme->rgFonts + pTheme->dwFontId; + } + else + { + const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd); + pFont = (!pControl || THEME_INVALID_ID == pControl->dwFontId) ? NULL : pTheme->rgFonts + pControl->dwFontId; + } + + if (pFont) + { + if (pFont->hForeground) + { + ::SetTextColor(hdc, pFont->crForeground); + } + + if (pFont->hBackground) + { + ::SetBkColor(hdc, pFont->crBackground); + + *phBackgroundBrush = pFont->hBackground; + fHasBackground = TRUE; + } + else + { + ::SetBkMode(hdc, TRANSPARENT); + *phBackgroundBrush = static_cast(::GetStockObject(NULL_BRUSH)); + fHasBackground = TRUE; + } + } + + return fHasBackground; +} + + +DAPI_(HRESULT) ThemeSetProgressControl( + __in THEME* pTheme, + __in DWORD dwControl, + __in DWORD dwProgressPercentage + ) +{ + HRESULT hr = E_NOTFOUND; + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + + if (hWnd) + { + THEME_CONTROL* pControl = const_cast(FindControlFromHWnd(pTheme, hWnd)); + if (pControl && THEME_CONTROL_TYPE_PROGRESSBAR == pControl->type) + { + DWORD dwCurrentProgress = LOWORD(pControl->dwData); + + if (dwCurrentProgress != dwProgressPercentage) + { + DWORD dwColor = HIWORD(pControl->dwData); + pControl->dwData = MAKEDWORD(dwProgressPercentage, dwColor); + + if (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_OWNER_DRAW) + { + if (!::InvalidateRect(hWnd, NULL, FALSE)) + { + ThmExitWithLastError(hr, "Failed to invalidate progress bar window."); + } + } + else + { + ::SendMessageW(hWnd, PBM_SETPOS, dwProgressPercentage, 0); + } + + hr = S_OK; + } + else + { + hr = S_FALSE; + } + } + } + +LExit: + return hr; +} + + +DAPI_(HRESULT) ThemeSetProgressControlColor( + __in THEME* pTheme, + __in DWORD dwControl, + __in DWORD dwColorIndex + ) +{ + HRESULT hr = S_FALSE; + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + if (hWnd) + { + THEME_CONTROL* pControl = const_cast(FindControlFromHWnd(pTheme, hWnd)); + + // Only set color on owner draw progress bars. + if (pControl && (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_OWNER_DRAW)) + { + DWORD dwCurrentColor = HIWORD(pControl->dwData); + + if (dwCurrentColor != dwColorIndex) + { + DWORD dwCurrentProgress = LOWORD(pControl->dwData); + pControl->dwData = MAKEDWORD(dwCurrentProgress, dwColorIndex); + + if (!::InvalidateRect(hWnd, NULL, FALSE)) + { + ThmExitWithLastError(hr, "Failed to invalidate progress bar window."); + } + + hr = S_OK; + } + } + } + +LExit: + return hr; +} + + +DAPI_(HRESULT) ThemeSetTextControl( + __in const THEME* pTheme, + __in DWORD dwControl, + __in_z_opt LPCWSTR wzText + ) +{ + return ThemeSetTextControlEx(pTheme, dwControl, FALSE, wzText); +} + + +DAPI_(HRESULT) ThemeSetTextControlEx( + __in const THEME* pTheme, + __in DWORD dwControl, + __in BOOL fUpdate, + __in_z_opt LPCWSTR wzText + ) +{ + HRESULT hr = S_OK; + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + + if (hWnd) + { + if (fUpdate) + { + ::ShowWindow(hWnd, SW_HIDE); + } + + if (!::SetWindowTextW(hWnd, wzText)) + { + ThmExitWithLastError(hr, "Failed to set control text."); + } + + if (fUpdate) + { + ::ShowWindow(hWnd, SW_SHOW); + } + } + +LExit: + return hr; +} + + +DAPI_(HRESULT) ThemeGetTextControl( + __in const THEME* pTheme, + __in DWORD dwControl, + __inout_z LPWSTR* psczText + ) +{ + HRESULT hr = S_OK; + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + SIZE_T cbSize = 0; + DWORD cchText = 0; + DWORD cchTextRead = 0; + + // Ensure the string has room for at least one character. + hr = StrMaxLength(*psczText, &cbSize); + ThmExitOnFailure(hr, "Failed to get text buffer length."); + + cchText = (DWORD)min(DWORD_MAX, cbSize); + + if (!cchText) + { + cchText = GROW_WINDOW_TEXT; + + hr = StrAlloc(psczText, cchText); + ThmExitOnFailure(hr, "Failed to grow text buffer."); + } + + // Read (and keep growing buffer) until we finally read less than there + // is room in the buffer. + for (;;) + { + cchTextRead = ::GetWindowTextW(hWnd, *psczText, cchText); + if (cchTextRead + 1 < cchText) + { + break; + } + else + { + cchText = cchTextRead + GROW_WINDOW_TEXT; + + hr = StrAlloc(psczText, cchText); + ThmExitOnFailure(hr, "Failed to grow text buffer again."); + } + } + +LExit: + return hr; +} + + +DAPI_(HRESULT) ThemeUpdateCaption( + __in THEME* pTheme, + __in_z LPCWSTR wzCaption + ) +{ + HRESULT hr = S_OK; + + hr = StrAllocString(&pTheme->sczCaption, wzCaption, 0); + ThmExitOnFailure(hr, "Failed to update theme caption."); + +LExit: + return hr; +} + + +DAPI_(void) ThemeSetFocus( + __in THEME* pTheme, + __in DWORD dwControl + ) +{ + HWND hwndFocus = ::GetDlgItem(pTheme->hwndParent, dwControl); + if (hwndFocus && !ThemeControlEnabled(pTheme, dwControl)) + { + hwndFocus = ::GetNextDlgTabItem(pTheme->hwndParent, hwndFocus, FALSE); + } + + if (hwndFocus) + { + ::SetFocus(hwndFocus); + } +} + + +DAPI_(void) ThemeShowChild( + __in THEME* pTheme, + __in THEME_CONTROL* pParentControl, + __in DWORD dwIndex + ) +{ + // show one child, hide the rest + for (DWORD i = 0; i < pParentControl->cControls; ++i) + { + THEME_CONTROL* pControl = pParentControl->rgControls + i; + ShowControl(pTheme, pControl, dwIndex == i ? SW_SHOW : SW_HIDE, FALSE, THEME_SHOW_PAGE_REASON_DEFAULT, 0, NULL); + } +} + + +// Internal functions. + +static HRESULT RegisterWindowClasses( + __in_opt HMODULE hModule + ) +{ + HRESULT hr = S_OK; + WNDCLASSW wcHyperlink = { }; + WNDCLASSW wcPanel = { }; + WNDCLASSW wcStaticOwnerDraw = { }; + WNDPROC pfnStaticOwnerDrawBaseWndProc = NULL; + + vhCursorHand = ::LoadCursorA(NULL, IDC_HAND); + + // Base the theme hyperlink class on a button but give it the "hand" icon. + if (!::GetClassInfoW(NULL, WC_BUTTONW, &wcHyperlink)) + { + ThmExitWithLastError(hr, "Failed to get button window class."); + } + + wcHyperlink.lpszClassName = THEME_WC_HYPERLINK; +#pragma prefast(push) +#pragma prefast(disable:25068) + wcHyperlink.hCursor = vhCursorHand; +#pragma prefast(pop) + + if (!::RegisterClassW(&wcHyperlink)) + { + ThmExitWithLastError(hr, "Failed to get button window class."); + } + vhHyperlinkRegisteredModule = hModule; + + // Panel is its own do-nothing class. + wcPanel.lpfnWndProc = PanelWndProc; + wcPanel.hInstance = hModule; + wcPanel.hCursor = ::LoadCursorW(NULL, (LPCWSTR) IDC_ARROW); + wcPanel.lpszClassName = THEME_WC_PANEL; + if (!::RegisterClassW(&wcPanel)) + { + ThmExitWithLastError(hr, "Failed to register window."); + } + vhPanelRegisteredModule = hModule; + + if (!::GetClassInfoW(NULL, WC_STATICW, &wcStaticOwnerDraw)) + { + ThmExitWithLastError(hr, "Failed to get static window class."); + } + + pfnStaticOwnerDrawBaseWndProc = wcStaticOwnerDraw.lpfnWndProc; + wcStaticOwnerDraw.lpfnWndProc = StaticOwnerDrawWndProc; + wcStaticOwnerDraw.hInstance = hModule; + wcStaticOwnerDraw.lpszClassName = THEME_WC_STATICOWNERDRAW; + if (!::RegisterClassW(&wcStaticOwnerDraw)) + { + ThmExitWithLastError(hr, "Failed to register OwnerDraw window class."); + } + vhStaticOwnerDrawRegisteredModule = hModule; + vpfnStaticOwnerDrawBaseWndProc = pfnStaticOwnerDrawBaseWndProc; + + +LExit: + return hr; +} + +static HRESULT ParseTheme( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMDocument* pixd, + __out THEME** ppTheme + ) +{ + static WORD wThemeId = 0; + + HRESULT hr = S_OK; + THEME* pTheme = NULL; + IXMLDOMElement *pThemeElement = NULL; + + hr = pixd->get_documentElement(&pThemeElement); + ThmExitOnFailure(hr, "Failed to get theme element."); + + pTheme = static_cast(MemAlloc(sizeof(THEME), TRUE)); + ThmExitOnNull(pTheme, hr, E_OUTOFMEMORY, "Failed to allocate memory for theme."); + + pTheme->wId = ++wThemeId; + pTheme->nDpi = USER_DEFAULT_SCREEN_DPI; + + // Parse the optional background resource image. + hr = ParseImage(hModule, wzRelativePath, pThemeElement, &pTheme->hImage); + ThmExitOnFailure(hr, "Failed while parsing theme image."); + + // Parse the fonts. + hr = ParseFonts(pThemeElement, pTheme); + ThmExitOnFailure(hr, "Failed to parse theme fonts."); + + // Parse the window element. + hr = ParseWindow(hModule, wzRelativePath, pThemeElement, pTheme); + ThmExitOnFailure(hr, "Failed to parse theme window element."); + + *ppTheme = pTheme; + pTheme = NULL; + +LExit: + ReleaseObject(pThemeElement); + + if (pTheme) + { + ThemeFree(pTheme); + } + + return hr; +} + +static HRESULT ParseImage( + __in_opt HMODULE hModule, + __in_z_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pElement, + __out HBITMAP* phImage + ) +{ + HRESULT hr = S_OK; + BSTR bstr = NULL; + LPWSTR sczImageFile = NULL; + int iResourceId = 0; + Gdiplus::Bitmap* pBitmap = NULL; + *phImage = NULL; + + hr = XmlGetAttribute(pElement, L"ImageResource", &bstr); + ThmExitOnFailure(hr, "Failed to get image resource attribute."); + + if (S_OK == hr) + { + iResourceId = wcstol(bstr, NULL, 10); + + hr = GdipBitmapFromResource(hModule, MAKEINTRESOURCE(iResourceId), &pBitmap); + // Don't fail. + } + + ReleaseNullBSTR(bstr); + + // Parse the optional background image from a given file. + if (!pBitmap) + { + hr = XmlGetAttribute(pElement, L"ImageFile", &bstr); + ThmExitOnFailure(hr, "Failed to get image file attribute."); + + if (S_OK == hr) + { + if (wzRelativePath) + { + hr = PathConcat(wzRelativePath, bstr, &sczImageFile); + ThmExitOnFailure(hr, "Failed to combine image file path."); + } + else + { + hr = PathRelativeToModule(&sczImageFile, bstr, hModule); + ThmExitOnFailure(hr, "Failed to get image filename."); + } + + hr = GdipBitmapFromFile(sczImageFile, &pBitmap); + // Don't fail. + } + } + + // If there is an image, convert it into a bitmap handle. + if (pBitmap) + { + Gdiplus::Color black; + Gdiplus::Status gs = pBitmap->GetHBITMAP(black, phImage); + ThmExitOnGdipFailure(gs, hr, "Failed to convert GDI+ bitmap into HBITMAP."); + } + + hr = S_OK; + +LExit: + if (pBitmap) + { + delete pBitmap; + } + + ReleaseStr(sczImageFile); + ReleaseBSTR(bstr); + + return hr; +} + + +static HRESULT ParseIcon( + __in_opt HMODULE hModule, + __in_z_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pElement, + __out HICON* phIcon + ) +{ + HRESULT hr = S_OK; + BSTR bstr = NULL; + LPWSTR sczImageFile = NULL; + int iResourceId = 0; + *phIcon = NULL; + + hr = XmlGetAttribute(pElement, L"IconResource", &bstr); + ThmExitOnFailure(hr, "Failed to get icon resource attribute."); + + if (S_OK == hr) + { + iResourceId = wcstol(bstr, NULL, 10); + + *phIcon = reinterpret_cast(::LoadImageW(hModule, MAKEINTRESOURCEW(iResourceId), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE)); + ThmExitOnNullWithLastError(*phIcon, hr, "Failed to load icon."); + } + else + { + ReleaseNullBSTR(bstr); + + hr = XmlGetAttribute(pElement, L"IconFile", &bstr); + ThmExitOnFailure(hr, "Failed to get icon file attribute."); + + if (S_OK == hr) + { + if (wzRelativePath) + { + hr = PathConcat(wzRelativePath, bstr, &sczImageFile); + ThmExitOnFailure(hr, "Failed to combine image file path."); + } + else + { + hr = PathRelativeToModule(&sczImageFile, bstr, hModule); + ThmExitOnFailure(hr, "Failed to get image filename."); + } + + *phIcon = reinterpret_cast(::LoadImageW(NULL, sczImageFile, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE)); + ThmExitOnNullWithLastError(*phIcon, hr, "Failed to load icon: %ls.", sczImageFile); + } + } + +LExit: + ReleaseStr(sczImageFile); + ReleaseBSTR(bstr); + + return hr; +} + + +static HRESULT ParseWindow( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMElement* pElement, + __in THEME* pTheme + ) +{ + HRESULT hr = S_OK; + IXMLDOMNode* pixn = NULL; + DWORD dwValue = 0; + BSTR bstr = NULL; + LPWSTR sczIconFile = NULL; + + hr = XmlSelectSingleNode(pElement, L"Window", &pixn); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + ThmExitOnFailure(hr, "Failed to find window element."); + + hr = XmlGetYesNoAttribute(pixn, L"AutoResize", &pTheme->fAutoResize); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed to get window AutoResize attribute."); + + hr = XmlGetAttributeNumber(pixn, L"Width", &dwValue); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ThmExitOnRootFailure(hr, "Failed to find window Width attribute."); + } + ThmExitOnFailure(hr, "Failed to get window Width attribute."); + + pTheme->nWidth = pTheme->nDefaultDpiWidth = pTheme->nWindowWidth = dwValue; + + hr = XmlGetAttributeNumber(pixn, L"Height", &dwValue); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ThmExitOnRootFailure(hr, "Failed to find window Height attribute."); + } + ThmExitOnFailure(hr, "Failed to get window Height attribute."); + + pTheme->nHeight = pTheme->nDefaultDpiHeight = pTheme->nWindowHeight = dwValue; + + hr = XmlGetAttributeNumber(pixn, L"MinimumWidth", &dwValue); + if (S_FALSE == hr) + { + dwValue = 0; + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed to get window MinimumWidth attribute."); + + pTheme->nMinimumWidth = pTheme->nDefaultDpiMinimumWidth = dwValue; + + hr = XmlGetAttributeNumber(pixn, L"MinimumHeight", &dwValue); + if (S_FALSE == hr) + { + dwValue = 0; + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed to get window MinimumHeight attribute."); + + pTheme->nMinimumHeight = pTheme->nDefaultDpiMinimumHeight = dwValue; + + hr = XmlGetAttributeNumber(pixn, L"FontId", &pTheme->dwFontId); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ThmExitOnRootFailure(hr, "Failed to find window FontId attribute."); + } + ThmExitOnFailure(hr, "Failed to get window FontId attribute."); + + // Get the optional window icon from a resource. + hr = XmlGetAttribute(pixn, L"IconResource", &bstr); + ThmExitOnFailure(hr, "Failed to get window IconResource attribute."); + + if (S_OK == hr) + { + pTheme->hIcon = ::LoadIconW(hModule, bstr); + ThmExitOnNullWithLastError(pTheme->hIcon, hr, "Failed to load window icon from IconResource."); + + ReleaseNullBSTR(bstr); + } + + // Get the optional window icon from a file. + hr = XmlGetAttribute(pixn, L"IconFile", &bstr); + ThmExitOnFailure(hr, "Failed to get window IconFile attribute."); + + if (S_OK == hr) + { + if (wzRelativePath) + { + hr = PathConcat(wzRelativePath, bstr, &sczIconFile); + ThmExitOnFailure(hr, "Failed to combine icon file path."); + } + else + { + hr = PathRelativeToModule(&sczIconFile, bstr, hModule); + ThmExitOnFailure(hr, "Failed to get icon filename."); + } + + pTheme->hIcon = ::LoadImageW(NULL, sczIconFile, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE); + ThmExitOnNullWithLastError(pTheme->hIcon, hr, "Failed to load window icon from IconFile: %ls.", bstr); + + ReleaseNullBSTR(bstr); + } + + hr = XmlGetAttributeNumber(pixn, L"SourceX", reinterpret_cast(&pTheme->nSourceX)); + if (S_FALSE == hr) + { + pTheme->nSourceX = -1; + } + ThmExitOnFailure(hr, "Failed to get window SourceX attribute."); + + hr = XmlGetAttributeNumber(pixn, L"SourceY", reinterpret_cast(&pTheme->nSourceY)); + if (S_FALSE == hr) + { + pTheme->nSourceY = -1; + } + ThmExitOnFailure(hr, "Failed to get window SourceY attribute."); + + // Parse the optional window style. + hr = XmlGetAttributeNumberBase(pixn, L"HexStyle", 16, &pTheme->dwStyle); + ThmExitOnFailure(hr, "Failed to get theme window style (Window@HexStyle) attribute."); + + if (S_FALSE == hr) + { + pTheme->dwStyle = WS_VISIBLE | WS_MINIMIZEBOX | WS_SYSMENU; + pTheme->dwStyle |= (0 <= pTheme->nSourceX && 0 <= pTheme->nSourceY) ? WS_POPUP : WS_OVERLAPPED; + } + + hr = XmlGetAttributeNumber(pixn, L"StringId", reinterpret_cast(&pTheme->uStringId)); + ThmExitOnFailure(hr, "Failed to get window StringId attribute."); + + if (S_FALSE == hr) + { + pTheme->uStringId = UINT_MAX; + + hr = XmlGetAttribute(pixn, L"Caption", &bstr); + ThmExitOnFailure(hr, "Failed to get window Caption attribute."); + + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ThmExitOnRootFailure(hr, "Window elements must contain the Caption or StringId attribute."); + } + + hr = StrAllocString(&pTheme->sczCaption, bstr, 0); + ThmExitOnFailure(hr, "Failed to copy window Caption attribute."); + } + + // Parse any image lists. + hr = ParseImageLists(hModule, wzRelativePath, pixn, pTheme); + ThmExitOnFailure(hr, "Failed to parse image lists."); + + // Parse the pages. + hr = ParsePages(hModule, wzRelativePath, pixn, pTheme); + ThmExitOnFailure(hr, "Failed to parse theme pages."); + + // Parse the non-paged controls. + hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, NULL, NULL); + ThmExitOnFailure(hr, "Failed to parse theme controls."); + +LExit: + ReleaseStr(sczIconFile); + ReleaseBSTR(bstr); + ReleaseObject(pixn); + + return hr; +} + + +static HRESULT ParseFonts( + __in IXMLDOMElement* pElement, + __in THEME* pTheme + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnl = NULL; + IXMLDOMNode* pixn = NULL; + BSTR bstrName = NULL; + DWORD dwId = 0; + COLORREF crForeground = THEME_INVISIBLE_COLORREF; + COLORREF crBackground = THEME_INVISIBLE_COLORREF; + DWORD dwSystemForegroundColor = FALSE; + DWORD dwSystemBackgroundColor = FALSE; + + hr = XmlSelectNodes(pElement, L"Font", &pixnl); + ThmExitOnFailure(hr, "Failed to find font elements."); + + hr = pixnl->get_length(reinterpret_cast(&pTheme->cFonts)); + ThmExitOnFailure(hr, "Failed to count the number of theme fonts."); + + if (!pTheme->cFonts) + { + ExitFunction1(hr = S_OK); + } + + pTheme->rgFonts = static_cast(MemAlloc(sizeof(THEME_FONT) * pTheme->cFonts, TRUE)); + ThmExitOnNull(pTheme->rgFonts, hr, E_OUTOFMEMORY, "Failed to allocate theme fonts."); + + while (S_OK == (hr = XmlNextElement(pixnl, &pixn, NULL))) + { + hr = XmlGetAttributeNumber(pixn, L"Id", &dwId); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + ThmExitOnFailure(hr, "Failed to find font id."); + + if (pTheme->cFonts <= dwId) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ThmExitOnRootFailure(hr, "Invalid theme font id."); + } + + THEME_FONT* pFont = pTheme->rgFonts + dwId; + if (pFont->cFontInstances) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ThmExitOnRootFailure(hr, "Theme font id duplicated."); + } + + pFont->lfQuality = CLEARTYPE_QUALITY; + + hr = XmlGetText(pixn, &bstrName); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + ThmExitOnFailure(hr, "Failed to get font name."); + + hr = StrAllocString(&pFont->sczFaceName, bstrName, 0); + ThmExitOnFailure(hr, "Failed to copy font name."); + + hr = XmlGetAttributeNumber(pixn, L"Height", reinterpret_cast(&pFont->lfHeight)); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + ThmExitOnFailure(hr, "Failed to find font height attribute."); + + hr = XmlGetAttributeNumber(pixn, L"Weight", reinterpret_cast(&pFont->lfWeight)); + if (S_FALSE == hr) + { + pFont->lfWeight = FW_DONTCARE; + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed to find font weight attribute."); + + hr = XmlGetYesNoAttribute(pixn, L"Underline", reinterpret_cast(&pFont->lfUnderline)); + if (E_NOTFOUND == hr) + { + pFont->lfUnderline = FALSE; + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed to find font underline attribute."); + + hr = GetFontColor(pixn, L"Foreground", &crForeground, &dwSystemForegroundColor); + ThmExitOnFailure(hr, "Failed to find font foreground color."); + + hr = GetFontColor(pixn, L"Background", &crBackground, &dwSystemBackgroundColor); + ThmExitOnFailure(hr, "Failed to find font background color."); + + pFont->crForeground = crForeground; + if (THEME_INVISIBLE_COLORREF != pFont->crForeground) + { + pFont->hForeground = dwSystemForegroundColor ? ::GetSysColorBrush(dwSystemForegroundColor) : ::CreateSolidBrush(pFont->crForeground); + ThmExitOnNull(pFont->hForeground, hr, E_OUTOFMEMORY, "Failed to create text foreground brush."); + } + + pFont->crBackground = crBackground; + if (THEME_INVISIBLE_COLORREF != pFont->crBackground) + { + pFont->hBackground = dwSystemBackgroundColor ? ::GetSysColorBrush(dwSystemBackgroundColor) : ::CreateSolidBrush(pFont->crBackground); + ThmExitOnNull(pFont->hBackground, hr, E_OUTOFMEMORY, "Failed to create text background brush."); + } + + ReleaseNullBSTR(bstrName); + ReleaseNullObject(pixn); + } + ThmExitOnFailure(hr, "Failed to enumerate all fonts."); + + if (S_FALSE == hr) + { + hr = S_OK; + } + +LExit: + ReleaseBSTR(bstrName); + ReleaseObject(pixn); + ReleaseObject(pixnl); + + return hr; +} + + +static HRESULT GetFontColor( + __in IXMLDOMNode* pixn, + __in_z LPCWSTR wzAttributeName, + __out COLORREF* pColorRef, + __out DWORD* pdwSystemColor + ) +{ + HRESULT hr = S_OK; + BSTR bstr = NULL; + + *pdwSystemColor = 0; + + hr = XmlGetAttribute(pixn, wzAttributeName, &bstr); + if (S_FALSE == hr) + { + *pColorRef = THEME_INVISIBLE_COLORREF; + ExitFunction1(hr = S_OK); + } + ThmExitOnFailure(hr, "Failed to find font %ls color.", wzAttributeName); + + if (pdwSystemColor) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"btnface", -1)) + { + *pdwSystemColor = COLOR_BTNFACE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"btntext", -1)) + { + *pdwSystemColor = COLOR_BTNTEXT; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"graytext", -1)) + { + *pdwSystemColor = COLOR_GRAYTEXT; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"highlight", -1)) + { + *pdwSystemColor = COLOR_HIGHLIGHT; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"highlighttext", -1)) + { + *pdwSystemColor = COLOR_HIGHLIGHTTEXT; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"hotlight", -1)) + { + *pdwSystemColor = COLOR_HOTLIGHT; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"window", -1)) + { + *pdwSystemColor = COLOR_WINDOW; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstr, -1, L"windowtext", -1)) + { + *pdwSystemColor = COLOR_WINDOWTEXT; + } + else + { + *pColorRef = wcstoul(bstr, NULL, 16); + } + + if (*pdwSystemColor) + { + *pColorRef = ::GetSysColor(*pdwSystemColor); + } + } + +LExit: + ReleaseBSTR(bstr); + + return hr; +} + +static HRESULT ParsePages( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pElement, + __in THEME* pTheme + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnl = NULL; + IXMLDOMNode* pixn = NULL; + BSTR bstrType = NULL; + THEME_PAGE* pPage = NULL; + DWORD iPage = 0; + + hr = XmlSelectNodes(pElement, L"Page", &pixnl); + ThmExitOnFailure(hr, "Failed to find page elements."); + + hr = pixnl->get_length(reinterpret_cast(&pTheme->cPages)); + ThmExitOnFailure(hr, "Failed to count the number of theme pages."); + + if (!pTheme->cPages) + { + ExitFunction1(hr = S_OK); + } + + pTheme->rgPages = static_cast(MemAlloc(sizeof(THEME_PAGE) * pTheme->cPages, TRUE)); + ThmExitOnNull(pTheme->rgPages, hr, E_OUTOFMEMORY, "Failed to allocate theme pages."); + + while (S_OK == (hr = XmlNextElement(pixnl, &pixn, &bstrType))) + { + pPage = pTheme->rgPages + iPage; + + pPage->wId = static_cast(iPage + 1); + + hr = XmlGetAttributeEx(pixn, L"Name", &pPage->sczName); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed when querying page Name."); + + hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, NULL, pPage); + ThmExitOnFailure(hr, "Failed to parse page controls."); + + ++iPage; + + ReleaseNullBSTR(bstrType); + ReleaseNullObject(pixn); + } + ThmExitOnFailure(hr, "Failed to enumerate all pages."); + + if (S_FALSE == hr) + { + hr = S_OK; + } + +LExit: + ReleaseBSTR(bstrType); + ReleaseObject(pixn); + ReleaseObject(pixnl); + + return hr; +} + + +static HRESULT ParseImageLists( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pElement, + __in THEME* pTheme + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnlImageLists = NULL; + IXMLDOMNode* pixnImageList = NULL; + IXMLDOMNodeList* pixnlImages = NULL; + IXMLDOMNode* pixnImage = NULL; + DWORD dwImageListIndex = 0; + DWORD dwImageCount = 0; + HBITMAP hBitmap = NULL; + BITMAP bm = { }; + BSTR bstr = NULL; + DWORD i = 0; + int iRetVal = 0; + + hr = XmlSelectNodes(pElement, L"ImageList", &pixnlImageLists); + ThmExitOnFailure(hr, "Failed to find ImageList elements."); + + hr = pixnlImageLists->get_length(reinterpret_cast(&pTheme->cImageLists)); + ThmExitOnFailure(hr, "Failed to count the number of image lists."); + + if (!pTheme->cImageLists) + { + ExitFunction1(hr = S_OK); + } + + pTheme->rgImageLists = static_cast(MemAlloc(sizeof(THEME_IMAGELIST) * pTheme->cImageLists, TRUE)); + ThmExitOnNull(pTheme->rgImageLists, hr, E_OUTOFMEMORY, "Failed to allocate theme image lists."); + + while (S_OK == (hr = XmlNextElement(pixnlImageLists, &pixnImageList, NULL))) + { + hr = XmlGetAttribute(pixnImageList, L"Name", &bstr); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + ThmExitOnFailure(hr, "Failed to find ImageList/@Name attribute."); + + hr = StrAllocString(&pTheme->rgImageLists[dwImageListIndex].sczName, bstr, 0); + ThmExitOnFailure(hr, "Failed to make copy of ImageList name."); + + hr = XmlSelectNodes(pixnImageList, L"Image", &pixnlImages); + ThmExitOnFailure(hr, "Failed to select child Image nodes."); + + hr = pixnlImages->get_length(reinterpret_cast(&dwImageCount)); + ThmExitOnFailure(hr, "Failed to count the number of images in list."); + + if (0 < dwImageCount) + { + i = 0; + while (S_OK == (hr = XmlNextElement(pixnlImages, &pixnImage, NULL))) + { + if (hBitmap) + { + ::DeleteObject(hBitmap); + hBitmap = NULL; + } + hr = ParseImage(hModule, wzRelativePath, pixnImage, &hBitmap); + ThmExitOnFailure(hr, "Failed to parse image: %u", i); + + if (0 == i) + { + ::GetObjectW(hBitmap, sizeof(BITMAP), &bm); + + pTheme->rgImageLists[dwImageListIndex].hImageList = ImageList_Create(bm.bmWidth, bm.bmHeight, ILC_COLOR24, dwImageCount, 0); + ThmExitOnNullWithLastError(pTheme->rgImageLists[dwImageListIndex].hImageList, hr, "Failed to create image list."); + } + + iRetVal = ImageList_Add(pTheme->rgImageLists[dwImageListIndex].hImageList, hBitmap, NULL); + if (-1 == iRetVal) + { + ThmExitWithLastError(hr, "Failed to add image %u to image list.", i); + } + + ++i; + } + } + ++dwImageListIndex; + } + +LExit: + if (hBitmap) + { + ::DeleteObject(hBitmap); + } + ReleaseBSTR(bstr); + ReleaseObject(pixnlImageLists); + ReleaseObject(pixnImageList); + ReleaseObject(pixnlImages); + ReleaseObject(pixnImage); + + return hr; +} + +static void GetControls( + __in THEME* pTheme, + __in_opt THEME_CONTROL* pParentControl, + __out DWORD** ppcControls, + __out THEME_CONTROL*** pprgControls + ) +{ + if (pParentControl) + { + *ppcControls = &pParentControl->cControls; + *pprgControls = &pParentControl->rgControls; + } + else + { + *ppcControls = &pTheme->cControls; + *pprgControls = &pTheme->rgControls; + } +} + +static void GetControls( + __in const THEME* pTheme, + __in_opt const THEME_CONTROL* pParentControl, + __out DWORD& cControls, + __out THEME_CONTROL*& rgControls + ) +{ + if (pParentControl) + { + cControls = pParentControl->cControls; + rgControls = pParentControl->rgControls; + } + else + { + cControls = pTheme->cControls; + rgControls = pTheme->rgControls; + } +} + +static HRESULT ParseControls( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pElement, + __in THEME* pTheme, + __in_opt THEME_CONTROL* pParentControl, + __in_opt THEME_PAGE* pPage + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnl = NULL; + IXMLDOMNode* pixn = NULL; + BSTR bstrType = NULL; + DWORD cNewControls = 0; + DWORD iControl = 0; + DWORD iPageControl = 0; + DWORD* pcControls = NULL; + THEME_CONTROL** prgControls = NULL; + + GetControls(pTheme, pParentControl, &pcControls, &prgControls); + + hr = ParseRadioButtons(hModule, wzRelativePath, pElement, pTheme, pParentControl, pPage); + ThmExitOnFailure(hr, "Failed to parse radio buttons."); + + hr = XmlSelectNodes(pElement, L"Billboard|Button|Checkbox|Combobox|CommandLink|Editbox|Hyperlink|Hypertext|ImageControl|Label|ListView|Panel|Progressbar|Richedit|Static|Tabs|TreeView", &pixnl); + ThmExitOnFailure(hr, "Failed to find control elements."); + + hr = pixnl->get_length(reinterpret_cast(&cNewControls)); + ThmExitOnFailure(hr, "Failed to count the number of theme controls."); + + if (!cNewControls) + { + ExitFunction1(hr = S_OK); + } + + hr = MemReAllocArray(reinterpret_cast(prgControls), *pcControls, sizeof(THEME_CONTROL), cNewControls); + ThmExitOnFailure(hr, "Failed to reallocate theme controls."); + + cNewControls += *pcControls; + + if (pPage) + { + iPageControl = pPage->cControlIndices; + pPage->cControlIndices += cNewControls; + } + + iControl = *pcControls; + *pcControls = cNewControls; + + while (S_OK == (hr = XmlNextElement(pixnl, &pixn, &bstrType))) + { + THEME_CONTROL_TYPE type = THEME_CONTROL_TYPE_UNKNOWN; + + if (!bstrType) + { + hr = E_UNEXPECTED; + ThmExitOnFailure(hr, "Null element encountered!"); + } + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Billboard", -1)) + { + type = THEME_CONTROL_TYPE_BILLBOARD; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Button", -1)) + { + type = THEME_CONTROL_TYPE_BUTTON; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Checkbox", -1)) + { + type = THEME_CONTROL_TYPE_CHECKBOX; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Combobox", -1)) + { + type = THEME_CONTROL_TYPE_COMBOBOX; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"CommandLink", -1)) + { + type = THEME_CONTROL_TYPE_COMMANDLINK; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Editbox", -1)) + { + type = THEME_CONTROL_TYPE_EDITBOX; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Hyperlink", -1)) + { + type = THEME_CONTROL_TYPE_HYPERLINK; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Hypertext", -1)) + { + type = THEME_CONTROL_TYPE_HYPERTEXT; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"ImageControl", -1)) + { + type = THEME_CONTROL_TYPE_IMAGE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Label", -1)) + { + type = THEME_CONTROL_TYPE_LABEL; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"ListView", -1)) + { + type = THEME_CONTROL_TYPE_LISTVIEW; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Panel", -1)) + { + type = THEME_CONTROL_TYPE_PANEL; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Progressbar", -1)) + { + type = THEME_CONTROL_TYPE_PROGRESSBAR; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Richedit", -1)) + { + type = THEME_CONTROL_TYPE_RICHEDIT; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Static", -1)) + { + type = THEME_CONTROL_TYPE_STATIC; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"Tabs", -1)) + { + type = THEME_CONTROL_TYPE_TAB; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"TreeView", -1)) + { + type = THEME_CONTROL_TYPE_TREEVIEW; + } + + if (THEME_CONTROL_TYPE_UNKNOWN != type) + { + THEME_CONTROL* pControl = *prgControls + iControl; + pControl->type = type; + + // billboard children are always the size of the billboard + BOOL fBillboardSizing = pParentControl && THEME_CONTROL_TYPE_BILLBOARD == pParentControl->type; + + hr = ParseControl(hModule, wzRelativePath, pixn, pTheme, pControl, fBillboardSizing, pPage); + ThmExitOnFailure(hr, "Failed to parse control."); + + if (fBillboardSizing) + { + pControl->nX = pControl->nDefaultDpiX = 0; + pControl->nY = pControl->nDefaultDpiY = 0; + pControl->nWidth = pControl->nDefaultDpiWidth = 0; + pControl->nHeight = pControl->nDefaultDpiHeight = 0; + } + + if (pPage) + { + pControl->wPageId = pPage->wId; + ++iPageControl; + } + + ++iControl; + } + + ReleaseNullBSTR(bstrType); + ReleaseNullObject(pixn); + } + ThmExitOnFailure(hr, "Failed to enumerate all controls."); + + if (S_FALSE == hr) + { + hr = S_OK; + } + + AssertSz(iControl == cNewControls, "The number of parsed controls didn't match the number of expected controls."); + +LExit: + ReleaseBSTR(bstrType); + ReleaseObject(pixn); + ReleaseObject(pixnl); + + return hr; +} + + +static HRESULT ParseControl( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pixn, + __in THEME* pTheme, + __in THEME_CONTROL* pControl, + __in BOOL fSkipDimensions, + __in_opt THEME_PAGE* pPage + ) +{ + HRESULT hr = S_OK; + DWORD dwValue = 0; + BOOL fValue = FALSE; + BSTR bstrText = NULL; + BOOL fAnyTextChildren = FALSE; + BOOL fAnyNoteChildren = FALSE; + + hr = XmlGetAttributeEx(pixn, L"Name", &pControl->sczName); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed when querying control Name attribute."); + + hr = XmlGetAttributeEx(pixn, L"EnableCondition", &pControl->sczEnableCondition); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed when querying control EnableCondition attribute."); + + hr = XmlGetAttributeEx(pixn, L"VisibleCondition", &pControl->sczVisibleCondition); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed when querying control VisibleCondition attribute."); + + if (!fSkipDimensions) + { + hr = XmlGetAttributeNumber(pixn, L"X", &dwValue); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + ThmExitOnFailure(hr, "Failed to find control X attribute."); + + pControl->nX = pControl->nDefaultDpiX = dwValue; + + hr = XmlGetAttributeNumber(pixn, L"Y", &dwValue); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + ThmExitOnFailure(hr, "Failed to find control Y attribute."); + + pControl->nY = pControl->nDefaultDpiY = dwValue; + + hr = XmlGetAttributeNumber(pixn, L"Height", &dwValue); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + ThmExitOnFailure(hr, "Failed to find control Height attribute."); + + pControl->nHeight = pControl->nDefaultDpiHeight = dwValue; + + hr = XmlGetAttributeNumber(pixn, L"Width", &dwValue); + if (S_FALSE == hr) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + ThmExitOnFailure(hr, "Failed to find control Width attribute."); + + pControl->nWidth = pControl->nDefaultDpiWidth = dwValue; + } + + // Parse the optional background resource image. + hr = ParseImage(hModule, wzRelativePath, pixn, &pControl->hImage); + ThmExitOnFailure(hr, "Failed while parsing control image."); + + hr = XmlGetAttributeNumber(pixn, L"SourceX", reinterpret_cast(&pControl->nSourceX)); + if (S_FALSE == hr) + { + pControl->nSourceX = -1; + } + ThmExitOnFailure(hr, "Failed when querying control SourceX attribute."); + + hr = XmlGetAttributeNumber(pixn, L"SourceY", reinterpret_cast(&pControl->nSourceY)); + if (S_FALSE == hr) + { + pControl->nSourceY = -1; + } + ThmExitOnFailure(hr, "Failed when querying control SourceY attribute."); + + hr = XmlGetAttributeNumber(pixn, L"FontId", &pControl->dwFontId); + if (S_FALSE == hr) + { + pControl->dwFontId = THEME_INVALID_ID; + } + ThmExitOnFailure(hr, "Failed when querying control FontId attribute."); + + // Parse the optional window style. + hr = XmlGetAttributeNumberBase(pixn, L"HexStyle", 16, &pControl->dwStyle); + ThmExitOnFailure(hr, "Failed when querying control HexStyle attribute."); + + // Parse the tabstop bit "shortcut nomenclature", this could have been set with the style above. + hr = XmlGetYesNoAttribute(pixn, L"TabStop", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else + { + ThmExitOnFailure(hr, "Failed when querying control TabStop attribute."); + + if (fValue) + { + pControl->dwStyle |= WS_TABSTOP; + } + } + + hr = XmlGetYesNoAttribute(pixn, L"Visible", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else + { + ThmExitOnFailure(hr, "Failed when querying control Visible attribute."); + + if (fValue) + { + pControl->dwStyle |= WS_VISIBLE; + } + } + + hr = XmlGetYesNoAttribute(pixn, L"HideWhenDisabled", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else + { + ThmExitOnFailure(hr, "Failed when querying control HideWhenDisabled attribute."); + + if (fValue) + { + pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED; + } + } + + hr = XmlGetYesNoAttribute(pixn, L"DisableAutomaticBehavior", &pControl->fDisableVariableFunctionality); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else + { + ThmExitOnFailure(hr, "Failed when querying control DisableAutomaticBehavior attribute."); + } + + hr = ParseActions(pixn, pControl); + ThmExitOnFailure(hr, "Failed to parse action nodes of the control."); + + hr = ParseText(pixn, pControl, &fAnyTextChildren); + ThmExitOnFailure(hr, "Failed to parse text nodes of the control."); + + hr = ParseTooltips(pixn, pControl, &fAnyTextChildren); + ThmExitOnFailure(hr, "Failed to parse control Tooltip."); + + if (THEME_CONTROL_TYPE_COMMANDLINK == pControl->type) + { + hr = ParseNotes(pixn, pControl, &fAnyNoteChildren); + ThmExitOnFailure(hr, "Failed to parse note text nodes of the control."); + } + + if (fAnyTextChildren || fAnyNoteChildren) + { + pControl->uStringId = UINT_MAX; + } + else + { + hr = XmlGetAttributeNumber(pixn, L"StringId", reinterpret_cast(&pControl->uStringId)); + ThmExitOnFailure(hr, "Failed when querying control StringId attribute."); + + if (S_FALSE == hr) + { + pControl->uStringId = UINT_MAX; + + if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type || THEME_CONTROL_TYPE_PANEL == pControl->type) + { + // Billboards and panels have child elements and we don't want to pick up child element text in the parents. + hr = S_OK; + } + else + { + hr = XmlGetText(pixn, &bstrText); + ThmExitOnFailure(hr, "Failed to get control inner text."); + + if (S_OK == hr) + { + hr = StrAllocString(&pControl->sczText, bstrText, 0); + ThmExitOnFailure(hr, "Failed to copy control text."); + + ReleaseNullBSTR(bstrText); + } + else if (S_FALSE == hr) + { + hr = S_OK; + } + } + } + } + + if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type) + { + hr = XmlGetYesNoAttribute(pixn, L"Loop", &pControl->fBillboardLoops); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed when querying Billboard/@Loop attribute."); + + pControl->wBillboardInterval = 5000; + hr = XmlGetAttributeNumber(pixn, L"Interval", &dwValue); + if (S_OK == hr && dwValue) + { + pControl->wBillboardInterval = static_cast(dwValue & 0xFFFF); + } + ThmExitOnFailure(hr, "Failed when querying Billboard/@Interval attribute."); + + hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, pControl, pPage); + ThmExitOnFailure(hr, "Failed to parse billboard children."); + } + else if (THEME_CONTROL_TYPE_COMMANDLINK == pControl->type) + { + hr = ParseIcon(hModule, wzRelativePath, pixn, &pControl->hIcon); + ThmExitOnFailure(hr, "Failed while parsing control icon."); + } + else if (THEME_CONTROL_TYPE_EDITBOX == pControl->type) + { + hr = XmlGetYesNoAttribute(pixn, L"FileSystemAutoComplete", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else + { + ThmExitOnFailure(hr, "Failed when querying Editbox/@FileSystemAutoComplete attribute."); + + if (fValue) + { + pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_FILESYSTEM_AUTOCOMPLETE; + } + } + } + else if (THEME_CONTROL_TYPE_HYPERLINK == pControl->type || THEME_CONTROL_TYPE_BUTTON == pControl->type) + { + hr = XmlGetAttributeNumber(pixn, L"HoverFontId", &pControl->dwFontHoverId); + if (S_FALSE == hr) + { + pControl->dwFontHoverId = THEME_INVALID_ID; + } + ThmExitOnFailure(hr, "Failed when querying control HoverFontId attribute."); + + hr = XmlGetAttributeNumber(pixn, L"SelectedFontId", &pControl->dwFontSelectedId); + if (S_FALSE == hr) + { + pControl->dwFontSelectedId = THEME_INVALID_ID; + } + ThmExitOnFailure(hr, "Failed when querying control SelectedFontId attribute."); + } + else if (THEME_CONTROL_TYPE_LABEL == pControl->type) + { + hr = XmlGetYesNoAttribute(pixn, L"Center", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else if (fValue) + { + pControl->dwStyle |= SS_CENTER; + } + ThmExitOnFailure(hr, "Failed when querying Label/@Center attribute."); + + hr = XmlGetYesNoAttribute(pixn, L"DisablePrefix", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else if (fValue) + { + pControl->dwStyle |= SS_NOPREFIX; + } + ThmExitOnFailure(hr, "Failed when querying Label/@DisablePrefix attribute."); + } + else if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type) + { + // Parse the optional extended window style. + hr = XmlGetAttributeNumberBase(pixn, L"HexExtendedStyle", 16, &pControl->dwExtendedStyle); + ThmExitOnFailure(hr, "Failed when querying ListView/@HexExtendedStyle attribute."); + + hr = XmlGetAttribute(pixn, L"ImageList", &bstrText); + if (S_FALSE != hr) + { + ThmExitOnFailure(hr, "Failed when querying ListView/@ImageList attribute."); + + hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[0]); + ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageList for ListView.", bstrText); + } + + hr = XmlGetAttribute(pixn, L"ImageListSmall", &bstrText); + if (S_FALSE != hr) + { + ThmExitOnFailure(hr, "Failed when querying ListView/@ImageListSmall attribute."); + + hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[1]); + ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageListSmall for ListView.", bstrText); + } + + hr = XmlGetAttribute(pixn, L"ImageListState", &bstrText); + if (S_FALSE != hr) + { + ThmExitOnFailure(hr, "Failed when querying ListView/@ImageListState attribute."); + + hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[2]); + ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageListState for ListView.", bstrText); + } + + hr = XmlGetAttribute(pixn, L"ImageListGroupHeader", &bstrText); + if (S_FALSE != hr) + { + ThmExitOnFailure(hr, "Failed when querying ListView/@ImageListGroupHeader attribute."); + + hr = FindImageList(pTheme, bstrText, &pControl->rghImageList[3]); + ThmExitOnFailure(hr, "Failed to find image list %ls while setting ImageListGroupHeader for ListView.", bstrText); + } + + hr = ParseColumns(pixn, pControl); + ThmExitOnFailure(hr, "Failed to parse columns."); + } + else if (THEME_CONTROL_TYPE_PANEL == pControl->type) + { + hr = ParseControls(hModule, wzRelativePath, pixn, pTheme, pControl, pPage); + ThmExitOnFailure(hr, "Failed to parse panel children."); + } + else if (THEME_CONTROL_TYPE_RADIOBUTTON == pControl->type) + { + hr = XmlGetAttributeEx(pixn, L"Value", &pControl->sczValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed when querying RadioButton/@Value attribute."); + } + else if (THEME_CONTROL_TYPE_TAB == pControl->type) + { + hr = ParseTabs(pixn, pControl); + ThmExitOnFailure(hr, "Failed to parse tabs"); + } + else if (THEME_CONTROL_TYPE_TREEVIEW == pControl->type) + { + pControl->dwStyle |= TVS_DISABLEDRAGDROP; + + hr = XmlGetYesNoAttribute(pixn, L"EnableDragDrop", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else if (fValue) + { + pControl->dwStyle &= ~TVS_DISABLEDRAGDROP; + } + ThmExitOnFailure(hr, "Failed when querying TreeView/@EnableDragDrop attribute."); + + hr = XmlGetYesNoAttribute(pixn, L"FullRowSelect", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else if (fValue) + { + pControl->dwStyle |= TVS_FULLROWSELECT; + } + ThmExitOnFailure(hr, "Failed when querying TreeView/@FullRowSelect attribute."); + + hr = XmlGetYesNoAttribute(pixn, L"HasButtons", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else if (fValue) + { + pControl->dwStyle |= TVS_HASBUTTONS; + } + ThmExitOnFailure(hr, "Failed when querying TreeView/@HasButtons attribute."); + + hr = XmlGetYesNoAttribute(pixn, L"AlwaysShowSelect", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else if (fValue) + { + pControl->dwStyle |= TVS_SHOWSELALWAYS; + } + ThmExitOnFailure(hr, "Failed when querying TreeView/@AlwaysShowSelect attribute."); + + hr = XmlGetYesNoAttribute(pixn, L"LinesAtRoot", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else if (fValue) + { + pControl->dwStyle |= TVS_LINESATROOT; + } + ThmExitOnFailure(hr, "Failed when querying TreeView/@LinesAtRoot attribute."); + + hr = XmlGetYesNoAttribute(pixn, L"HasLines", &fValue); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + else if (fValue) + { + pControl->dwStyle |= TVS_HASLINES; + } + ThmExitOnFailure(hr, "Failed when querying TreeView/@HasLines attribute."); + } + +LExit: + ReleaseBSTR(bstrText); + + return hr; +} + + +static HRESULT ParseActions( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl + ) +{ + HRESULT hr = S_OK; + DWORD i = 0; + IXMLDOMNodeList* pixnl = NULL; + IXMLDOMNode* pixnChild = NULL; + BSTR bstrType = NULL; + + hr = XmlSelectNodes(pixn, L"BrowseDirectoryAction|ChangePageAction|CloseWindowAction", &pixnl); + ThmExitOnFailure(hr, "Failed to select child action nodes."); + + hr = pixnl->get_length(reinterpret_cast(&pControl->cActions)); + ThmExitOnFailure(hr, "Failed to count the number of action nodes."); + + if (0 < pControl->cActions) + { + MemAllocArray(reinterpret_cast(&pControl->rgActions), sizeof(THEME_ACTION), pControl->cActions); + ThmExitOnNull(pControl->rgActions, hr, E_OUTOFMEMORY, "Failed to allocate THEME_ACTION structs."); + + i = 0; + while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, &bstrType))) + { + if (!bstrType) + { + hr = E_UNEXPECTED; + ThmExitOnFailure(hr, "Null element encountered!"); + } + + THEME_ACTION* pAction = pControl->rgActions + i; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"BrowseDirectoryAction", -1)) + { + pAction->type = THEME_ACTION_TYPE_BROWSE_DIRECTORY; + + hr = XmlGetAttributeEx(pixnChild, L"VariableName", &pAction->BrowseDirectory.sczVariableName); + ThmExitOnFailure(hr, "Failed when querying BrowseDirectoryAction/@VariableName attribute."); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"ChangePageAction", -1)) + { + pAction->type = THEME_ACTION_TYPE_CHANGE_PAGE; + + hr = XmlGetAttributeEx(pixnChild, L"Page", &pAction->ChangePage.sczPageName); + ThmExitOnFailure(hr, "Failed when querying ChangePageAction/@Page attribute."); + + hr = XmlGetYesNoAttribute(pixnChild, L"Cancel", &pAction->ChangePage.fCancel); + if (E_NOTFOUND != hr) + { + ThmExitOnFailure(hr, "Failed when querying ChangePageAction/@Cancel attribute."); + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrType, -1, L"CloseWindowAction", -1)) + { + pAction->type = THEME_ACTION_TYPE_CLOSE_WINDOW; + } + else + { + hr = E_UNEXPECTED; + ThmExitOnFailure(hr, "Unexpected element encountered: %ls", bstrType); + } + + hr = XmlGetAttributeEx(pixnChild, L"Condition", &pAction->sczCondition); + if (E_NOTFOUND != hr) + { + ThmExitOnFailure(hr, "Failed when querying %ls/@Condition attribute.", bstrType); + } + + if (!pAction->sczCondition) + { + if (pControl->pDefaultAction) + { + hr = E_INVALIDDATA; + ThmExitOnFailure(hr, "Control '%ls' has multiple actions without a condition.", pControl->sczName); + } + + pControl->pDefaultAction = pAction; + } + + ++i; + ReleaseNullBSTR(bstrType); + ReleaseObject(pixnChild); + } + } + +LExit: + ReleaseObject(pixnl); + ReleaseObject(pixnChild); + ReleaseBSTR(bstrType); + + return hr; +} + + +static HRESULT ParseColumns( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl + ) +{ + HRESULT hr = S_OK; + DWORD i = 0; + IXMLDOMNodeList* pixnl = NULL; + IXMLDOMNode* pixnChild = NULL; + BSTR bstrText = NULL; + DWORD dwValue = 0; + + hr = XmlSelectNodes(pixn, L"Column", &pixnl); + ThmExitOnFailure(hr, "Failed to select child column nodes."); + + hr = pixnl->get_length(reinterpret_cast(&pControl->cColumns)); + ThmExitOnFailure(hr, "Failed to count the number of control columns."); + + if (0 < pControl->cColumns) + { + hr = MemAllocArray(reinterpret_cast(&pControl->ptcColumns), sizeof(THEME_COLUMN), pControl->cColumns); + ThmExitOnFailure(hr, "Failed to allocate column structs."); + + i = 0; + while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL))) + { + THEME_COLUMN* pColumn = pControl->ptcColumns + i; + + hr = XmlGetText(pixnChild, &bstrText); + ThmExitOnFailure(hr, "Failed to get inner text of column element."); + + hr = XmlGetAttributeNumber(pixnChild, L"Width", &dwValue); + if (S_FALSE == hr) + { + dwValue = 100; + } + ThmExitOnFailure(hr, "Failed to get column width attribute."); + + pColumn->nBaseWidth = pColumn->nDefaultDpiBaseWidth = dwValue; + + hr = XmlGetYesNoAttribute(pixnChild, L"Expands", reinterpret_cast(&pColumn->fExpands)); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed to get expands attribute."); + + hr = StrAllocString(&pColumn->pszName, bstrText, 0); + ThmExitOnFailure(hr, "Failed to copy column name."); + + ++i; + ReleaseNullBSTR(bstrText); + } + } + +LExit: + ReleaseObject(pixnl); + ReleaseObject(pixnChild); + ReleaseBSTR(bstrText); + + return hr; +} + + +static HRESULT ParseRadioButtons( + __in_opt HMODULE hModule, + __in_opt LPCWSTR wzRelativePath, + __in IXMLDOMNode* pixn, + __in THEME* pTheme, + __in_opt THEME_CONTROL* pParentControl, + __in THEME_PAGE* pPage + ) +{ + HRESULT hr = S_OK; + DWORD cRadioButtons = 0; + DWORD iControl = 0; + DWORD iPageControl = 0; + IXMLDOMNodeList* pixnlRadioButtons = NULL; + IXMLDOMNodeList* pixnl = NULL; + IXMLDOMNode* pixnRadioButtons = NULL; + IXMLDOMNode* pixnChild = NULL; + LPWSTR sczName = NULL; + THEME_CONTROL* pControl = NULL; + BOOL fFirst = FALSE; + DWORD* pcControls = NULL; + THEME_CONTROL** prgControls = NULL; + + GetControls(pTheme, pParentControl, &pcControls, &prgControls); + + hr = XmlSelectNodes(pixn, L"RadioButtons", &pixnlRadioButtons); + ThmExitOnFailure(hr, "Failed to select RadioButtons nodes."); + + while (S_OK == (hr = XmlNextElement(pixnlRadioButtons, &pixnRadioButtons, NULL))) + { + hr = XmlGetAttributeEx(pixnRadioButtons, L"Name", &sczName); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed when querying RadioButtons Name."); + + hr = XmlSelectNodes(pixnRadioButtons, L"RadioButton", &pixnl); + ThmExitOnFailure(hr, "Failed to select RadioButton nodes."); + + hr = pixnl->get_length(reinterpret_cast(&cRadioButtons)); + ThmExitOnFailure(hr, "Failed to count the number of RadioButton nodes."); + + if (cRadioButtons) + { + if (pPage) + { + iPageControl = pPage->cControlIndices; + pPage->cControlIndices += cRadioButtons; + } + + hr = MemReAllocArray(reinterpret_cast(prgControls), *pcControls, sizeof(THEME_CONTROL), cRadioButtons); + ThmExitOnFailure(hr, "Failed to reallocate theme controls."); + + iControl = *pcControls; + *pcControls += cRadioButtons; + + fFirst = TRUE; + + while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL))) + { + pControl = *prgControls + iControl; + pControl->type = THEME_CONTROL_TYPE_RADIOBUTTON; + + hr = ParseControl(hModule, wzRelativePath, pixnChild, pTheme, pControl, FALSE, pPage); + ThmExitOnFailure(hr, "Failed to parse control."); + + if (fFirst) + { + pControl->dwStyle |= WS_GROUP; + fFirst = FALSE; + } + + hr = StrAllocString(&pControl->sczVariable, sczName, 0); + ThmExitOnFailure(hr, "Failed to copy radio button variable."); + + if (pPage) + { + pControl->wPageId = pPage->wId; + ++iPageControl; + } + + ++iControl; + } + + if (!fFirst) + { + pControl->fLastRadioButton = TRUE; + } + } + } + +LExit: + ReleaseStr(sczName); + ReleaseObject(pixnl); + ReleaseObject(pixnChild); + ReleaseObject(pixnlRadioButtons); + ReleaseObject(pixnRadioButtons); + + return hr; +} + + +static HRESULT ParseTabs( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl + ) +{ + HRESULT hr = S_OK; + DWORD i = 0; + IXMLDOMNodeList* pixnl = NULL; + IXMLDOMNode* pixnChild = NULL; + BSTR bstrText = NULL; + + hr = XmlSelectNodes(pixn, L"Tab", &pixnl); + ThmExitOnFailure(hr, "Failed to select child tab nodes."); + + hr = pixnl->get_length(reinterpret_cast(&pControl->cTabs)); + ThmExitOnFailure(hr, "Failed to count the number of tabs."); + + if (0 < pControl->cTabs) + { + hr = MemAllocArray(reinterpret_cast(&pControl->pttTabs), sizeof(THEME_TAB), pControl->cTabs); + ThmExitOnFailure(hr, "Failed to allocate tab structs."); + + i = 0; + while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL))) + { + hr = XmlGetText(pixnChild, &bstrText); + ThmExitOnFailure(hr, "Failed to get inner text of tab element."); + + hr = StrAllocString(&(pControl->pttTabs[i].pszName), bstrText, 0); + ThmExitOnFailure(hr, "Failed to copy tab name."); + + ++i; + ReleaseNullBSTR(bstrText); + } + } + +LExit: + ReleaseObject(pixnl); + ReleaseObject(pixnChild); + ReleaseBSTR(bstrText); + + return hr; +} + + +static HRESULT ParseText( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl, + __inout BOOL* pfAnyChildren + ) +{ + HRESULT hr = S_OK; + DWORD i = 0; + IXMLDOMNodeList* pixnl = NULL; + IXMLDOMNode* pixnChild = NULL; + BSTR bstrText = NULL; + + hr = XmlSelectNodes(pixn, L"Text", &pixnl); + ThmExitOnFailure(hr, "Failed to select child Text nodes."); + + hr = pixnl->get_length(reinterpret_cast(&pControl->cConditionalText)); + ThmExitOnFailure(hr, "Failed to count the number of Text nodes."); + + *pfAnyChildren |= 0 < pControl->cConditionalText; + + if (0 < pControl->cConditionalText) + { + MemAllocArray(reinterpret_cast(&pControl->rgConditionalText), sizeof(THEME_CONDITIONAL_TEXT), pControl->cConditionalText); + ThmExitOnNull(pControl->rgConditionalText, hr, E_OUTOFMEMORY, "Failed to allocate THEME_CONDITIONAL_TEXT structs."); + + i = 0; + while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL))) + { + THEME_CONDITIONAL_TEXT* pConditionalText = pControl->rgConditionalText + i; + + hr = XmlGetAttributeEx(pixnChild, L"Condition", &pConditionalText->sczCondition); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed when querying Text/@Condition attribute."); + + hr = XmlGetText(pixnChild, &bstrText); + ThmExitOnFailure(hr, "Failed to get inner text of Text element."); + + if (S_OK == hr) + { + if (pConditionalText->sczCondition) + { + hr = StrAllocString(&pConditionalText->sczText, bstrText, 0); + ThmExitOnFailure(hr, "Failed to copy text to conditional text."); + + ++i; + } + else + { + if (pControl->sczText) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ThmExitOnFailure(hr, "Unconditional text for the '%ls' control is specified multiple times.", pControl->sczName); + } + + hr = StrAllocString(&pControl->sczText, bstrText, 0); + ThmExitOnFailure(hr, "Failed to copy text to control."); + + // Unconditional text entries aren't stored in the conditional text list. + --pControl->cConditionalText; + } + } + + ReleaseNullBSTR(bstrText); + } + } + +LExit: + ReleaseObject(pixnl); + ReleaseObject(pixnChild); + ReleaseBSTR(bstrText); + + return hr; +} + + +static HRESULT ParseTooltips( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl, + __inout BOOL* pfAnyChildren +) +{ + HRESULT hr = S_OK; + IXMLDOMNode* pixnChild = NULL; + BSTR bstrText = NULL; + + hr = XmlSelectSingleNode(pixn, L"Tooltip", &pixnChild); + ThmExitOnFailure(hr, "Failed to select child Tooltip node."); + + if (S_OK == hr) + { + *pfAnyChildren |= TRUE; + + hr = XmlGetText(pixnChild, &bstrText); + ThmExitOnFailure(hr, "Failed to get inner text of Tooltip element."); + + if (S_OK == hr) + { + hr = StrAllocString(&pControl->sczTooltip, bstrText, 0); + ThmExitOnFailure(hr, "Failed to copy tooltip text to control."); + } + } + +LExit: + ReleaseObject(pixnChild); + ReleaseBSTR(bstrText); + + return hr; +} + + +static HRESULT ParseNotes( + __in IXMLDOMNode* pixn, + __in THEME_CONTROL* pControl, + __out BOOL* pfAnyChildren + ) +{ + HRESULT hr = S_OK; + DWORD i = 0; + IXMLDOMNodeList* pixnl = NULL; + IXMLDOMNode* pixnChild = NULL; + BSTR bstrText = NULL; + + hr = XmlSelectNodes(pixn, L"Note", &pixnl); + ThmExitOnFailure(hr, "Failed to select child Note nodes."); + + hr = pixnl->get_length(reinterpret_cast(&pControl->cConditionalNotes)); + ThmExitOnFailure(hr, "Failed to count the number of Note nodes."); + + if (pfAnyChildren) + { + *pfAnyChildren = 0 < pControl->cConditionalNotes; + } + + if (0 < pControl->cConditionalNotes) + { + MemAllocArray(reinterpret_cast(&pControl->rgConditionalNotes), sizeof(THEME_CONDITIONAL_TEXT), pControl->cConditionalNotes); + ThmExitOnNull(pControl->rgConditionalNotes, hr, E_OUTOFMEMORY, "Failed to allocate note THEME_CONDITIONAL_TEXT structs."); + + i = 0; + while (S_OK == (hr = XmlNextElement(pixnl, &pixnChild, NULL))) + { + THEME_CONDITIONAL_TEXT* pConditionalNote = pControl->rgConditionalNotes + i; + + hr = XmlGetAttributeEx(pixnChild, L"Condition", &pConditionalNote->sczCondition); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed when querying Note/@Condition attribute."); + + hr = XmlGetText(pixnChild, &bstrText); + ThmExitOnFailure(hr, "Failed to get inner text of Note element."); + + if (S_OK == hr) + { + if (pConditionalNote->sczCondition) + { + hr = StrAllocString(&pConditionalNote->sczText, bstrText, 0); + ThmExitOnFailure(hr, "Failed to copy text to conditional note text."); + + ++i; + } + else + { + if (pControl->sczNote) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ThmExitOnFailure(hr, "Unconditional note text for the '%ls' control is specified multiple times.", pControl->sczName); + } + + hr = StrAllocString(&pControl->sczNote, bstrText, 0); + ThmExitOnFailure(hr, "Failed to copy text to command link control."); + + // Unconditional note entries aren't stored in the conditional notes list. + --pControl->cConditionalNotes; + } + } + + ReleaseNullBSTR(bstrText); + } + } + +LExit: + ReleaseObject(pixnl); + ReleaseObject(pixnChild); + ReleaseBSTR(bstrText); + + return hr; +} + + +static HRESULT StartBillboard( + __in THEME* pTheme, + __in DWORD dwControl + ) +{ + HRESULT hr = E_NOTFOUND; + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + + if (hWnd) + { + THEME_CONTROL* pControl = const_cast(FindControlFromHWnd(pTheme, hWnd)); + if (pControl && THEME_CONTROL_TYPE_BILLBOARD == pControl->type) + { + // kick off + pControl->dwData = 0; + OnBillboardTimer(pTheme, pTheme->hwndParent, dwControl); + + if (!::SetTimer(pTheme->hwndParent, pControl->wId, pControl->wBillboardInterval, NULL)) + { + ThmExitWithLastError(hr, "Failed to start billboard."); + } + + hr = S_OK; + } + } + +LExit: + return hr; +} + + +static HRESULT StopBillboard( + __in THEME* pTheme, + __in DWORD dwControl + ) +{ + HRESULT hr = E_NOTFOUND; + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + + if (hWnd) + { + const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd); + if (pControl && THEME_CONTROL_TYPE_BILLBOARD == pControl->type) + { + ThemeControlEnable(pTheme, dwControl, FALSE); + + if (::KillTimer(pTheme->hwndParent, pControl->wId)) + { + hr = S_OK; + } + } + } + + return hr; +} + +static HRESULT EnsureFontInstance( + __in THEME* pTheme, + __in THEME_FONT* pFont, + __out THEME_FONT_INSTANCE** ppFontInstance + ) +{ + HRESULT hr = S_OK; + THEME_FONT_INSTANCE* pFontInstance = NULL; + LOGFONTW lf = { }; + + for (DWORD i = 0; i < pFont->cFontInstances; ++i) + { + pFontInstance = pFont->rgFontInstances + i; + if (pTheme->nDpi == pFontInstance->nDpi) + { + *ppFontInstance = pFontInstance; + ExitFunction(); + } + } + + hr = MemEnsureArraySize(reinterpret_cast(&pFont->rgFontInstances), pFont->cFontInstances, sizeof(THEME_FONT_INSTANCE), GROW_FONT_INSTANCES); + ThmExitOnFailure(hr, "Failed to allocate memory for font instances."); + + pFontInstance = pFont->rgFontInstances + pFont->cFontInstances; + pFontInstance->nDpi = pTheme->nDpi; + + lf.lfHeight = DpiuScaleValue(pFont->lfHeight, pFontInstance->nDpi); + lf.lfWeight = pFont->lfWeight; + lf.lfUnderline = pFont->lfUnderline; + lf.lfQuality = pFont->lfQuality; + + hr = ::StringCchCopyW(lf.lfFaceName, countof(lf.lfFaceName), pFont->sczFaceName); + ThmExitOnFailure(hr, "Failed to copy font name to create font."); + + pFontInstance->hFont = ::CreateFontIndirectW(&lf); + ThmExitOnNull(pFontInstance->hFont, hr, E_OUTOFMEMORY, "Failed to create DPI specific font."); + + ++pFont->cFontInstances; + *ppFontInstance = pFontInstance; + +LExit: + return hr; +} + + +static HRESULT FindImageList( + __in THEME* pTheme, + __in_z LPCWSTR wzImageListName, + __out HIMAGELIST *phImageList + ) +{ + HRESULT hr = S_OK; + + for (DWORD i = 0; i < pTheme->cImageLists; ++i) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pTheme->rgImageLists[i].sczName, -1, wzImageListName, -1)) + { + *phImageList = pTheme->rgImageLists[i].hImageList; + ExitFunction1(hr = S_OK); + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} + + +static HRESULT DrawButton( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis, + __in const THEME_CONTROL* pControl + ) +{ + int nSourceX = pControl->hImage ? 0 : pControl->nSourceX; + int nSourceY = pControl->hImage ? 0 : pControl->nSourceY; + DWORD dwSourceWidth = pControl->nDefaultDpiWidth; + DWORD dwSourceHeight = pControl->nDefaultDpiHeight; + + HDC hdcMem = ::CreateCompatibleDC(pdis->hDC); + HBITMAP hDefaultBitmap = static_cast(::SelectObject(hdcMem, pControl->hImage ? pControl->hImage : pTheme->hImage)); + + DWORD_PTR dwStyle = ::GetWindowLongPtrW(pdis->hwndItem, GWL_STYLE); + // "clicked" gets priority + if (ODS_SELECTED & pdis->itemState) + { + nSourceY += pControl->nDefaultDpiHeight * 2; + } + // then hover + else if (pControl->dwData & THEME_CONTROL_DATA_HOVER) + { + nSourceY += pControl->nDefaultDpiHeight; + } + // then focused + else if (WS_TABSTOP & dwStyle && ODS_FOCUS & pdis->itemState) + { + nSourceY += pControl->nDefaultDpiHeight * 3; + } + + ::StretchBlt(pdis->hDC, 0, 0, pControl->nWidth, pControl->nHeight, hdcMem, nSourceX, nSourceY, dwSourceWidth, dwSourceHeight, SRCCOPY); + + ::SelectObject(hdcMem, hDefaultBitmap); + ::DeleteDC(hdcMem); + + DrawControlText(pTheme, pdis, pControl, TRUE, FALSE); + + return S_OK; +} + + +static HRESULT DrawHyperlink( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis, + __in const THEME_CONTROL* pControl + ) +{ + DrawControlText(pTheme, pdis, pControl, FALSE, TRUE); + return S_OK; +} + + +static void DrawControlText( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis, + __in const THEME_CONTROL* pControl, + __in BOOL fCentered, + __in BOOL fDrawFocusRect + ) +{ + HRESULT hr = S_OK; + WCHAR wzText[256] = { }; + DWORD cchText = 0; + THEME_FONT* pFont = NULL; + THEME_FONT_INSTANCE* pFontInstance = NULL; + HFONT hfPrev = NULL; + + if (0 == (cchText = ::GetWindowTextW(pdis->hwndItem, wzText, countof(wzText)))) + { + // nothing to do + return; + } + + if (ODS_SELECTED & pdis->itemState) + { + pFont = pTheme->rgFonts + (THEME_INVALID_ID != pControl->dwFontSelectedId ? pControl->dwFontSelectedId : pControl->dwFontId); + } + else if (pControl->dwData & THEME_CONTROL_DATA_HOVER) + { + pFont = pTheme->rgFonts + (THEME_INVALID_ID != pControl->dwFontHoverId ? pControl->dwFontHoverId : pControl->dwFontId); + } + else + { + pFont = pTheme->rgFonts + pControl->dwFontId; + } + + hr = EnsureFontInstance(pTheme, pFont, &pFontInstance); + if (SUCCEEDED(hr)) + { + hfPrev = SelectFont(pdis->hDC, pFontInstance->hFont); + } + + ::DrawTextExW(pdis->hDC, wzText, cchText, &pdis->rcItem, DT_SINGLELINE | (fCentered ? (DT_CENTER | DT_VCENTER) : 0), NULL); + + if (fDrawFocusRect && (WS_TABSTOP & ::GetWindowLongPtrW(pdis->hwndItem, GWL_STYLE)) && (ODS_FOCUS & pdis->itemState)) + { + ::DrawFocusRect(pdis->hDC, &pdis->rcItem); + } + + if (hfPrev) + { + SelectFont(pdis->hDC, hfPrev); + } +} + + +static HRESULT DrawImage( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis, + __in const THEME_CONTROL* pControl + ) +{ + DWORD dwHeight = pdis->rcItem.bottom - pdis->rcItem.top; + DWORD dwWidth = pdis->rcItem.right - pdis->rcItem.left; + int nSourceX = pControl->hImage ? 0 : pControl->nSourceX; + int nSourceY = pControl->hImage ? 0 : pControl->nSourceY; + DWORD dwSourceHeight = pControl->nDefaultDpiHeight; + DWORD dwSourceWidth = pControl->nDefaultDpiWidth; + + BLENDFUNCTION bf = { }; + bf.BlendOp = AC_SRC_OVER; + bf.SourceConstantAlpha = 255; + bf.AlphaFormat = AC_SRC_ALPHA; + + HDC hdcMem = ::CreateCompatibleDC(pdis->hDC); + HBITMAP hDefaultBitmap = static_cast(::SelectObject(hdcMem, pControl->hImage ? pControl->hImage : pTheme->hImage)); + + // Try to draw the image with transparency and if that fails (usually because the image has no + // alpha channel) then draw the image as is. + if (!::AlphaBlend(pdis->hDC, 0, 0, dwWidth, dwHeight, hdcMem, nSourceX, nSourceY, dwSourceWidth, dwSourceHeight, bf)) + { + ::StretchBlt(pdis->hDC, 0, 0, dwWidth, dwHeight, hdcMem, nSourceX, nSourceY, dwSourceWidth, dwSourceHeight, SRCCOPY); + } + + ::SelectObject(hdcMem, hDefaultBitmap); + ::DeleteDC(hdcMem); + return S_OK; +} + + +static HRESULT DrawProgressBar( + __in THEME* pTheme, + __in DRAWITEMSTRUCT* pdis, + __in const THEME_CONTROL* pControl + ) +{ + DWORD dwProgressColor = HIWORD(pControl->dwData); + DWORD dwProgressPercentage = LOWORD(pControl->dwData); + DWORD dwHeight = pdis->rcItem.bottom - pdis->rcItem.top; + DWORD dwCenter = (pdis->rcItem.right - 2) * dwProgressPercentage / 100; + DWORD dwSourceHeight = pControl->nDefaultDpiHeight; + int nSourceX = pControl->hImage ? 0 : pControl->nSourceX; + int nSourceY = (pControl->hImage ? 0 : pControl->nSourceY) + (dwProgressColor * dwSourceHeight); + + HDC hdcMem = ::CreateCompatibleDC(pdis->hDC); + HBITMAP hDefaultBitmap = static_cast(::SelectObject(hdcMem, pControl->hImage ? pControl->hImage : pTheme->hImage)); + + // Draw the left side of the progress bar. + ::StretchBlt(pdis->hDC, 0, 0, 1, dwHeight, hdcMem, nSourceX, nSourceY, 1, dwSourceHeight, SRCCOPY); + + // Draw the filled side of the progress bar, if there is any. + if (0 < dwCenter) + { + ::StretchBlt(pdis->hDC, 1, 0, dwCenter, dwHeight, hdcMem, nSourceX + 1, nSourceY, 1, dwSourceHeight, SRCCOPY); + } + + // Draw the unfilled side of the progress bar, if there is any. + if (dwCenter < static_cast(pdis->rcItem.right - 2)) + { + ::StretchBlt(pdis->hDC, 1 + dwCenter, 0, pdis->rcItem.right - dwCenter - 1, dwHeight, hdcMem, nSourceX + 2, nSourceY, 1, dwSourceHeight, SRCCOPY); + } + + // Draw the right side of the progress bar. + ::StretchBlt(pdis->hDC, pdis->rcItem.right - 1, 0, 1, dwHeight, hdcMem, nSourceX + 3, nSourceY, 1, dwSourceHeight, SRCCOPY); + + ::SelectObject(hdcMem, hDefaultBitmap); + ::DeleteDC(hdcMem); + return S_OK; +} + + +static BOOL DrawHoverControl( + __in THEME* pTheme, + __in BOOL fHover + ) +{ + BOOL fChangedHover = FALSE; + THEME_CONTROL* pControl = const_cast(FindControlFromHWnd(pTheme, pTheme->hwndHover)); + + // Only hyperlinks and owner-drawn buttons have hover states. + if (pControl && (THEME_CONTROL_TYPE_HYPERLINK == pControl->type || + (THEME_CONTROL_TYPE_BUTTON == pControl->type && (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_OWNER_DRAW)))) + { + if (fHover) + { + pControl->dwData |= THEME_CONTROL_DATA_HOVER; + } + else + { + pControl->dwData &= ~THEME_CONTROL_DATA_HOVER; + } + + ::InvalidateRect(pControl->hWnd, NULL, FALSE); + fChangedHover = TRUE; + } + + return fChangedHover; +} + + +static void FreePage( + __in THEME_PAGE* pPage + ) +{ + if (pPage) + { + ReleaseStr(pPage->sczName); + + if (pPage->cSavedVariables) + { + for (DWORD i = 0; i < pPage->cSavedVariables; ++i) + { + ReleaseStr(pPage->rgSavedVariables[i].sczValue); + } + } + + ReleaseMem(pPage->rgSavedVariables); + } +} + + +static void FreeImageList( + __in THEME_IMAGELIST* pImageList + ) +{ + if (pImageList) + { + ReleaseStr(pImageList->sczName); + ImageList_Destroy(pImageList->hImageList); + } +} + +static void FreeControl( + __in THEME_CONTROL* pControl + ) +{ + if (pControl) + { + if (::IsWindow(pControl->hWnd)) + { + ::CloseWindow(pControl->hWnd); + pControl->hWnd = NULL; + } + + ReleaseStr(pControl->sczName); + ReleaseStr(pControl->sczText); + ReleaseStr(pControl->sczTooltip); + ReleaseStr(pControl->sczNote); + ReleaseStr(pControl->sczEnableCondition); + ReleaseStr(pControl->sczVisibleCondition); + ReleaseStr(pControl->sczValue); + ReleaseStr(pControl->sczVariable); + + if (pControl->hImage) + { + ::DeleteBitmap(pControl->hImage); + } + + for (DWORD i = 0; i < pControl->cControls; ++i) + { + FreeControl(pControl->rgControls + i); + } + + for (DWORD i = 0; i < pControl->cActions; ++i) + { + FreeAction(&(pControl->rgActions[i])); + } + + for (DWORD i = 0; i < pControl->cColumns; ++i) + { + FreeColumn(&(pControl->ptcColumns[i])); + } + + for (DWORD i = 0; i < pControl->cConditionalText; ++i) + { + FreeConditionalText(&(pControl->rgConditionalText[i])); + } + + for (DWORD i = 0; i < pControl->cConditionalNotes; ++i) + { + FreeConditionalText(&(pControl->rgConditionalNotes[i])); + } + + for (DWORD i = 0; i < pControl->cTabs; ++i) + { + FreeTab(&(pControl->pttTabs[i])); + } + + ReleaseMem(pControl->rgActions) + ReleaseMem(pControl->ptcColumns); + ReleaseMem(pControl->rgConditionalText); + ReleaseMem(pControl->rgConditionalNotes); + ReleaseMem(pControl->pttTabs); + } +} + + +static void FreeAction( + __in THEME_ACTION* pAction + ) +{ + switch (pAction->type) + { + case THEME_ACTION_TYPE_BROWSE_DIRECTORY: + ReleaseStr(pAction->BrowseDirectory.sczVariableName); + break; + case THEME_ACTION_TYPE_CHANGE_PAGE: + ReleaseStr(pAction->ChangePage.sczPageName); + break; + } + + ReleaseStr(pAction->sczCondition); +} + + +static void FreeColumn( + __in THEME_COLUMN* pColumn + ) +{ + ReleaseStr(pColumn->pszName); +} + + +static void FreeConditionalText( + __in THEME_CONDITIONAL_TEXT* pConditionalText + ) +{ + ReleaseStr(pConditionalText->sczCondition); + ReleaseStr(pConditionalText->sczText); +} + + +static void FreeTab( + __in THEME_TAB* pTab + ) +{ + ReleaseStr(pTab->pszName); +} + + +static void FreeFontInstance( + __in THEME_FONT_INSTANCE* pFontInstance + ) +{ + if (pFontInstance->hFont) + { + ::DeleteObject(pFontInstance->hFont); + pFontInstance->hFont = NULL; + } +} + + +static void FreeFont( + __in THEME_FONT* pFont + ) +{ + if (pFont) + { + if (pFont->hBackground) + { + ::DeleteObject(pFont->hBackground); + pFont->hBackground = NULL; + } + + if (pFont->hForeground) + { + ::DeleteObject(pFont->hForeground); + pFont->hForeground = NULL; + } + + for (DWORD i = 0; i < pFont->cFontInstances; ++i) + { + FreeFontInstance(&(pFont->rgFontInstances[i])); + } + + ReleaseMem(pFont->rgFontInstances); + ReleaseStr(pFont->sczFaceName); + } +} + + +static DWORD CALLBACK RichEditStreamFromFileHandleCallback( + __in DWORD_PTR dwCookie, + __in_bcount(cb) LPBYTE pbBuff, + __in LONG cb, + __in LONG* pcb + ) +{ + HRESULT hr = S_OK; + HANDLE hFile = reinterpret_cast(dwCookie); + + if (!::ReadFile(hFile, pbBuff, cb, reinterpret_cast(pcb), NULL)) + { + ThmExitWithLastError(hr, "Failed to read file"); + } + +LExit: + return hr; +} + + +static DWORD CALLBACK RichEditStreamFromMemoryCallback( + __in DWORD_PTR dwCookie, + __in_bcount(cb) LPBYTE pbBuff, + __in LONG cb, + __in LONG* pcb + ) +{ + HRESULT hr = S_OK; + MEMBUFFER_FOR_RICHEDIT* pBuffer = reinterpret_cast(dwCookie); + DWORD cbCopy = 0; + + if (pBuffer->iData < pBuffer->cbData) + { + cbCopy = min(static_cast(cb), pBuffer->cbData - pBuffer->iData); + memcpy(pbBuff, pBuffer->rgbData + pBuffer->iData, cbCopy); + + pBuffer->iData += cbCopy; + Assert(pBuffer->iData <= pBuffer->cbData); + } + + *pcb = cbCopy; + return hr; +} + + +static void CALLBACK OnBillboardTimer( + __in THEME* pTheme, + __in HWND hwnd, + __in UINT_PTR idEvent + ) +{ + HWND hwndControl = ::GetDlgItem(hwnd, static_cast(idEvent)); + if (hwndControl) + { + THEME_CONTROL* pControl = const_cast(FindControlFromHWnd(pTheme, hwndControl)); + AssertSz(pControl && THEME_CONTROL_TYPE_BILLBOARD == pControl->type, "Only billboard controls should get billboard timer messages."); + + if (pControl) + { + if (pControl->dwData < pControl->cControls) + { + ThemeShowChild(pTheme, pControl, pControl->dwData); + } + else if (pControl->fBillboardLoops) + { + pControl->dwData = 0; + ThemeShowChild(pTheme, pControl, pControl->dwData); + } + else // no more looping + { + ::KillTimer(hwnd, idEvent); + } + + ++pControl->dwData; + } + } +} + +static void OnBrowseDirectory( + __in THEME* pTheme, + __in HWND hWnd, + __in const THEME_ACTION* pAction + ) +{ + HRESULT hr = S_OK; + WCHAR wzPath[MAX_PATH] = { }; + BROWSEINFOW browseInfo = { }; + PIDLIST_ABSOLUTE pidl = NULL; + + browseInfo.hwndOwner = hWnd; + browseInfo.pszDisplayName = wzPath; + browseInfo.lpszTitle = pTheme->sczCaption; + browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI; + pidl = ::SHBrowseForFolderW(&browseInfo); + if (pidl && ::SHGetPathFromIDListW(pidl, wzPath)) + { + // Since editbox changes aren't immediately saved off, we have to treat them differently. + THEME_CONTROL* pTargetControl = NULL; + + for (DWORD i = 0; i < pTheme->cControls; ++i) + { + THEME_CONTROL* pControl = pTheme->rgControls + i; + + if ((!pControl->wPageId || pControl->wPageId == pTheme->dwCurrentPageId) && + CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pControl->sczName, -1, pAction->BrowseDirectory.sczVariableName, -1)) + { + pTargetControl = pControl; + break; + } + } + + if (pTargetControl && THEME_CONTROL_TYPE_EDITBOX == pTargetControl->type && !pTargetControl->fDisableVariableFunctionality) + { + hr = ThemeSetTextControl(pTheme, pTargetControl->wId, wzPath); + ThmExitOnFailure(hr, "Failed to set text on editbox: %ls", pTargetControl->sczName); + } + else if (pTheme->pfnSetStringVariable) + { + hr = pTheme->pfnSetStringVariable(pAction->BrowseDirectory.sczVariableName, wzPath, FALSE, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to set variable: %ls", pAction->BrowseDirectory.sczVariableName); + } + else if (pTargetControl) + { + hr = ThemeSetTextControl(pTheme, pTargetControl->wId, wzPath); + ThmExitOnFailure(hr, "Failed to set text on control: %ls", pTargetControl->sczName); + } + + ThemeShowPageEx(pTheme, pTheme->dwCurrentPageId, SW_SHOW, THEME_SHOW_PAGE_REASON_REFRESH); + } + +LExit: + if (pidl) + { + ::CoTaskMemFree(pidl); + } +} + +static BOOL OnButtonClicked( + __in THEME* pTheme, + __in HWND hWnd, + __in const THEME_CONTROL* pControl + ) +{ + HRESULT hr = S_OK; + BOOL fHandled = FALSE; + + if (THEME_CONTROL_TYPE_BUTTON == pControl->type || THEME_CONTROL_TYPE_COMMANDLINK == pControl->type) + { + if (pControl->cActions) + { + fHandled = TRUE; + THEME_ACTION* pChosenAction = pControl->pDefaultAction; + + if (pTheme->pfnEvaluateCondition) + { + // As documented in the xsd, if there are multiple conditions that are true at the same time then the behavior is undefined. + // This is the current implementation and can change at any time. + for (DWORD j = 0; j < pControl->cActions; ++j) + { + THEME_ACTION* pAction = pControl->rgActions + j; + + if (pAction->sczCondition) + { + BOOL fCondition = FALSE; + + hr = pTheme->pfnEvaluateCondition(pAction->sczCondition, &fCondition, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to evaluate condition: %ls", pAction->sczCondition); + + if (fCondition) + { + pChosenAction = pAction; + break; + } + } + } + } + + if (pChosenAction) + { + switch (pChosenAction->type) + { + case THEME_ACTION_TYPE_BROWSE_DIRECTORY: + OnBrowseDirectory(pTheme, hWnd, pChosenAction); + break; + + case THEME_ACTION_TYPE_CLOSE_WINDOW: + ::SendMessageW(hWnd, WM_CLOSE, 0, 0); + break; + + case THEME_ACTION_TYPE_CHANGE_PAGE: + DWORD dwPageId = 0; + LPCWSTR pPageNames = pChosenAction->ChangePage.sczPageName; + ThemeGetPageIds(pTheme, &pPageNames, &dwPageId, 1); + + if (!dwPageId) + { + ThmExitOnFailure(E_INVALIDDATA, "Unknown page: %ls", pChosenAction->ChangePage.sczPageName); + } + + ThemeShowPageEx(pTheme, pTheme->dwCurrentPageId, SW_HIDE, pChosenAction->ChangePage.fCancel ? THEME_SHOW_PAGE_REASON_CANCEL : THEME_SHOW_PAGE_REASON_DEFAULT); + ThemeShowPage(pTheme, dwPageId, SW_SHOW); + break; + } + } + } + } + else if (!pControl->fDisableVariableFunctionality && (pTheme->pfnSetNumericVariable || pTheme->pfnSetStringVariable)) + { + BOOL fRefresh = FALSE; + + switch (pControl->type) + { + case THEME_CONTROL_TYPE_CHECKBOX: + if (pTheme->pfnSetNumericVariable && pControl->sczName && *pControl->sczName) + { + BOOL fChecked = ThemeIsControlChecked(pTheme, pControl->wId); + pTheme->pfnSetNumericVariable(pControl->sczName, fChecked ? 1 : 0, pTheme->pvVariableContext); + fRefresh = TRUE; + } + break; + case THEME_CONTROL_TYPE_RADIOBUTTON: + if (pTheme->pfnSetStringVariable && pControl->sczVariable && *pControl->sczVariable && ThemeIsControlChecked(pTheme, pControl->wId)) + { + pTheme->pfnSetStringVariable(pControl->sczVariable, pControl->sczValue, FALSE, pTheme->pvVariableContext); + fRefresh = TRUE; + } + break; + } + + if (fRefresh) + { + ThemeShowPageEx(pTheme, pTheme->dwCurrentPageId, SW_SHOW, THEME_SHOW_PAGE_REASON_REFRESH); + fHandled = TRUE; + } + } + +LExit: + return fHandled; +} + +static BOOL OnDpiChanged( + __in THEME* pTheme, + __in WPARAM wParam, + __in LPARAM lParam + ) +{ + UINT nDpi = HIWORD(wParam); + RECT* pRect = reinterpret_cast(lParam); + BOOL fIgnored = pTheme->nDpi == nDpi; + + if (fIgnored) + { + ExitFunction(); + } + + + pTheme->fForceResize = !pTheme->fAutoResize; + ScaleThemeFromWindow(pTheme, nDpi, pRect->left, pRect->top); + +LExit: + return !fIgnored; +} + +static void OnNcCreate( + __in THEME* pTheme, + __in HWND hWnd, + __in LPARAM lParam + ) +{ + DPIU_WINDOW_CONTEXT windowContext = { }; + CREATESTRUCTW* pCreateStruct = reinterpret_cast(lParam); + + pTheme->hwndParent = hWnd; + + DpiuGetWindowContext(pTheme->hwndParent, &windowContext); + + if (windowContext.nDpi != pTheme->nDpi) + { + ScaleTheme(pTheme, windowContext.nDpi, pCreateStruct->x, pCreateStruct->y, pCreateStruct->style, NULL != pCreateStruct->hMenu, pCreateStruct->dwExStyle); + } +} + +static HRESULT OnRichEditEnLink( + __in LPARAM lParam, + __in HWND hWndRichEdit, + __in HWND hWnd + ) +{ + HRESULT hr = S_OK; + LPWSTR sczLink = NULL; + ENLINK* link = reinterpret_cast(lParam); + + switch (link->msg) + { + case WM_LBUTTONDOWN: + { + hr = StrAlloc(&sczLink, link->chrg.cpMax - link->chrg.cpMin + 2); + ThmExitOnFailure(hr, "Failed to allocate string for link."); + + TEXTRANGEW tr; + tr.chrg.cpMin = link->chrg.cpMin; + tr.chrg.cpMax = link->chrg.cpMax; + tr.lpstrText = sczLink; + + if (0 < ::SendMessageW(hWndRichEdit, EM_GETTEXTRANGE, 0, reinterpret_cast(&tr))) + { + hr = ShelExec(sczLink, NULL, L"open", NULL, SW_SHOWDEFAULT, hWnd, NULL); + ThmExitOnFailure(hr, "Failed to launch link: %ls", sczLink); + } + + break; + } + + case WM_SETCURSOR: + ::SetCursor(vhCursorHand); + break; + } + +LExit: + ReleaseStr(sczLink); + + return hr; +} + +static BOOL ControlIsType( + __in const THEME* pTheme, + __in DWORD dwControl, + __in const THEME_CONTROL_TYPE type + ) +{ + BOOL fIsType = FALSE; + HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); + if (hWnd) + { + const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, hWnd); + fIsType = (pControl && type == pControl->type); + } + + return fIsType; +} + +static const THEME_CONTROL* FindControlFromHWnd( + __in const THEME* pTheme, + __in HWND hWnd, + __in_opt const THEME_CONTROL* pParentControl + ) +{ + DWORD cControls = 0; + THEME_CONTROL* rgControls = NULL; + + GetControls(pTheme, pParentControl, cControls, rgControls); + + // As we can't use GWLP_USERDATA (SysLink controls on Windows XP uses it too)... + for (DWORD i = 0; i < cControls; ++i) + { + if (hWnd == rgControls[i].hWnd) + { + return rgControls + i; + } + else if (0 < rgControls[i].cControls) + { + const THEME_CONTROL* pChildControl = FindControlFromHWnd(pTheme, hWnd, rgControls + i); + if (pChildControl) + { + return pChildControl; + } + } + } + + return NULL; +} + +static void GetControlDimensions( + __in const THEME_CONTROL* pControl, + __in const RECT* prcParent, + __out int* piWidth, + __out int* piHeight, + __out int* piX, + __out int* piY + ) +{ + *piWidth = pControl->nWidth + (0 < pControl->nWidth ? 0 : prcParent->right - max(0, pControl->nX)); + *piHeight = pControl->nHeight + (0 < pControl->nHeight ? 0 : prcParent->bottom - max(0, pControl->nY)); + *piX = pControl->nX + (-1 < pControl->nX ? 0 : prcParent->right - *piWidth); + *piY = pControl->nY + (-1 < pControl->nY ? 0 : prcParent->bottom - *piHeight); +} + +static HRESULT SizeListViewColumns( + __inout THEME_CONTROL* pControl + ) +{ + HRESULT hr = S_OK; + RECT rcParent = { }; + int cNumExpandingColumns = 0; + int iExtraAvailableSize; + + if (!::GetWindowRect(pControl->hWnd, &rcParent)) + { + ThmExitWithLastError(hr, "Failed to get window rect of listview control."); + } + + iExtraAvailableSize = rcParent.right - rcParent.left; + + for (DWORD i = 0; i < pControl->cColumns; ++i) + { + if (pControl->ptcColumns[i].fExpands) + { + ++cNumExpandingColumns; + } + + iExtraAvailableSize -= pControl->ptcColumns[i].nBaseWidth; + } + + // Leave room for a vertical scroll bar just in case. + iExtraAvailableSize -= ::GetSystemMetrics(SM_CXVSCROLL); + + for (DWORD i = 0; i < pControl->cColumns; ++i) + { + if (pControl->ptcColumns[i].fExpands) + { + pControl->ptcColumns[i].nWidth = pControl->ptcColumns[i].nBaseWidth + (iExtraAvailableSize / cNumExpandingColumns); + // In case there is any remainder, use it up the first chance we get. + pControl->ptcColumns[i].nWidth += iExtraAvailableSize % cNumExpandingColumns; + iExtraAvailableSize -= iExtraAvailableSize % cNumExpandingColumns; + } + else + { + pControl->ptcColumns[i].nWidth = pControl->ptcColumns[i].nBaseWidth; + } + } + +LExit: + return hr; +} + + +static HRESULT ShowControl( + __in THEME* pTheme, + __in THEME_CONTROL* pControl, + __in int nCmdShow, + __in BOOL fSaveEditboxes, + __in THEME_SHOW_PAGE_REASON reason, + __in DWORD dwPageId, + __out_opt HWND* phwndFocus + ) +{ + HRESULT hr = S_OK; + DWORD iPageControl = 0; + HWND hwndFocus = NULL; + LPWSTR sczFormatString = NULL; + LPWSTR sczText = NULL; + THEME_SAVEDVARIABLE* pSavedVariable = NULL; + BOOL fHide = SW_HIDE == nCmdShow; + THEME_PAGE* pPage = ThemeGetPage(pTheme, dwPageId); + + // Save the editbox value if necessary (other control types save their values immediately). + if (pTheme->pfnSetStringVariable && !pControl->fDisableVariableFunctionality && + fSaveEditboxes && THEME_CONTROL_TYPE_EDITBOX == pControl->type && pControl->sczName && *pControl->sczName) + { + hr = ThemeGetTextControl(pTheme, pControl->wId, &sczText); + ThmExitOnFailure(hr, "Failed to get the text for control: %ls", pControl->sczName); + + hr = pTheme->pfnSetStringVariable(pControl->sczName, sczText, FALSE, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to set the variable '%ls' to '%ls'", pControl->sczName, sczText); + } + + HWND hWnd = pControl->hWnd; + + if (fHide && pControl->wPageId) + { + ::ShowWindow(hWnd, SW_HIDE); + + if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type) + { + StopBillboard(pTheme, pControl->wId); + } + + ExitFunction(); + } + + BOOL fEnabled = !(pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_DISABLED); + BOOL fVisible = !(pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDDEN); + + if (!pControl->fDisableVariableFunctionality) + { + if (pTheme->pfnEvaluateCondition) + { + // If the control has a VisibleCondition, check if it's true. + if (pControl->sczVisibleCondition) + { + hr = pTheme->pfnEvaluateCondition(pControl->sczVisibleCondition, &fVisible, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to evaluate VisibleCondition: %ls", pControl->sczVisibleCondition); + } + + // If the control has an EnableCondition, check if it's true. + if (pControl->sczEnableCondition) + { + hr = pTheme->pfnEvaluateCondition(pControl->sczEnableCondition, &fEnabled, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to evaluate EnableCondition: %ls", pControl->sczEnableCondition); + } + } + + // Try to format each control's text based on context, except for editboxes since their text comes from the user. + if (pTheme->pfnFormatString && ((pControl->sczText && *pControl->sczText) || pControl->cConditionalText) && THEME_CONTROL_TYPE_EDITBOX != pControl->type) + { + LPWSTR wzText = pControl->sczText; + LPWSTR wzNote = pControl->sczNote; + + if (pTheme->pfnEvaluateCondition) + { + // As documented in the xsd, if there are multiple conditions that are true at the same time then the behavior is undefined. + // This is the current implementation and can change at any time. + for (DWORD j = 0; j < pControl->cConditionalText; ++j) + { + THEME_CONDITIONAL_TEXT* pConditionalText = pControl->rgConditionalText + j; + wzText = pConditionalText->sczText; + + if (pConditionalText->sczCondition) + { + BOOL fCondition = FALSE; + + hr = pTheme->pfnEvaluateCondition(pConditionalText->sczCondition, &fCondition, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to evaluate condition: %ls", pConditionalText->sczCondition); + + if (fCondition) + { + wzText = pConditionalText->sczText; + break; + } + } + } + + for (DWORD j = 0; j < pControl->cConditionalNotes; ++j) + { + THEME_CONDITIONAL_TEXT* pConditionalNote = pControl->rgConditionalNotes + j; + wzNote = pConditionalNote->sczText; + + if (pConditionalNote->sczCondition) + { + BOOL fCondition = FALSE; + + hr = pTheme->pfnEvaluateCondition(pConditionalNote->sczCondition, &fCondition, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to evaluate note condition: %ls", pConditionalNote->sczCondition); + + if (fCondition) + { + wzNote = pConditionalNote->sczText; + break; + } + } + } + } + + if (wzText && *wzText) + { + hr = pTheme->pfnFormatString(wzText, &sczText, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to format string: %ls", wzText); + } + else + { + ReleaseNullStr(sczText); + } + + ThemeSetTextControl(pTheme, pControl->wId, sczText); + + if (wzNote && *wzNote) + { + hr = pTheme->pfnFormatString(wzNote, &sczText, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to format note: %ls", wzNote); + } + else + { + ReleaseNullStr(sczText); + } + + ::SendMessageW(pControl->hWnd, BCM_SETNOTE, 0, reinterpret_cast(sczText)); + } + + // If this is a named control, do variable magic. + if (pControl->sczName && *pControl->sczName) + { + // If this is a checkbox control, + // try to set its default state to the state of a matching named variable. + if (pTheme->pfnGetNumericVariable && THEME_CONTROL_TYPE_CHECKBOX == pControl->type) + { + LONGLONG llValue = 0; + hr = pTheme->pfnGetNumericVariable(pControl->sczName, &llValue, pTheme->pvVariableContext); + if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ThmExitOnFailure(hr, "Failed to get numeric variable: %ls", pControl->sczName); + + if (THEME_SHOW_PAGE_REASON_REFRESH != reason && pPage && pControl->wPageId) + { + pSavedVariable = pPage->rgSavedVariables + iPageControl; + pSavedVariable->wzName = pControl->sczName; + + if (SUCCEEDED(hr)) + { + hr = StrAllocFormattedSecure(&pSavedVariable->sczValue, L"%lld", llValue); + ThmExitOnFailure(hr, "Failed to save variable: %ls", pControl->sczName); + } + + ++iPageControl; + } + + ThemeSendControlMessage(pTheme, pControl->wId, BM_SETCHECK, SUCCEEDED(hr) && llValue ? BST_CHECKED : BST_UNCHECKED, 0); + } + + // If this is an editbox control, + // try to set its default state to the state of a matching named variable. + if (pTheme->pfnFormatString && THEME_CONTROL_TYPE_EDITBOX == pControl->type) + { + hr = StrAllocFormatted(&sczFormatString, L"[%ls]", pControl->sczName); + ThmExitOnFailure(hr, "Failed to create format string: '%ls'", pControl->sczName); + + hr = pTheme->pfnFormatString(sczFormatString, &sczText, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to format string: '%ls'", sczFormatString); + + if (THEME_SHOW_PAGE_REASON_REFRESH != reason && pPage && pControl->wPageId) + { + pSavedVariable = pPage->rgSavedVariables + iPageControl; + pSavedVariable->wzName = pControl->sczName; + + if (SUCCEEDED(hr)) + { + hr = StrAllocStringSecure(&pSavedVariable->sczValue, sczText, 0); + ThmExitOnFailure(hr, "Failed to save variable: %ls", pControl->sczName); + } + + ++iPageControl; + } + + ThemeSetTextControl(pTheme, pControl->wId, sczText); + } + } + + // If this is a radio button associated with a variable, + // try to set its default state to the state of the variable. + if (pTheme->pfnGetStringVariable && THEME_CONTROL_TYPE_RADIOBUTTON == pControl->type && pControl->sczVariable && *pControl->sczVariable) + { + hr = pTheme->pfnGetStringVariable(pControl->sczVariable, &sczText, pTheme->pvVariableContext); + if (E_NOTFOUND == hr) + { + ReleaseNullStr(sczText); + } + else + { + ThmExitOnFailure(hr, "Failed to get string variable: %ls", pControl->sczVariable); + } + + if (THEME_SHOW_PAGE_REASON_REFRESH != reason && pPage && pControl->wPageId && pControl->fLastRadioButton) + { + pSavedVariable = pPage->rgSavedVariables + iPageControl; + pSavedVariable->wzName = pControl->sczVariable; + + if (SUCCEEDED(hr)) + { + hr = StrAllocStringSecure(&pSavedVariable->sczValue, sczText, 0); + ThmExitOnFailure(hr, "Failed to save variable: %ls", pControl->sczVariable); + } + + ++iPageControl; + } + + hr = S_OK; + + Button_SetCheck(hWnd, (!sczText && !pControl->sczValue) || (sczText && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczText, -1, pControl->sczValue, -1))); + } + } + + if (!fVisible || (!fEnabled && (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED))) + { + ::ShowWindow(hWnd, SW_HIDE); + } + else + { + ::EnableWindow(hWnd, !fHide && fEnabled); + + if (!hwndFocus && pControl->wPageId && (pControl->dwStyle & WS_TABSTOP)) + { + hwndFocus = hWnd; + } + + ::ShowWindow(hWnd, nCmdShow); + } + + if (0 < pControl->cControls) + { + ShowControls(pTheme, pControl, nCmdShow, fSaveEditboxes, reason, dwPageId); + } + + if (THEME_CONTROL_TYPE_BILLBOARD == pControl->type && pControl->wPageId) + { + if (fEnabled) + { + StartBillboard(pTheme, pControl->wId); + } + else + { + StopBillboard(pTheme, pControl->wId); + } + } + + if (phwndFocus) + { + *phwndFocus = hwndFocus; + } + +LExit: + ReleaseStr(sczFormatString); + ReleaseStr(sczText); + + return hr; +} + +static HRESULT ShowControls( + __in THEME* pTheme, + __in_opt const THEME_CONTROL* pParentControl, + __in int nCmdShow, + __in BOOL fSaveEditboxes, + __in THEME_SHOW_PAGE_REASON reason, + __in DWORD dwPageId + ) +{ + HRESULT hr = S_OK; + HWND hwndFocus = NULL; + DWORD cControls = 0; + THEME_CONTROL* rgControls = NULL; + + GetControls(pTheme, pParentControl, cControls, rgControls); + + for (DWORD i = 0; i < cControls; ++i) + { + THEME_CONTROL* pControl = rgControls + i; + + // Only look at non-page controls and the specified page's controls. + if (!pControl->wPageId || pControl->wPageId == dwPageId) + { + hr = ShowControl(pTheme, pControl, nCmdShow, fSaveEditboxes, reason, dwPageId, &hwndFocus); + ThmExitOnFailure(hr, "Failed to show control '%ls' at index %d.", pControl->sczName, i); + } + } + + if (hwndFocus) + { + ::SetFocus(hwndFocus); + } + +LExit: + return hr; +} + + +static LRESULT CALLBACK ControlGroupDefWindowProc( + __in_opt THEME* pTheme, + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ) +{ + if (pTheme) + { + switch (uMsg) + { + case WM_DRAWITEM: + ThemeDrawControl(pTheme, reinterpret_cast(lParam)); + return TRUE; + + case WM_CTLCOLORBTN: __fallthrough; + case WM_CTLCOLORSTATIC: + { + HBRUSH hBrush = NULL; + if (ThemeSetControlColor(pTheme, reinterpret_cast(wParam), reinterpret_cast(lParam), &hBrush)) + { + return reinterpret_cast(hBrush); + } + } + break; + + case WM_SETCURSOR: + if (ThemeHoverControl(pTheme, hWnd, reinterpret_cast(wParam))) + { + return TRUE; + } + break; + + case WM_PAINT: + if (::GetUpdateRect(hWnd, NULL, FALSE)) + { + PAINTSTRUCT ps; + ::BeginPaint(hWnd, &ps); + if (hWnd == pTheme->hwndParent) + { + ThemeDrawBackground(pTheme, &ps); + } + ::EndPaint(hWnd, &ps); + } + return 0; + + case WM_TIMER: + OnBillboardTimer(pTheme, hWnd, wParam); + break; + + case WM_NOTIFY: + if (lParam) + { + LPNMHDR pnmhdr = reinterpret_cast(lParam); + switch (pnmhdr->code) + { + // Tab/Shift+Tab support for rich-edit control. + case EN_MSGFILTER: + { + MSGFILTER* msgFilter = reinterpret_cast(lParam); + if (WM_KEYDOWN == msgFilter->msg && VK_TAB == msgFilter->wParam) + { + BOOL fShift = 0x8000 & ::GetKeyState(VK_SHIFT); + HWND hwndFocus = ::GetNextDlgTabItem(hWnd, msgFilter->nmhdr.hwndFrom, fShift); + ::SetFocus(hwndFocus); + return 1; + } + break; + } + + // Hyperlink clicks from rich-edit control. + case EN_LINK: + return SUCCEEDED(OnRichEditEnLink(lParam, pnmhdr->hwndFrom, hWnd)); + + // Clicks on a hypertext/syslink control. + case NM_CLICK: __fallthrough; + case NM_RETURN: + if (ControlIsType(pTheme, static_cast(pnmhdr->idFrom), THEME_CONTROL_TYPE_HYPERTEXT)) + { + PNMLINK pnmlink = reinterpret_cast(lParam); + LITEM litem = pnmlink->item; + ShelExec(litem.szUrl, NULL, L"open", NULL, SW_SHOWDEFAULT, hWnd, NULL); + return 1; + } + + return 0; + } + } + break; + + case WM_COMMAND: + switch (HIWORD(wParam)) + { + case BN_CLICKED: + if (lParam) + { + const THEME_CONTROL* pControl = FindControlFromHWnd(pTheme, (HWND)lParam); + if (pControl && OnButtonClicked(pTheme, hWnd, pControl)) + { + return 0; + } + } + break; + } + break; + } + } + + return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + + +static LRESULT CALLBACK PanelWndProc( + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ) +{ + LRESULT lres = 0; + THEME* pTheme = reinterpret_cast(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)); + + switch (uMsg) + { + case WM_NCCREATE: + { + LPCREATESTRUCTW lpcs = reinterpret_cast(lParam); + pTheme = reinterpret_cast(lpcs->lpCreateParams); + ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast(pTheme)); + } + break; + + case WM_NCDESTROY: + lres = ::DefWindowProcW(hWnd, uMsg, wParam, lParam); + ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0); + return lres; + + case WM_NCHITTEST: + return HTCLIENT; + break; + } + + return ControlGroupDefWindowProc(pTheme, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK StaticOwnerDrawWndProc( + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_UPDATEUISTATE: + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + default: + return (*vpfnStaticOwnerDrawBaseWndProc)(hWnd, uMsg, wParam, lParam); + } +} + +static HRESULT LoadControls( + __in THEME* pTheme, + __in_opt THEME_CONTROL* pParentControl, + __in_ecount_opt(cAssignControlIds) const THEME_ASSIGN_CONTROL_ID* rgAssignControlIds, + __in DWORD cAssignControlIds + ) +{ + HRESULT hr = S_OK; + RECT rcParent = { }; + LPWSTR sczText = NULL; + BOOL fStartNewGroup = FALSE; + DWORD cControls = 0; + THEME_CONTROL* rgControls = NULL; + HWND hwndParent = pParentControl ? pParentControl->hWnd : pTheme->hwndParent; + int w = 0; + int h = 0; + int x = 0; + int y = 0; + + GetControls(pTheme, pParentControl, cControls, rgControls); + ::GetClientRect(hwndParent, &rcParent); + + for (DWORD i = 0; i < cControls; ++i) + { + THEME_CONTROL* pControl = rgControls + i; + THEME_FONT* pControlFont = (pTheme->cFonts > pControl->dwFontId) ? pTheme->rgFonts + pControl->dwFontId : NULL; + THEME_FONT_INSTANCE* pControlFontInstance = NULL; + LPCWSTR wzWindowClass = NULL; + DWORD dwWindowBits = WS_CHILD; + DWORD dwWindowExBits = 0; + + if (fStartNewGroup) + { + dwWindowBits |= WS_GROUP; + fStartNewGroup = FALSE; + } + + switch (pControl->type) + { + case THEME_CONTROL_TYPE_BILLBOARD: + __fallthrough; + case THEME_CONTROL_TYPE_PANEL: + wzWindowClass = THEME_WC_PANEL; + dwWindowBits |= WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + dwWindowExBits |= WS_EX_TRANSPARENT | WS_EX_CONTROLPARENT; +#ifdef DEBUG + StrAllocFormatted(&pControl->sczText, L"Panel '%ls', id: %d", pControl->sczName, pControl->wId); +#endif + break; + + case THEME_CONTROL_TYPE_CHECKBOX: + dwWindowBits |= BS_AUTOCHECKBOX | BS_MULTILINE; // checkboxes are basically buttons with an extra bit tossed in. + __fallthrough; + case THEME_CONTROL_TYPE_BUTTON: + wzWindowClass = WC_BUTTONW; + if (pControl->hImage || (pTheme->hImage && 0 <= pControl->nSourceX && 0 <= pControl->nSourceY)) + { + dwWindowBits |= BS_OWNERDRAW; + pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_OWNER_DRAW; + } + break; + + case THEME_CONTROL_TYPE_COMBOBOX: + wzWindowClass = WC_COMBOBOXW; + dwWindowBits |= CBS_DROPDOWNLIST | CBS_HASSTRINGS; + break; + + case THEME_CONTROL_TYPE_COMMANDLINK: + wzWindowClass = WC_BUTTONW; + dwWindowBits |= BS_COMMANDLINK; + break; + + case THEME_CONTROL_TYPE_EDITBOX: + wzWindowClass = WC_EDITW; + dwWindowBits |= ES_LEFT | ES_AUTOHSCROLL; + dwWindowExBits = WS_EX_CLIENTEDGE; + break; + + case THEME_CONTROL_TYPE_HYPERLINK: // hyperlinks are basically just owner drawn buttons. + wzWindowClass = THEME_WC_HYPERLINK; + dwWindowBits |= BS_OWNERDRAW | BTNS_NOPREFIX; + break; + + case THEME_CONTROL_TYPE_HYPERTEXT: + wzWindowClass = WC_LINK; + dwWindowBits |= LWS_NOPREFIX; + break; + + case THEME_CONTROL_TYPE_IMAGE: // images are basically just owner drawn static controls (so we can draw .jpgs and .pngs instead of just bitmaps). + if (pControl->hImage || (pTheme->hImage && 0 <= pControl->nSourceX && 0 <= pControl->nSourceY)) + { + wzWindowClass = THEME_WC_STATICOWNERDRAW; + dwWindowBits |= SS_OWNERDRAW; + pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_OWNER_DRAW; + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ThmExitOnRootFailure(hr, "Invalid image or image list coordinates."); + } + break; + + case THEME_CONTROL_TYPE_LABEL: + wzWindowClass = WC_STATICW; + break; + + case THEME_CONTROL_TYPE_LISTVIEW: + // If thmutil is handling the image list for this listview, tell Windows not to free it when the control is destroyed. + if (pControl->rghImageList[0] || pControl->rghImageList[1] || pControl->rghImageList[2] || pControl->rghImageList[3]) + { + pControl->dwStyle |= LVS_SHAREIMAGELISTS; + } + wzWindowClass = WC_LISTVIEWW; + break; + + case THEME_CONTROL_TYPE_PROGRESSBAR: + if (pControl->hImage || (pTheme->hImage && 0 <= pControl->nSourceX && 0 <= pControl->nSourceY)) + { + wzWindowClass = THEME_WC_STATICOWNERDRAW; // no such thing as an owner drawn progress bar so we'll make our own out of a static control. + dwWindowBits |= SS_OWNERDRAW; + pControl->dwInternalStyle |= INTERNAL_CONTROL_STYLE_OWNER_DRAW; + } + else + { + wzWindowClass = PROGRESS_CLASSW; + } + break; + + case THEME_CONTROL_TYPE_RADIOBUTTON: + dwWindowBits |= BS_AUTORADIOBUTTON | BS_MULTILINE; + wzWindowClass = WC_BUTTONW; + + if (pControl->fLastRadioButton) + { + fStartNewGroup = TRUE; + } + break; + + case THEME_CONTROL_TYPE_RICHEDIT: + if (!vhModuleMsftEdit && !vhModuleRichEd) + { + hr = LoadSystemLibrary(L"Msftedit.dll", &vhModuleMsftEdit); + if (FAILED(hr)) + { + hr = LoadSystemLibrary(L"Riched20.dll", &vhModuleRichEd); + ThmExitOnFailure(hr, "Failed to load Rich Edit control library."); + } + } + + wzWindowClass = vhModuleMsftEdit ? MSFTEDIT_CLASS : RICHEDIT_CLASSW; + dwWindowBits |= ES_AUTOVSCROLL | ES_MULTILINE | WS_VSCROLL | ES_READONLY; + break; + + case THEME_CONTROL_TYPE_STATIC: + wzWindowClass = WC_STATICW; + dwWindowBits |= SS_ETCHEDHORZ; + break; + + case THEME_CONTROL_TYPE_TAB: + wzWindowClass = WC_TABCONTROLW; + break; + + case THEME_CONTROL_TYPE_TREEVIEW: + wzWindowClass = WC_TREEVIEWW; + break; + } + ThmExitOnNull(wzWindowClass, hr, E_INVALIDDATA, "Failed to configure control %u because of unknown type: %u", i, pControl->type); + + // Default control ids to the theme id and its index in the control array, unless there + // is a specific id to assign to a named control. + WORD wControlId = MAKEWORD(i, pTheme->wId); + for (DWORD iAssignControl = 0; pControl->sczName && iAssignControl < cAssignControlIds; ++iAssignControl) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pControl->sczName, -1, rgAssignControlIds[iAssignControl].wzName, -1)) + { + wControlId = rgAssignControlIds[iAssignControl].wId; + break; + } + } + + pControl->wId = wControlId; + + GetControlDimensions(pControl, &rcParent, &w, &h, &x, &y); + + BOOL fVisible = pControl->dwStyle & WS_VISIBLE; + BOOL fDisabled = pControl->dwStyle & WS_DISABLED; + + // If the control is supposed to be initially visible and it has a VisibleCondition, check if it's true. + if (fVisible && pControl->sczVisibleCondition && pTheme->pfnEvaluateCondition && !pControl->fDisableVariableFunctionality) + { + hr = pTheme->pfnEvaluateCondition(pControl->sczVisibleCondition, &fVisible, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to evaluate VisibleCondition: %ls", pControl->sczVisibleCondition); + + if (!fVisible) + { + pControl->dwStyle &= ~WS_VISIBLE; + } + } + + // Disable controls that aren't visible so their shortcut keys don't trigger. + if (!fVisible) + { + dwWindowBits |= WS_DISABLED; + fDisabled = TRUE; + } + + // If the control is supposed to be initially enabled and it has an EnableCondition, check if it's true. + if (!fDisabled && pControl->sczEnableCondition && pTheme->pfnEvaluateCondition && !pControl->fDisableVariableFunctionality) + { + BOOL fEnable = TRUE; + + hr = pTheme->pfnEvaluateCondition(pControl->sczEnableCondition, &fEnable, pTheme->pvVariableContext); + ThmExitOnFailure(hr, "Failed to evaluate EnableCondition: %ls", pControl->sczEnableCondition); + + fDisabled = !fEnable; + dwWindowBits |= fDisabled ? WS_DISABLED : 0; + } + + // Honor the HideWhenDisabled option. + if ((pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_HIDE_WHEN_DISABLED) && fVisible && fDisabled) + { + fVisible = FALSE; + pControl->dwStyle &= ~WS_VISIBLE; + } + + pControl->hWnd = ::CreateWindowExW(dwWindowExBits, wzWindowClass, pControl->sczText, pControl->dwStyle | dwWindowBits, x, y, w, h, hwndParent, reinterpret_cast(wControlId), NULL, pTheme); + ThmExitOnNullWithLastError(pControl->hWnd, hr, "Failed to create window."); + + if (pControl->sczTooltip) + { + if (!pTheme->hwndTooltip) + { + 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); + } + + if (pTheme->hwndTooltip) + { + TOOLINFOW toolinfo = {}; + toolinfo.cbSize = sizeof(toolinfo); + toolinfo.hwnd = hwndParent; + toolinfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; + toolinfo.uId = reinterpret_cast(pControl->hWnd); + toolinfo.lpszText = pControl->sczTooltip; + ::SendMessageW(pTheme->hwndTooltip, TTM_ADDTOOLW, 0, reinterpret_cast(&toolinfo)); + } + } + + if (THEME_CONTROL_TYPE_COMMANDLINK == pControl->type) + { + if (pControl->sczNote) + { + ::SendMessageW(pControl->hWnd, BCM_SETNOTE, 0, reinterpret_cast(pControl->sczNote)); + } + + if (pControl->hImage) + { + ::SendMessageW(pControl->hWnd, BM_SETIMAGE, IMAGE_BITMAP, reinterpret_cast(pControl->hImage)); + } + else if (pControl->hIcon) + { + ::SendMessageW(pControl->hWnd, BM_SETIMAGE, IMAGE_ICON, reinterpret_cast(pControl->hIcon)); + } + } + else if (THEME_CONTROL_TYPE_EDITBOX == pControl->type) + { + if (pControl->dwInternalStyle & INTERNAL_CONTROL_STYLE_FILESYSTEM_AUTOCOMPLETE) + { + hr = ::SHAutoComplete(pControl->hWnd, SHACF_FILESYS_ONLY); + } + } + else if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type) + { + ::SendMessageW(pControl->hWnd, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, pControl->dwExtendedStyle); + + hr = SizeListViewColumns(pControl); + ThmExitOnFailure(hr, "Failed to get size of list view columns."); + + for (DWORD j = 0; j < pControl->cColumns; ++j) + { + LVCOLUMNW lvc = { }; + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + lvc.cx = pControl->ptcColumns[j].nWidth; + lvc.iSubItem = j; + lvc.pszText = pControl->ptcColumns[j].pszName; + lvc.fmt = LVCFMT_LEFT; + lvc.cchTextMax = 4; + + if (-1 == ::SendMessageW(pControl->hWnd, LVM_INSERTCOLUMNW, (WPARAM) (int) (j), (LPARAM) (const LV_COLUMNW *) (&lvc))) + { + ThmExitWithLastError(hr, "Failed to insert listview column %u into tab control.", j); + } + + // Return value tells us the old image list, we don't care. + if (pControl->rghImageList[0]) + { + ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast(LVSIL_NORMAL), reinterpret_cast(pControl->rghImageList[0])); + } + else if (pControl->rghImageList[1]) + { + ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast(LVSIL_SMALL), reinterpret_cast(pControl->rghImageList[1])); + } + else if (pControl->rghImageList[2]) + { + ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast(LVSIL_STATE), reinterpret_cast(pControl->rghImageList[2])); + } + else if (pControl->rghImageList[3]) + { + ::SendMessageW(pControl->hWnd, LVM_SETIMAGELIST, static_cast(LVSIL_GROUPHEADER), reinterpret_cast(pControl->rghImageList[3])); + } + } + } + else if (THEME_CONTROL_TYPE_RICHEDIT == pControl->type) + { + ::SendMessageW(pControl->hWnd, EM_AUTOURLDETECT, static_cast(TRUE), 0); + ::SendMessageW(pControl->hWnd, EM_SETEVENTMASK, 0, ENM_KEYEVENTS | ENM_LINK); + } + else if (THEME_CONTROL_TYPE_TAB == pControl->type) + { + ULONG_PTR hbrBackground = 0; + if (THEME_INVALID_ID != pControl->dwFontId) + { + hbrBackground = reinterpret_cast(pTheme->rgFonts[pControl->dwFontId].hBackground); + } + else + { + hbrBackground = ::GetClassLongPtr(pTheme->hwndParent, GCLP_HBRBACKGROUND); + } + ::SetClassLongPtr(pControl->hWnd, GCLP_HBRBACKGROUND, hbrBackground); + + for (DWORD j = 0; j < pControl->cTabs; ++j) + { + TCITEMW tci = { }; + tci.mask = TCIF_TEXT | TCIF_IMAGE; + tci.iImage = -1; + tci.pszText = pControl->pttTabs[j].pszName; + + if (-1 == ::SendMessageW(pControl->hWnd, TCM_INSERTITEMW, (WPARAM) (int) (j), (LPARAM) (const TC_ITEMW *) (&tci))) + { + ThmExitWithLastError(hr, "Failed to insert tab %u into tab control.", j); + } + } + } + + if (pControlFont) + { + hr = EnsureFontInstance(pTheme, pControlFont, &pControlFontInstance); + ThmExitOnFailure(hr, "Failed to get DPI specific font."); + + ::SendMessageW(pControl->hWnd, WM_SETFONT, (WPARAM) pControlFontInstance->hFont, FALSE); + } + + // Initialize the text on all "application" (non-page) controls, best effort only. + if (pTheme->pfnFormatString && !pControl->wPageId && pControl->sczText && *pControl->sczText) + { + HRESULT hrFormat = pTheme->pfnFormatString(pControl->sczText, &sczText, pTheme->pvVariableContext); + if (SUCCEEDED(hrFormat)) + { + ThemeSetTextControl(pTheme, pControl->wId, sczText); + } + } + + if (pControl->cControls) + { + hr = LoadControls(pTheme, pControl, rgAssignControlIds, cAssignControlIds); + ThmExitOnFailure(hr, "Failed to load child controls."); + } + } + +LExit: + ReleaseStr(sczText); + + return hr; +} + +static HRESULT LocalizeControls( + __in DWORD cControls, + __in THEME_CONTROL* rgControls, + __in const WIX_LOCALIZATION *pWixLoc + ) +{ + HRESULT hr = S_OK; + + for (DWORD i = 0; i < cControls; ++i) + { + THEME_CONTROL* pControl = rgControls + i; + hr = LocalizeControl(pControl, pWixLoc); + ThmExitOnFailure(hr, "Failed to localize control: %ls", pControl->sczName); + } + +LExit: + return hr; +} + +static HRESULT LocalizeControl( + __in THEME_CONTROL* pControl, + __in const WIX_LOCALIZATION *pWixLoc + ) +{ + HRESULT hr = S_OK; + LOC_CONTROL* pLocControl = NULL; + LPWSTR sczLocStringId = NULL; + + if (pControl->sczText && *pControl->sczText) + { + hr = LocLocalizeString(pWixLoc, &pControl->sczText); + ThmExitOnFailure(hr, "Failed to localize control text."); + } + else if (pControl->sczName) + { + LOC_STRING* plocString = NULL; + + hr = StrAllocFormatted(&sczLocStringId, L"#(loc.%ls)", pControl->sczName); + ThmExitOnFailure(hr, "Failed to format loc string id: %ls", pControl->sczName); + + hr = LocGetString(pWixLoc, sczLocStringId, &plocString); + if (E_NOTFOUND != hr) + { + ThmExitOnFailure(hr, "Failed to get loc string: %ls", pControl->sczName); + + hr = StrAllocString(&pControl->sczText, plocString->wzText, 0); + ThmExitOnFailure(hr, "Failed to copy loc string to control: %ls", plocString->wzText); + } + } + + if (pControl->sczTooltip && *pControl->sczTooltip) + { + hr = LocLocalizeString(pWixLoc, &pControl->sczTooltip); + ThmExitOnFailure(hr, "Failed to localize control tooltip text."); + } + + if (pControl->sczNote && *pControl->sczNote) + { + hr = LocLocalizeString(pWixLoc, &pControl->sczNote); + ThmExitOnFailure(hr, "Failed to localize control note text."); + } + + for (DWORD j = 0; j < pControl->cConditionalText; ++j) + { + hr = LocLocalizeString(pWixLoc, &pControl->rgConditionalText[j].sczText); + ThmExitOnFailure(hr, "Failed to localize conditional text."); + } + + for (DWORD j = 0; j < pControl->cConditionalNotes; ++j) + { + hr = LocLocalizeString(pWixLoc, &pControl->rgConditionalNotes[j].sczText); + ThmExitOnFailure(hr, "Failed to localize conditional note."); + } + + for (DWORD j = 0; j < pControl->cColumns; ++j) + { + hr = LocLocalizeString(pWixLoc, &pControl->ptcColumns[j].pszName); + ThmExitOnFailure(hr, "Failed to localize column text."); + } + + for (DWORD j = 0; j < pControl->cTabs; ++j) + { + hr = LocLocalizeString(pWixLoc, &pControl->pttTabs[j].pszName); + ThmExitOnFailure(hr, "Failed to localize tab text."); + } + + // Localize control's size, location, and text. + if (pControl->sczName) + { + hr = LocGetControl(pWixLoc, pControl->sczName, &pLocControl); + if (E_NOTFOUND == hr) + { + ExitFunction1(hr = S_OK); + } + ThmExitOnFailure(hr, "Failed to localize control."); + + if (LOC_CONTROL_NOT_SET != pLocControl->nX) + { + pControl->nDefaultDpiX = pLocControl->nX; + } + + if (LOC_CONTROL_NOT_SET != pLocControl->nY) + { + pControl->nDefaultDpiY = pLocControl->nY; + } + + if (LOC_CONTROL_NOT_SET != pLocControl->nWidth) + { + pControl->nDefaultDpiWidth = pLocControl->nWidth; + } + + if (LOC_CONTROL_NOT_SET != pLocControl->nHeight) + { + pControl->nDefaultDpiHeight = pLocControl->nHeight; + } + + if (pLocControl->wzText && *pLocControl->wzText) + { + hr = StrAllocString(&pControl->sczText, pLocControl->wzText, 0); + ThmExitOnFailure(hr, "Failed to localize control text."); + } + } + + hr = LocalizeControls(pControl->cControls, pControl->rgControls, pWixLoc); + +LExit: + ReleaseStr(sczLocStringId); + + return hr; +} + +static HRESULT LoadControlsString( + __in DWORD cControls, + __in THEME_CONTROL* rgControls, + __in HMODULE hResModule + ) +{ + HRESULT hr = S_OK; + + for (DWORD i = 0; i < cControls; ++i) + { + THEME_CONTROL* pControl = rgControls + i; + hr = LoadControlString(pControl, hResModule); + ThmExitOnFailure(hr, "Failed to load string for control: %ls", pControl->sczName); + } + +LExit: + return hr; +} + +static HRESULT LoadControlString( + __in THEME_CONTROL* pControl, + __in HMODULE hResModule + ) +{ + HRESULT hr = S_OK; + if (UINT_MAX != pControl->uStringId) + { + hr = ResReadString(hResModule, pControl->uStringId, &pControl->sczText); + ThmExitOnFailure(hr, "Failed to load control text."); + + for (DWORD j = 0; j < pControl->cColumns; ++j) + { + if (UINT_MAX != pControl->ptcColumns[j].uStringId) + { + hr = ResReadString(hResModule, pControl->ptcColumns[j].uStringId, &pControl->ptcColumns[j].pszName); + ThmExitOnFailure(hr, "Failed to load column text."); + } + } + + for (DWORD j = 0; j < pControl->cTabs; ++j) + { + if (UINT_MAX != pControl->pttTabs[j].uStringId) + { + hr = ResReadString(hResModule, pControl->pttTabs[j].uStringId, &pControl->pttTabs[j].pszName); + ThmExitOnFailure(hr, "Failed to load tab text."); + } + } + } + + hr = LoadControlsString(pControl->cControls, pControl->rgControls, hResModule); + +LExit: + return hr; +} + +static void ResizeControls( + __in DWORD cControls, + __in THEME_CONTROL* rgControls, + __in const RECT* prcParent + ) +{ + for (DWORD i = 0; i < cControls; ++i) + { + THEME_CONTROL* pControl = rgControls + i; + ResizeControl(pControl, prcParent); + } +} + +static void ResizeControl( + __in THEME_CONTROL* pControl, + __in const RECT* prcParent + ) +{ + int w = 0; + int h = 0; + int x = 0; + int y = 0; + RECT rcControl = { }; + + GetControlDimensions(pControl, prcParent, &w, &h, &x, &y); + ::SetWindowPos(pControl->hWnd, NULL, x, y, w, h, SWP_NOACTIVATE | SWP_NOZORDER); + +#ifdef DEBUG + if (THEME_CONTROL_TYPE_BUTTON == pControl->type) + { + Trace(REPORT_STANDARD, "Resizing button (%ls/%ls) to (%d,%d)+(%d,%d) for parent (%d,%d)-(%d,%d)", + pControl->sczName, pControl->sczText, x, y, w, h, prcParent->left, prcParent->top, prcParent->right, prcParent->bottom); + } +#endif + + if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type) + { + SizeListViewColumns(pControl); + + for (DWORD j = 0; j < pControl->cColumns; ++j) + { + if (-1 == ::SendMessageW(pControl->hWnd, LVM_SETCOLUMNWIDTH, (WPARAM) (int) (j), (LPARAM) (pControl->ptcColumns[j].nWidth))) + { + Trace(REPORT_DEBUG, "Failed to resize listview column %u with error %u", j, ::GetLastError()); + return; + } + } + } + + if (pControl->cControls) + { + ::GetClientRect(pControl->hWnd, &rcControl); + ResizeControls(pControl->cControls, pControl->rgControls, &rcControl); + } +} + +static void ScaleThemeFromWindow( + __in THEME* pTheme, + __in UINT nDpi, + __in int x, + __in int y + ) +{ + DWORD dwStyle = GetWindowStyle(pTheme->hwndParent); + BOOL fMenu = NULL != ::GetMenu(pTheme->hwndParent); + DWORD dwExStyle = GetWindowExStyle(pTheme->hwndParent); + + ScaleTheme(pTheme, nDpi, x, y, dwStyle, fMenu, dwExStyle); +} + +static void ScaleTheme( + __in THEME* pTheme, + __in UINT nDpi, + __in int x, + __in int y, + __in DWORD dwStyle, + __in BOOL fMenu, + __in DWORD dwExStyle + ) +{ + RECT rect = { }; + + pTheme->nDpi = nDpi; + + pTheme->nHeight = DpiuScaleValue(pTheme->nDefaultDpiHeight, pTheme->nDpi); + pTheme->nWidth = DpiuScaleValue(pTheme->nDefaultDpiWidth, pTheme->nDpi); + pTheme->nMinimumHeight = DpiuScaleValue(pTheme->nDefaultDpiMinimumHeight, pTheme->nDpi); + pTheme->nMinimumWidth = DpiuScaleValue(pTheme->nDefaultDpiMinimumWidth, pTheme->nDpi); + + rect.left = x; + rect.top = y; + rect.right = x + pTheme->nWidth; + rect.bottom = y + pTheme->nHeight; + DpiuAdjustWindowRect(&rect, dwStyle, fMenu, dwExStyle, pTheme->nDpi); + pTheme->nWindowWidth = rect.right - rect.left; + pTheme->nWindowHeight = rect.bottom - rect.top; + + ScaleControls(pTheme, pTheme->cControls, pTheme->rgControls, pTheme->nDpi); + + if (pTheme->hwndParent) + { + ::SetWindowPos(pTheme->hwndParent, NULL, x, y, pTheme->nWindowWidth, pTheme->nWindowHeight, SWP_NOACTIVATE | SWP_NOZORDER); + } +} + +static void ScaleControls( + __in THEME* pTheme, + __in DWORD cControls, + __in THEME_CONTROL* rgControls, + __in UINT nDpi + ) +{ + for (DWORD i = 0; i < cControls; ++i) + { + THEME_CONTROL* pControl = rgControls + i; + ScaleControl(pTheme, pControl, nDpi); + } +} + +static void ScaleControl( + __in THEME* pTheme, + __in THEME_CONTROL* pControl, + __in UINT nDpi + ) +{ + HRESULT hr = S_OK; + THEME_FONT* pControlFont = (pTheme->cFonts > pControl->dwFontId) ? pTheme->rgFonts + pControl->dwFontId : NULL; + THEME_FONT_INSTANCE* pControlFontInstance = NULL; + + if (pControlFont) + { + hr = EnsureFontInstance(pTheme, pControlFont, &pControlFontInstance); + if (SUCCEEDED(hr) && pControl->hWnd) + { + ::SendMessageW(pControl->hWnd, WM_SETFONT, (WPARAM)pControlFontInstance->hFont, FALSE); + } + } + + if (THEME_CONTROL_TYPE_LISTVIEW == pControl->type) + { + for (DWORD i = 0; i < pControl->cColumns; ++i) + { + THEME_COLUMN* pColumn = pControl->ptcColumns + i; + + pColumn->nBaseWidth = DpiuScaleValue(pColumn->nDefaultDpiBaseWidth, nDpi); + } + } + + pControl->nWidth = DpiuScaleValue(pControl->nDefaultDpiWidth, nDpi); + pControl->nHeight = DpiuScaleValue(pControl->nDefaultDpiHeight, nDpi); + pControl->nX = DpiuScaleValue(pControl->nDefaultDpiX, nDpi); + pControl->nY = DpiuScaleValue(pControl->nDefaultDpiY, nDpi); + + if (pControl->cControls) + { + ScaleControls(pTheme, pControl->cControls, pControl->rgControls, nDpi); + } +} + +static void UnloadControls( + __in DWORD cControls, + __in THEME_CONTROL* rgControls + ) +{ + for (DWORD i = 0; i < cControls; ++i) + { + THEME_CONTROL* pControl = rgControls + i; + pControl->hWnd = NULL; + + UnloadControls(pControl->cControls, pControl->rgControls); + } +} + -- cgit v1.2.3-55-g6feb