// 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"; 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 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 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 (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: MemFree(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, __out_z LPWSTR* psczText ) { HRESULT hr = S_OK; HWND hWnd = ::GetDlgItem(pTheme->hwndParent, dwControl); DWORD cchText = 0; DWORD cchTextRead = 0; // Ensure the string has room for at least one character. hr = StrMaxLength(*psczText, reinterpret_cast(&cchText)); ThmExitOnFailure(hr, "Failed to get text buffer length."); 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 = { }; 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; 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; 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; 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) || 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 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 = WC_STATICW; 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 = WC_STATICW; // 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); } }