diff --git a/buildenv/CMakeLists.txt b/buildenv/CMakeLists.txt index ba526f3..0b85e10 100644 --- a/buildenv/CMakeLists.txt +++ b/buildenv/CMakeLists.txt @@ -56,6 +56,8 @@ add_custom_command( "${CMAKE_CURRENT_LIST_DIR}/${MAIN_TARGET_NAME}.sln" -t:restore -p:RestorePackagesConfig=true + -p:Configuration=$ + -p:Platform=${TARGET_ARCH_ID} COMMENT "Restoring NuGet packages" ) diff --git a/buildenv/WinPinMenu.sln b/buildenv/WinPinMenu.sln index e45d913..95953ea 100644 --- a/buildenv/WinPinMenu.sln +++ b/buildenv/WinPinMenu.sln @@ -4,6 +4,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 17.8.34931.61 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinPinMenu", "..\src\app\WinPinMenu.vcxproj", "{C12D10FE-D63C-4834-9FF5-F26DD4BEA551}" + ProjectSection(ProjectDependencies) = postProject + {7D0481BF-835E-4FAA-820C-0EF96D007C7A} = {7D0481BF-835E-4FAA-820C-0EF96D007C7A} + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CMake", "CMake", "{4A16127A-5EE7-4701-99FE-D36C5F8C3866}" ProjectSection(SolutionItems) = preProject @@ -16,6 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wtlx", "..\src\wtlx\wtlx.vcxproj", "{7D0481BF-835E-4FAA-820C-0EF96D007C7A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -38,6 +43,18 @@ Global {C12D10FE-D63C-4834-9FF5-F26DD4BEA551}.Release|x64.Build.0 = Release|x64 {C12D10FE-D63C-4834-9FF5-F26DD4BEA551}.Release|x86.ActiveCfg = Release|Win32 {C12D10FE-D63C-4834-9FF5-F26DD4BEA551}.Release|x86.Build.0 = Release|Win32 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Debug|ARM64.ActiveCfg = Debug|x64 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Debug|ARM64.Build.0 = Debug|x64 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Debug|x64.ActiveCfg = Debug|x64 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Debug|x64.Build.0 = Debug|x64 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Debug|x86.ActiveCfg = Debug|Win32 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Debug|x86.Build.0 = Debug|Win32 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Release|ARM64.ActiveCfg = Release|x64 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Release|ARM64.Build.0 = Release|x64 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Release|x64.ActiveCfg = Release|x64 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Release|x64.Build.0 = Release|x64 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Release|x86.ActiveCfg = Release|Win32 + {7D0481BF-835E-4FAA-820C-0EF96D007C7A}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/app/AboutDlg.cpp b/src/app/AboutDlg.cpp index 5ded32a..468da2e 100644 --- a/src/app/AboutDlg.cpp +++ b/src/app/AboutDlg.cpp @@ -1,11 +1,10 @@ // aboutdlg.cpp : implementation of the CAboutDlg class // ///////////////////////////////////////////////////////////////////////////// - #include "stdafx.h" + #include "resource.h" #include "productmeta.h" -#include "Draw.h" #include "aboutdlg.h" @@ -31,42 +30,12 @@ LRESULT CAboutDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lPara #else m_lnkRelNotes.ShowWindow(SW_HIDE); #endif - m_clrLink = m_lnkLicense.m_clrLink; - m_clrVisited = m_lnkLicense.m_clrVisited; - UpdateColors(); return TRUE; } -LRESULT CAboutDlg::OnThemeChange(UINT, WPARAM, LPARAM, BOOL& bHandled) -{ - bHandled = FALSE; - UpdateColors(); - return 0L; -} - - LRESULT CAboutDlg::OnCloseCmd(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { EndDialog(wID); return 0; } - -void CAboutDlg::UpdateColors() -{ - auto clrLink = m_clrLink; - auto clrVisited = m_clrVisited; - if (IsInDarkMode()) - { - clrLink = HLS_TRANSFORM(clrLink, 35, 0); - clrVisited = HLS_TRANSFORM(clrVisited, 30, 0); - } - ApplyLinkColor(m_lnkLicense, clrLink, clrVisited); - ApplyLinkColor(m_lnkRelNotes, clrLink, clrVisited); -} - -void CAboutDlg::ApplyLinkColor(CHyperLink& link, COLORREF clrLink, COLORREF clrVisited) const -{ - link.m_clrLink = clrLink; - link.m_clrVisited = clrVisited; -} diff --git a/src/app/AboutDlg.h b/src/app/AboutDlg.h index 30dc7d7..b61a8af 100644 --- a/src/app/AboutDlg.h +++ b/src/app/AboutDlg.h @@ -3,7 +3,7 @@ ///////////////////////////////////////////////////////////////////////////// #pragma once -#include "FileVersionInfo.h" +#include class CAboutDlg; using CUxAboutDialog = CUxModeWindow; @@ -17,7 +17,6 @@ class CAboutDlg enum { IDD = IDD_ABOUTBOX }; BEGIN_MSG_MAP(CAboutDlg) - MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChange) CHAIN_MSG_MAP(CUxAboutDialog) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_ID_HANDLER(IDOK, OnCloseCmd) @@ -37,16 +36,10 @@ class CAboutDlg // LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/) LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); - LRESULT OnThemeChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); LRESULT OnCloseCmd(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/); - void UpdateColors(); - void ApplyLinkColor(CHyperLink& link, COLORREF clrLink, COLORREF clrVisited) const; - private: - COLORREF m_clrLink{ 0 }; - COLORREF m_clrVisited{ 0 }; - CHyperLink m_lnkLicense; - CHyperLink m_lnkRelNotes; + CUxModeHyperLink m_lnkLicense; + CUxModeHyperLink m_lnkRelNotes; CFileVersionInfo m_fvi; }; diff --git a/src/app/ShellBrowseMenu.cpp b/src/app/ShellBrowseMenu.cpp index 608d7a3..e062ee0 100644 --- a/src/app/ShellBrowseMenu.cpp +++ b/src/app/ShellBrowseMenu.cpp @@ -2,7 +2,6 @@ #include "resource.h" -#include "Draw.h" #include "ShellMgr.h" #include "ShellBrowseMenu.h" @@ -10,7 +9,6 @@ CShellBrowseMenu::CShellBrowseMenu(const ShellMenuController* controller) : m_controller(controller), m_isRendered(false), m_isCtxMenuShowing(false) { LoadIconImages(); - UpdateMetrics(); } HRESULT CShellBrowseMenu::Rebuild() @@ -57,11 +55,13 @@ BOOL CShellBrowseMenu::InvokeWithSelection(LPCTSTR strVerb) const return FALSE; } -LRESULT CShellBrowseMenu::OnThemeChange(UINT, WPARAM, LPARAM, BOOL& bHandled) +void CShellBrowseMenu::UxModeUpdateColorSettings() { - bHandled = FALSE; - UpdateMetrics(true); - return 0L; + CUxModeMenuHelper::UxModeUpdateColorSettings(); + if (!uxTheme.IsInDarkMode()) + { + m_menuColors.crHighlightBg = UXCOLOR_LIGHTER(m_menuColors.crHighlightBg, 0.3); + } } LRESULT CShellBrowseMenu::OnInitMenuPopup(UINT, WPARAM wParam, LPARAM lParam, BOOL& bHandled) @@ -303,29 +303,42 @@ BOOL CShellBrowseMenu::CustomDrawMenuItem(LPDRAWITEMSTRUCT lpDis) CDCHandle dc = lpDis->hDC; RECT& rcItem = lpDis->rcItem; + CRect rcOverpaint(rcItem); + rcOverpaint.top -= 1; + rcOverpaint.bottom += 1; + m_menuTheme.DrawThemeBackground(dc, MENU_POPUPBACKGROUND, 0, rcOverpaint, NULL); + auto isDisabled = ODS_GRAYED == (lpDis->itemState & ODS_GRAYED); auto isSelected = ODS_SELECTED == (lpDis->itemState & ODS_SELECTED); auto isSeparator = 0 == pData->caption.GetLength(); - CPenDC pen(dc, isSelected && !isDisabled ? m_metrics.crHihghlight : m_metrics.crBg); - CBrushDC brush(dc, isSelected && !isDisabled ? m_metrics.crHighlightBg : m_metrics.crBg); - dc.Rectangle(&rcItem); + auto itemState = isDisabled ? MPIF_DISABLED : MPI_NORMAL; + if (isSelected) + itemState = isDisabled ? MPI_DISABLEDHOT : MPI_HOT; + m_menuTheme.DrawThemeBackground(dc, MENU_POPUPITEM, itemState, &rcItem, NULL); + + if (isSelected && !isDisabled) + { + CPen pen; + pen.CreatePen(PS_SOLID, 1, m_menuColors.crHihghlight); + auto oldPen = dc.SelectPen(pen); + auto oldBrush = dc.SelectBrush((HBRUSH)::GetStockObject(HOLLOW_BRUSH)); + dc.Rectangle(&rcItem); + if (oldBrush) + dc.SelectBrush(oldBrush); + if (oldPen) + dc.SelectPen(oldPen); + } CRect rc(rcItem); - if (m_metrics.sizeIcon.cx) + if (m_menuMetrics.sizeIcon.cx) { - rc.left += m_metrics.sizeIcon.cx + (m_metrics.paddingIcon.cx * 2); + rc.left += m_menuMetrics.sizeIcon.cx + (m_menuMetrics.paddingIcon.cx * 2); } if (isSeparator) { - rc.top += rc.Height() / 2; - rc.bottom = rc.top + 1; - CPenDC penSep(dc, m_metrics.crBorder); - //CBrushDC brushSep(dc, m_metrics.crBorder); - dc.MoveTo(rc.left, rc.top); - dc.LineTo(rc.right, rc.top); - //dc.DrawEdge(rc, EDGE_ETCHED, BF_TOP); + m_menuTheme.DrawThemeBackground(dc, MENU_POPUPSEPARATOR, 0, rc, NULL); } else { @@ -339,8 +352,8 @@ BOOL CShellBrowseMenu::CustomDrawMenuItem(LPDRAWITEMSTRUCT lpDis) ildp.himl = (HIMAGELIST)m_pImageList.p; ildp.i = pData->iconIndex; ildp.hdcDst = lpDis->hDC; - ildp.x = lpDis->rcItem.left + m_metrics.paddingIcon.cx; - ildp.y = lpDis->rcItem.top + m_metrics.paddingIcon.cy; + ildp.x = lpDis->rcItem.left + m_menuMetrics.paddingIcon.cx; + ildp.y = lpDis->rcItem.top + m_menuMetrics.paddingIcon.cy; ildp.rgbBk = CLR_NONE; ildp.rgbFg = CLR_DEFAULT; ildp.fStyle = ILD_TRANSPARENT; @@ -348,12 +361,14 @@ BOOL CShellBrowseMenu::CustomDrawMenuItem(LPDRAWITEMSTRUCT lpDis) m_pImageList->Draw(&ildp); } - dc.SetTextColor(isDisabled ? m_metrics.crTextDisabled : m_metrics.crText); dc.SetBkMode(TRANSPARENT); - rc.left += m_metrics.paddingText.cx; - CFontDC font(dc, m_metrics.fontMnu); - dc.DrawText(pData->caption, pData->caption.GetLength(), rc, DT_SINGLELINE | DT_VCENTER | DT_LEFT | DT_END_ELLIPSIS); + rc.left += m_menuMetrics.paddingText.cx; + DWORD dwFlags = DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS; + if (ODS_NOACCEL == (ODS_NOACCEL & lpDis->itemState)) { + dwFlags |= DT_HIDEPREFIX; + } + m_menuTheme.DrawThemeText(dc, MENU_POPUPITEM, itemState, pData->caption, -1, dwFlags, 0, rc); if (isSubmenu) { @@ -365,47 +380,6 @@ BOOL CShellBrowseMenu::CustomDrawMenuItem(LPDRAWITEMSTRUCT lpDis) return TRUE; } -BOOL CShellBrowseMenu::CustomDrawMenuArrow(HDC hdcItem, LPRECT rcItem, bool isDisabled) -{ - CRect rcArrow(rcItem); - rcArrow.left = rcItem->right - m_metrics.sizeMnuArrow.cx; - - if (rcArrow.Height() > m_metrics.sizeMnuArrow.cy) - { - rcArrow.top += (rcArrow.Height() - m_metrics.sizeMnuArrow.cy) / 2; - rcArrow.bottom = rcArrow.top + m_metrics.sizeMnuArrow.cy; - } - - CDCHandle dc(hdcItem); - - CDC arrowDC; - arrowDC.CreateCompatibleDC(hdcItem); - - CBitmap arrowBitmap; - arrowBitmap.CreateCompatibleBitmap(dc, rcArrow.Width(), rcArrow.Height()); - auto oldArrowBmp = arrowDC.SelectBitmap(arrowBitmap); - - CDC fillDC; - fillDC.CreateCompatibleDC(hdcItem); - - CBitmap fillBitmap; - fillBitmap.CreateCompatibleBitmap(dc, rcArrow.Width(), rcArrow.Height()); - auto oldFillBmp = fillDC.SelectBitmap(fillBitmap); - - CRect rcTemp(0, 0, rcArrow.Width(), rcArrow.Height()); - auto result = arrowDC.DrawFrameControl(&rcTemp, DFC_MENU, DFCS_MENUARROW); - - fillDC.FillRect(rcTemp, isDisabled ? m_metrics.brushTextDisabled : m_metrics.brushText); - - dc.BitBlt(rcArrow.left, rcArrow.top, rcArrow.Width(), rcArrow.Height(), fillDC, 0, 0, SRCINVERT); - dc.BitBlt(rcArrow.left, rcArrow.top, rcArrow.Width(), rcArrow.Height(), arrowDC, 0, 0, SRCAND); - dc.BitBlt(rcArrow.left, rcArrow.top, rcArrow.Width(), rcArrow.Height(), fillDC, 0, 0, SRCINVERT); - - arrowDC.SelectBitmap(oldArrowBmp); - fillDC.SelectBitmap(oldFillBmp); - return result; -} - BOOL CShellBrowseMenu::MeasureMenuItem(LPMEASUREITEMSTRUCT lpMis) { auto pData = (LPSHELLMENUITEMDATA)lpMis->itemData; @@ -418,17 +392,15 @@ BOOL CShellBrowseMenu::MeasureMenuItem(LPMEASUREITEMSTRUCT lpMis) } else { - CWindowDC dc(NULL); - - CFontDC font(dc, m_metrics.fontMnu); + CWindowDC dc(GetOwnerHWND()); CRect rcText; - dc.DrawText(pData->caption, -1, rcText, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_CALCRECT); + m_menuTheme.GetThemeTextExtent(dc, MENU_POPUPITEM, MPI_NORMAL, pData->caption, -1, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_CALCRECT, NULL, rcText); - lpMis->itemHeight = m_metrics.itemHeight; - lpMis->itemWidth = rcText.Width() + m_metrics.paddingIcon.cx + m_metrics.sizeMnuArrow.cx; - if (m_metrics.sizeIcon.cx) + lpMis->itemHeight = m_menuMetrics.itemHeight; + lpMis->itemWidth = rcText.Width() + m_menuMetrics.paddingIcon.cx + m_menuMetrics.sizeMnuArrow.cx; + if (m_menuMetrics.sizeIcon.cx) { - lpMis->itemWidth += (m_metrics.paddingText.cx * 2) + m_metrics.sizeIcon.cx; + lpMis->itemWidth += (m_menuMetrics.paddingText.cx * 2) + m_menuMetrics.sizeIcon.cx; } } @@ -437,64 +409,13 @@ BOOL CShellBrowseMenu::MeasureMenuItem(LPMEASUREITEMSTRUCT lpMis) HRESULT CShellBrowseMenu::LoadIconImages() { - return ::SHGetImageList(SHIL_SMALL, IID_IImageList, (void**) &m_pImageList); -} - -void CShellBrowseMenu::UpdateMetrics(bool colorsOnly) -{ - auto isDark = IsInDarkMode(); - m_metrics.crBg = isDark ? HLS_TRANSFORM(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background), +20, -20) : ::GetSysColor(COLOR_3DFACE); - m_metrics.crText = isDark ? uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground) : ::GetSysColor(COLOR_MENUTEXT); - m_metrics.crTextDisabled = isDark ? HLS_TRANSFORM(m_metrics.crText, -50, 0) : ::GetSysColor(COLOR_GRAYTEXT); - m_metrics.crBorder = isDark ? HLS_TRANSFORM(m_metrics.crTextDisabled, -20, 0) : ::GetSysColor(COLOR_SCROLLBAR); - m_metrics.crHihghlight = isDark ? uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background) : ::GetSysColor(COLOR_MENUHILIGHT); - m_metrics.crHighlightBg = isDark ? HLS_TRANSFORM(m_metrics.crHihghlight, +40, -17) : HLS_TRANSFORM(m_metrics.crHihghlight, +70, -57); - if (isDark) + auto result = ::SHGetImageList(SHIL_SMALL, IID_IImageList, (void**) &m_pImageList); + if (SUCCEEDED(result) && m_pImageList) { - if (!m_metrics.brushBg.IsNull()) - m_metrics.brushBg.DeleteObject(); - m_metrics.brushBg.CreateSolidBrush(m_metrics.crBg); - } - if (!m_metrics.brushText.IsNull()) - m_metrics.brushText.DeleteObject(); - m_metrics.brushText.CreateSolidBrush(m_metrics.crText); - if (!m_metrics.brushTextDisabled.IsNull()) - m_metrics.brushTextDisabled.DeleteObject(); - m_metrics.brushTextDisabled.CreateSolidBrush(m_metrics.crTextDisabled); - - if (!colorsOnly) - { - CBitmap bmpArrow; - BITMAP bm; - // ::LoadImage(NULL, MAKEINTRESOURCE(OBM_MNARROW), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_SHARED) - if (bmpArrow.LoadOEMBitmap(OBM_MNARROW) && bmpArrow.GetBitmap(bm)) - { - m_metrics.sizeMnuArrow.cx = bm.bmWidth; - m_metrics.sizeMnuArrow.cy = bm.bmHeight; - } - - if (m_pImageList) - { - m_pImageList->GetIconSize((int*)&m_metrics.sizeIcon.cx, (int*)&m_metrics.sizeIcon.cy); - m_pImageList->GetIconSize((int*)&m_metrics.sizeIcon.cx, (int*)&m_metrics.sizeIcon.cy); - } - - m_metrics.metricsNC.cbSize = sizeof(NONCLIENTMETRICS); - if (::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, m_metrics.metricsNC.cbSize, &m_metrics.metricsNC, 0)) - { - m_metrics.fontMnu.CreateFontIndirect(&m_metrics.metricsNC.lfMenuFont); - - m_metrics.itemHeight = max(m_metrics.metricsNC.iMenuHeight, ::abs(m_metrics.metricsNC.lfMenuFont.lfHeight) + (m_metrics.paddingText.cy * 2)); - if (m_metrics.sizeIcon.cy && m_metrics.itemHeight < (m_metrics.paddingIcon.cy * 2) + m_metrics.sizeIcon.cy) - { - m_metrics.itemHeight = (m_metrics.paddingIcon.cy * 2) + m_metrics.sizeIcon.cy; - } - if (m_metrics.paddingIcon.cy > m_metrics.itemHeight - m_metrics.sizeIcon.cy - m_metrics.paddingIcon.cy) - { - m_metrics.paddingIcon.cy = (m_metrics.itemHeight - m_metrics.sizeIcon.cy) / 2; - } - } + m_pImageList->GetIconSize((int*)&m_menuMetrics.sizeIcon.cx, (int*)&m_menuMetrics.sizeIcon.cy); + m_pImageList->GetIconSize((int*)&m_menuMetrics.sizeIcon.cx, (int*)&m_menuMetrics.sizeIcon.cy); } + return result; } BOOL CShellBrowseMenu::SetupMenuInfo(CMenuHandle& menu) @@ -506,11 +427,7 @@ BOOL CShellBrowseMenu::SetupMenuInfo(CMenuHandle& menu) mi.cbSize = sizeof(mi); mi.fMask = MIM_STYLE; mi.dwStyle = MNS_CHECKORBMP; - if (IsInDarkMode()) - { - mi.fMask |= MIM_BACKGROUND; - mi.hbrBack = m_metrics.brushBg; - } + UxModeUpdateMenuInfo(mi); return menu.SetMenuInfo(&mi); } return FALSE; diff --git a/src/app/ShellBrowseMenu.h b/src/app/ShellBrowseMenu.h index 84226ed..aab9d78 100644 --- a/src/app/ShellBrowseMenu.h +++ b/src/app/ShellBrowseMenu.h @@ -7,6 +7,7 @@ #endif class CShellBrowseMenu + : public CUxModeMenuHelper { public: enum MessageID @@ -54,31 +55,12 @@ class CShellBrowseMenu virtual ~FolderSelection() = default; }; - struct MenuMetrics - { - COLORREF crText{RGB(0,0,0)}; - COLORREF crTextDisabled{ RGB(128,128,128) }; - COLORREF crBg{ RGB(255,255,255) }; - COLORREF crHihghlight{ RGB(200,200,200) }; - COLORREF crHighlightBg{ RGB(200,200,200) }; - COLORREF crBorder{ RGB(200,200,200) }; - CSize sizeIcon{ 0, 0 }; - CSize sizeMnuArrow{ 16, 16 }; - CSize paddingText{ 5, 5 }; - CSize paddingIcon{ 2, 2 }; - int itemHeight{-1}; - NONCLIENTMETRICS metricsNC{ 0 }; - CBrush brushText; - CBrush brushTextDisabled; - CBrush brushBg; - CFont fontMnu; - }; - CShellBrowseMenu(const ShellMenuController* controller = NULL); virtual ~CShellBrowseMenu() = default; BEGIN_MSG_MAP(CShellBrowseMenu) + CHAIN_MSG_MAP(CUxModeMenuHelper) MESSAGE_HANDLER(WM_MENURBUTTONUP, OnMenuRButtonUp) MESSAGE_HANDLER(WM_RBUTTONUP, OnRButtonUp) MESSAGE_HANDLER(WM_MENUCOMMAND, OnMenuCommand) @@ -87,7 +69,6 @@ class CShellBrowseMenu MESSAGE_HANDLER(WM_UNINITMENUPOPUP, OnUninitMenuPopup) MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem) MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem) - MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChange) END_MSG_MAP() const CShellItemIDList& GetRootIDL() const @@ -110,6 +91,7 @@ class CShellBrowseMenu ATLASSERT(controller); m_controller = controller; m_mnuTop.Attach(m_controller ? m_controller->GetTopHMenu() : NULL); + UxModeSetup(); SetupMenuInfo(m_mnuTop); if (!m_rootIDL.IsNull()) { @@ -127,9 +109,14 @@ class CShellBrowseMenu HRESULT Rebuild(); BOOL InvokeWithSelection(LPCTSTR strVerb = _T("Open")) const; +protected: + virtual void UxModeUpdateColorSettings() override; + virtual HWND GetOwnerHWND() override + { + return m_controller ? m_controller->GetHWnd() : NULL; + } private: - LRESULT OnThemeChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled); LRESULT OnInitMenuPopup(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnUninitMenuPopup(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnMenuSelect(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled); @@ -140,26 +127,18 @@ class CShellBrowseMenu LRESULT OnMeasureItem(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled); BOOL CustomDrawMenuItem(LPDRAWITEMSTRUCT lpDis); - BOOL CustomDrawMenuArrow(HDC hdcItem, LPRECT rcItem, bool isDisabled); BOOL MeasureMenuItem(LPMEASUREITEMSTRUCT lpMis); HRESULT LoadIconImages(); - void UpdateMetrics(bool colorsOnly = false); BOOL SetupMenuInfo(CMenuHandle& menu); HRESULT BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu); void CleanUpMenuData(HMENU hMenu); - static inline bool IsInDarkMode() - { - return uxTheme.ShouldAppsUseDarkMode() && !uxTheme.IsHighContrast(); - } - private: const ShellMenuController* m_controller; CMenuHandle m_mnuTop; bool m_isRendered; bool m_isCtxMenuShowing; - MenuMetrics m_metrics; CShellItemIDList m_rootIDL; CSimpleStack m_openMenus; CComPtr m_pImageList; diff --git a/src/app/WinPinMenu.vcxproj b/src/app/WinPinMenu.vcxproj index 061b126..7d7a313 100644 --- a/src/app/WinPinMenu.vcxproj +++ b/src/app/WinPinMenu.vcxproj @@ -140,6 +140,7 @@ Disabled _WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) stdcpp17 + ../wtlx Windows @@ -171,6 +172,7 @@ Disabled _WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) stdcpp17 + ../wtlx Windows @@ -203,6 +205,7 @@ Disabled WIN32;_WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) stdcpp17 + ../wtlx Windows @@ -234,6 +237,7 @@ WIN32;_WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) stdcpp17 + ../wtlx Windows @@ -264,6 +268,7 @@ _WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) stdcpp17 + ../wtlx Windows @@ -294,6 +299,7 @@ _WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) stdcpp17 + ../wtlx Windows @@ -317,8 +323,6 @@ - - @@ -334,8 +338,6 @@ - - @@ -343,8 +345,6 @@ - - @@ -361,6 +361,11 @@ + + + {7d0481bf-835e-4faa-820c-0ef96d007c7a} + + diff --git a/src/app/WinPinMenu.vcxproj.filters b/src/app/WinPinMenu.vcxproj.filters index 2b8f47f..ebd54f1 100644 --- a/src/app/WinPinMenu.vcxproj.filters +++ b/src/app/WinPinMenu.vcxproj.filters @@ -33,12 +33,6 @@ Source Files - - Source Files - - - Source Files - @@ -56,27 +50,15 @@ Header Files - - Header Files - - - Header Files - Header Files Header Files - - Header Files - Header Files - - Header Files - diff --git a/src/app/buildnumber.txt b/src/app/buildnumber.txt index 72f523f..31ff414 100644 --- a/src/app/buildnumber.txt +++ b/src/app/buildnumber.txt @@ -1 +1 @@ -39 \ No newline at end of file +48 \ No newline at end of file diff --git a/src/app/productmeta.h b/src/app/productmeta.h index 316612a..a05f05e 100644 --- a/src/app/productmeta.h +++ b/src/app/productmeta.h @@ -4,7 +4,7 @@ #define PRODUCT_VERSION_MAJOR 0 #define PRODUCT_VERSION_MINOR 1 #define PRODUCT_VERSION_PATCH 1 -#define PRODUCT_VERSION_TWEAK 39 +#define PRODUCT_VERSION_TWEAK 48 #define PRODUCT_NAME _T("WinPinMenu\0") #define PRODUCT_TITLE _T("Pinnable Taskbar Menu\0") #define PRODUCT_DESCRIPTION _T("diVISION Pinnable Taskbar Menu For Windows\0") @@ -20,5 +20,5 @@ #define FILE_NAME _T("WinPinMenu\0") #define FILE_DESCRIPTION _T("diVISION Pinnable Taskbar Menu For Windows\0") -#define FILE_VERSION 0,1,1,39 -#define FILE_VERSION_S _T("0.1.1.39\0") +#define FILE_VERSION 0,1,1,48 +#define FILE_VERSION_S _T("0.1.1.48\0") diff --git a/src/app/res/app.manifest b/src/app/res/app.manifest index aaa0996..711209e 100644 --- a/src/app/res/app.manifest +++ b/src/app/res/app.manifest @@ -1,6 +1,6 @@ - + diVISION Pinnable Taskbar Menu For Windows diff --git a/src/app/stdafx.h b/src/app/stdafx.h index 665d2b8..3eba108 100644 --- a/src/app/stdafx.h +++ b/src/app/stdafx.h @@ -14,8 +14,6 @@ #include -#include - #include #if (_ATL_VER >= 0x0700) #include @@ -40,5 +38,6 @@ extern CAppModule _Module; #include -#include "taskbarautomation.h" -#include "uxthemehelper.h" +#include +#include +#include diff --git a/src/app/uxthemehelper.h b/src/app/uxthemehelper.h deleted file mode 100644 index 3cd0f75..0000000 --- a/src/app/uxthemehelper.h +++ /dev/null @@ -1,451 +0,0 @@ -#pragma once - -#include -#include - -#include - -#pragma comment(lib, "Dwmapi.lib") - -enum class IMMERSIVE_HC_CACHE_MODE -{ - IHCM_USE_CACHED_VALUE, - IHCM_REFRESH -}; - -enum class PreferredAppMode -{ - Default, - AllowDark, - ForceDark, - ForceLight, - Max -}; - -enum WINDOWCOMPOSITIONATTRIB -{ - WCA_UNDEFINED = 0, - WCA_NCRENDERING_ENABLED = 1, - WCA_NCRENDERING_POLICY = 2, - WCA_TRANSITIONS_FORCEDISABLED = 3, - WCA_ALLOW_NCPAINT = 4, - WCA_CAPTION_BUTTON_BOUNDS = 5, - WCA_NONCLIENT_RTL_LAYOUT = 6, - WCA_FORCE_ICONIC_REPRESENTATION = 7, - WCA_EXTENDED_FRAME_BOUNDS = 8, - WCA_HAS_ICONIC_BITMAP = 9, - WCA_THEME_ATTRIBUTES = 10, - WCA_NCRENDERING_EXILED = 11, - WCA_NCADORNMENTINFO = 12, - WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, - WCA_VIDEO_OVERLAY_ACTIVE = 14, - WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, - WCA_DISALLOW_PEEK = 16, - WCA_CLOAK = 17, - WCA_CLOAKED = 18, - WCA_ACCENT_POLICY = 19, - WCA_FREEZE_REPRESENTATION = 20, - WCA_EVER_UNCLOAKED = 21, - WCA_VISUAL_OWNER = 22, - WCA_HOLOGRAPHIC = 23, - WCA_EXCLUDED_FROM_DDA = 24, - WCA_PASSIVEUPDATEMODE = 25, - WCA_USEDARKMODECOLORS = 26, - WCA_LAST = 27 -}; - -struct WINDOWCOMPOSITIONATTRIBDATA -{ - WINDOWCOMPOSITIONATTRIB Attrib; - PVOID pvData; - SIZE_T cbData; -}; - - -using FnRefreshImmersiveColorPolicyState = void (WINAPI*)(); // ordinal 104 -using FnGetIsImmersiveColorUsingHighContrast = bool (WINAPI*)(IMMERSIVE_HC_CACHE_MODE mode); // ordinal 106 -using FnShouldAppsUseDarkMode = bool (WINAPI*)(); // ordinal 132 -using FnAllowDarkModeForWindow = bool (WINAPI*)(HWND hWnd, bool allow); // ordinal 133 -using FnSetPreferredAppMode = PreferredAppMode(WINAPI*)(PreferredAppMode appMode); // ordinal 135, in 1903 -using FnFlushMenuThemes = void (WINAPI*)(); // ordinal 136 -using FnSetWindowCompositionAttribute = BOOL(WINAPI*)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*); - -#define UXTHEME_DECLARE_FUNC(name) \ -Fn##name m_pfn##name{NULL} - -#define UXTHEME_INIT_FUNC(name, hMod) \ -m_pfn##name = reinterpret_cast(::GetProcAddress(hMod, #name)); \ -ATLASSERT(m_pfn##name) - -#define UXTHEME_INIT_ORD_FUNC(name, ord, hMod) \ -m_pfn##name = reinterpret_cast(::GetProcAddress(hMod, MAKEINTRESOURCEA(ord))); \ -ATLASSERT(m_pfn##name) - -class CUxTheme -{ -public: - using UIColorType = winrt::Windows::UI::ViewManagement::UIColorType; - using UIElementType = winrt::Windows::UI::ViewManagement::UIElementType; - - CUxTheme() - { - m_hUxtheme = ::LoadLibraryEx(_T("uxtheme.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); - ATLASSERT(m_hUxtheme); - if (m_hUxtheme) - { - UXTHEME_INIT_ORD_FUNC(RefreshImmersiveColorPolicyState, 104, m_hUxtheme); - UXTHEME_INIT_ORD_FUNC(GetIsImmersiveColorUsingHighContrast, 106, m_hUxtheme); - UXTHEME_INIT_ORD_FUNC(ShouldAppsUseDarkMode, 132, m_hUxtheme); - UXTHEME_INIT_ORD_FUNC(AllowDarkModeForWindow, 133, m_hUxtheme); - UXTHEME_INIT_ORD_FUNC(SetPreferredAppMode, 135, m_hUxtheme); - UXTHEME_INIT_ORD_FUNC(FlushMenuThemes, 136, m_hUxtheme); - } - auto hModule = ::GetModuleHandle(_T("user32.dll")); - if (hModule) - { - UXTHEME_INIT_FUNC(SetWindowCompositionAttribute, hModule); - } - } - - ~CUxTheme() - { - if (m_hUxtheme) - ::FreeLibrary(m_hUxtheme); - } - - bool ShouldAppsUseDarkMode() const - { - if (m_pfnShouldAppsUseDarkMode) - return m_pfnShouldAppsUseDarkMode(); - return false; - } - - bool IsHighContrast() const - { - HIGHCONTRASTW highContrast = { sizeof(highContrast) }; - if (::SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(highContrast), &highContrast, FALSE)) - return highContrast.dwFlags & HCF_HIGHCONTRASTON; - return false; - } - - PreferredAppMode SetPreferredAppMode(PreferredAppMode appMode) const - { - if (m_pfnSetPreferredAppMode) - return m_pfnSetPreferredAppMode(appMode); - return PreferredAppMode::Default; - } - - void FlushMenuThemes() const - { - if (m_pfnFlushMenuThemes) - m_pfnFlushMenuThemes(); - } - - bool AllowDarkModeForWindow(HWND hWnd, bool allow) const - { - DWMNCRENDERINGPOLICY ncrp = allow ? DWMNCRP_ENABLED : DWMNCRP_DISABLED; - ::DwmSetWindowAttribute(hWnd, DWMWA_NCRENDERING_POLICY, &ncrp, sizeof(ncrp)); - - auto result = false; - if (m_pfnAllowDarkModeForWindow) - result = m_pfnAllowDarkModeForWindow(hWnd, allow); - - return result; - } - - BOOL SetWindowCompositionAttribute(HWND hWnd, WINDOWCOMPOSITIONATTRIB attr, BOOL value) const - { - if (m_pfnSetWindowCompositionAttribute) - { - WINDOWCOMPOSITIONATTRIBDATA data = { attr, &value, sizeof(value) }; - return m_pfnSetWindowCompositionAttribute(hWnd, &data); - } - return FALSE; - } - - bool SwitchWindowDarkMode(HWND hWnd, bool setDark, bool immersive = false) const - { - auto result = false; - if (immersive) - { - BOOL value = setDark ? TRUE : FALSE; - result = SUCCEEDED(::DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value))); - } - else - { - result = SetWindowCompositionAttribute(hWnd, WINDOWCOMPOSITIONATTRIB::WCA_USEDARKMODECOLORS, setDark ? TRUE : FALSE); - } - return result; - } - - bool IsColorSchemeChangeMessage(LPARAM lParam) const - { - auto result = false; - - if (lParam && 0 == ::lstrcmpi(reinterpret_cast(lParam), _T("ImmersiveColorSet"))) - { - if (m_pfnRefreshImmersiveColorPolicyState) - { - m_pfnRefreshImmersiveColorPolicyState(); - } - result = true; - } - if (m_pfnGetIsImmersiveColorUsingHighContrast) - { - m_pfnGetIsImmersiveColorUsingHighContrast(IMMERSIVE_HC_CACHE_MODE::IHCM_REFRESH); - } - return result; - } - - bool IsColorSchemeChangeMessage(UINT message, LPARAM lParam) const - { - if (message == WM_SETTINGCHANGE) - return IsColorSchemeChangeMessage(lParam); - return false; - } - - COLORREF UIElementSysColor(UIElementType type) const - { - auto col = WinRTSettings.UIElementColor(type); - return RGB(col.R, col.G, col.B); - } - - COLORREF GetSysColorValue(UIColorType type) const - { - auto col = WinRTSettings.GetColorValue(type); - return RGB(col.R, col.G, col.B); - } - -private: - - HMODULE m_hUxtheme{NULL}; - UXTHEME_DECLARE_FUNC(RefreshImmersiveColorPolicyState); - UXTHEME_DECLARE_FUNC(GetIsImmersiveColorUsingHighContrast); - UXTHEME_DECLARE_FUNC(AllowDarkModeForWindow); - UXTHEME_DECLARE_FUNC(ShouldAppsUseDarkMode); - UXTHEME_DECLARE_FUNC(SetPreferredAppMode); - UXTHEME_DECLARE_FUNC(FlushMenuThemes); - UXTHEME_DECLARE_FUNC(SetWindowCompositionAttribute); - winrt::Windows::UI::ViewManagement::UISettings WinRTSettings; -}; - -extern CUxTheme uxTheme; - -#define UXCOLOR_DARKER(color, factor) \ -max(RGB(0x50, 0x50, 0x50), RGB(((color & 0xff0000) >> 16) * factor, ((color & 0x00ff00) >> 8) * factor, (color & 0x0000ff) * factor)) - -#define UXCOLOR_LIGHTER(color, factor) \ -min(RGB(0xfe, 0xfe, 0xfe), RGB(((color & 0xff0000) >> 16) * factor, ((color & 0x00ff00) >> 8) * factor, (color & 0x0000ff) * factor)) - -template -struct is_dialog -{ -private: - typedef std::true_type yes; - typedef std::false_type no; - - template static auto test(int) -> decltype(std::declval().GetDialogProc() != nullptr, yes()); - - template static no test(...); - -public: - - static constexpr bool value = std::is_same(0)), yes>::value; -}; - -template -class CUxModeWindow -{ -public: - BEGIN_MSG_MAP(CUxModeWindow) - if (IsDialog) - { - MESSAGE_HANDLER(WM_INITDIALOG, UxModeOnInitDialog) - } - else - { - MESSAGE_HANDLER(WM_CREATE, UxModeOnCreate) - } - MESSAGE_HANDLER(WM_CTLCOLORDLG, UxModeOnCtlColorDlg) - MESSAGE_HANDLER(WM_CTLCOLORSTATIC, UxModeOnCtlColorDlg) - MESSAGE_HANDLER(WM_SETTINGCHANGE, UxModeOnSettingChange) - MESSAGE_HANDLER(WM_THEMECHANGED, UxModeOnThemeChange) - END_MSG_MAP() - - static bool IsInDarkMode() - { - return uxTheme.ShouldAppsUseDarkMode() && !uxTheme.IsHighContrast(); - } - -public: - static bool IsDialog; - -protected: - - bool SetUxModeForButton(HWND hWnd) const - { - auto r = uxTheme.AllowDarkModeForWindow(hWnd, IsInDarkMode()); - auto hr = m_initialized ? S_OK : ::SetWindowTheme(hWnd, L"Explorer", NULL); - if (m_initialized) - { - ::SendMessage(hWnd, WM_THEMECHANGED, 0, 0); - } - return r && SUCCEEDED(hr); - } - - bool UxModeDrawGroupBox(HWND hWnd, HDC hDC) - { - CWindow wnd(hWnd); - CDCHandle dc(hDC); - - CRect rc; - wnd.GetClientRect(rc); - - CRect rcFrame(rc); - rcFrame.top += 10; - auto crBorder = uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground); - crBorder = UXCOLOR_DARKER(crBorder, 0.4); - CPen pen; - pen.CreatePen(PS_SOLID, 1, crBorder); - auto oldPen = dc.SelectPen(pen); - auto oldBrush = dc.SelectBrush(reinterpret_cast(::GetStockObject(HOLLOW_BRUSH))); - auto result = TRUE == dc.Rectangle(rcFrame); - dc.SelectPen(oldPen); - dc.SelectBrush(oldBrush); - - CString str; - wnd.GetWindowText(str); - if (str.GetLength()) - { - CRect rcText(rc); - rcText.left += 10; - rcText.bottom = 20; - auto oldFont = dc.SelectFont(reinterpret_cast(::GetStockObject(DEFAULT_GUI_FONT))); - result = 0 != dc.DrawText(str, str.GetLength(), rcText, DT_SINGLELINE | DT_VCENTER | DT_LEFT) && result; - dc.SelectFont(oldFont); - } - - if (result) - { - dc.ExcludeClipRect(rc); - } - return result; - } - -private: - - LRESULT UxModeOnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) - { - bHandled = FALSE; - UxModeSetup(); - return 0L; - } - - LRESULT UxModeOnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) - { - bHandled = FALSE; - UxModeSetup(); - return TRUE; - } - - LRESULT UxModeOnCtlColorDlg(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled) - { - bHandled = FALSE; - //ATLTRACE(_T(__FUNCTION__) _T("\n")); - if (IsInDarkMode() && !m_brushBg.IsNull()) - { - bHandled = TRUE; - - CWindow wnd(reinterpret_cast(lParam)); - - CDCHandle dc(reinterpret_cast(wParam)); - dc.SetBkColor(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); - dc.SetTextColor(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground)); - - if (BS_GROUPBOX == (BS_GROUPBOX & wnd.GetWindowLongPtr(GWL_STYLE))) - { - UxModeDrawGroupBox(wnd, dc); - } - return reinterpret_cast(WS_EX_TRANSPARENT == (WS_EX_TRANSPARENT & wnd.GetWindowLongPtr(GWL_EXSTYLE)) - ? ::GetStockObject(HOLLOW_BRUSH) - : m_brushBg.m_hBrush); - } - return FALSE; - } - - LRESULT UxModeOnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) - { - bHandled = FALSE; - if (uxTheme.IsColorSchemeChangeMessage(lParam)) - { - bHandled = TRUE; - GetThis()->SendMessage(WM_THEMECHANGED); - } - return 0L; - } - - LRESULT UxModeOnThemeChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) - { - bHandled = FALSE; - if (m_lastModeWasDark != IsInDarkMode()) - { - bHandled = TRUE; - UxModeSetup(); - GetThis()->UpdateWindow(); - } - return 0L; - } - - T* GetThis() - { - return static_cast(this); - } - - void UxModeSetup() - { - ATLTRACE(_T(__FUNCTION__) _T(" IsDialog=%d IsAppThemed=%d IsThemeActive=%d bgColor=%x\n"), IsDialog, ::IsAppThemed(), ::IsThemeActive() - , uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); - - auto isDark = IsInDarkMode(); - if (isDark && m_lastModeWasDark != isDark) - { - if (!m_brushBg.IsNull()) - m_brushBg.DeleteObject(); - m_brushBg.CreateSolidBrush(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); - } - - m_lastModeWasDark = isDark; - - auto pSelf = GetThis(); - if (!m_initialized) - uxTheme.AllowDarkModeForWindow(pSelf->m_hWnd, true); - uxTheme.SwitchWindowDarkMode(pSelf->m_hWnd, isDark); - - ::EnumChildWindows(pSelf->m_hWnd, [](HWND hwnd, LPARAM lParam) -> BOOL - { - auto pParent = reinterpret_cast(lParam); - CString str; - ::GetClassName(hwnd, str.GetBufferSetLength(MAX_PATH), MAX_PATH); - str.ReleaseBuffer(); - if (_T("Button") == str) - { - pParent->SetUxModeForButton(hwnd); - } - return TRUE; - }, reinterpret_cast(this)); - - if (!m_initialized) - pSelf->SendMessage(WM_THEMECHANGED); - - m_initialized = true; - } - -protected: - bool m_initialized{ false }; - bool m_lastModeWasDark{false}; - CBrush m_brushBg; - -}; - -template -bool CUxModeWindow::IsDialog = is_dialog::value; \ No newline at end of file diff --git a/src/app/Draw.cpp b/src/wtlx/Draw.cpp similarity index 94% rename from src/app/Draw.cpp rename to src/wtlx/Draw.cpp index 8763581..e1d0266 100644 --- a/src/app/Draw.cpp +++ b/src/wtlx/Draw.cpp @@ -9,9 +9,9 @@ * @author Igor Vigdorchik (http://www.codeproject.com/Members/IgorVigdorchik) ************************************************************************/ -#include "stdafx.h" +#include "pch.h" //#include "Tools.h" -#include "Draw.h" +#include "wtlx/Draw.h" /////////////////////////////////////////////////////////////////////////////// HLSCOLOR RGB2HLS (COLORREF rgb) @@ -104,6 +104,22 @@ COLORREF HLS_TRANSFORM (COLORREF rgb, int percent_L, int percent_S) return HLS2RGB (HLS(h, l, s)); } +UINT ImageList_GetBitsPerPixelFlag() +{ + UINT bppFlag = ILC_COLOR; + static const UINT colorDepths[] = { ILC_COLOR32, ILC_COLOR24, ILC_COLOR16, ILC_COLOR8, ILC_COLOR4 }; + auto bpp = (UINT)::GetDeviceCaps(::GetDC(::GetDesktopWindow()), BITSPIXEL); + for (auto colorDepth : colorDepths) + { + if (bpp >= colorDepth) + { + bppFlag = colorDepth; + break; + } + } + return bppFlag; +} + /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// diff --git a/src/app/FileVersionInfo.cpp b/src/wtlx/FileVersionInfo.cpp similarity index 98% rename from src/app/FileVersionInfo.cpp rename to src/wtlx/FileVersionInfo.cpp index 6f38c7b..5e44615 100644 --- a/src/app/FileVersionInfo.cpp +++ b/src/wtlx/FileVersionInfo.cpp @@ -8,11 +8,9 @@ * License: GPLv3, s. LICENSE.txt * @author Dmitri Zoubkov (dimamizou@users.sf.net) ************************************************************************/ -#include "stdafx.h" +#include "pch.h" #include -#include "FileVersionInfo.h" - -#pragma comment(lib,"Version.lib") +#include "wtlx/FileVersionInfo.h" CFileVersionInfo::CFileVersionInfo() : m_lpData(NULL) diff --git a/src/wtlx/IatHook.cpp b/src/wtlx/IatHook.cpp new file mode 100644 index 0000000..b9891fd --- /dev/null +++ b/src/wtlx/IatHook.cpp @@ -0,0 +1,72 @@ +#include "pch.h" +#include "wtlx/IatHook.h" + +PIMAGE_THUNK_DATA FindAddressByName(void* moduleBase, PIMAGE_THUNK_DATA impName, PIMAGE_THUNK_DATA impAddr, const char* funcName) +{ + for (; impName->u1.Ordinal; ++impName, ++impAddr) + { + if (IMAGE_SNAP_BY_ORDINAL(impName->u1.Ordinal)) + continue; + + auto import = RVA2VA(moduleBase, impName->u1.AddressOfData); + if (strcmp(import->Name, funcName) != 0) + continue; + return impAddr; + } + return nullptr; +} + +PIMAGE_THUNK_DATA FindAddressByOrdinal(void*/*moduleBase*/, PIMAGE_THUNK_DATA impName, PIMAGE_THUNK_DATA impAddr, uint16_t ordinal) +{ + for (; impName->u1.Ordinal; ++impName, ++impAddr) + { + if (IMAGE_SNAP_BY_ORDINAL(impName->u1.Ordinal) && IMAGE_ORDINAL(impName->u1.Ordinal) == ordinal) + return impAddr; + } + return nullptr; +} + +PIMAGE_THUNK_DATA FindIatThunkInModule(void* moduleBase, const char* dllName, const char* funcName) +{ + auto imports = DataDirectoryFromModuleBase(moduleBase, IMAGE_DIRECTORY_ENTRY_IMPORT); + for (; imports->Name; ++imports) + { + if (_stricmp(RVA2VA(moduleBase, imports->Name), dllName) != 0) + continue; + + auto origThunk = RVA2VA(moduleBase, imports->OriginalFirstThunk); + auto thunk = RVA2VA(moduleBase, imports->FirstThunk); + return FindAddressByName(moduleBase, origThunk, thunk, funcName); + } + return nullptr; +} + +PIMAGE_THUNK_DATA FindDelayLoadThunkInModule(void* moduleBase, const char* dllName, const char* funcName) +{ + auto imports = DataDirectoryFromModuleBase(moduleBase, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + for (; imports->DllNameRVA; ++imports) + { + if (_stricmp(RVA2VA(moduleBase, imports->DllNameRVA), dllName) != 0) + continue; + + auto impName = RVA2VA(moduleBase, imports->ImportNameTableRVA); + auto impAddr = RVA2VA(moduleBase, imports->ImportAddressTableRVA); + return FindAddressByName(moduleBase, impName, impAddr, funcName); + } + return nullptr; +} + +PIMAGE_THUNK_DATA FindDelayLoadThunkInModule(void* moduleBase, const char* dllName, uint16_t ordinal) +{ + auto imports = DataDirectoryFromModuleBase(moduleBase, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + for (; imports->DllNameRVA; ++imports) + { + if (_stricmp(RVA2VA(moduleBase, imports->DllNameRVA), dllName) != 0) + continue; + + auto impName = RVA2VA(moduleBase, imports->ImportNameTableRVA); + auto impAddr = RVA2VA(moduleBase, imports->ImportAddressTableRVA); + return FindAddressByOrdinal(moduleBase, impName, impAddr, ordinal); + } + return nullptr; +} diff --git a/src/wtlx/formattools.cpp b/src/wtlx/formattools.cpp new file mode 100644 index 0000000..7fc8df0 --- /dev/null +++ b/src/wtlx/formattools.cpp @@ -0,0 +1,194 @@ +/************************************************************************ + * $Revision: 33 $ + * $Date: 2024-10-28 17:50:24 +0100 (Mon, 28 Oct 2024) $ + ************************************************************************/ +/************************************************************************ + * File: formattools.cpp + * Copyright: 2016 diVISION Soft, some rights reserved + * License: GPLv3, s. LICENSE.txt + * @author Dmitri Zoubkov (dimamizou@users.sf.net) + ************************************************************************/ +#include "pch.h" +#include "wtlx/formattools.h" +#include +#include +#include + + +static const _bstr_t MU_ABBREVIATIONS[] = { + _bstr_t(L"B"), + _bstr_t(L"KiB"), + _bstr_t(L"MiB"), + _bstr_t(L"GiB"), + _bstr_t(L"TiB") +}; + +STDAPI FormatMemoryUnits(_In_ const DECIMAL *pdecIn, _In_ LCID lcid, _Out_ BSTR *pbstrOut) +{ + DECIMAL din = *pdecIn; + DECIMAL dres = *pdecIn; + BSTR lpUnit = MU_ABBREVIATIONS[MU_BYTES]; + DECIMAL ddiv; + HRESULT hr = ::VarDecFromInt(1024, &ddiv); + for (int i = MU_KB; SUCCEEDED(hr) && i < sizeof(MU_ABBREVIATIONS) / sizeof(_bstr_t); i++) + { + hr = ::VarDecCmp(&dres, &ddiv); + if (VARCMP_LT < hr) + { + lpUnit = MU_ABBREVIATIONS[i]; + hr = ::VarDecDiv(&din, &ddiv, &dres); + din = dres; + } + else { + break; + } + } + if (FAILED(hr)) return hr; + + hr = ::VarDecRound(&din, 2, &dres); + if (FAILED(hr)) return hr; + + _bstr_t bstr; + hr = ::VarBstrFromDec(&dres, lcid, 0, bstr.GetAddress()); + if (SUCCEEDED(hr)) + { + bstr += L" "; + bstr += lpUnit; + *pbstrOut = bstr.copy(); + } + return hr; +} + +STDAPI FormatError(_In_ HRESULT hr, _In_ LCID lcid, _Out_ BSTR *pbstrOut) +{ + HRESULT result = S_OK; + HINSTANCE hInst = NULL; + if (FACILITY_MSMQ == HRESULT_FACILITY(hr)) { + hInst = ::LoadLibrary(_T("MQUTIL.DLL")); + } + else if (FACILITY_WINDOWSUPDATE == HRESULT_FACILITY(hr)) { + //TODO: is WUAPI.DLL right for error messages? + hInst = ::LoadLibrary(_T("WUAPI.DLL")); + } + else if (NERR_BASE <= HRESULT_CODE(hr) && MAX_NERR >= HRESULT_CODE(hr)) { + hInst = ::LoadLibrary(_T("NETMSG.DLL")); + } + + HLOCAL pBuffer = NULL; + DWORD dwRes = ::FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | (hInst ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM) + | FORMAT_MESSAGE_IGNORE_INSERTS + , hInst, (DWORD)hr, lcid, (LPTSTR) &pBuffer, 1024, NULL + ); + + _bstr_t bstr; + if (0 == dwRes) + { + result = HRESULT_FROM_WIN32(::GetLastError()); + + dwRes = ::FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS + , NULL, (DWORD)E_FAIL, lcid, (LPTSTR)&pBuffer, 1024, NULL + ); + + _bstr_t bstrDesc; + if (dwRes && pBuffer) + { + bstrDesc = (LPTSTR)pBuffer; + ::LocalFree(pBuffer); + } + else { + bstrDesc = _T("Unknown error"); + } + + pBuffer = ::LocalAlloc(0, 64 * sizeof(TCHAR)); + if (pBuffer) + { + ZeroMemory(pBuffer, 64 * sizeof(TCHAR)); + ::_stprintf_s((LPTSTR)pBuffer, 64, _T("%s 0x%0lX"), bstrDesc.GetBSTR(), hr); + } + } + if (pBuffer) bstr = (LPTSTR)pBuffer; + *pbstrOut = bstr.copy(); + + if (pBuffer) ::LocalFree(pBuffer); + if (hInst) ::FreeLibrary(hInst); + return result; +} + +STDAPI FormatSysDateTime(_In_ const LPSYSTEMTIME pST, _In_ LCID lcid, _In_ DWORD dwDateFlags, _In_ DWORD dwTimeFlags, _Out_ BSTR *pbstrOut) +{ + HRESULT hr = S_OK; + if (NULL == pbstrOut || NULL == pST) return E_POINTER; + if (DTFORMAT_NODATE == dwDateFlags && DTFORMAT_NOTIME == dwTimeFlags) return E_INVALIDARG; + + _bstr_t bstr; + int iRes = 0; + LPSYSTEMTIME lpST = pST; + if (DTFORMAT_LOCALTIME & dwTimeFlags) + { + dwTimeFlags &= ~DTFORMAT_LOCALTIME; + + TIME_ZONE_INFORMATION tzi{ 0 }; + if (TIME_ZONE_ID_INVALID == ::GetTimeZoneInformation(&tzi)) { + hr = HRESULT_FROM_WIN32(::GetLastError()); + } + else + { + lpST = new SYSTEMTIME{ 0 }; + if (!::SystemTimeToTzSpecificLocalTime(&tzi, pST, lpST)) { + hr = ATL::AtlHresultFromLastError(); + } + } + + } + if (SUCCEEDED(hr)) + { + TCHAR szBuff[128]; + if (DTFORMAT_NODATE != dwDateFlags) + { + iRes = ::GetDateFormat(lcid, dwDateFlags, lpST, NULL, szBuff, 128); + if (0 == iRes) { + hr = HRESULT_FROM_WIN32(::GetLastError()); + } + } + + if (SUCCEEDED(hr)) + { + bstr = szBuff; + if (DTFORMAT_NOTIME != dwTimeFlags) + { + szBuff[0] = _T('\0'); + iRes = ::GetTimeFormat(lcid, dwTimeFlags, lpST, NULL, szBuff, 128); + if (0 == iRes) { + hr = HRESULT_FROM_WIN32(::GetLastError()); + } + else + { + bstr += _T(" "); + bstr += szBuff; + } + } + } + } + if (pST != lpST) delete lpST; + if (SUCCEEDED(hr)) *pbstrOut = bstr.copy(); + + return hr; +} + +STDAPI FormatOLEDateTime(_In_ DATE dt, _In_ LCID lcid, _In_ DWORD dwDateFlags, _In_ DWORD dwTimeFlags, _Out_ BSTR *pbstrOut) +{ + HRESULT hr = S_OK; + SYSTEMTIME st = { 0 }; + if (!::VariantTimeToSystemTime(dt, &st)) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + if (SUCCEEDED(hr)) hr = E_FAIL; + } + if (SUCCEEDED(hr)) hr = ::FormatSysDateTime(&st, lcid, dwDateFlags, dwTimeFlags, pbstrOut); + return hr; +} \ No newline at end of file diff --git a/src/wtlx/framework.h b/src/wtlx/framework.h new file mode 100644 index 0000000..01426d9 --- /dev/null +++ b/src/wtlx/framework.h @@ -0,0 +1,18 @@ +#pragma once + +#include "targetver.h" +#ifndef OEMRESOURCE +#define OEMRESOURCE 1 +#endif // !OEMRESOURCE + +#include +#if (_ATL_VER >= 0x0700) +#include +#include +#define _WTL_NO_CSTRING +#define _WTL_NO_WTYPES +#define _WTL_NO_UNION_CLASSES +#endif + +#include +#include diff --git a/src/wtlx/packages.config b/src/wtlx/packages.config new file mode 100644 index 0000000..3b4929a --- /dev/null +++ b/src/wtlx/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/wtlx/pch.cpp b/src/wtlx/pch.cpp new file mode 100644 index 0000000..64b7eef --- /dev/null +++ b/src/wtlx/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/wtlx/pch.h b/src/wtlx/pch.h new file mode 100644 index 0000000..885d5d6 --- /dev/null +++ b/src/wtlx/pch.h @@ -0,0 +1,13 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#endif //PCH_H diff --git a/src/wtlx/targetver.h b/src/wtlx/targetver.h new file mode 100644 index 0000000..7390d7e --- /dev/null +++ b/src/wtlx/targetver.h @@ -0,0 +1,6 @@ +#pragma once +// Change these values to use different versions +#define WINVER 0x0A00 +#define _WIN32_WINNT 0x0A00 +#define _WIN32_IE 0x0A00 +#define _RICHEDIT_VER 0x0500 diff --git a/src/wtlx/wtlx.vcxproj b/src/wtlx/wtlx.vcxproj new file mode 100644 index 0000000..5cf2b30 --- /dev/null +++ b/src/wtlx/wtlx.vcxproj @@ -0,0 +1,275 @@ + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {7d0481bf-835e-4faa-820c-0ef96d007c7a} + wtlx + 10.0 + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + true + v143 + Unicode + Spectre + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + Spectre + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + + + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + + + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + + + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + + + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + + + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + ..\..\build\wtlx-$(Configuration)-$(PlatformShortName)\ + + + + Level4 + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp17 + MultiThreadedDebug + + + + + true + + + + + Level4 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp17 + MultiThreaded + + + + + true + true + true + + + + + Level4 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp17 + MultiThreadedDebug + + + + + true + + + + + Level4 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp17 + MultiThreadedDebug + + + + + true + + + + + Level4 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp17 + MultiThreaded + + + + + true + true + true + + + + + Level4 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp17 + MultiThreaded + + + + + true + true + true + + + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/src/wtlx/wtlx.vcxproj.filters b/src/wtlx/wtlx.vcxproj.filters new file mode 100644 index 0000000..915a09a --- /dev/null +++ b/src/wtlx/wtlx.vcxproj.filters @@ -0,0 +1,53 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + \ No newline at end of file diff --git a/src/wtlx/wtlx/CoolContextMenu.h b/src/wtlx/wtlx/CoolContextMenu.h new file mode 100644 index 0000000..d7d385f --- /dev/null +++ b/src/wtlx/wtlx/CoolContextMenu.h @@ -0,0 +1,496 @@ +/************************************************************************ + * $Revision: 33 $ + * $Date: 2024-10-28 17:50:24 +0100 (Mon, 28 Oct 2024) $ + ************************************************************************/ +/************************************************************************ + * File: CoolContextMenu.h - implementation of the CCoolContextMenu class + * Copyright: 2006 Igor Vigdorchik, some rights reserved + * License: CPOL, s. https://en.wikipedia.org/wiki/Code_Project_Open_License + * @author Igor Vigdorchik (http://www.codeproject.com/Members/IgorVigdorchik) + ************************************************************************/ + +#pragma once +#include "Draw.h" + +#define MAX_MENU_ITEM_TEXT_LENGTH 100 +#define IMGPADDING 6 +#define TEXTPADDING 8 + +// From +#ifndef OBM_CHECK +#define OBM_CHECK 32760 +#endif + +template +class CCoolContextMenu +{ +private: + SIZE m_szBitmap; + SIZE m_szButton; + + CFont m_fontMenu; // used internally, only to measure text + int m_cxExtraSpacing; + bool m_bFlatMenus; + COLORREF m_clrMask; + CBitmap m_bmpCheck; + + CSimpleStack m_stackMenuHandle; + +protected: + struct MenuItemData // menu item data + { + LPTSTR lpstrText; + UINT fType; + UINT fState; + int iImage; + + MenuItemData(LPCTSTR lpszText) + : lpstrText(NULL) + { + SetText(lpszText); + } + + ~MenuItemData() + { + delete[] lpstrText; + } + + void SetText(LPCTSTR lpszText) + { + if (NULL == lpszText) + { + delete[] lpstrText; + lpstrText = NULL; + } + else + { + lpstrText = new TCHAR[::lstrlen(lpszText) + 1]; + ATLASSERT(lpstrText != NULL); + if (lpstrText != NULL) + ::lstrcpyn(lpstrText, lpszText, MAX_MENU_ITEM_TEXT_LENGTH); + } + } + }; + +public: + CImageList m_imlCoolMenu; + +protected: + void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) + { + MenuItemData* pmd = (MenuItemData*)lpMeasureItemStruct->itemData; + + if (pmd->fType & MFT_SEPARATOR) // separator - use half system height and zero width + { + lpMeasureItemStruct->itemHeight = ::GetSystemMetrics(SM_CYMENU) / 2; + lpMeasureItemStruct->itemWidth = 0; + } + else + { + // Compute size of text - use DrawText with DT_CALCRECT + CWindowDC dc(NULL); + CFont fontBold; + HFONT hOldFont = NULL; + if (pmd->fState & MFS_DEFAULT) + { + // Need bold version of font + LOGFONT lf = { 0 }; + m_fontMenu.GetLogFont(lf); + lf.lfWeight += 200; + fontBold.CreateFontIndirect(&lf); + ATLASSERT(fontBold.m_hFont != NULL); + hOldFont = dc.SelectFont(fontBold); + fontBold.DeleteObject(); + } + else + hOldFont = dc.SelectFont(m_fontMenu); + + RECT rcText = { 0, 0, 0, 0 }; + dc.DrawText(pmd->lpstrText, -1, &rcText, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_CALCRECT); + + int cx = rcText.right - rcText.left; + + dc.SelectFont(hOldFont); + + LOGFONT lf = { 0 }; + m_fontMenu.GetLogFont(lf); + int cy = lf.lfHeight; + if (cy < 0) + cy = -cy; + const int cyMargin = 10; + cy += cyMargin; + + lpMeasureItemStruct->itemHeight = cy; + + // Width is width of text plus some + cx += 4; // L/R margin for readability + cx += 1; // space between button and menu text + cx += 2 * m_szButton.cx; // button width + cx += m_cxExtraSpacing; // extra between item text and accelerator keys + + // Windows adds 1 to returned value + cx -= ::GetSystemMetrics(SM_CXMENUCHECK) - 1; + lpMeasureItemStruct->itemWidth = cx; // we are done + } + } + + void DrawItem (LPDRAWITEMSTRUCT lpDrawItemStruct) + { + MenuItemData* pmd = (MenuItemData*)lpDrawItemStruct->itemData; + CDCHandle dc = lpDrawItemStruct->hDC; + RECT& rcItem = lpDrawItemStruct->rcItem; + LPCRECT pRect = &rcItem; + BOOL bDisabled = lpDrawItemStruct->itemState & ODS_GRAYED; + BOOL bSelected = lpDrawItemStruct->itemState & ODS_SELECTED; + BOOL bChecked = lpDrawItemStruct->itemState & ODS_CHECKED; + COLORREF crBackImg = CLR_NONE; + CDCHandle* pDC = &dc; + + if (bSelected && !bDisabled) + { + COLORREF crHighLight = ::GetSysColor(COLOR_HIGHLIGHT); + CPenDC pen(*pDC, crHighLight); + CBrushDC brush(*pDC, crBackImg = bDisabled ? HLS_TRANSFORM(::GetSysColor (COLOR_3DFACE), +73, 0) : + HLS_TRANSFORM (crHighLight, +70, -57)); + + pDC->Rectangle(pRect); + } + else + { + // Draw the menu item background + CRect rc(pRect); + + rc.right = m_szBitmap.cx + IMGPADDING; + pDC->FillSolidRect(rc, crBackImg = HLS_TRANSFORM(::GetSysColor(COLOR_3DFACE), +20, 0)); + rc.left = rc.right; + rc.right = pRect->right; + pDC->FillSolidRect(rc, HLS_TRANSFORM(::GetSysColor(COLOR_3DFACE), +75, 0)); + } + + // Menu item is a separator + if (pmd->fType & MFT_SEPARATOR) + { + rcItem.top += (rcItem.bottom - rcItem.top) / 2; // vertical center + dc.DrawEdge(&rcItem, EDGE_ETCHED, BF_TOP); // draw separator line + } + else + { + // Draw the text + CRect rc (pRect); + CString sCaption = pmd->lpstrText; + int nTab = sCaption.Find('\t'); + + if (nTab >= 0) + { + sCaption = sCaption.Left (nTab); + } + pDC->SetTextColor(bDisabled ? HLS_TRANSFORM(::GetSysColor(COLOR_3DFACE), -18, 0) : ::GetSysColor(COLOR_MENUTEXT)); + pDC->SetBkMode(TRANSPARENT); + + CBoldDC bold(*pDC, (lpDrawItemStruct->itemState & ODS_DEFAULT) != 0); + + rc.left = m_szBitmap.cx + IMGPADDING + TEXTPADDING; + pDC->DrawText(sCaption, sCaption.GetLength(), rc, DT_SINGLELINE|DT_VCENTER|DT_LEFT); + + if (nTab >= 0) + { + rc.right -= TEXTPADDING + 4; + pDC->DrawText(pmd->lpstrText + nTab + 1, (int) _tcslen(pmd->lpstrText + nTab + 1), rc, DT_SINGLELINE|DT_VCENTER|DT_RIGHT); + } + + // Draw background and border around the check mark + if (bChecked) + { + COLORREF crHighLight = ::GetSysColor(COLOR_HIGHLIGHT); + CPenDC pen(*pDC, crHighLight); + CBrushDC brush(*pDC, crBackImg = bDisabled ? HLS_TRANSFORM(::GetSysColor (COLOR_3DFACE), +73, 0) : + (bSelected ? HLS_TRANSFORM(crHighLight, +50, -50) : HLS_TRANSFORM(crHighLight, +70, -57))); + + pDC->Rectangle(CRect(pRect->left + 1, pRect->top + 1, pRect->left + m_szButton.cx - 2, pRect->bottom - 1)); + } + + if (m_imlCoolMenu != NULL && pmd->iImage >= 0) + { + bool bOver = !bDisabled && bSelected; + + if (bDisabled || (bSelected && !bChecked)) + { + CIcon ico = m_imlCoolMenu.ExtractIcon(pmd->iImage); + CBrush brush; + + brush.CreateSolidBrush(bOver ? HLS_TRANSFORM(::GetSysColor(COLOR_HIGHLIGHT), +50, -66) : HLS_TRANSFORM(::GetSysColor(COLOR_3DFACE), -27, 0)); + pDC->DrawState(CPoint(pRect->left + (bOver ? 4 : 3), rc.top + (bOver ? 5 : 4)), + CSize(m_szBitmap.cx, m_szBitmap.cx), ico, DSS_MONO, brush); + } + if (!bDisabled) + { + ::ImageList_Draw(m_imlCoolMenu, pmd->iImage, pDC->m_hDC, + pRect->left + ((bSelected && !bChecked) ? 2 : 3), rc.top + ((bSelected && !bChecked) ? 3 : 4), ILD_TRANSPARENT); + } + } + else if (bChecked) + { + // Draw the check mark + rc.left = pRect->left + 3; + rc.right = rc.left + m_szBitmap.cx + IMGPADDING; + pDC->SetBkColor(crBackImg); + if (!m_bmpCheck) + m_bmpCheck.LoadOEMBitmap(OBM_CHECK); + pDC->DrawState(CPoint(rc.left, rc.top + 3), CSize(rc.Size()), m_bmpCheck, DSS_NORMAL, (HBRUSH)NULL); + } + } + } + + LRESULT InitMenuPopupHandler(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + // System menu, do nothing + if ((BOOL)HIWORD(lParam)) + { + bHandled = FALSE; + return 1; + } + + CMenuHandle menuPopup = (HMENU)wParam; + ATLASSERT(menuPopup.m_hMenu != NULL); + + TCHAR szString[MAX_MENU_ITEM_TEXT_LENGTH]; + szString[0] = _T('\0'); + BOOL bRet = FALSE; + + for (UINT i = 0; i < (UINT) menuPopup.GetMenuItemCount(); i++) + { + CMenuItemInfo mii; + mii.cch = MAX_MENU_ITEM_TEXT_LENGTH; + mii.fMask = MIIM_CHECKMARKS | MIIM_DATA | MIIM_ID | MIIM_STATE | MIIM_SUBMENU | MIIM_TYPE; + mii.dwTypeData = szString; + bRet = menuPopup.GetMenuItemInfo(i, TRUE, &mii); + ATLASSERT(bRet); + + if (bRet && !(mii.fType & MFT_OWNERDRAW)) // not already an ownerdraw item + { + MenuItemData* pMI = new MenuItemData(szString); + ATLASSERT(pMI != NULL); + + if (pMI) + { + pMI->fType = mii.fType; + // Make this menu item an owner-drawn + mii.fType |= MFT_OWNERDRAW; + + + pMI->fState = mii.fState; + // SetMenuItemInfo won't accept MFS_DEFAULT if is already set + mii.fState &= ~MFS_DEFAULT; + + // Associate an image with a menu item + static_cast(this)->AssociateMenuImage(mii, pMI); + + mii.dwItemData = (ULONG_PTR) pMI; + + bRet = menuPopup.SetMenuItemInfo(i, TRUE, &mii); + if (!bRet) + { + ATLTRACE(_T("SetMenuItemInfo() failed with error: 0x%0lX, fType=0x%0lX, fState=0x%0lX\n") + , ATL::AtlHresultFromLastError(), mii.fType, mii.fState); + //delete pMI; + } + } + } + } + + // Add it to the list + m_stackMenuHandle.Push(menuPopup.m_hMenu); + + return 0; + } + + LRESULT MenuSelectHandler(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + // Check if a menu is closing, do a cleanup + if (HIWORD(wParam) == 0xFFFF && lParam == NULL) // Menu closing + { + // Restore the menu items to the previous state for all menus that were converted + HMENU hMenu = NULL; + while ((hMenu = m_stackMenuHandle.Pop()) != NULL) + { + CMenuHandle menuPopup = hMenu; + ATLASSERT(menuPopup.m_hMenu != NULL); + // Restore state and delete menu item data + BOOL bRet = FALSE; + for (int i = 0; i < menuPopup.GetMenuItemCount(); i++) + { + CMenuItemInfo mii; + mii.fMask = MIIM_DATA | MIIM_TYPE; + bRet = menuPopup.GetMenuItemInfo(i, TRUE, &mii); + ATLASSERT(bRet); + + MenuItemData * pMI = (MenuItemData*) mii.dwItemData; + if (pMI != NULL) + { + mii.fMask = MIIM_DATA | MIIM_TYPE | MIIM_STATE; + mii.fType = pMI->fType; + mii.dwTypeData = pMI->lpstrText; + mii.cch = ::lstrlen(pMI->lpstrText); + mii.dwItemData = NULL; + + bRet = menuPopup.SetMenuItemInfo(i, TRUE, &mii); + ATLASSERT(bRet); + + delete pMI; + } + } + } + } + + bHandled = FALSE; + return 1; + } + + LRESULT MeasureItemHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) + { + LPMEASUREITEMSTRUCT lpMeasureItemStruct = (LPMEASUREITEMSTRUCT)lParam; + MenuItemData * pmd = (MenuItemData*)lpMeasureItemStruct->itemData; + + if (lpMeasureItemStruct->CtlType == ODT_MENU && pmd != NULL) + MeasureItem(lpMeasureItemStruct); + else + bHandled = FALSE; + + return (LRESULT)TRUE; + } + + LRESULT DrawItemHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) + { + LPDRAWITEMSTRUCT lpDrawItemStruct = (LPDRAWITEMSTRUCT)lParam; + + MenuItemData * pMI = (MenuItemData*)lpDrawItemStruct->itemData; + if (lpDrawItemStruct->CtlType == ODT_MENU && pMI != NULL) // only owner-drawn menu item + DrawItem(lpDrawItemStruct); + else + bHandled = FALSE; + + return TRUE; + } + +public: + CCoolContextMenu() + { + m_cxExtraSpacing = 0; + m_clrMask = RGB(192, 192, 192); + + m_szBitmap.cx = 16; + m_szBitmap.cy = 15; + + m_szButton.cx = m_szBitmap.cx + 6; + m_szButton.cy = m_szBitmap.cy + 6; + } + + ~CCoolContextMenu() + { + m_imlCoolMenu.Destroy(); + m_fontMenu.DeleteObject(); + } + + // Note: do not forget to put CHAIN_MSG_MAP in your message map. + BEGIN_MSG_MAP(CCoolContextMenu) + MESSAGE_HANDLER(WM_INITMENUPOPUP, OnInitMenuPopup) + MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelect) + MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem) + MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem) + END_MSG_MAP() + + // Mostly copied from atlctrlw.h + void GetSystemSettings() + { + // Set up the font + NONCLIENTMETRICS info = { 0 }; + info.cbSize = sizeof(info); + BOOL bRet = ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(info), &info, 0); + ATLASSERT(bRet); + + if (bRet) + { + LOGFONT logfont = { 0 }; + if (m_fontMenu.m_hFont != NULL) + m_fontMenu.GetLogFont(logfont); + + if (logfont.lfHeight != info.lfMenuFont.lfHeight || + logfont.lfWidth != info.lfMenuFont.lfWidth || + logfont.lfEscapement != info.lfMenuFont.lfEscapement || + logfont.lfOrientation != info.lfMenuFont.lfOrientation || + logfont.lfWeight != info.lfMenuFont.lfWeight || + logfont.lfItalic != info.lfMenuFont.lfItalic || + logfont.lfUnderline != info.lfMenuFont.lfUnderline || + logfont.lfStrikeOut != info.lfMenuFont.lfStrikeOut || + logfont.lfCharSet != info.lfMenuFont.lfCharSet || + logfont.lfOutPrecision != info.lfMenuFont.lfOutPrecision || + logfont.lfClipPrecision != info.lfMenuFont.lfClipPrecision || + logfont.lfQuality != info.lfMenuFont.lfQuality || + logfont.lfPitchAndFamily != info.lfMenuFont.lfPitchAndFamily || + lstrcmp(logfont.lfFaceName, info.lfMenuFont.lfFaceName) != 0) + { + HFONT hFontMenu = ::CreateFontIndirect(&info.lfMenuFont); + ATLASSERT(hFontMenu != NULL); + if (hFontMenu != NULL) + { + if (m_fontMenu.m_hFont != NULL) + m_fontMenu.DeleteObject(); + m_fontMenu.Attach(hFontMenu); + static_cast(this)->SetFont(m_fontMenu); + } + } + } + + // Check if we need extra spacing for menu item text + CWindowDC dc(static_cast(this)->m_hWnd); + HFONT hFontOld = dc.SelectFont(m_fontMenu); + RECT rcText = { 0, 0, 0, 0 }; + dc.DrawText(_T("\t"), -1, &rcText, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_CALCRECT); + if ((rcText.right - rcText.left) < 4) + { + ::SetRectEmpty(&rcText); + dc.DrawText(_T("x"), -1, &rcText, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_CALCRECT); + m_cxExtraSpacing = rcText.right - rcText.left; + } + else + m_cxExtraSpacing = 0; + + dc.SelectFont(hFontOld); + + // Query flat menu mode (Windows XP or later) + if (::IsWindowsXPSP1OrGreater()) + { +#ifndef SPI_GETFLATMENU + const UINT SPI_GETFLATMENU = 0x1022; +#endif // !SPI_GETFLATMENU + BOOL bRetVal = FALSE; + bRet = ::SystemParametersInfo(SPI_GETFLATMENU, 0, &bRetVal, 0); + m_bFlatMenus = (bRet && bRetVal); + } + } + + LRESULT OnInitMenuPopup(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + return InitMenuPopupHandler(uMsg, wParam, lParam, bHandled); + } + + LRESULT OnMenuSelect(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + return MenuSelectHandler(uMsg, wParam, lParam, bHandled); + } + + LRESULT OnMeasureItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + return MeasureItemHandler(uMsg, wParam, lParam, bHandled); + } + + LRESULT OnDrawItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + return DrawItemHandler(uMsg, wParam, lParam, bHandled); + } + + void AssociateMenuImage(CMenuItemInfo& mii, MenuItemData * pMI) + { + // Default implementation, does not do anything + } +}; \ No newline at end of file diff --git a/src/app/Draw.h b/src/wtlx/wtlx/Draw.h similarity index 98% rename from src/app/Draw.h rename to src/wtlx/wtlx/Draw.h index 8160fa4..4841153 100644 --- a/src/app/Draw.h +++ b/src/wtlx/wtlx/Draw.h @@ -28,6 +28,7 @@ COLORREF HLS2RGB (HLSCOLOR hls); // Performs some modifications on the specified color : luminance and saturation COLORREF HLS_TRANSFORM (COLORREF rgb, int percent_L, int percent_S); +UINT ImageList_GetBitsPerPixelFlag(); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// diff --git a/src/app/FileVersionInfo.h b/src/wtlx/wtlx/FileVersionInfo.h similarity index 98% rename from src/app/FileVersionInfo.h rename to src/wtlx/wtlx/FileVersionInfo.h index a8ee593..1f4fb2a 100644 --- a/src/app/FileVersionInfo.h +++ b/src/wtlx/wtlx/FileVersionInfo.h @@ -10,6 +10,8 @@ ************************************************************************/ #pragma once +#pragma comment(lib,"Version.lib") + #define SFI_COMPANYNAME _T("CompanyName") #define SFI_FILEDESCRIPTION _T("FileDescription") #define SFI_FILEVERSION _T("FileVersion") diff --git a/src/wtlx/wtlx/IatHook.h b/src/wtlx/wtlx/IatHook.h new file mode 100644 index 0000000..3a063d8 --- /dev/null +++ b/src/wtlx/wtlx/IatHook.h @@ -0,0 +1,31 @@ +// This file contains code from +// https://github.com/stevemk14ebr/PolyHook_2_0/blob/master/sources/IatHook.cpp +// which is licensed under the MIT License. +// See PolyHook_2_0-LICENSE for more information. + +#pragma once + +template +constexpr T RVA2VA(T1 base, T2 rva) +{ + return reinterpret_cast(reinterpret_cast(base) + rva); +} + +template +constexpr T DataDirectoryFromModuleBase(void *moduleBase, size_t entryID) +{ + auto dosHdr = reinterpret_cast(moduleBase); + auto ntHdr = RVA2VA(moduleBase, dosHdr->e_lfanew); + auto dataDir = ntHdr->OptionalHeader.DataDirectory; + return RVA2VA(moduleBase, dataDir[entryID].VirtualAddress); +} + +PIMAGE_THUNK_DATA FindAddressByName(void* moduleBase, PIMAGE_THUNK_DATA impName, PIMAGE_THUNK_DATA impAddr, const char* funcName); + +PIMAGE_THUNK_DATA FindAddressByOrdinal(void*/*moduleBase*/, PIMAGE_THUNK_DATA impName, PIMAGE_THUNK_DATA impAddr, uint16_t ordinal); + +PIMAGE_THUNK_DATA FindIatThunkInModule(void* moduleBase, const char* dllName, const char* funcName); + +PIMAGE_THUNK_DATA FindDelayLoadThunkInModule(void* moduleBase, const char* dllName, const char* funcName); + +PIMAGE_THUNK_DATA FindDelayLoadThunkInModule(void* moduleBase, const char* dllName, uint16_t ordinal); diff --git a/src/wtlx/wtlx/NotifyTrayIcon.h b/src/wtlx/wtlx/NotifyTrayIcon.h new file mode 100644 index 0000000..0839b12 --- /dev/null +++ b/src/wtlx/wtlx/NotifyTrayIcon.h @@ -0,0 +1,241 @@ +/************************************************************************ + * $Revision: 33 $ + * $Date: 2024-10-28 17:50:24 +0100 (Mon, 28 Oct 2024) $ + ************************************************************************/ +/************************************************************************ + * File: NotifyTrayIcon.h - Implementation of the CNotifyIconData class and the CNotifyTrayIcon template. + * Copyright: 2002 Rob Caldecott, 2016 diVISION Soft, some rights reserved + * License: GPLv3, s. LICENSE.txt + * @author Rob Caldecott (http://www.codeproject.com/script/Membership/View.aspx?mid=3688) + * @author Dmitri Zoubkov (dimamizou@users.sf.net) + ************************************************************************/ +#pragma once + +#include +#include + +#ifndef WM_TRAYICON +#define WM_TRAYICON (WM_APP + 100) +#endif + +// Wrapper class for the Win32 NOTIFYICONDATA structure +class CNotifyIconData : public NOTIFYICONDATA +{ +public: + CNotifyIconData() + { + ::ZeroMemory(this, sizeof(NOTIFYICONDATA)); + cbSize = sizeof(NOTIFYICONDATA); + } +}; + +// Template used to support adding an icon to the taskbar. +// This class will maintain a taskbar icon and associated context menu. +template +class CNotifyTrayIcon +{ +private: + CNotifyIconData m_nid; + bool m_bTrayIconInstalled; + UINT m_uDefaultTrayMID; + UINT m_uVersion; +public: + CNotifyTrayIcon() + : m_bTrayIconInstalled(false) + , m_uDefaultTrayMID(0) + , m_uVersion(NOTIFYICON_VERSION) + { + } + + ~CNotifyTrayIcon() + { + // Remove the icon + RemoveNotifyIcon(); + } + + // Install a taskbar icon + // lpszToolTip - The tooltip to display + // hIcon - The icon to display + // nID - The resource ID of the context menu + /// returns true on success + bool AddNotifyIcon(HICON hIcon, UINT nID, LPCTSTR lpszToolTip = NULL, UINT uVersion = NOTIFYICON_VERSION) + { + m_uVersion = uVersion; + T* pT = static_cast(this); + // Fill in the data + m_nid.hWnd = pT->m_hWnd; + m_nid.uID = nID; + m_nid.hIcon = hIcon; + m_nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_SHOWTIP; + m_nid.uCallbackMessage = WM_TRAYICON; + if (NULL == lpszToolTip) + { + CString s; + pT->GetWindowText(s); + ::lstrcpyn(m_nid.szTip, s, sizeof(m_nid.szTip)); + } + else { + ::lstrcpyn(m_nid.szTip, lpszToolTip, sizeof(m_nid.szTip)); + } + // Install + m_bTrayIconInstalled = ::Shell_NotifyIcon(NIM_ADD, &m_nid) ? true : false; + + if (m_bTrayIconInstalled && 0 < m_uVersion && ::IsWindowsVistaOrGreater()) + { + m_nid.uFlags = NIF_SHOWTIP; + m_nid.uVersion = m_uVersion; + ::Shell_NotifyIcon(NIM_SETVERSION, &m_nid); + m_nid.uVersion = 0; + } + // Done + return m_bTrayIconInstalled; + } + + // Remove taskbar icon + // returns true on success + bool RemoveNotifyIcon() + { + if (!m_bTrayIconInstalled) + return false; + // Remove + m_nid.uFlags = 0; + return ::Shell_NotifyIcon(NIM_DELETE, &m_nid) ? true : false; + } + + // Set the icon tooltip text + // returns true on success + bool SetNotifyTooltipText(LPCTSTR pszTooltipText = NULL) + { + if (!m_bTrayIconInstalled) + return false; + // Fill the structure + m_nid.uFlags = NIF_TIP; + if (NULL == pszTooltipText) + { + T* pT = static_cast(this); + CString s; + pT->GetWindowText(s); + ::lstrcpyn(m_nid.szTip, s, sizeof(m_nid.szTip)); + } + else { + ::lstrcpyn(m_nid.szTip, pszTooltipText, sizeof(m_nid.szTip)); + } + // Set + return ::Shell_NotifyIcon(NIM_MODIFY, &m_nid) ? true : false; + } + + bool SetNotifyIcon(HICON hIcon) + { + if (!m_bTrayIconInstalled) + return false; + m_nid.uFlags = NIF_ICON; + m_nid.hIcon = hIcon; + return ::Shell_NotifyIcon(NIM_MODIFY, &m_nid) ? true : false; + } + + bool DisplayNotifyInfo(LPCTSTR pszInfoText, LPCTSTR pszInfoTitle = NULL, DWORD dwInfoFlags = NIIF_USER | 0x00000080, UINT uTimeout = 20000) + { + if (!pszInfoText || !m_bTrayIconInstalled) + return false; + m_nid.uFlags = NIF_INFO; + m_nid.dwInfoFlags = dwInfoFlags; + m_nid.uTimeout = uTimeout; + ::lstrcpyn(m_nid.szInfo, pszInfoText, sizeof(m_nid.szInfo)); + + if (NULL == pszInfoTitle) + { + T* pT = static_cast(this); + CString s; + pT->GetWindowText(s); + ::lstrcpyn(m_nid.szInfoTitle, s, sizeof(m_nid.szInfoTitle)); + } + else { + ::lstrcpyn(m_nid.szInfoTitle, pszInfoTitle, sizeof(m_nid.szInfoTitle)); + } + BOOL fRes = ::Shell_NotifyIcon(NIM_MODIFY, &m_nid); + m_nid.uTimeout = 0; + return fRes ? true : false; + } + + bool HideNotifyInfo() + { + if (!m_bTrayIconInstalled) + return false; + m_nid.uFlags = NIF_INFO; + m_nid.szInfo[0] = _T('\0'); + return ::Shell_NotifyIcon(NIM_MODIFY, &m_nid) ? true : false; + } + + // Set the default menu item ID + inline void SetDefaultItem(UINT nID) { m_uDefaultTrayMID = nID; } + + BEGIN_MSG_MAP(CTrayIcon) + MESSAGE_HANDLER(WM_TRAYICON, OnTrayIcon) + END_MSG_MAP() + + LRESULT OnTrayIcon(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) + { + // Is this the ID we want? + if (wParam != m_nid.uID) + return 0; + T* pT = static_cast(this); + // Was the right-button clicked? + if (LOWORD(lParam) == WM_RBUTTONUP) + { + // Load the menu + CMenu oMenu; + if (!oMenu.LoadMenu(m_nid.uID)) + return 0; + // Get the sub-menu + CMenuHandle oPopup(oMenu.GetSubMenu(0)); + // Prepare + pT->PrepareNotifyMenu(oPopup); + // Get the menu position + CPoint pos; + ::GetCursorPos(&pos); + // Make app the foreground + ::SetForegroundWindow(pT->m_hWnd); + // Set the default menu item + if (m_uDefaultTrayMID == 0) + oPopup.SetMenuDefaultItem(0, TRUE); + else + oPopup.SetMenuDefaultItem(m_uDefaultTrayMID); + // Track + oPopup.TrackPopupMenu(TPM_LEFTALIGN, pos.x, pos.y, pT->m_hWnd); + // BUGFIX: See "PRB: Menus for Notification Icons Don't Work Correctly" + pT->PostMessage(WM_NULL); + // Done + } + else if (LOWORD(lParam) == WM_LBUTTONDBLCLK) + { + // Make app the foreground + ::SetForegroundWindow(pT->m_hWnd); + // Load the menu + CMenu oMenu; + if (!oMenu.LoadMenu(m_nid.uID)) + return 0; + // Get the sub-menu + CMenuHandle oPopup(oMenu.GetSubMenu(0)); + // Get the item + if (m_uDefaultTrayMID) + { + // Send + pT->SendMessage(WM_COMMAND, m_uDefaultTrayMID, 0); + } + else + { + UINT nItem = oPopup.GetMenuItemID(0); + // Send + pT->SendMessage(WM_COMMAND, nItem, 0); + } + // Done + } + return 0; + } + + // Allow the menu items to be enabled/checked/etc. + virtual void PrepareNotifyMenu(HMENU /*hMenu*/) + { + // Stub + } +}; diff --git a/src/wtlx/wtlx/PictureCtrl.h b/src/wtlx/wtlx/PictureCtrl.h new file mode 100644 index 0000000..f789a33 --- /dev/null +++ b/src/wtlx/wtlx/PictureCtrl.h @@ -0,0 +1,495 @@ +/************************************************************************ + * $Revision: 41 $ + * $Date: 2024-12-05 17:37:38 +0100 (Thu, 05 Dec 2024) $ + ************************************************************************/ +/************************************************************************ + * File: PictureCtrl.h - implementation of the CPictureCtrl class + * Copyright: 2011 Ed Gadziemski, 2016 diVISION Soft, some rights reserved + * License: CPOL, s. https://en.wikipedia.org/wiki/Code_Project_Open_License + * @author Ed Gadziemski (http://www.codeproject.com/Members/Ed-Gadziemski) + * @author Dmitri Zoubkov (dimamizou@users.sf.net) + ************************************************************************/ +////////////////////////////////////////////////////// +// +// Classes in this file: +// +// CPictureCtrl - GDI+ picture control implementation +// CISSHelperWTL - implementation of ISquentialStream +// for reading OLEDB binary columns +// +////////////////////////////////////////////////////// +#if !defined(__PICTURECTRL_H__) +#define __PICTURECTRL_H__ + +#pragma once + +#include + +// The dictionary for image format GUIDs +typedef CSimpleMap CFormatMap; + +// Provisions for image scaling +#include +enum ImageScale { Normal, AutoFit, Stretch }; + +// Comment out this define if you don't want or need database +// functionality. Those sections of code will be left out +//#define __DATABASE_SUPPORT__ + +// OLEDB client header and ISequentialStream implementation +#ifdef __DATABASE_SUPPORT__ +#include +class CISSHelperWTL; +#endif + +#include "Draw.h" + +//////////////////////////////////////////////////////////////// +// CPictureCtrl attaches to an owner-drawn picture control and // +// reads, displays, and saves disk or database images // +//////////////////////////////////////////////////////////////// +template +class CPictureCtrlT : public TBase +{ +public: + CImage m_img; + CFormatMap m_formatMap; + CImageListManaged m_iml; + INT m_iIcon; + +#ifdef __DATABASE_SUPPORT__ + CISSHelperWTL m_stream; +#endif + +// Constructors, etc. + CPictureCtrlT(HWND hWnd = NULL) + : TBase(hWnd) + , m_iIcon(0) + { + // Initialize the format map with the encoder GUIDs + m_formatMap.Add(L"BMP", Gdiplus::ImageFormatBMP); + m_formatMap.Add(L"GIF", Gdiplus::ImageFormatGIF); + m_formatMap.Add(L"JPG", Gdiplus::ImageFormatJPEG); + m_formatMap.Add(L"PNG", Gdiplus::ImageFormatPNG); + m_formatMap.Add(L"TIF", Gdiplus::ImageFormatTIFF); + m_img.SetHasAlphaChannel(true); + + m_iml.Create(::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), ::ImageList_GetBitsPerPixelFlag() | ILC_MASK, 0, 1); + } + + CPictureCtrlT< TBase >& operator =(HWND hWnd) + { + m_hWnd = hWnd; + return *this; + } + + HWND Create(HWND hWndParent, ATL::_U_RECT rect = NULL, LPCTSTR szWindowName = NULL, + DWORD dwStyle = 0, DWORD dwExStyle = 0, + ATL::_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL) + { + return TBase::Create(_T("WTLPICTURE"), hWndParent, rect.m_lpRect, szWindowName, dwStyle, dwExStyle, MenuOrID.m_hMenu, lpCreateParam); + } + + ~CPictureCtrlT() + { + Release(); + } + + /////////////////////// + // Utility Functions // + /////////////////////// + inline BOOL IsNull() + { + return m_img.IsNull(); + } + + void RefreshWindow() + { + if (::IsWindow(m_hWnd)) + { + RECT rc; + GetClientRect(&rc); + InvalidateRect(&rc, true); + } + } + + void Release(BOOL fDestroyWnd = FALSE) + { + if (!IsNull()) { + m_img.Destroy(); + } + if (fDestroyWnd) DestroyWindow(); + } + + GUID GetFormatGUID(int index) + { + return m_formatMap.GetValueAt(index); + } + +// Drawing functions + BOOL Render(ImageScale imageScale = AutoFit) + { + BOOL res = FALSE; + if (m_img.IsNull()) + { + if (0 < m_iml.GetImageCount()) + { + CDC dc = GetDC(); + dc.SetBkColor(CLR_NONE); + res = m_iml.Draw(dc, m_iIcon, 0, 0, ILD_TRANSPARENT); + } + } + else + { + CRect rcDest; + CalculateScale(rcDest, imageScale); + + // Draw the image + CDC dc = GetDC(); + dc.SetBkColor(CLR_NONE); + res = m_img.Draw(dc, rcDest); + } + return res; + } + + void CalculateScale(RECT& rcDest, ImageScale imageScale) + { + auto width = m_img.GetWidth(); + auto height = m_img.GetHeight(); + DOUBLE scaleWidth = 1.0, scaleHeight = 1.0; + RECT rcBmp = { 0, 0, width, height }; + RECT rcClnt = { 0 }; + + GetClientRect(&rcClnt); + + if (imageScale == AutoFit) + { + if (rcBmp.right > rcClnt.right) + { + scaleWidth = ((DOUBLE)rcClnt.right / (DOUBLE)rcBmp.right); + } + if (rcBmp.bottom > rcClnt.bottom) + { + scaleHeight = ((DOUBLE)rcClnt.bottom / (DOUBLE)rcBmp.bottom); + } + if (scaleWidth > scaleHeight) scaleWidth = scaleHeight; + else scaleHeight = scaleWidth; + + rcDest.right = (LONG)(DOUBLE)(width * scaleWidth); + rcDest.bottom = (LONG)(DOUBLE)(height * scaleHeight); + } + else if (imageScale == Stretch) + { + rcDest.right = rcClnt.right; + rcDest.bottom = rcClnt.bottom; + } + else if (imageScale == Normal) + { + rcDest.right = rcBmp.right; + rcDest.bottom = rcBmp.bottom; + } + } + + int AddIcon(HICON hIcon) + { + auto result = m_iml.AddIcon(hIcon); + return m_iml.ReplaceIcon(result, hIcon); + } + + INT SetIconIndex(INT iIndex) + { + ATLASSERT(-1 < iIndex && iIndex < m_iml.GetImageCount()); + INT result = m_iIcon; + m_iIcon = iIndex; + RefreshWindow(); + return result; + } + + HRESULT LoadResource(ATL::_U_STRINGorID nID, ATL::_U_STRINGorID nType = RT_BITMAP) + { + m_img.Destroy(); + + HINSTANCE hInst = ModuleHelper::GetResourceInstance(); + HRSRC hResource = NULL; + LPVOID pResourceData = NULL; + if (RT_ICON == nType.m_lpstr) { + nType = RT_GROUP_ICON; + hResource = ::FindResource(hInst, nID.m_lpstr, nType.m_lpstr); + if (!hResource) + return ATL::AtlHresultFromLastError(); + pResourceData = ::LockResource(::LoadResource(hInst, hResource)); + if (!pResourceData) + return ATL::AtlHresultFromLastError(); + + CRect rc; + GetClientRect(&rc); + UINT iID = ::LookupIconIdFromDirectoryEx((PBYTE)pResourceData, TRUE, rc.right - rc.left, rc.bottom - rc.top, LR_DEFAULTCOLOR); + if (!iID) + return ATL::AtlHresultFromLastError(); + nID = iID; + nType = RT_ICON; + } + hResource = ::FindResource(hInst, nID.m_lpstr, nType.m_lpstr); + if (!hResource) + return ATL::AtlHresultFromLastError(); + + DWORD imageSize = ::SizeofResource(hInst, hResource); + if (!imageSize) + return ATL::AtlHresultFromLastError(); + + pResourceData = ::LockResource(::LoadResource(hInst, hResource)); + if (!pResourceData) + return ATL::AtlHresultFromLastError(); + + HGLOBAL hBuffer = ::GlobalAlloc(GMEM_MOVEABLE, imageSize); + if (!hBuffer) + return ATL::AtlHresultFromLastError(); + + HRESULT hr = E_FAIL; + LPVOID pBuffer = ::GlobalLock(hBuffer); + if (pBuffer) + { + CopyMemory(pBuffer, pResourceData, imageSize); + + CComPtr pStream; + hr = ::CreateStreamOnHGlobal(hBuffer, FALSE, &pStream); + if (SUCCEEDED(hr)) { + hr = m_img.Load(pStream); + } + ::GlobalUnlock(hBuffer); + } + ::GlobalFree(hBuffer); + + hr = SUCCEEDED(hr) && m_img.IsNull() ? E_FAIL : hr; + if (SUCCEEDED(hr)) { + RefreshWindow(); + } + return hr; + } + + HRESULT LoadURL(LPCTSTR lpszURL) + { + m_img.Destroy(); + CComPtr pStream; + // TODO make LoadURL non-blocking + HRESULT hr = ::URLOpenBlockingStream(NULL, lpszURL, &pStream, 0, NULL); + if (SUCCEEDED(hr)) { + hr = m_img.Load(pStream); + } + if (SUCCEEDED(hr)) { + RefreshWindow(); + } + return hr; + } + + HRESULT LoadFile(LPCTSTR lpszFileName) + { + m_img.Destroy(); + HRESULT hr = m_img.Load(lpszFileName); + if (SUCCEEDED(hr)) { + RefreshWindow(); + } + return hr; + } + + HRESULT SaveToFile(LPCTSTR lpszFileName) + { + if (m_img.IsNull()) return E_NOT_VALID_STATE; + return m_img.Save(lpszFileName); + } + +#ifdef __DATABASE_SUPPORT__ +/////////////////////////////// +// Database Stream Functions // +/////////////////////////////// + BOOL ReadISS(ISequentialStream* &pISS, ULONG& ulLength, ULONG& ulStatus) + { + BOOL bSuccess = FALSE; + + if (ulStatus == DBSTATUS_S_OK) + { + // Read the supplied ISequential stream + LPVOID buffer = ::CoTaskMemAlloc(ulLength); + if (pISS->Read(buffer, ulLength, NULL) == S_OK) + { + m_img.Destroy(); + + // Create an IStream and load the image + CComPtr pStream; + HRESULT hr = ::CreateStreamOnHGlobal(buffer, TRUE, &pStream); + if (SUCCEEDED(hr)) { + hr = m_img.Load(pStream); + } + bSuccess = SUCCEEDED(hr); + RefreshWindow(); + } + if (pISS != NULL) + { + pISS->Release(); + pISS = NULL; + } + ::CoTaskMemFree(buffer); + } + return bSuccess; + } + + BOOL WriteISS(ISequentialStream* &pISS, ULONG& ulLength, ULONG& ulStatus, int format = 0) + { + BOOL bSuccess = FALSE; + LARGE_INTEGER liOffset = { 0, 0 }; + ULARGE_INTEGER lBytesWritten = { 0, 0 }; + + // Create the IStream and place the picture bytes in it + CComPtr pStream = NULL; + ::CreateStreamOnHGlobal(NULL, TRUE, &pStream); + HRESULT hr = m_img.Save(pStream, GetFormatGUID(format)); + if (SUCCEEDED(hr)) + { + // Get the count of bytes written to the stream + STATSTG stat; + pStream->Stat(&stat, STATFLAG_NONAME); + lBytesWritten = stat.cbSize; + + // Reset read position to 0 + if (pStream->Seek(liOffset, STREAM_SEEK_SET, NULL) == S_OK) + { + m_stream.Clear(); + + // Copy the image data to the stream helper + LPVOID buffer = ::CoTaskMemAlloc((size_t)lBytesWritten.QuadPart); + if (pStream->Read(buffer, (ULONG)lBytesWritten.QuadPart, NULL) == S_OK) + bSuccess = SUCCEEDED(m_stream.Write(buffer, (ULONG)lBytesWritten.QuadPart, &ulLength)); + + // Assign the newly writtem stream to the passed-in stream + if (bSuccess) + { + if (pISS != NULL) pISS->Release(); + pISS = &m_stream; + ulStatus = DBSTATUS_S_OK; + } + + ::CoTaskMemFree(buffer); + } + } + return bSuccess; + } +#endif // database support +}; + +typedef CPictureCtrlT CPictureCtrl; + + +#ifdef __DATABASE_SUPPORT__ +///////////////////////////////////////////////////// +// WTL port of the AOTBLOB sample CISSHelper class // +///////////////////////////////////////////////////// +class CISSHelperWTL : public ISequentialStream +{ +private: + ULONG m_cRef; // Reference count. + ULONG m_iReadPos; // Current index position for reading from the buffer. + ULONG m_iWritePos; // Current index position for writing to the buffer. + +public: + void* m_pBuffer; // Buffer + ULONG m_ulLength; // Total buffer size. + ULONG m_ulStatus; // Column status. + + CISSHelperWTL() + { + m_cRef = 0; + m_pBuffer = NULL; + m_ulLength = 0; + m_ulStatus = DBSTATUS_S_OK; + m_iReadPos = 0; + m_iWritePos = 0; + } + + ~CISSHelperWTL() { Clear(); } + + void Clear() + { + CoTaskMemFree( m_pBuffer ); + m_cRef = 0; + m_pBuffer = NULL; + m_ulLength = 0; + m_ulStatus = DBSTATUS_S_OK; + m_iReadPos = 0; + m_iWritePos = 0; + } + + STDMETHODIMP_(ULONG) AddRef(void) { return ++m_cRef; } + + STDMETHODIMP_(ULONG) Release(void) { return --m_cRef; } + + STDMETHODIMP_(HRESULT) QueryInterface( REFIID riid, void** ppv ) + { + *ppv = NULL; + if ( riid == IID_IUnknown ) *ppv = this; + if ( riid == IID_ISequentialStream ) *ppv = this; + if ( *ppv ) + { + ( (IUnknown*) *ppv )->AddRef(); + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHODIMP_(HRESULT) Read( void *pv, ULONG cb, ULONG* pcbRead ) + { + // Check parameters. + if ( pcbRead ) *pcbRead = 0; + if ( !pv ) return STG_E_INVALIDPOINTER; + if ( 0 == cb ) return S_OK; + + // Calculate bytes left and bytes to read. + ULONG cBytesLeft = m_ulLength - m_iReadPos; + ULONG cBytesRead = cb > cBytesLeft ? cBytesLeft : cb; + + // If no more bytes to retrieve return S_FALSE. + if ( 0 == cBytesLeft ) return S_FALSE; + + // Copy to users buffer the number of bytes requested or remaining + memcpy( pv, (void*)((BYTE*)m_pBuffer + m_iReadPos), cBytesRead ); + m_iReadPos += cBytesRead; + + // Return bytes read to caller. + if ( pcbRead ) *pcbRead = cBytesRead; + if ( cb != cBytesRead ) return S_FALSE; + + return S_OK; + } + + STDMETHODIMP_(HRESULT) Write( const void *pv, ULONG cb, ULONG* pcbWritten ) + { + // Check parameters. + if ( !pv ) return STG_E_INVALIDPOINTER; + if ( pcbWritten ) *pcbWritten = 0; + if ( 0 == cb ) return S_OK; + + // Enlarge the current buffer. + m_ulLength += cb; + + // Grow internal buffer to new size. + m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_ulLength ); + + // Check for out of memory situation. + if ( NULL == m_pBuffer ) + { + Clear(); + return E_OUTOFMEMORY; + } + + // Copy callers memory to internal buffer and update write position. + memcpy( (void*)((BYTE*)m_pBuffer + m_iWritePos), pv, cb ); + m_iWritePos += cb; + + // Return bytes written to caller. + if ( pcbWritten ) *pcbWritten = cb; + + return S_OK; + } +}; +#endif + +#endif // !defined(__PICTURECTRL_H__) \ No newline at end of file diff --git a/src/wtlx/wtlx/TaskBarList.h b/src/wtlx/wtlx/TaskBarList.h new file mode 100644 index 0000000..f2672d0 --- /dev/null +++ b/src/wtlx/wtlx/TaskBarList.h @@ -0,0 +1,173 @@ +/************************************************************************ + * $Revision: 33 $ + * $Date: 2024-10-28 17:50:24 +0100 (Mon, 28 Oct 2024) $ + ************************************************************************/ +/************************************************************************ + * File: TaskBarList.h + * Copyright: 2016 diVISION Soft, some rights reserved + * License: GPLv3, s. LICENSE.txt + * @author Dmitri Zoubkov (dimamizou@users.sf.net) + ************************************************************************/ +#pragma once + +#include + +#if (WINVER <= 0x0600) + +#define MSGFLT_RESET (0) +#define MSGFLT_ALLOW (1) +#define MSGFLT_DISALLOW (2) + +typedef struct tagCHANGEFILTERSTRUCT { + DWORD cbSize; + DWORD ExtStatus; +} CHANGEFILTERSTRUCT, *PCHANGEFILTERSTRUCT; + +typedef BOOL(WINAPI *PFN_ChangeWindowMessageFilterEx)( + _In_ HWND hWnd, + _In_ UINT message, + _In_ DWORD action, + _Inout_opt_ PCHANGEFILTERSTRUCT pChangeFilterStruct + ); + +static BOOL WINAPI AllowWindowMessageFilter(_In_ HWND hWnd, _In_ UINT message) +{ + BOOL fRes = FALSE; + HMODULE hLib = ::LoadLibrary(_T("user32.dll")); + if (hLib) + { + PFN_ChangeWindowMessageFilterEx pfn = (PFN_ChangeWindowMessageFilterEx) ::GetProcAddress(hLib, "ChangeWindowMessageFilterEx"); + if (pfn) { + fRes = pfn(hWnd, message, MSGFLT_ALLOW, NULL); + } + ::FreeLibrary(hLib); + } + if (!fRes) { + fRes = ::ChangeWindowMessageFilter(message, MSGFLT_ADD); + } + return fRes; +} +#else +static BOOL WINAPI AllowWindowMessageFilter(_In_ HWND hWnd, _In_ UINT message) +{ + return ::ChangeWindowMessageFilterEx(hWnd, message, MSGFLT_ALLOW, NULL); +} +#endif + +class CTaskBarList +{ +private: + BOOL m_fIsInitialized; + HWND m_hwnd; + CComPtr m_pTaskbarList; + ULONGLONG m_ullPrgCompleted; + ULONGLONG m_ullPrgTotal; + CString m_strTip; + TBPFLAG m_tbpFlags; + +public: + const UINT WM_TaskbarButtonCreated; + + CTaskBarList() + : WM_TaskbarButtonCreated(::RegisterWindowMessage(_T("TaskbarButtonCreated"))) + , m_fIsInitialized(FALSE) + , m_ullPrgCompleted(0LL) + , m_ullPrgTotal(0LL) + , m_tbpFlags(TBPFLAG::TBPF_NOPROGRESS) + { + } + + virtual ~CTaskBarList() + { + } + + BEGIN_MSG_MAP(CTaskBarList) + MESSAGE_HANDLER(WM_TaskbarButtonCreated, OnTaskbarButtonCreated) + END_MSG_MAP() + + BOOL Initialize(HWND hwnd) + { + if (!::IsWindows7OrGreater()) + { + ATLTRACE(_T("Unsupported Windows version\n")); + return FALSE; + } + + m_fIsInitialized = 0 != WM_TaskbarButtonCreated; + ATLASSERT(m_fIsInitialized); + if (m_fIsInitialized) + { + m_hwnd = hwnd; + m_fIsInitialized = ::IsWindow(m_hwnd); + ATLASSERT(m_fIsInitialized); + } + if (m_fIsInitialized) { + m_fIsInitialized = ::AllowWindowMessageFilter(m_hwnd, WM_TaskbarButtonCreated); + } + return m_fIsInitialized; + } + + HRESULT SetProgressState(TBPFLAG tbpFlags) + { + if (m_pTaskbarList) { + return m_pTaskbarList->SetProgressState(m_hwnd, tbpFlags); + } + else + { + m_tbpFlags = tbpFlags; + } + return E_NOT_VALID_STATE; + } + + HRESULT SetProgressValue(ULONGLONG ullCompleted, ULONGLONG ullTotal = 100LL) + { + if (m_pTaskbarList) { + return m_pTaskbarList->SetProgressValue(m_hwnd, ullCompleted, ullTotal); + } + else + { + m_ullPrgCompleted = ullCompleted; + m_ullPrgTotal = ullTotal; + } + return E_NOT_VALID_STATE; + } + + HRESULT SetThumbnailTooltip(LPCWSTR pszTip = NULL) + { + if (m_pTaskbarList) { + return m_pTaskbarList->SetThumbnailTooltip(m_hwnd, pszTip); + } + else + { + if (NULL == pszTip) + m_strTip.Empty(); + else + m_strTip = pszTip; + } + return E_NOT_VALID_STATE; + } + +public: + LRESULT OnTaskbarButtonCreated(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) + { + ATLTRACE(_T("OnTaskbarButtonCreated() m_fIsInitialized=%d\n"), m_fIsInitialized); + if (m_fIsInitialized) + { + HRESULT hr = m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList); + if (SUCCEEDED(hr) && m_pTaskbarList) + { + if (TBPFLAG::TBPF_NOPROGRESS != m_tbpFlags) + { + SetProgressState(m_tbpFlags); + if (TBPFLAG::TBPF_NORMAL == m_tbpFlags) SetProgressValue(m_ullPrgCompleted, m_ullPrgTotal); + } + if (m_strTip.GetLength()) SetThumbnailTooltip(m_strTip); + } + else if (FAILED(hr)) { + ATLTRACE(_T("Failed to create TaskbarList: 0x%0lX\n"), hr); + } + } + return TRUE; + } +}; + diff --git a/src/wtlx/wtlx/UxModeHyperLink.h b/src/wtlx/wtlx/UxModeHyperLink.h new file mode 100644 index 0000000..7f3b9e3 --- /dev/null +++ b/src/wtlx/wtlx/UxModeHyperLink.h @@ -0,0 +1,56 @@ +#pragma once + +#ifndef __ATLAPP_H__ +#error UxModeHyperLink.h requires atlapp.h to be included first +#endif + +#ifndef __ATLCTRLS_H__ +#error UxModeHyperLink.h requires atlctrls.h to be included first +#endif + +class CUxModeHyperLink + : public CHyperLinkImpl +{ +public: + DECLARE_WND_CLASS(_T("WTLX_HyperLink")) + + BEGIN_MSG_MAP(CUxModeHyperLink) + MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChange) + CHAIN_MSG_MAP(CHyperLinkImpl) + END_MSG_MAP() + + LRESULT OnThemeChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) + { + bHandled = FALSE; + UpdateColors(); + return 0L; + } + + void Init() + { + CHyperLinkImpl::Init(); + m_clrDefLink = m_clrLink; + m_clrDefVisited = m_clrVisited; + UpdateColors(false); + } +protected: + void UpdateColors(bool repaint = true) + { + if (uxTheme.IsInDarkMode()) + { + m_clrLink = UXCOLOR_LIGHTER(m_clrDefLink, 0.5); + m_clrVisited = UXCOLOR_LIGHTER(m_clrDefVisited, 0.2); + } + else + { + m_clrLink = m_clrDefLink; + m_clrVisited = m_clrDefVisited; + } + if (repaint) + UpdateWindow(); + } + +protected: + COLORREF m_clrDefLink{ 0 }; + COLORREF m_clrDefVisited{ 0 }; +}; \ No newline at end of file diff --git a/src/wtlx/wtlx/dialogresizeex.h b/src/wtlx/wtlx/dialogresizeex.h new file mode 100644 index 0000000..f69cafd --- /dev/null +++ b/src/wtlx/wtlx/dialogresizeex.h @@ -0,0 +1,180 @@ +/************************************************************************ + * $Revision: 4 $ + * $Date: 2016-11-25 01:28:53 +0100 (Fri, 25 Nov 2016) $ + ************************************************************************/ +/************************************************************************ + * File: dialogresizeex.h + * Copyright: 2004 Rob Caldecott, 2016 diVISION Soft, some rights reserved + * License: GPLv3, s. LICENSE.txt + * @author Rob Caldecott (http://www.codeproject.com/script/Membership/View.aspx?mid=3688) + * @author Dmitri Zoubkov (dimamizou@users.sf.net) + ************************************************************************/ +#pragma once + +#define DECLARE_DLGRESIZEEX_REGKEY(key) \ + virtual LPCTSTR GetDlgResizeExKey() \ + { \ + if (m_strKeyName.IsEmpty()) m_strKeyName = (key); \ + return m_strKeyName; \ + } + +#if (_ATL_VER >= 0x0700) +#define DLGRESIZEEX_REGQUERY_DWORD(regk, name, val) regk.QueryDWORDValue(FormatDlgRegValueName(name), val) +#define DLGRESIZEEX_REGSET_DWORD(regk, name, val) regk.SetDWORDValue(FormatDlgRegValueName(name), val) +#else +#define DLGRESIZEEX_REGQUERY_DWORD(regk, name, val) regk.QueryValue(val, FormatDlgRegValueName(name)) +#define DLGRESIZEEX_REGSET_DWORD(regk, name, val) regk.SetValue(val, FormatDlgRegValueName(name)) +#endif + +// Extension to WTL CDialogResize allowing persistent dialog size +template +class CDialogResizeEx : public CDialogResize +{ +private: + WINDOWPLACEMENT m_wp; + +protected: + HKEY m_hKeyParent; + CString m_strKeyName; + +public: + + CDialogResizeEx(void) + : m_wp({ 0 }) + , m_hKeyParent(NULL) + { + }; + + virtual LPCTSTR GetDlgResizeExKey() + { + return m_strKeyName; + } + + void DlgResize_InitEx(bool bAddGripper = true, bool bUseMinTrackSize = true, DWORD dwForceStyle = WS_CLIPCHILDREN) + { + DlgResize_Init(bAddGripper, bUseMinTrackSize, dwForceStyle); + } + + // Load the dialog size from the registry. Base the registry + // value on the dialog ID. + void LoadSize(LPCTSTR lpszKeyName = NULL, HKEY hKeyParent = NULL) + { + m_hKeyParent = NULL == hKeyParent ? HKEY_CURRENT_USER : hKeyParent; + if (NULL != lpszKeyName) m_strKeyName = lpszKeyName; + ATLASSERT(m_hKeyParent); + ATLASSERT(m_strKeyName.GetLength()); + + ATL::CRegKey reg; + LSTATUS lstat = reg.Open(m_hKeyParent, m_strKeyName, KEY_READ); + if (ERROR_SUCCESS == lstat) + { + ::ZeroMemory(&m_wp, sizeof(WINDOWPLACEMENT)); + + DWORD dw; + lstat = DLGRESIZEEX_REGQUERY_DWORD(reg, _T("left"), dw); + if (ERROR_SUCCESS == lstat) + m_wp.rcNormalPosition.left = dw; + if (ERROR_SUCCESS == lstat) lstat = DLGRESIZEEX_REGQUERY_DWORD(reg, _T("top"), dw); + if (ERROR_SUCCESS == lstat) + m_wp.rcNormalPosition.top = dw; + if (ERROR_SUCCESS == lstat) lstat = DLGRESIZEEX_REGQUERY_DWORD(reg, _T("right"), dw); + if (ERROR_SUCCESS == lstat) + m_wp.rcNormalPosition.right = dw; + if (ERROR_SUCCESS == lstat) lstat = DLGRESIZEEX_REGQUERY_DWORD(reg, _T("bottom"), dw); + if (ERROR_SUCCESS == lstat) + m_wp.rcNormalPosition.bottom = dw; + if (ERROR_SUCCESS == lstat) lstat = DLGRESIZEEX_REGQUERY_DWORD(reg, _T("max_x"), dw); + if (ERROR_SUCCESS == lstat) + m_wp.ptMaxPosition.x = dw; + if (ERROR_SUCCESS == lstat) lstat = DLGRESIZEEX_REGQUERY_DWORD(reg, _T("max_y"), dw); + if (ERROR_SUCCESS == lstat) + m_wp.ptMaxPosition.y = dw; + if (ERROR_SUCCESS == lstat) lstat = DLGRESIZEEX_REGQUERY_DWORD(reg, _T("min_x"), dw); + if (ERROR_SUCCESS == lstat) + m_wp.ptMinPosition.x = dw; + if (ERROR_SUCCESS == lstat) lstat = DLGRESIZEEX_REGQUERY_DWORD(reg, _T("min_y"), dw); + if (ERROR_SUCCESS == lstat) + m_wp.ptMinPosition.y = dw; + if (ERROR_SUCCESS == lstat) lstat = DLGRESIZEEX_REGQUERY_DWORD(reg, _T("show"), dw); + if (ERROR_SUCCESS == lstat) + m_wp.showCmd = dw; + + if (SW_SHOWMINIMIZED == m_wp.showCmd || SW_HIDE == m_wp.showCmd) m_wp.showCmd = SW_SHOWDEFAULT; + if (ERROR_SUCCESS == lstat) m_wp.length = sizeof(WINDOWPLACEMENT); + } + } + + // Save the dialog size to the registry. + void SaveSize() const + { + if (0 < m_wp.length && m_hKeyParent && !m_strKeyName.IsEmpty()) + { + ATL::CRegKey reg; + if (reg.Create(m_hKeyParent, m_strKeyName) == ERROR_SUCCESS) + { + DLGRESIZEEX_REGSET_DWORD(reg, _T("left"), m_wp.rcNormalPosition.left); + DLGRESIZEEX_REGSET_DWORD(reg, _T("top"), m_wp.rcNormalPosition.top); + DLGRESIZEEX_REGSET_DWORD(reg, _T("right"), m_wp.rcNormalPosition.right); + DLGRESIZEEX_REGSET_DWORD(reg, _T("bottom"), m_wp.rcNormalPosition.bottom); + DLGRESIZEEX_REGSET_DWORD(reg, _T("max_x"), m_wp.ptMaxPosition.x); + DLGRESIZEEX_REGSET_DWORD(reg, _T("max_y"), m_wp.ptMaxPosition.y); + DLGRESIZEEX_REGSET_DWORD(reg, _T("min_x"), m_wp.ptMinPosition.x); + DLGRESIZEEX_REGSET_DWORD(reg, _T("min_y"), m_wp.ptMinPosition.y); + DLGRESIZEEX_REGSET_DWORD(reg, _T("show"), m_wp.showCmd); + } + } + } + + CString FormatDlgRegValueName(LPCTSTR lpstrSuff) const + { + CString result; + result.Format(_T("dialog_%d_%s"), T::IDD, lpstrSuff); + return result; + } + + BEGIN_MSG_MAP(CDialogResizeEx) + MESSAGE_HANDLER(WM_SHOWWINDOW, OnShowWindow) + MESSAGE_HANDLER(WM_DESTROY, OnDestroy) + CHAIN_MSG_MAP(CDialogResize) + END_MSG_MAP() + + LRESULT OnShowWindow(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled) + { + GetDlgResizeExKey(); + if (!m_strKeyName.IsEmpty()) LoadSize(); + + T* pT = static_cast(this); + if (wParam && !pT->IsWindowVisible()) + { + // Size the dialog and update the control layout + if (0 < m_wp.length) + { + pT->SetWindowPlacement(&m_wp); + + CRect rectClient; + pT->GetClientRect(&rectClient); + DlgResize_UpdateLayout(rectClient.Width(), rectClient.Height()); + } + else { + pT->CenterWindow(); + } + } + bHandled = FALSE; + return 0; + } + + LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) + { + T* pT = static_cast(this); + + // Save the window size + ZeroMemory(&m_wp, sizeof(WINDOWPLACEMENT)); + m_wp.length = sizeof(WINDOWPLACEMENT); + if (pT->GetWindowPlacement(&m_wp)) { + SaveSize(); + } + + bHandled = FALSE; + return 0; + } +}; diff --git a/src/wtlx/wtlx/formattools.h b/src/wtlx/wtlx/formattools.h new file mode 100644 index 0000000..3f57a3a --- /dev/null +++ b/src/wtlx/wtlx/formattools.h @@ -0,0 +1,29 @@ +/************************************************************************ + * $Revision: 4 $ + * $Date: 2016-11-25 01:28:53 +0100 (Fri, 25 Nov 2016) $ + ************************************************************************/ +/************************************************************************ + * File: formattools.h + * Copyright: 2016 diVISION Soft, some rights reserved + * License: GPLv3, s. LICENSE.txt + * @author Dmitri Zoubkov (dimamizou@users.sf.net) + ************************************************************************/ +#pragma once +#include + +#define DTFORMAT_LOCALTIME 0x10000000 +#define DTFORMAT_NODATE 0x20000000 +#define DTFORMAT_NOTIME 0x20000000 + +enum MemoryUnits +{ + MU_BYTES + , MU_KB + , MU_MB + , MU_GB + , MU_TB +}; +STDAPI FormatMemoryUnits(_In_ const DECIMAL *pdecIn, _In_ LCID lcid, _Out_ BSTR *pbstrOut); +STDAPI FormatError(_In_ HRESULT hr, _In_ LCID lcid, _Out_ BSTR *pbstrOut); +STDAPI FormatSysDateTime(_In_ const LPSYSTEMTIME pST, _In_ LCID lcid, _In_ DWORD dwDateFlags, _In_ DWORD dwTimeFlags, _Out_ BSTR *pbstrOut); +STDAPI FormatOLEDateTime(_In_ DATE dt, _In_ LCID lcid, _In_ DWORD dwDateFlags, _In_ DWORD dwTimeFlags, _Out_ BSTR *pbstrOut); \ No newline at end of file diff --git a/src/app/taskbarautomation.h b/src/wtlx/wtlx/taskbarautomation.h similarity index 100% rename from src/app/taskbarautomation.h rename to src/wtlx/wtlx/taskbarautomation.h diff --git a/src/wtlx/wtlx/uxthemehelper.h b/src/wtlx/wtlx/uxthemehelper.h new file mode 100644 index 0000000..b6d15b4 --- /dev/null +++ b/src/wtlx/wtlx/uxthemehelper.h @@ -0,0 +1,1164 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include + +#include + +#pragma comment(lib, "Dwmapi.lib") + +#if !defined(UXMODE_SUPPORT_SCROLLBAR) && (defined(UXMODE_SUPPORT_LISTVIEW) || defined(UXMODE_SUPPORT_TREEVIEW)) +#define UXMODE_SUPPORT_SCROLLBAR +#endif + +#ifdef UXMODE_SUPPORT_SCROLLBAR +#include "wtlx/IatHook.h" +#endif // UXMODE_SUPPORT_SCROLLBAR + + +enum class IMMERSIVE_HC_CACHE_MODE +{ + IHCM_USE_CACHED_VALUE, + IHCM_REFRESH +}; + +enum class PreferredAppMode +{ + Default, + AllowDark, + ForceDark, + ForceLight, + Max +}; + +enum WINDOWCOMPOSITIONATTRIB +{ + WCA_UNDEFINED = 0, + WCA_NCRENDERING_ENABLED = 1, + WCA_NCRENDERING_POLICY = 2, + WCA_TRANSITIONS_FORCEDISABLED = 3, + WCA_ALLOW_NCPAINT = 4, + WCA_CAPTION_BUTTON_BOUNDS = 5, + WCA_NONCLIENT_RTL_LAYOUT = 6, + WCA_FORCE_ICONIC_REPRESENTATION = 7, + WCA_EXTENDED_FRAME_BOUNDS = 8, + WCA_HAS_ICONIC_BITMAP = 9, + WCA_THEME_ATTRIBUTES = 10, + WCA_NCRENDERING_EXILED = 11, + WCA_NCADORNMENTINFO = 12, + WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, + WCA_VIDEO_OVERLAY_ACTIVE = 14, + WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, + WCA_DISALLOW_PEEK = 16, + WCA_CLOAK = 17, + WCA_CLOAKED = 18, + WCA_ACCENT_POLICY = 19, + WCA_FREEZE_REPRESENTATION = 20, + WCA_EVER_UNCLOAKED = 21, + WCA_VISUAL_OWNER = 22, + WCA_HOLOGRAPHIC = 23, + WCA_EXCLUDED_FROM_DDA = 24, + WCA_PASSIVEUPDATEMODE = 25, + WCA_USEDARKMODECOLORS = 26, + WCA_LAST = 27 +}; + +struct WINDOWCOMPOSITIONATTRIBDATA +{ + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; +}; + + +using FnOpenNcThemeData = HTHEME(WINAPI*)(HWND hWnd, LPCWSTR pszClassList); // ordinal 49 +using FnRefreshImmersiveColorPolicyState = void (WINAPI*)(); // ordinal 104 +using FnGetIsImmersiveColorUsingHighContrast = bool (WINAPI*)(IMMERSIVE_HC_CACHE_MODE mode); // ordinal 106 +using FnShouldAppsUseDarkMode = bool (WINAPI*)(); // ordinal 132 +using FnAllowDarkModeForWindow = bool (WINAPI*)(HWND hWnd, bool allow); // ordinal 133 +using FnSetPreferredAppMode = PreferredAppMode(WINAPI*)(PreferredAppMode appMode); // ordinal 135, in 1903 +using FnFlushMenuThemes = void (WINAPI*)(); // ordinal 136 +using FnSetWindowCompositionAttribute = BOOL(WINAPI*)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*); + +#define UXTHEME_DECLARE_FUNC(name) \ +Fn##name m_pfn##name{NULL} + +#define UXTHEME_INIT_FUNC(name, hMod) \ +m_pfn##name = reinterpret_cast(::GetProcAddress(hMod, #name)); \ +ATLASSERT(m_pfn##name) + +#define UXTHEME_INIT_ORD_FUNC(name, ord, hMod) \ +m_pfn##name = reinterpret_cast(::GetProcAddress(hMod, MAKEINTRESOURCEA(ord))); \ +ATLASSERT(m_pfn##name) + +class CUxTheme +{ +public: + using UIColorType = winrt::Windows::UI::ViewManagement::UIColorType; + using UIElementType = winrt::Windows::UI::ViewManagement::UIElementType; + + CUxTheme() + { + m_hUxtheme = ::LoadLibraryEx(_T("uxtheme.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + ATLASSERT(m_hUxtheme); + if (m_hUxtheme) + { + UXTHEME_INIT_ORD_FUNC(OpenNcThemeData, 49, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(RefreshImmersiveColorPolicyState, 104, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(GetIsImmersiveColorUsingHighContrast, 106, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(ShouldAppsUseDarkMode, 132, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(AllowDarkModeForWindow, 133, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(SetPreferredAppMode, 135, m_hUxtheme); + UXTHEME_INIT_ORD_FUNC(FlushMenuThemes, 136, m_hUxtheme); + } + auto hModule = ::GetModuleHandle(_T("user32.dll")); + if (hModule) + { + UXTHEME_INIT_FUNC(SetWindowCompositionAttribute, hModule); + } + } + + ~CUxTheme() + { + if (m_hUxtheme) + ::FreeLibrary(m_hUxtheme); + } + + bool ShouldAppsUseDarkMode() const + { + if (m_pfnShouldAppsUseDarkMode) + return m_pfnShouldAppsUseDarkMode(); + return false; + } + + bool IsHighContrast() const + { + HIGHCONTRASTW highContrast = { sizeof(highContrast) }; + if (::SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(highContrast), &highContrast, FALSE)) + return highContrast.dwFlags & HCF_HIGHCONTRASTON; + return false; + } + + PreferredAppMode SetPreferredAppMode(PreferredAppMode appMode) const + { + if (m_pfnSetPreferredAppMode) + return m_pfnSetPreferredAppMode(appMode); + return PreferredAppMode::Default; + } + + void FlushMenuThemes() const + { + if (m_pfnFlushMenuThemes) + m_pfnFlushMenuThemes(); + } + + HTHEME OpenNcThemeData(HWND hWnd, LPCWSTR pszClassList) const + { + if (m_pfnOpenNcThemeData) + return m_pfnOpenNcThemeData(hWnd, pszClassList); + return NULL; + } + + bool AllowDarkModeForWindow(HWND hWnd, bool allow) const + { + DWMNCRENDERINGPOLICY ncrp = allow ? DWMNCRP_ENABLED : DWMNCRP_DISABLED; + ::DwmSetWindowAttribute(hWnd, DWMWA_NCRENDERING_POLICY, &ncrp, sizeof(ncrp)); + + auto result = false; + if (m_pfnAllowDarkModeForWindow) + result = m_pfnAllowDarkModeForWindow(hWnd, allow); + + return result; + } + + BOOL SetWindowCompositionAttribute(HWND hWnd, WINDOWCOMPOSITIONATTRIB attr, BOOL value) const + { + if (m_pfnSetWindowCompositionAttribute) + { + WINDOWCOMPOSITIONATTRIBDATA data = { attr, &value, sizeof(value) }; + return m_pfnSetWindowCompositionAttribute(hWnd, &data); + } + return FALSE; + } + + bool SwitchWindowDarkMode(HWND hWnd, bool setDark, bool immersive = false) const + { + auto result = false; + if (immersive) + { + auto value = setDark ? DWM_BB_ENABLE : FALSE; + result = SUCCEEDED(::DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value))); + } + else + { + result = SetWindowCompositionAttribute(hWnd, WINDOWCOMPOSITIONATTRIB::WCA_USEDARKMODECOLORS, setDark ? TRUE : FALSE); + } + return result; + } + + bool IsColorSchemeChangeMessage(LPARAM lParam) const + { + auto result = false; + + if (lParam && 0 == ::lstrcmpi(reinterpret_cast(lParam), _T("ImmersiveColorSet"))) + { + if (m_pfnRefreshImmersiveColorPolicyState) + { + m_pfnRefreshImmersiveColorPolicyState(); + } + result = true; + } + if (m_pfnGetIsImmersiveColorUsingHighContrast) + { + m_pfnGetIsImmersiveColorUsingHighContrast(IMMERSIVE_HC_CACHE_MODE::IHCM_REFRESH); + } + return result; + } + + bool IsColorSchemeChangeMessage(UINT message, LPARAM lParam) const + { + if (message == WM_SETTINGCHANGE) + return IsColorSchemeChangeMessage(lParam); + return false; + } + + bool IsInDarkMode() + { + return ShouldAppsUseDarkMode() && !IsHighContrast(); + } + + COLORREF UIElementSysColor(UIElementType type) const + { + auto col = WinRTSettings.UIElementColor(type); + return RGB(col.R, col.G, col.B); + } + + COLORREF GetSysColorValue(UIColorType type) const + { + auto col = WinRTSettings.GetColorValue(type); + return RGB(col.R, col.G, col.B); + } + + +#ifdef UXMODE_SUPPORT_SCROLLBAR + inline void FixDarkScrollBar(); +#endif // UXMODE_SUPPORT_SCROLLBAR + +private: + + HMODULE m_hUxtheme{NULL}; + UXTHEME_DECLARE_FUNC(OpenNcThemeData); + UXTHEME_DECLARE_FUNC(RefreshImmersiveColorPolicyState); + UXTHEME_DECLARE_FUNC(GetIsImmersiveColorUsingHighContrast); + UXTHEME_DECLARE_FUNC(AllowDarkModeForWindow); + UXTHEME_DECLARE_FUNC(ShouldAppsUseDarkMode); + UXTHEME_DECLARE_FUNC(SetPreferredAppMode); + UXTHEME_DECLARE_FUNC(FlushMenuThemes); + UXTHEME_DECLARE_FUNC(SetWindowCompositionAttribute); + winrt::Windows::UI::ViewManagement::UISettings WinRTSettings; + bool m_hasComCtlHook{ false }; +}; + +extern CUxTheme uxTheme; + +#ifdef UXMODE_SUPPORT_SCROLLBAR +void CUxTheme::FixDarkScrollBar() +{ + if (!m_hasComCtlHook) + { + HMODULE hComctl = ::LoadLibraryEx(_T("comctl32.dll"), nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hComctl) + { + auto addr = ::FindDelayLoadThunkInModule(hComctl, "uxtheme.dll", 49); // OpenNcThemeData + if (addr) + { + DWORD oldProtect; + if (::VirtualProtect(addr, sizeof(IMAGE_THUNK_DATA), PAGE_READWRITE, &oldProtect)) + { + auto MyOpenThemeData = [](HWND hWnd, LPCWSTR classList) -> HTHEME { + if (wcscmp(classList, L"ScrollBar") == 0) + { + hWnd = nullptr; + classList = L"Explorer::ScrollBar"; + } + return uxTheme.OpenNcThemeData(hWnd, classList); + }; + + addr->u1.Function = reinterpret_cast(static_cast(MyOpenThemeData)); + ::VirtualProtect(addr, sizeof(IMAGE_THUNK_DATA), oldProtect, &oldProtect); + } + } + } + m_hasComCtlHook = true; + } +} +#endif // UXMODE_SUPPORT_SCROLLBAR + +#define UXCOLOR_DARKER(color, factor) \ +RGB(max(0x0, GetRValue(color) - (0xff * factor)), max(0x0, GetGValue(color) - (0xff * factor)), max(0x0, GetBValue(color) - (0xff * factor))) + +#define UXCOLOR_LIGHTER(color, factor) \ +RGB( \ +min(0xff, GetRValue(color) + (0xff * factor)), \ +min(0xff, GetGValue(color) + (0xff * factor)), \ +min(0xff, GetBValue(color) + (0xff * factor)) \ +) + +struct UxModeMenuColors +{ + COLORREF crText{ RGB(0,0,0) }; + COLORREF crTextDisabled{ RGB(128,128,128) }; + COLORREF crBg{ RGB(255,255,255) }; + COLORREF crHihghlight{ RGB(200,200,200) }; + COLORREF crHighlightBg{ RGB(200,200,200) }; + COLORREF crBorder{ RGB(200,200,200) }; + CBrush brushText; + CBrush brushTextDisabled; + CBrush brushBg; + + void Update(bool isDark) + { + crBg = isDark ? UXCOLOR_LIGHTER(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background), 0.2) : ::GetSysColor(COLOR_3DFACE); + crText = isDark ? uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground) : ::GetSysColor(COLOR_MENUTEXT); + crTextDisabled = isDark ? UXCOLOR_DARKER(crText, 0.5) : ::GetSysColor(COLOR_GRAYTEXT); + crBorder = isDark ? UXCOLOR_DARKER(crText, 0.6) : ::GetSysColor(COLOR_SCROLLBAR); + crHihghlight = isDark ? uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background) : ::GetSysColor(COLOR_HIGHLIGHT); + crHighlightBg = isDark ? UXCOLOR_LIGHTER(crBg, 0.18) : uxTheme.GetSysColorValue(CUxTheme::UIColorType::AccentLight3); + if (isDark) + { + if (!brushBg.IsNull()) + brushBg.DeleteObject(); + brushBg.CreateSolidBrush(crBg); + } + if (!brushText.IsNull()) + brushText.DeleteObject(); + brushText.CreateSolidBrush(crText); + if (!brushTextDisabled.IsNull()) + brushTextDisabled.DeleteObject(); + brushTextDisabled.CreateSolidBrush(crTextDisabled); + + } +}; +struct UxModeMenuMetrics +{ + CSize sizeIcon{ 0, 0 }; + CSize sizeMnuArrow{ 16, 16 }; + CSize paddingText{ 5, 5 }; + CSize paddingIcon{ 2, 2 }; + int itemHeight{ -1 }; + NONCLIENTMETRICS metricsNC{ 0 }; +}; + +template +class CUxModeBase +{ +protected: + + virtual void UxModeSetup() + { + if (!m_menuTheme.IsThemeNull()) + m_menuTheme.CloseThemeData(); + auto hwnd = GetOwnerHWND(); + if (::IsWindow(hwnd)) + m_menuTheme.OpenThemeData(hwnd, VSCLASS_MENU); + UxModeUpdateColorSettings(); + m_lastModeWasDark = uxTheme.IsInDarkMode(); + } + + virtual void UxModeUpdateColorSettings() + { + } + + virtual HWND GetOwnerHWND() + { + return NULL; + } + + virtual T* GetThis() + { + return dynamic_cast(this); + } + + LRESULT UxModeOnThemeChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) + { + bHandled = FALSE; + if (m_lastModeWasDark != uxTheme.IsInDarkMode()) + { + bHandled = TRUE; + UxModeSetup(); + auto hwnd = GetOwnerHWND(); + if (::IsWindow(hwnd)) + ::UpdateWindow(hwnd); + } + return 0L; + } + + LRESULT UxModeOnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) + { + bHandled = FALSE; + if (!m_menuTheme.IsThemeNull()) + m_menuTheme.CloseThemeData(); + + return 1; + } + +protected: + bool m_lastModeWasDark{ false }; + CTheme m_menuTheme; +}; + +template +class CUxModeMenuHelper + : public virtual CUxModeBase +{ +public: + + BEGIN_MSG_MAP(CUxModeMenuHelper) + MESSAGE_HANDLER(WM_THEMECHANGED, UxModeOnThemeChange) + MESSAGE_HANDLER(WM_DESTROY, UxModeOnDestroy) + END_MSG_MAP() + +protected: + virtual void UxModeSetup() override + { + CBitmap bmpArrow; + BITMAP bm; + if (bmpArrow.LoadOEMBitmap(OBM_MNARROW) && bmpArrow.GetBitmap(bm)) + { + m_menuMetrics.sizeMnuArrow.cx = bm.bmWidth; + m_menuMetrics.sizeMnuArrow.cy = bm.bmHeight; + } + + m_menuMetrics.metricsNC.cbSize = sizeof(NONCLIENTMETRICS); + if (::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, m_menuMetrics.metricsNC.cbSize, &m_menuMetrics.metricsNC, 0)) + { + m_menuMetrics.itemHeight = max(m_menuMetrics.metricsNC.iMenuHeight, ::abs(m_menuMetrics.metricsNC.lfMenuFont.lfHeight) + (m_menuMetrics.paddingText.cy * 2)); + if (m_menuMetrics.sizeIcon.cy && m_menuMetrics.itemHeight < (m_menuMetrics.paddingIcon.cy * 2) + m_menuMetrics.sizeIcon.cy) + { + m_menuMetrics.itemHeight = (m_menuMetrics.paddingIcon.cy * 2) + m_menuMetrics.sizeIcon.cy; + } + if (m_menuMetrics.paddingIcon.cy > m_menuMetrics.itemHeight - m_menuMetrics.sizeIcon.cy - m_menuMetrics.paddingIcon.cy) + { + m_menuMetrics.paddingIcon.cy = (m_menuMetrics.itemHeight - m_menuMetrics.sizeIcon.cy) / 2; + } + } + CUxModeBase::UxModeSetup(); + if (!m_menuTheme.IsThemeNull()) + { + HBITMAP hBmp = NULL; + auto hr = m_menuTheme.GetThemeBitmap(MENU_POPUPBACKGROUND, 0, TMT_DIBDATA, GBF_DIRECT, hBmp); + if (SUCCEEDED(hr)) + { + if (!m_menuColors.brushBg.IsNull()) + m_menuColors.brushBg.DeleteObject(); + m_menuColors.brushBg.CreatePatternBrush(hBmp); + } + } + } + + virtual void UxModeUpdateColorSettings() override + { + m_menuColors.Update(uxTheme.IsInDarkMode()); + } + + void UxModeUpdateMenuInfo(MENUINFO& mi) + { + if (uxTheme.IsInDarkMode()) + { + mi.fMask |= MIM_BACKGROUND; + mi.hbrBack = m_menuColors.brushBg; + } + } + + BOOL CustomDrawMenuArrow(HDC hdcItem, LPRECT rcItem, bool isDisabled) + { + CRect rcArrow(rcItem); + rcArrow.left = rcItem->right - m_menuMetrics.sizeMnuArrow.cx; + + if (rcArrow.Height() > m_menuMetrics.sizeMnuArrow.cy) + { + rcArrow.top += (rcArrow.Height() - m_menuMetrics.sizeMnuArrow.cy) / 2; + rcArrow.bottom = rcArrow.top + m_menuMetrics.sizeMnuArrow.cy; + } + + auto result = FALSE; + CDCHandle dc(hdcItem); + +#ifdef UXMODE_FULL_CUSTOMDRAWN_MENUARROW + CDC arrowDC; + arrowDC.CreateCompatibleDC(hdcItem); + + CBitmap arrowBitmap; + arrowBitmap.CreateCompatibleBitmap(dc, rcArrow.Width(), rcArrow.Height()); + auto oldArrowBmp = arrowDC.SelectBitmap(arrowBitmap); + + CDC fillDC; + fillDC.CreateCompatibleDC(hdcItem); + + CBitmap fillBitmap; + fillBitmap.CreateCompatibleBitmap(dc, rcArrow.Width(), rcArrow.Height()); + auto oldFillBmp = fillDC.SelectBitmap(fillBitmap); + + CRect rcTemp(0, 0, rcArrow.Width(), rcArrow.Height()); + result = arrowDC.DrawFrameControl(&rcTemp, DFC_MENU, DFCS_MENUARROW); + + fillDC.FillRect(rcTemp, isDisabled ? m_menuColors.brushTextDisabled : m_menuColors.brushText); + + dc.BitBlt(rcArrow.left, rcArrow.top, rcArrow.Width(), rcArrow.Height(), fillDC, 0, 0, SRCINVERT); + dc.BitBlt(rcArrow.left, rcArrow.top, rcArrow.Width(), rcArrow.Height(), arrowDC, 0, 0, SRCAND); + dc.BitBlt(rcArrow.left, rcArrow.top, rcArrow.Width(), rcArrow.Height(), fillDC, 0, 0, SRCINVERT); + + arrowDC.SelectBitmap(oldArrowBmp); + fillDC.SelectBitmap(oldFillBmp); +#else + result = SUCCEEDED(m_menuTheme.DrawThemeBackground(dc, MENU_POPUPSUBMENU, isDisabled ? MSM_DISABLED : MSM_NORMAL, rcArrow, NULL)); + +#endif //UXMODE_FULL_CUSTOMDRAW_MENUARROW + return result; + } + +protected: + UxModeMenuMetrics m_menuMetrics; + UxModeMenuColors m_menuColors; +}; + +#define WM_UAHDRAWMENU 0x0091 // lParam is UAHMENU +#define WM_UAHDRAWMENUITEM 0x0092 // lParam is UAHDRAWMENUITEM +#define WM_UAHMEASUREMENUITEM 0x0094 // lParam is UAHMEASUREMENUITEM + +// hmenu is the main window menu; hdc is the context to draw in +typedef struct tagUAHMENU +{ + HMENU hmenu; + HDC hdc; + DWORD dwFlags; // no idea what these mean, in my testing it's either 0x00000a00 or sometimes 0x00000a10 +} UAHMENU; + +// describes the sizes of the menu bar or menu item +typedef union tagUAHMENUITEMMETRICS +{ + // cx appears to be 14 / 0xE less than rcItem's width! + // cy 0x14 seems stable, i wonder if it is 4 less than rcItem's height which is always 24 atm + struct { + DWORD cx; + DWORD cy; + } rgsizeBar[2]; + struct { + DWORD cx; + DWORD cy; + } rgsizePopup[4]; +} UAHMENUITEMMETRICS; + +// not really used in our case but part of the other structures +typedef struct tagUAHMENUPOPUPMETRICS +{ + DWORD rgcx[4]; + DWORD fUpdateMaxWidths : 2; // from kernel symbols, padded to full dword +} UAHMENUPOPUPMETRICS; + +// menu items are always referred to by iPosition here +typedef struct tagUAHMENUITEM +{ + int iPosition; // 0-based position of menu item in menubar + UAHMENUITEMMETRICS umim; + UAHMENUPOPUPMETRICS umpm; +} UAHMENUITEM; + +// the DRAWITEMSTRUCT contains the states of the menu items, as well as +// the position index of the item in the menu, which is duplicated in +// the UAHMENUITEM's iPosition as well +typedef struct UAHDRAWMENUITEM +{ + DRAWITEMSTRUCT dis; // itemID looks uninitialized + UAHMENU um; + UAHMENUITEM umi; +} UAHDRAWMENUITEM; + +template +struct is_dialog +{ +private: + typedef std::true_type yes; + typedef std::false_type no; + + template static auto test(int) -> decltype(std::declval().GetDialogProc() != nullptr, yes()); + + template static no test(...); + +public: + + static constexpr bool value = std::is_same(0)), yes>::value; +}; +template +struct is_prop_sheet +{ +private: + typedef std::true_type yes; + typedef std::false_type no; + + template static auto test(int) -> decltype(std::declval().GetActivePage() != nullptr, yes()); + + template static no test(...); + +public: + + static constexpr bool value = std::is_same(0)), yes>::value; +}; +enum class UxModeWindowType +{ + GENERIC, DIALOG, PROP_SHEET +}; + +#define UXPROP_LISTVIEWPROC _T("UxModeListViewProc") + +template +class CUxModeWindow + : public virtual CUxModeBase +{ +public: + + BEGIN_MSG_MAP(CUxModeWindow) + switch (UxWindowType) + { + case UxModeWindowType::DIALOG: + MESSAGE_HANDLER(WM_INITDIALOG, UxModeOnInitDialog) + break; + default: + MESSAGE_HANDLER(WM_CREATE, UxModeOnCreate) + break; + } + MESSAGE_HANDLER(WM_CTLCOLORDLG, UxModeOnCtlColorDlg) + MESSAGE_HANDLER(WM_CTLCOLORSTATIC, UxModeOnCtlColorDlg) + MESSAGE_HANDLER(WM_SETTINGCHANGE, UxModeOnSettingChange) + MESSAGE_HANDLER(WM_THEMECHANGED, UxModeOnThemeChange) + MESSAGE_HANDLER(WM_NCPAINT, UxModeOnNcPaint) + MESSAGE_HANDLER(WM_NCACTIVATE, UxModeOnNcPaint) + MESSAGE_HANDLER(WM_UAHDRAWMENU, UxModeOnUahDrawMenu) + MESSAGE_HANDLER(WM_UAHDRAWMENUITEM, UxModeOnUahDrawMenuItem) + MESSAGE_HANDLER(WM_DESTROY, UxModeOnDestroy) +#ifdef UXMODE_SUPPORT_LISTVIEW +#ifdef UXMODE_CUSTOMDRAW_LISTVIEW_GROUPS +#pragma warning( push ) +#pragma warning( disable : 26454 ) + NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, UxModeOnCustomDraw) +#pragma warning( pop ) +#endif +#endif // UXMODE_SUPPORT_LISTVIEW + END_MSG_MAP() + +#ifdef UXMODE_SUPPORT_LISTVIEW + + static LRESULT CALLBACK UxModeSubclassedListViewProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + auto OldProc = ::GetProp(hWnd, UXPROP_LISTVIEWPROC); + LRESULT result = 0L; + switch (uMsg) + { + case WM_NOTIFY: + { + auto lpHdr = reinterpret_cast(lParam); + if (lpHdr->code == NM_CUSTOMDRAW && ListView_GetHeader(hWnd) == lpHdr->hwndFrom) + { + LPNMCUSTOMDRAW lpcd = (LPNMCUSTOMDRAW)lParam; + switch (lpcd->dwDrawStage) + { + case CDDS_PREPAINT: + if (uxTheme.IsInDarkMode()) + return CDRF_NOTIFYITEMDRAW; + break; + case CDDS_ITEMPREPAINT: + { + auto cr = uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground); + if (CDIS_GRAYED == (CDIS_GRAYED & lpcd->uItemState) || CDIS_DISABLED == (CDIS_DISABLED & lpcd->uItemState)) + { + cr = uxTheme.IsInDarkMode() ? UXCOLOR_DARKER(cr, 0.3) : ::GetSysColor(COLOR_GRAYTEXT); + } + SetTextColor(lpcd->hdc, cr); + return CDRF_NEWFONT; + } + break; + } + } + break; + } + case WM_DESTROY: + ::RemoveProp(hWnd, UXPROP_LISTVIEWPROC); + if (OldProc) + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast(OldProc)); + break; + } + if (OldProc) + { + result = ::CallWindowProc(reinterpret_cast(OldProc), hWnd, uMsg, wParam, lParam); + } + return result; + } +#endif // UXMODE_SUPPORT_LISTVIEW + +public: + static UxModeWindowType UxWindowType; + +protected: + + virtual HWND GetOwnerHWND() override + { + return GetThis()->m_hWnd; + } + + virtual T* GetThis() override + { + return static_cast(this); + } + + virtual void UxModeSetup() override + { + ATLTRACE(_T(__FUNCTION__) _T(" WindowType=%d IsAppThemed=%d IsThemeActive=%d bgColor=%x\n"), UxWindowType, ::IsAppThemed(), ::IsThemeActive() + , uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); + + auto isDark = uxTheme.IsInDarkMode(); + if (isDark && m_lastModeWasDark != isDark) + { + if (!m_brushBg.IsNull()) + m_brushBg.DeleteObject(); + m_brushBg.CreateSolidBrush(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); + } + + m_lastModeWasDark = isDark; + + auto pSelf = GetThis(); + if (!m_initialized) + { + uxTheme.AllowDarkModeForWindow(pSelf->m_hWnd, true); + MENUBARINFO mbi = { sizeof(mbi) }; + m_hasMenuBar = ::GetMenuBarInfo(pSelf->m_hWnd, OBJID_MENU, 0, &mbi); +#ifdef UXMODE_SUPPORT_SCROLLBAR + uxTheme.FixDarkScrollBar(); +#endif //UXMODE_SUPPORT_SCROLLBAR + } + uxTheme.SwitchWindowDarkMode(pSelf->m_hWnd, isDark); + + CUxModeBase::UxModeSetup(); + + ::EnumChildWindows(pSelf->m_hWnd, [](HWND hwnd, LPARAM lParam) -> BOOL + { + auto pParent = reinterpret_cast(lParam); + CString str; + ::GetClassName(hwnd, str.GetBufferSetLength(MAX_PATH), MAX_PATH); + str.ReleaseBuffer(); + if (_T("Button") == str || _T("Edit") == str) + { + pParent->SetUxModeForThemedControl(hwnd, L"Explorer"); + } + else if (_T("ComboBox") == str) + { + pParent->SetUxModeForThemedControl(hwnd, L"CFD"); + } +#ifdef UXMODE_SUPPORT_LISTVIEW + else if (_T("SysListView32") == str) + { + pParent->SetUxModeForListView(hwnd); + } +#endif // UXMODE_SUPPORT_LISTVIEW +#ifdef UXMODE_SUPPORT_PROGRESSBAR + else if (_T("msctls_progress32") == str) + { + pParent->SetUxModeForProgressBar(hwnd); + } +#endif // UXMODE_SUPPORT_PROGRESSBAR +#ifdef UXMODE_SUPPORT_TABCONTROL + else if (_T("SysTabControl32") == str) + { + pParent->SetUxModeForThemedControl(hwnd, L"DarkMode_Explorer"); + } +#endif // UXMODE_SUPPORT_TABCONTROL + else + { + ::SendMessage(hwnd, WM_THEMECHANGED, 0, 0); + } + + return TRUE; + }, reinterpret_cast(this)); + + if (!m_initialized) + pSelf->SendMessage(WM_THEMECHANGED); + + m_initialized = true; + } + + bool SetUxModeForThemedControl(HWND hWnd, LPCWSTR strTheme, LPCWSTR strSublist = NULL, bool force = false) const + { + auto r = uxTheme.AllowDarkModeForWindow(hWnd, uxTheme.IsInDarkMode()); + auto hr = !force && m_initialized ? S_FALSE : ::SetWindowTheme(hWnd, strTheme, strSublist); + if (S_FALSE == hr) + { + ::SendMessage(hWnd, WM_THEMECHANGED, 0, 0); + } + return r && SUCCEEDED(hr); + } + +#ifdef UXMODE_SUPPORT_PROGRESSBAR + bool SetUxModeForProgressBar(HWND hWnd) + { + HRESULT hr = S_OK; + if (uxTheme.IsInDarkMode()) + { + hr = ::SetWindowTheme(hWnd, L"", L""); + ::SendMessage(hWnd, PBM_SETBKCOLOR, 0, UXCOLOR_LIGHTER(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background), 0.18)); + } + else { + hr = ::SetWindowTheme(hWnd, VSCLASS_PROGRESS, NULL); + } + return SUCCEEDED(hr); + } +#endif // UXMODE_SUPPORT_PROGRESSBAR + +#ifdef UXMODE_SUPPORT_LISTVIEW + bool SetUxModeForListView(HWND hWnd) + { + ListView_SetBkColor(hWnd, uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); + ListView_SetTextBkColor(hWnd, uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); + ListView_SetTextColor(hWnd, uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground)); + auto isDark = uxTheme.IsInDarkMode(); + if (isDark) + UxModeSubclassListView(hWnd); + + //auto result = SetUxModeForThemedControl(hWnd, isDark ? L"DarkMode_Explorer" : L"Explorer", NULL, true); + auto result = SetUxModeForThemedControl(hWnd, isDark ? L"DarkMode_ItemsView" : L"ItemsView", NULL, true); + SetUxModeForThemedControl(ListView_GetHeader(hWnd), isDark ? L"DarkMode_ItemsView" : L"ItemsView", L"Header", true); + return result; + } + + LONG_PTR UxModeSubclassListView(HWND hWnd) const + { + auto result = reinterpret_cast(::GetProp(hWnd, UXPROP_LISTVIEWPROC)); + if (!result) + { + result = ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast(CUxModeWindow::UxModeSubclassedListViewProc)); + if (result) + { + ::SetProp(hWnd, UXPROP_LISTVIEWPROC, reinterpret_cast(result)); + } + } + return result; + } +#endif // UXMODE_SUPPORT_LISTVIEW + + bool UxModeDrawGroupBox(HWND hWnd, HDC hDC) + { + CWindow wnd(hWnd); + CDCHandle dc(hDC); + + CRect rc; + wnd.GetClientRect(rc); + + CRect rcFrame(rc); + rcFrame.top += 10; + auto crBorder = uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground); + crBorder = UXCOLOR_DARKER(crBorder, 0.6); + CPen pen; + pen.CreatePen(PS_SOLID, 1, crBorder); + auto oldPen = dc.SelectPen(pen); + auto oldBrush = dc.SelectBrush(reinterpret_cast(::GetStockObject(HOLLOW_BRUSH))); + auto result = TRUE == dc.Rectangle(rcFrame); + dc.SelectPen(oldPen); + dc.SelectBrush(oldBrush); + + CString str; + wnd.GetWindowText(str); + if (str.GetLength()) + { + CRect rcText(rc); + rcText.left += 10; + rcText.bottom = 20; + auto oldFont = dc.SelectFont(reinterpret_cast(::GetStockObject(DEFAULT_GUI_FONT))); + result = 0 != dc.DrawText(str, str.GetLength(), rcText, DT_SINGLELINE | DT_VCENTER | DT_LEFT) && result; + dc.SelectFont(oldFont); + } + + if (result) + { + dc.ExcludeClipRect(rc); + } + return result; + } + + int UxModeDrawMenuBar(UAHMENU* pUDM) + { + auto pSelf = GetThis(); + MENUBARINFO mbi = { sizeof(mbi) }; + if (::GetMenuBarInfo(pSelf->m_hWnd, OBJID_MENU, 0, &mbi)) + { + CRect rcWindow; + pSelf->GetWindowRect(rcWindow); + + CRect rc = mbi.rcBar; + rc.OffsetRect(-rcWindow.left, -rcWindow.top); + + m_menuTheme.DrawThemeBackground(pUDM->hdc, MENU_POPUPBACKGROUND, 0, rc, NULL); + } + return 0; + } + + BOOL UxModeDrawMenuBarSeparator() + { + auto pSelf = GetThis(); + CRect rcClient; + pSelf->GetClientRect(rcClient); + pSelf->MapWindowPoints(nullptr, rcClient); + + CRect rcWindow; + pSelf->GetWindowRect(rcWindow); + + rcClient.OffsetRect(-rcWindow.left, -rcWindow.top); + + CRect rcLine = rcClient; + rcLine.bottom = rcLine.top; + rcLine.top--; + + CWindowDC dc(pSelf->m_hWnd); + m_menuTheme.DrawThemeBackground(dc, MENU_POPUPBACKGROUND, 0, rcLine, NULL); + return FALSE; + } + + BOOL UxModeDrawMenuBarItem(UAHDRAWMENUITEM* pUDMI) + { + CDCHandle dc(pUDMI->um.hdc); + + auto isDisabled = ODS_DISABLED == (ODS_DISABLED & pUDMI->dis.itemState) || ODS_GRAYED == (ODS_GRAYED & pUDMI->dis.itemState); + auto isHot = ODS_HOTLIGHT == (ODS_HOTLIGHT & pUDMI->dis.itemState) || ODS_FOCUS == (ODS_FOCUS & pUDMI->dis.itemState); + auto itemState = MPI_NORMAL; + //if (isHot) + // itemState = isDisabled ? MBI_DISABLEDHOT : MBI_HOT; + if (isHot || ODS_SELECTED == (ODS_SELECTED & pUDMI->dis.itemState)) + itemState = isDisabled ? MPI_DISABLEDHOT : MPI_HOT; + m_menuTheme.DrawThemeBackground(dc, MENU_POPUPITEM, itemState, &pUDMI->dis.rcItem, NULL); + + CString caption; + CMenuItemInfo mii; + mii.fMask = MIIM_STRING; + mii.dwTypeData = caption.GetBufferSetLength(MAX_PATH); + mii.cch = MAX_PATH; + + ::GetMenuItemInfo(pUDMI->um.hmenu, pUDMI->umi.iPosition, TRUE, &mii); + caption.ReleaseBuffer(); + + DWORD dwFlags = DT_CENTER | DT_SINGLELINE | DT_VCENTER; + if (ODS_NOACCEL == (ODS_NOACCEL & pUDMI->dis.itemState)) { + dwFlags |= DT_HIDEPREFIX; + } + + m_menuTheme.DrawThemeText(dc, MENU_POPUPITEM, itemState, caption, -1, dwFlags, 0, &pUDMI->dis.rcItem); + return FALSE; + } + +#ifdef UXMODE_SUPPORT_LISTVIEW +#ifdef UXMODE_CUSTOMDRAW_LISTVIEW_GROUPS + LRESULT UxModeCustomDrawListView(LPNMLVCUSTOMDRAW pNMLVCD) + { + LRESULT result = CDRF_DODEFAULT; + switch (pNMLVCD->nmcd.dwDrawStage) + { + case CDDS_PREPAINT: + //result = CDRF_NOTIFYITEMDRAW; + if (LVCDI_GROUP == pNMLVCD->dwItemType) + { + LVGROUP group{sizeof(LVGROUP)}; + group.mask = LVGF_GROUPID | LVGF_HEADER | LVGF_STATE; + group.stateMask = LVGS_FOCUSED | LVGS_SELECTED; + ListView_GetGroupInfo(pNMLVCD->nmcd.hdr.hwndFrom, pNMLVCD->nmcd.dwItemSpec, &group); + ATLTRACE(_T(__FUNCTION__) _T(" CDDS_PREPAINT hwnd=%p, groupId=%d, itemState=%d, groupState=%d, pszHeader=%s\n") + , pNMLVCD->nmcd.hdr.hwndFrom, pNMLVCD->nmcd.dwItemSpec, pNMLVCD->nmcd.uItemState, group.state, group.pszHeader); + + CRect rcHeader(pNMLVCD->rcText); + + auto isSelected = LVGS_FOCUSED ==(LVGS_FOCUSED & group.state) || LVGS_SELECTED == (LVGS_SELECTED & group.state); //CDIS_HOT == (CDIS_HOT & pNMLVCD->nmcd.uItemState) || CDIS_FOCUS == (CDIS_FOCUS & pNMLVCD->nmcd.uItemState) || CDIS_SELECTED == (CDIS_SELECTED & pNMLVCD->nmcd.uItemState); + auto isDisabled = CDIS_DISABLED == (CDIS_DISABLED & pNMLVCD->nmcd.uItemState) || CDIS_GRAYED == (CDIS_GRAYED & pNMLVCD->nmcd.uItemState); + + pNMLVCD->clrText = uxTheme.GetSysColorValue(CUxTheme::UIColorType::AccentLight2); + pNMLVCD->clrTextBk = ListView_GetBkColor(pNMLVCD->nmcd.hdr.hwndFrom); + + CDCHandle dc(pNMLVCD->nmcd.hdc); + + CPen pen; + pen.CreatePen(PS_SOLID, 1, isSelected && !isDisabled ? m_menuColors.crHihghlight : pNMLVCD->clrTextBk); + CBrush brush; + brush.CreateSolidBrush(isSelected && !isDisabled ? m_menuColors.crHighlightBg : pNMLVCD->clrTextBk); + auto oldPen = dc.SelectPen(pen); + auto oldBrush = dc.SelectBrush(brush); + + dc.Rectangle(rcHeader); + + if (oldPen) + dc.SelectPen(oldPen); + if (oldBrush) + dc.SelectBrush(oldBrush); + + + CRect rcLabel; + ListView_GetGroupRect(pNMLVCD->nmcd.hdr.hwndFrom, pNMLVCD->nmcd.dwItemSpec, LVGGR_LABEL, rcLabel); + + CRect rcLine; + rcLine.left = rcHeader.left + rcLabel.Width() + (rcLabel.left - rcHeader.left) + 5; + rcLine.right = rcHeader.right - (rcLabel.left - rcHeader.left); + rcLine.top = rcHeader.top + (rcHeader.Height() / 2); + rcLine.bottom = rcLine.top + 1; + + brush.DeleteObject(); + brush.CreateSolidBrush(isSelected ? UXCOLOR_DARKER(m_menuColors.crBorder, 0.5) : m_menuColors.crBorder); + if (!brush.IsNull()) + { + dc.FillRect(rcLine, brush); + } + + dc.SetTextColor(pNMLVCD->clrText); + dc.SetBkColor(pNMLVCD->clrTextBk); + dc.SetBkMode(TRANSPARENT); + dc.DrawText(group.pszHeader, group.cchHeader, rcLabel, DT_SINGLELINE | DT_VCENTER | DT_LEFT); + + DWORD dwFlags = DT_CENTER | DT_SINGLELINE | DT_VCENTER; + DTTOPTS opts = { sizeof(opts), DTT_TEXTCOLOR, pNMLVCD->clrText }; + if (isDisabled) + { + opts.crText = m_menuColors.crTextDisabled; + } + m_menuTheme.DrawThemeTextEx(dc, MENU_BARITEM, MBI_NORMAL, group.pszHeader, -1, dwFlags, rcLabel, &opts); + //dc.ExcludeClipRect(rcHeader); + result = CDRF_SKIPDEFAULT; + } + break; + } + return result; + } +#endif +#endif // UXMODE_SUPPORT_LISTVIEW + +private: + + LRESULT UxModeOnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) + { + bHandled = FALSE; + UxModeSetup(); + return 0L; + } + + LRESULT UxModeOnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) + { + bHandled = FALSE; + UxModeSetup(); + return TRUE; + } + + LRESULT UxModeOnCtlColorDlg(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + bHandled = FALSE; + //ATLTRACE(_T(__FUNCTION__) _T("\n")); + if (uxTheme.IsInDarkMode() && !m_brushBg.IsNull()) + { + bHandled = TRUE; + + CWindow wnd(reinterpret_cast(lParam)); + + CDCHandle dc(reinterpret_cast(wParam)); + dc.SetBkColor(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Background)); + dc.SetTextColor(uxTheme.GetSysColorValue(CUxTheme::UIColorType::Foreground)); + + if (BS_GROUPBOX == (BS_GROUPBOX & wnd.GetWindowLongPtr(GWL_STYLE))) + { + UxModeDrawGroupBox(wnd, dc); + } + return reinterpret_cast(WS_EX_TRANSPARENT == (WS_EX_TRANSPARENT & wnd.GetWindowLongPtr(GWL_EXSTYLE)) + ? ::GetStockObject(HOLLOW_BRUSH) + : m_brushBg.m_hBrush); + } + return FALSE; + } + + LRESULT UxModeOnUahDrawMenu(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) + { + bHandled = FALSE; + //ATLTRACE(_T(__FUNCTION__) _T("\n")); + if (m_hasMenuBar && uxTheme.IsInDarkMode()) + { + bHandled = TRUE; + UxModeDrawMenuBar(reinterpret_cast(lParam)); + return TRUE; + } + return 0L; + } + + LRESULT UxModeOnNcPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + bHandled = TRUE; + auto pSelf = GetThis(); + auto result = ::DefWindowProc(pSelf->m_hWnd, uMsg, wParam, lParam); + if (m_hasMenuBar && uxTheme.IsInDarkMode()) + { + MENUBARINFO mbi = { sizeof(mbi) }; + if (::GetMenuBarInfo(pSelf->m_hWnd, OBJID_MENU, 0, &mbi)) + { + UxModeDrawMenuBarSeparator(); + } + + } + return result; + } + + LRESULT UxModeOnUahDrawMenuItem(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) + { + bHandled = FALSE; + if (m_hasMenuBar && uxTheme.IsInDarkMode()) + { + bHandled = TRUE; + UxModeDrawMenuBarItem(reinterpret_cast(lParam)); + return TRUE; + } + return FALSE; + } + +#ifdef UXMODE_CUSTOMDRAW_LISTVIEW_GROUPS + LRESULT UxModeOnCustomDraw(int /*idCtrl*/, LPNMHDR pnmh, BOOL& bHandled) + { + bHandled = FALSE; + if (uxTheme.IsInDarkMode()) + { + CString str; + ::GetClassName(pnmh->hwndFrom, str.GetBufferSetLength(MAX_PATH), MAX_PATH); + str.ReleaseBuffer(); + ATLTRACE(_T(__FUNCTION__) _T(" class=%s\n"), (LPCTSTR)str); + if (_T("SysListView32") == str) + { + auto result = UxModeCustomDrawListView(reinterpret_cast(pnmh)); + if (CDRF_DODEFAULT != result) + bHandled = TRUE; + return result; + } + } + return CDRF_DODEFAULT; + } +#endif + + LRESULT UxModeOnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) + { + bHandled = FALSE; + if (uxTheme.IsColorSchemeChangeMessage(lParam)) + { + bHandled = TRUE; + GetThis()->SendMessage(WM_THEMECHANGED); + } + return 0L; + } + +protected: + bool m_initialized{ false }; + bool m_hasMenuBar{ false }; + CBrush m_brushBg; +}; + +template +UxModeWindowType CUxModeWindow::UxWindowType = (is_dialog::value ? UxModeWindowType::DIALOG : (is_prop_sheet::value ? UxModeWindowType::PROP_SHEET : UxModeWindowType::GENERIC));