diff --git a/.github/workflows/cmake-publish.yml b/.github/workflows/cmake-publish.yml index afa97d0..2ee37be 100644 --- a/.github/workflows/cmake-publish.yml +++ b/.github/workflows/cmake-publish.yml @@ -45,7 +45,7 @@ jobs: env: CODESIGN_PW: ${{ secrets.CODESIGN_PW }} - name: Publish to GitHub - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: | distr/*.exe @@ -57,5 +57,4 @@ jobs: if: always() continue-on-error: true run: | - del /q "$env:TEMP\signcert.txt" 2>nul - del /q "$env:TEMP\signcert.pfx" 2>nul + del "$env:TEMP\signcert.*" diff --git a/.gitignore b/.gitignore index 85cfabd..7a27b50 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /build **/LocalBuild.* +/test/data # User-specific files *.rsuser diff --git a/buildenv/CMakeLists.txt b/buildenv/CMakeLists.txt index aad15ad..0b85e10 100644 --- a/buildenv/CMakeLists.txt +++ b/buildenv/CMakeLists.txt @@ -2,6 +2,7 @@ # cmake --build build\Cmake-Debug-x64 --config Debug # cmake -Sbuildenv -Bbuild\Cmake-Debug-x86 -DCMAKE_BUILD_TYPE=Debug -T host=x64 -A Win32 # cmake --build build\Cmake-Debug-x86 --config Debug +# cmake -Sbuildenv -Bbuild\Cmake-Release-ARM64 -DCMAKE_BUILD_TYPE=Release -DSIGNER_ISSUER:STRING="" -T host=x64 -A ARM64 # cmake -Sbuildenv -Bbuild\Cmake-Release-x64 -DCMAKE_BUILD_TYPE=Release -DSIGNER_CERT:STRING= -DSIGNER_PASS:STRING= # cmake --build build\Cmake-Release-x64 --config Release --target WinPinMenu-package-inst @@ -11,10 +12,14 @@ if(${CMAKE_VERSION} VERSION_LESS 3.12) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) endif() +get_filename_component(WORKSPACE_ROOT ".." ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}") +set(MAIN_TARGET_SOURCE_DIR "${WORKSPACE_ROOT}/src/app") +set(MAIN_TARGET_RESOURCE_DIR "${MAIN_TARGET_SOURCE_DIR}/res") + include("${CMAKE_CURRENT_LIST_DIR}/LocalBuild.cmake" OPTIONAL) +file(READ "${MAIN_TARGET_SOURCE_DIR}/buildnumber.txt" BUILD_NUMBER) include("${CMAKE_CURRENT_LIST_DIR}/Product.cmake") -file(READ "${CMAKE_CURRENT_LIST_DIR}/buildnumber.txt" BUILD_NUMBER) if(NOT ${BUILD_NUMBER}) set(BUILD_NUMBER 1) endif() @@ -35,14 +40,12 @@ set(CPACK_SINGLE_TARGET_SYSTEM ON) include(${CMAKE_CURRENT_LIST_DIR}/cmake/TargetArch.cmake) include(${CMAKE_CURRENT_LIST_DIR}/cmake/InstallSetup.cmake) -get_filename_component(WORKSPACE_ROOT ".." ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}") set(PRODUCT_LICENSE_FILE "${WORKSPACE_ROOT}/LICENSE") set(MAIN_TARGET_NAME ${PROJECT_NAME}) -set(MAIN_TARGET_SOURCE_DIR "${WORKSPACE_ROOT}/src/app") -set(MAIN_TARGET_RESOURCE_DIR "${MAIN_TARGET_SOURCE_DIR}/res") configure_file("${MAIN_TARGET_SOURCE_DIR}/productmeta.h.in" "${MAIN_TARGET_SOURCE_DIR}/productmeta.h") +configure_file("${MAIN_TARGET_SOURCE_DIR}/app.manifest.in" "${MAIN_TARGET_RESOURCE_DIR}/app.manifest") add_custom_target(${MAIN_TARGET_NAME} ALL) @@ -53,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" ) @@ -64,9 +69,9 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -DBINARY_DIR=${CMAKE_BINARY_DIR} -DSOURCE_DIR=${CMAKE_CURRENT_LIST_DIR} - -DMAIN_TARGET_SOURCE_DIR=${MAIN_TARGET_SOURCE_DIR} + -DTARGET_SOURCE_DIR=${MAIN_TARGET_SOURCE_DIR} -DBUILD_TYPE=$ - -DMAIN_TARGET_NAME=${MAIN_TARGET_NAME} + -DTARGET_NAME=${MAIN_TARGET_NAME} -P ${CMAKE_CURRENT_LIST_DIR}/cmake/PostBuild.cmake COMMENT "Performing post-build tasks ($)" ) @@ -84,18 +89,12 @@ add_custom_command( add_dependencies(${MAIN_TARGET_NAME} ${MAIN_TARGET_NAME}-restore ${MAIN_TARGET_NAME}-vc) -install(FILES ${PRODUCT_LICENSE_FILE} CONFIGURATIONS Release DESTINATION ${INSTALL_DOCDIR}) -install(PROGRAMS "${CMAKE_BINARY_DIR}/../$-${TARGET_ARCH_ID}/${MAIN_TARGET_NAME}.exe" CONFIGURATIONS Release DESTINATION ${CMAKE_INSTALL_BINDIR}) +set(CPACK_INSTALLER_GENERATORS "ZIP" "INNOSETUP") +set(CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS + "bin/${MAIN_TARGET_NAME}.exe" "Source: \\\"bin\\\\${MAIN_TARGET_NAME}.exe\\\"\\; DestDir: \\\"{app}\\\\bin\\\"\\; Flags: restartreplace promptifolder") +set(CPACK_INNOSETUP_MENU_LINKS "bin\\\\${MAIN_TARGET_NAME}.exe" "${PRODUCT_NAME}") include(${CMAKE_CURRENT_LIST_DIR}/cpack/Package.cmake) -add_custom_command( - TARGET ${MAIN_TARGET_NAME}-package-inst POST_BUILD - COMMAND ${CMAKE_COMMAND} - -DSIGNER_ISSUER=${SIGNER_ISSUER} - -DSIGNER_CERT=${SIGNER_CERT} - -DSIGNER_PASS=${SIGNER_PASS} - -DTARGET="${CPACK_PACKAGE_DIRECTORY}/${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_SYSTEM_NAME}.exe" - -P ${CMAKE_CURRENT_LIST_DIR}/cmake/SignTool.cmake - COMMENT "Signing ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_SYSTEM_NAME}" -) +install(FILES ${PRODUCT_LICENSE_FILE} CONFIGURATIONS Release DESTINATION ${INSTALL_DOCDIR}) +install(PROGRAMS "${CMAKE_BINARY_DIR}/../$-${TARGET_ARCH_ID}/${MAIN_TARGET_NAME}.exe" CONFIGURATIONS Release DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/buildenv/Product.cmake b/buildenv/Product.cmake index f943973..4f645b3 100644 --- a/buildenv/Product.cmake +++ b/buildenv/Product.cmake @@ -1,14 +1,15 @@ set(PRODUCT_VERSION_MAJOR 0) set(PRODUCT_VERSION_MINOR 1) -set(PRODUCT_VERSION_PATCH 0) +set(PRODUCT_VERSION_PATCH 1) set(PRODUCT_NAME WinPinMenu) -set(PRODUCT_VERSION ${PRODUCT_VERSION_MAJOR}.${PRODUCT_VERSION_MINOR}.${PRODUCT_VERSION_PATCH}.${BUILD_NUMBER}) +set(PRODUCT_TITLE "Pinnable Taskbar Menu") +set(PRODUCT_VERSION ${PRODUCT_VERSION_MAJOR}.${PRODUCT_VERSION_MINOR}.${PRODUCT_VERSION_PATCH}) set(PRODUCT_VENDOR "diVISION") set(PRODUCT_DESCRIPTON "diVISION Pinnable Taskbar Menu For Windows") -set(PRODUCT_MAINTANER "dimamizou@users.sf.net") +set(PRODUCT_MAINTANER "dimamizou@jasics.net") set(PRODUCT_HOMEPAGE_URL "https://github.com/hatelamers/WinPinMenu") set(PROJECT_LICENSE "GNU/GPL") set(PROJECT_LICENSE_URL "https://www.gnu.org/licenses/gpl-3.0.en.html") set(PROJECT_COPYRIGHT "© 2024, some rights reserved") -string(TOLOWER "${PROJECT_NAME}" PRODUCT_IDENTIFIER) +string(TOLOWER "${PRODUCT_NAME}" PRODUCT_IDENTIFIER) set(PACKAGE_IDENTIFIER "net.jasics.${PRODUCT_IDENTIFIER}") 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/buildenv/buildnumber.txt b/buildenv/buildnumber.txt deleted file mode 100644 index cabf43b..0000000 --- a/buildenv/buildnumber.txt +++ /dev/null @@ -1 +0,0 @@ -24 \ No newline at end of file diff --git a/buildenv/cmake/PostBuild.cmake b/buildenv/cmake/PostBuild.cmake index d8bf85a..8559c7b 100644 --- a/buildenv/cmake/PostBuild.cmake +++ b/buildenv/cmake/PostBuild.cmake @@ -1,19 +1,19 @@ if(NOT ${BUILD_TYPE} MATCHES Debug) - file(READ "${SOURCE_DIR}/buildnumber.txt" BUILD_NUMBER) + include("${SOURCE_DIR}/Product.cmake") + file(READ "${TARGET_SOURCE_DIR}/buildnumber.txt" BUILD_NUMBER) if(NOT ${BUILD_NUMBER}) set(BUILD_NUMBER 1) endif() - include("${SOURCE_DIR}/Product.cmake") - file(REMOVE "${MAIN_TARGET_SOURCE_DIR}/productmeta.h") - configure_file("${MAIN_TARGET_SOURCE_DIR}/productmeta.h.in" "${MAIN_TARGET_SOURCE_DIR}/productmeta.h") math(EXPR BUILD_NUMBER "${BUILD_NUMBER} + 1") if(${CMAKE_VERSION} VERSION_LESS 3.18) - file(WRITE "${SOURCE_DIR}/buildnumber.txt" "${BUILD_NUMBER}") + file(WRITE "${TARGET_SOURCE_DIR}/buildnumber.txt" "${BUILD_NUMBER}") else() - file(CONFIGURE OUTPUT "${SOURCE_DIR}/buildnumber.txt" CONTENT "${BUILD_NUMBER}") + file(CONFIGURE OUTPUT "${TARGET_SOURCE_DIR}/buildnumber.txt" CONTENT "${BUILD_NUMBER}") endif() - file(TOUCH_NOCREATE "${MAIN_TARGET_SOURCE_DIR}/${MAIN_TARGET_NAME}.rc") + file(REMOVE "${TARGET_SOURCE_DIR}/productmeta.h") + configure_file("${TARGET_SOURCE_DIR}/productmeta.h.in" "${TARGET_SOURCE_DIR}/productmeta.h") + file(TOUCH_NOCREATE "${TARGET_SOURCE_DIR}/${TARGET_NAME}.rc") - message(STATUS "Build number set to ${BUILD_NUMBER} in ${SOURCE_DIR}/buildnumber.txt") + message(STATUS "Build number set to ${BUILD_NUMBER} in ${TARGET_SOURCE_DIR}/buildnumber.txt") endif() diff --git a/buildenv/cpack/Package.cmake b/buildenv/cpack/Package.cmake index 2321d7f..d5970c1 100644 --- a/buildenv/cpack/Package.cmake +++ b/buildenv/cpack/Package.cmake @@ -1,46 +1,129 @@ +if(NOT PRODUCT_NAME) + set(PRODUCT_NAME "${CMAKE_PROJECT_NAME}") +endif() +if(NOT PRODUCT_TITLE) + set(PRODUCT_TITLE "${CMAKE_PROJECT_NAME}") +endif() +if(NOT MAIN_TARGET_NAME) + set(MAIN_TARGET_NAME "${CMAKE_PROJECT_NAME}") +endif() +if(NOT TARGET_ARCH_ID) + set(TARGET_ARCH_ID "${CMAKE_SYSTEM_PROCESSOR}") +endif() + set(CPACK_PACKAGE_VENDOR "${PRODUCT_VENDOR}") set(CPACK_PACKAGE_CONTACT ${PRODUCT_MAINTANER}) +set(CPACK_PACKAGE_NAME "${PRODUCT_NAME}") if(DEFINED CPACK_SINGLE_TARGET_SYSTEM) set(CPACK_SYSTEM_NAME "${TARGET_ARCH_ID}") else() set(CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${TARGET_ARCH_ID}") endif() -set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PRODUCT_NAME}-${PROJECT_VERSION}") -set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "${PRODUCT_NAME}-${PROJECT_VERSION}") -set(CPACK_PACKAGE_EXECUTABLES ${MAIN_TARGET_NAME};${PRODUCT_NAME}) -set(CPACK_PACKAGE_DIRECTORY "${WORKSPACE_ROOT}/distr") - -file(TO_NATIVE_PATH ${PRODUCT_LICENSE_FILE} CPACK_RESOURCE_FILE_LICENSE) +set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PRODUCT_NAME}-${PRODUCT_VERSION}") +set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "${PRODUCT_NAME}-${PRODUCT_VERSION}") +set(CPACK_PACKAGE_EXECUTABLES ${MAIN_TARGET_NAME};${PRODUCT_TITLE}) +if(WORKSPACE_ROOT) + set(CPACK_PACKAGE_DIRECTORY "${WORKSPACE_ROOT}/distr") +endif() set(CPACK_PACKAGE_CHECKSUM "SHA256") set(CPACK_SOURCE_IGNORE_FILES "/thirdparty/[^/]+/(src|stamp|stage)/;/build/;/CVS/;/\\\\.svn/;/\\\\.bzr/;/\\\\.hg/;/\\\\.git/;\\\\.swp\$;\\\\.#;/#") + +if(PRODUCT_LICENSE_FILE) + file(TO_NATIVE_PATH ${PRODUCT_LICENSE_FILE} CPACK_RESOURCE_FILE_LICENSE) +endif() + if(${CMAKE_SYSTEM_NAME} MATCHES Windows) - STRING(REGEX REPLACE "\\\\" "\\\\\\\\" CPACK_RESOURCE_FILE_LICENSE "${CPACK_RESOURCE_FILE_LICENSE}") - file(TO_NATIVE_PATH "${MAIN_TARGET_RESOURCE_DIR}/app.ico" CPACK_PACKAGE_ICON) - STRING(REGEX REPLACE "\\\\" "\\\\\\\\" CPACK_PACKAGE_ICON "${CPACK_PACKAGE_ICON}") - set(CPACK_NSIS_MUI_ICON "${CPACK_PACKAGE_ICON}") - #set(CPACK_NSIS_MUI_UNIICON "${CPACK_PACKAGE_ICON}") - set(CPACK_NSIS_DISPLAY_NAME "${PRODUCT_NAME} ${PROJECT_VERSION}") - set(CPACK_INSTALLER_GENERATOR_ARG "-G" "ZIP\\;NSIS") + if(PRODUCT_LICENSE_FILE) + STRING(REGEX REPLACE "\\\\" "\\\\\\\\" CPACK_RESOURCE_FILE_LICENSE "${CPACK_RESOURCE_FILE_LICENSE}") + endif() + if(NOT CPACK_PACKAGE_ICON) + file(TO_NATIVE_PATH "${MAIN_TARGET_RESOURCE_DIR}/app.ico" CPACK_PACKAGE_ICON) + STRING(REGEX REPLACE "\\\\" "\\\\\\\\" CPACK_PACKAGE_ICON "${CPACK_PACKAGE_ICON}") + endif() + + if(NOT CPACK_INSTALLER_GENERATORS) + set(CPACK_INSTALLER_GENERATORS "ZIP" "NSIS") + endif() + + if("NSIS" IN_LIST CPACK_INSTALLER_GENERATORS) + message(STATUS "Configuring NSIS") + set(CPACK_NSIS_MUI_ICON "${CPACK_PACKAGE_ICON}") + #set(CPACK_NSIS_MUI_UNIICON "${CPACK_PACKAGE_ICON}") + set(CPACK_NSIS_DISPLAY_NAME "${PRODUCT_NAME} ${PRODUCT_VERSION}") + + else("INNOSETUP" IN_LIST CPACK_INSTALLER_GENERATORS) + message(STATUS "Configuring INNOSETUP") + find_program(ISCC_COMMAND iscc HINTS $ENV{INNOSETUP_HOME}) + if(ISCC_COMMAND) + set(CPACK_INNOSETUP_EXECUTABLE "${ISCC_COMMAND}") + endif() + string(TOLOWER ${TARGET_ARCH_ID} CPACK_INNOSETUP_ARCHITECTURE) + set(CPACK_INNOSETUP_ICON_FILE "${CPACK_PACKAGE_ICON}") + set(CPACK_INNOSETUP_SETUP_WizardSmallImageFile "compiler:WizClassicSmallImage.bmp") + set(CPACK_INNOSETUP_PROGRAM_MENU_FOLDER "${PRODUCT_TITLE}") + set(CPACK_INNOSETUP_DEFINE_MAIN_TARGET ${MAIN_TARGET_NAME}.exe) + set(CPACK_INNOSETUP_DEFINE_PRODUCT_NAME ${PRODUCT_NAME}) + set(CPACK_INNOSETUP_DEFINE_PRODUCT_TITLE ${PRODUCT_TITLE}) + set(CPACK_INNOSETUP_DEFINE_PRODUCT_VENDOR ${PRODUCT_VENDOR}) + set(CPACK_INNOSETUP_SETUP_AppName ${PRODUCT_TITLE}) + set(CPACK_INNOSETUP_SETUP_AppVerName "${PRODUCT_TITLE} ${PRODUCT_VERSION}") + set(CPACK_INNOSETUP_SETUP_UninstallDisplayName ${PRODUCT_TITLE}) + set(CPACK_INNOSETUP_SETUP_UninstallDisplayIcon "{app}\\\\bin\\\\${MAIN_TARGET_NAME}.exe") + set(CPACK_INNOSETUP_SETUP_VersionInfoVersion "${PRODUCT_VERSION}.${BUILD_NUMBER}") + set(CPACK_INNOSETUP_SETUP_VersionInfoTextVersion "${PRODUCT_VERSION}") + set(CPACK_INNOSETUP_SETUP_VersionInfoCompany "${PRODUCT_VENDOR}") + set(CPACK_INNOSETUP_SETUP_VersionInfoCopyright "${PROJECT_COPYRIGHT}") + + find_program(SIGNTOOL_COMMAND signtool) + if(SIGNTOOL_COMMAND) + if(SIGNER_ISSUER) + set(SIGNTOOL_OBJECT_ARG "/i $q${SIGNER_ISSUER}$q") + elseif(SIGNER_CERT) + STRING(REGEX REPLACE "\\\\" "\\\\\\\\" SIGNER_CERT_INNO "${SIGNER_CERT}") + set(SIGNTOOL_OBJECT_ARG "/f $q${SIGNER_CERT_INNO}$q") + endif() + if(SIGNER_PASS) + set(SIGNTOOL_PASS_ARG "/p $q${SIGNER_PASS}$q") + endif() + if(SIGNTOOL_OBJECT_ARG) + set(CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS + "\\\"/Sscripted=$q${SIGNTOOL_COMMAND}$q sign /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 /v ${SIGNTOOL_OBJECT_ARG} ${SIGNTOOL_PASS_ARG} $f\\\"") + set(CPACK_INNOSETUP_SETUP_SignTool "scripted") + set(CPACK_INNOSETUP_SETUP_SignedUninstaller "yes") + endif() + endif() + endif() + elseif(${CMAKE_SYSTEM_NAME} MATCHES Darwin) - file(TO_NATIVE_PATH "${MAIN_TARGET_RESOURCE_DIR}/app.icns" CPACK_PACKAGE_ICON) - set(CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE "ON") - set(CPACK_INSTALLER_GENERATOR_ARG "-G" "ZIP\\\;DragNDrop") + if(NOT CPACK_PACKAGE_ICON) + file(TO_NATIVE_PATH "${MAIN_TARGET_RESOURCE_DIR}/app.icns" CPACK_PACKAGE_ICON) + endif() + if(PRODUCT_LICENSE_FILE) + set(CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE "ON") + endif() + if(NOT CPACK_INSTALLER_GENERATORS) + set(CPACK_INSTALLER_GENERATORS "ZIP" "DragNDrop") + endif() + elseif(${CMAKE_SYSTEM_NAME} MATCHES Linux) - set(CPACK_INSTALLER_GENERATORS "TGZ") - find_program(rpmbuild_Found rpmbuild) - if (rpmbuild_Found) - list(APPEND CPACK_INSTALLER_GENERATORS "RPM") - set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_BINARY_DIR}/postinst") - endif() - find_program(dpkg_Found dpkg) - if (dpkg_Found) - list(APPEND CPACK_INSTALLER_GENERATORS "DEB") - set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_BINARY_DIR}/postinst") - endif() - string(JOIN "\\\\;" CPACK_INSTALLER_GENERATOR_LIST ${CPACK_INSTALLER_GENERATORS}) - set(CPACK_INSTALLER_GENERATOR_ARG "-G" "${CPACK_INSTALLER_GENERATOR_LIST}") + if(NOT CPACK_INSTALLER_GENERATORS) + set(CPACK_INSTALLER_GENERATORS "TGZ") + find_program(rpmbuild_Found rpmbuild) + if (rpmbuild_Found) + list(APPEND CPACK_INSTALLER_GENERATORS "RPM") + set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_BINARY_DIR}/postinst") + endif() + find_program(dpkg_Found dpkg) + if (dpkg_Found) + list(APPEND CPACK_INSTALLER_GENERATORS "DEB") + set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_BINARY_DIR}/postinst") + endif() + endif() endif() +string(JOIN "\\\\;" CPACK_INSTALLER_GENERATOR_LIST ${CPACK_INSTALLER_GENERATORS}) +set(CPACK_INSTALLER_GENERATOR_ARG "-G" ${CPACK_INSTALLER_GENERATOR_LIST}) + include(CPack) add_custom_target(${MAIN_TARGET_NAME}-package-zip diff --git a/src/app/AboutDlg.cpp b/src/app/AboutDlg.cpp index bc3afde..468da2e 100644 --- a/src/app/AboutDlg.cpp +++ b/src/app/AboutDlg.cpp @@ -1,8 +1,8 @@ // aboutdlg.cpp : implementation of the CAboutDlg class // ///////////////////////////////////////////////////////////////////////////// - #include "stdafx.h" + #include "resource.h" #include "productmeta.h" @@ -10,8 +10,9 @@ LRESULT CAboutDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { + ATLTRACE(_T(__FUNCTION__) _T("\n")); + DoDataExchange(FALSE); - //m_lnkLicense.SetHyperLinkExtendedStyle(HLINK_COMMANDBUTTON, HLINK_COMMANDBUTTON); if (m_fvi.Open()) { m_fvi.SetInfoDlgItemText(m_hWnd, IDC_TXT_PRODUCTNAME, SFI_PRODUCTNAME); @@ -21,10 +22,6 @@ LRESULT CAboutDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lPara m_fvi.SetInfoDlgItemText(m_hWnd, IDC_TXT_FILEVERSION, SFI_FILEVERSION); m_fvi.SetInfoDlgItemText(m_hWnd, IDC_TXT_COMPANYNAME, SFI_COMPANYNAME); - //LPTSTR lpValue(NULL); - //UINT uLen(0); - //if (m_fvi.GetStringFileInfo(SFI_LEGALTRADEMARKS, lpValue, &uLen)) - // m_lnkLicense.SetToolTipText(lpValue); } m_lnkLicense.SetHyperLink(PRODUCT_LICENSE_URL); @@ -33,6 +30,7 @@ LRESULT CAboutDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lPara #else m_lnkRelNotes.ShowWindow(SW_HIDE); #endif + return TRUE; } diff --git a/src/app/AboutDlg.h b/src/app/AboutDlg.h index ebd2d29..b61a8af 100644 --- a/src/app/AboutDlg.h +++ b/src/app/AboutDlg.h @@ -3,18 +3,24 @@ ///////////////////////////////////////////////////////////////////////////// #pragma once -#include "FileVersionInfo.h" +#include + +class CAboutDlg; +using CUxAboutDialog = CUxModeWindow; class CAboutDlg : public CDialogImpl + , public CUxAboutDialog , public CWinDataExchange { public: enum { IDD = IDD_ABOUTBOX }; BEGIN_MSG_MAP(CAboutDlg) + CHAIN_MSG_MAP(CUxAboutDialog) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_ID_HANDLER(IDOK, OnCloseCmd) + COMMAND_ID_HANDLER(IDRETRY, OnCloseCmd) COMMAND_ID_HANDLER(IDCANCEL, OnCloseCmd) END_MSG_MAP() @@ -33,7 +39,7 @@ class CAboutDlg LRESULT OnCloseCmd(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/); private: - CHyperLink m_lnkLicense; - CHyperLink m_lnkRelNotes; + CUxModeHyperLink m_lnkLicense; + CUxModeHyperLink m_lnkRelNotes; CFileVersionInfo m_fvi; }; diff --git a/src/app/MainFrm.cpp b/src/app/MainFrm.cpp index 5322a3a..ab7da2a 100644 --- a/src/app/MainFrm.cpp +++ b/src/app/MainFrm.cpp @@ -51,8 +51,6 @@ LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/ LRESULT CMainFrame::OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { - m_mnuMain.Detach(); - // unregister message filtering and idle updates CMessageLoop* pLoop = _Module.GetMessageLoop(); ATLASSERT(pLoop != NULL); @@ -60,6 +58,7 @@ LRESULT CMainFrame::OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam* pLoop->RemoveIdleHandler(this); bHandled = FALSE; + PostQuitMessage(0); return 1; } @@ -128,8 +127,14 @@ LRESULT CMainFrame::OnFileExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCt LRESULT CMainFrame::OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { CAboutDlg dlg; - dlg.DoModal(); - TriggerActionPopup(); + if (IDRETRY == dlg.DoModal()) + { + TriggerActionPopup(); + } + else + { + PostMessage(WM_CLOSE); + } return 0; } @@ -141,7 +146,11 @@ bool CMainFrame::ParseCommandLine() for (auto i = 1; argv && i < argc; i++) { CString p(argv[i]); - if (_T("/t") == p && 0 == m_strSourceTitle.GetLength()) + if (_T("/h") == p) + { + SetWindowLongPtr(GWL_EXSTYLE, WS_EX_TOOLWINDOW); + } + else if (_T("/t") == p && 0 == m_strSourceTitle.GetLength()) { expectToken = 1; } @@ -228,51 +237,59 @@ INT CMainFrame::TriggerActionPopup() POINT CMainFrame::CalculatePopupPosision() { - POINT result{ 0,0 }; + ATLTRACE(__FUNCTION__ _T(" START\n")); + POINT result{ -1,-1 }; + POINT cursorPos{ 0,0 }; + if (::GetCursorPos(&cursorPos)) + { + result.x = cursorPos.x; + result.y = cursorPos.y; + } + CTaskbarAutomation tba; - tba.ForEveryButton([this,&result](const CComPtr& button, int) + auto hrButtons = tba.ForEveryButton([this,&result,&cursorPos](const CComPtr& button, int /*tbIndex*/, int /*btnIndex*/) { - CComBSTR bstrName; - auto hr = button->get_CurrentName(&bstrName); - //ATLTRACE(_T("Button[%d].Name=%s\n"), index, (LPCOLESTR)bstrName); - if (SUCCEEDED(hr)) + RECT rc{ 0,0,0,0 }; + if (SUCCEEDED(button->get_CachedBoundingRectangle(&rc))) { - CString strName(bstrName); - CString strTitle; - GetWindowText(strTitle); - if (0 == strName.Find(strTitle)) + auto hasFocus = FALSE; + button->get_CurrentHasKeyboardFocus(&hasFocus); + if (!hasFocus) + { + hasFocus = (cursorPos.x >= rc.left && cursorPos.x <= rc.right && cursorPos.y >= rc.top && cursorPos.y <= rc.bottom); + } + ATLTRACE(__FUNCTION__ _T(" taskbar button at x=%d y=%d, hasFocus=%d\n"), rc.left, rc.top, hasFocus); + if (hasFocus) { - RECT rc{ 0,0,0,0 }; - if (SUCCEEDED(button->get_CurrentBoundingRectangle(&rc))) - { - result.x = rc.left; - result.y = rc.top; - } + result.x = 10 > rc.left ? rc.right : rc.left; + result.y = 10 > rc.top ? rc.bottom + 2 : rc.top - 2; return false; } } return true; }); - if (0 == result.x && 0 == result.y) + if (S_FALSE != hrButtons) { - if (!::GetCursorPos(&result)) + if (-1 == result.x) { result.x = 100; result.y = -1; } + auto hTaskBar = ::FindWindow(_T("Shell_TrayWnd"), NULL); if (hTaskBar) { RECT rc{ 0,0,0,0 }; if (::GetWindowRect(hTaskBar, &rc) && (0 > result.y - || (result.x >= rc.left && result.x <= rc.right && result.y >= rc.top && result.y <= rc.bottom))) + || (cursorPos.x >= rc.left && cursorPos.x <= rc.right && cursorPos.y >= rc.top && cursorPos.y <= rc.bottom))) { - result.y = rc.top; + result.y = 10 > rc.top ? rc.bottom + 2 : rc.top - 2; } } } + ATLTRACE(__FUNCTION__ _T(" END\n")); return result; } diff --git a/src/app/MainFrm.h b/src/app/MainFrm.h index 7cca4c4..52277ad 100644 --- a/src/app/MainFrm.h +++ b/src/app/MainFrm.h @@ -11,7 +11,8 @@ #define MNUPOS_APP_EXIT 0 class CMainFrame : - public CFrameWindowImpl, + public CFrameWindowImpl, + public CUxModeWindow, public CUpdateUI, public CMessageFilter, public CIdleHandler, public CShellBrowseMenu::ShellMenuController { @@ -43,6 +44,7 @@ class CMainFrame : END_UPDATE_UI_MAP() BEGIN_MSG_MAP(CMainFrame) + CHAIN_MSG_MAP(CUxModeWindow) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) MESSAGE_HANDLER(WM_MENUCOMMAND, OnMenuCommand) @@ -77,10 +79,8 @@ class CMainFrame : INT TriggerActionPopup(); POINT CalculatePopupPosision(); -public: - bool m_hasChildren{ false }; private: - CMenu m_mnuMain; + CMenuHandle m_mnuMain; CShellBrowseMenu m_shellMenu; CString m_strActionSource; CString m_strSourceTitle; diff --git a/src/app/ShellBrowseMenu.cpp b/src/app/ShellBrowseMenu.cpp index bf5a326..e062ee0 100644 --- a/src/app/ShellBrowseMenu.cpp +++ b/src/app/ShellBrowseMenu.cpp @@ -1,9 +1,16 @@ #include "stdafx.h" #include "resource.h" + #include "ShellMgr.h" #include "ShellBrowseMenu.h" +CShellBrowseMenu::CShellBrowseMenu(const ShellMenuController* controller) + : m_controller(controller), m_isRendered(false), m_isCtxMenuShowing(false) +{ + LoadIconImages(); +} + HRESULT CShellBrowseMenu::Rebuild() { ATLASSERT(m_controller); @@ -48,6 +55,15 @@ BOOL CShellBrowseMenu::InvokeWithSelection(LPCTSTR strVerb) const return FALSE; } +void CShellBrowseMenu::UxModeUpdateColorSettings() +{ + 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) { bHandled = FALSE; @@ -65,6 +81,9 @@ LRESULT CShellBrowseMenu::OnInitMenuPopup(UINT, WPARAM wParam, LPARAM lParam, BO ATLTRACE(_T(__FUNCTION__) _T(" hr=%p pFolder=%p\n"), hr, pFolder.p); } BuildFolderMenu(pFolder, hMenu); + + //auto hMenuWnd = ::FindWindow(_T("#32768"), NULL); + //::SetWindowTheme(hMenuWnd, L"DarkMode", L"Menu"); } else { @@ -159,23 +178,24 @@ LRESULT CShellBrowseMenu::OnMenuSelect(UINT, WPARAM wParam, LPARAM lParam, BOOL& if (::GetMenuItemInfo(hMenu, item, isPopup, &mii) && mii.dwItemData) { auto pData = (LPSHELLITEMDATA)mii.dwItemData; + if (pData->parentFolder && !pData->relativeIDL.IsNull()) + { + ShellItemSelection& selObject = isPopup ? m_folderSelection : m_selection; - ShellItemSelection& selObject = isPopup ? m_folderSelection : m_selection; - - selObject.hMenu = hMenu; - selObject.byPosition = isPopup; - selObject.menuItem = item; + selObject.hMenu = hMenu; + selObject.byPosition = isPopup; + selObject.menuItem = item; - selObject.parentFolder = pData->parentFolder.p; - selObject.parentFolder.p->AddRef(); - selObject.relativeIDL.CopyFrom(pData->relativeIDL); + selObject.parentFolder = pData->parentFolder.p; + selObject.parentFolder.p->AddRef(); + selObject.relativeIDL.CopyFrom(pData->relativeIDL); - if (isPopup) - { - ::GetMenuItemRect(m_controller->GetHWnd(), hMenu, item, &m_folderSelection.itemRect); + if (isPopup) + { + ::GetMenuItemRect(m_controller->GetHWnd(), hMenu, item, &m_folderSelection.itemRect); + } + bHandled = TRUE; } - - bHandled = TRUE; } } return 1; @@ -253,74 +273,152 @@ LRESULT CShellBrowseMenu::OnDrawItem(UINT, WPARAM /*wParam*/, LPARAM lParam, BOO { bHandled = FALSE; LPDRAWITEMSTRUCT lpDis = (LPDRAWITEMSTRUCT)lParam; - if (NULL == lpDis || ODT_MENU != lpDis->CtlType || NULL == lpDis->itemData || !m_pImageList) + if (NULL == lpDis || ODT_MENU != lpDis->CtlType || NULL == lpDis->itemData) { return FALSE; } bHandled = TRUE; - - auto pData = (LPSHELLITEMDATA)lpDis->itemData; - - CShellItemIDList idlParent; - CComQIPtr persFolder(pData->parentFolder); - if (!persFolder || FAILED(persFolder->GetCurFolder(&idlParent))) - return FALSE; - - CShellItemIDList idl(CShellMgr::ConcatIDLs(idlParent, pData->relativeIDL)); - // quicker method but doesn't return all IDLs - //CShellItemIDList idl(CShellMgr::GetAbsoluteIDL(pData->parentFolder, pData->relativeIDL)); - ////ATLASSERT(!idl.IsNull()); - if (idl.IsNull()) - return FALSE; - - IMAGELISTDRAWPARAMS ildp; - ::ZeroMemory(&ildp, sizeof(IMAGELISTDRAWPARAMS)); - ildp.cbSize = sizeof(IMAGELISTDRAWPARAMS); - ildp.himl = (HIMAGELIST)m_pImageList.p; - ildp.i = CShellMgr::GetIconIndex(idl, SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON); - ildp.hdcDst = lpDis->hDC; - ildp.x = lpDis->rcItem.left + 1; - ildp.y = lpDis->rcItem.top + 2; - ildp.rgbBk = CLR_NONE; - ildp.rgbFg = CLR_DEFAULT; - ildp.fStyle = ILD_TRANSPARENT; - - auto hr = m_pImageList->Draw(&ildp); - //ATLTRACE(_T(__FUNCTION__) _T(" hr=%p\n"), hr); - - return SUCCEEDED(hr) ? TRUE : FALSE; + return CustomDrawMenuItem(lpDis); } LRESULT CShellBrowseMenu::OnMeasureItem(UINT, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) { bHandled = FALSE; LPMEASUREITEMSTRUCT lpMis = (LPMEASUREITEMSTRUCT)lParam; - if (NULL == lpMis || ODT_MENU != lpMis->CtlType || NULL == lpMis->itemData || !m_pImageList) + if (NULL == lpMis || ODT_MENU != lpMis->CtlType || NULL == lpMis->itemData) { return FALSE; } bHandled = TRUE; + return MeasureMenuItem(lpMis); +} + +BOOL CShellBrowseMenu::CustomDrawMenuItem(LPDRAWITEMSTRUCT lpDis) +{ + + auto pData = (LPSHELLMENUITEMDATA)lpDis->itemData; + + CDCHandle dc = lpDis->hDC; + RECT& rcItem = lpDis->rcItem; - CSize sz; - auto hr = m_pImageList->GetIconSize((int*) &sz.cx, (int*) &sz.cy); - //ATLTRACE(_T(__FUNCTION__) _T(" hr=%p itemWidth=%d itemHeight=%d\n"), hr, sz.cx, sz.cy); - if (SUCCEEDED(hr)) + 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(); + + 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) { - lpMis->itemHeight = sz.cx + 4; - lpMis->itemWidth = sz.cy + 4; - return TRUE; + 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); } - return FALSE; + + CRect rc(rcItem); + if (m_menuMetrics.sizeIcon.cx) + { + rc.left += m_menuMetrics.sizeIcon.cx + (m_menuMetrics.paddingIcon.cx * 2); + } + + if (isSeparator) + { + m_menuTheme.DrawThemeBackground(dc, MENU_POPUPSEPARATOR, 0, rc, NULL); + } + else + { + auto isSubmenu = ::IsMenu((HMENU)(UINT_PTR)lpDis->itemID); + + if (-1 < pData->iconIndex && m_pImageList) + { + IMAGELISTDRAWPARAMS ildp; + ::ZeroMemory(&ildp, sizeof(IMAGELISTDRAWPARAMS)); + ildp.cbSize = sizeof(IMAGELISTDRAWPARAMS); + ildp.himl = (HIMAGELIST)m_pImageList.p; + ildp.i = pData->iconIndex; + ildp.hdcDst = lpDis->hDC; + 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; + + m_pImageList->Draw(&ildp); + } + + dc.SetBkMode(TRANSPARENT); + + 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) + { + CustomDrawMenuArrow(dc, &rcItem, isDisabled); + } + + dc.ExcludeClipRect(&rcItem); + } + return TRUE; +} + +BOOL CShellBrowseMenu::MeasureMenuItem(LPMEASUREITEMSTRUCT lpMis) +{ + auto pData = (LPSHELLMENUITEMDATA)lpMis->itemData; + + auto isSeparator = 0 == pData->caption.GetLength(); + if (isSeparator) + { + lpMis->itemHeight = ::GetSystemMetrics(SM_CYMENU) / 2; + lpMis->itemWidth = 0; + } + else + { + CWindowDC dc(GetOwnerHWND()); + CRect rcText; + m_menuTheme.GetThemeTextExtent(dc, MENU_POPUPITEM, MPI_NORMAL, pData->caption, -1, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_CALCRECT, NULL, rcText); + + 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_menuMetrics.paddingText.cx * 2) + m_menuMetrics.sizeIcon.cx; + } + } + + return TRUE; } HRESULT CShellBrowseMenu::LoadIconImages() { - return ::SHGetImageList(SHIL_SMALL, IID_IImageList, (void**) &m_pImageList); + auto result = ::SHGetImageList(SHIL_SMALL, IID_IImageList, (void**) &m_pImageList); + if (SUCCEEDED(result) && m_pImageList) + { + 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) const +BOOL CShellBrowseMenu::SetupMenuInfo(CMenuHandle& menu) { if (menu.IsMenu()) { @@ -329,6 +427,7 @@ BOOL CShellBrowseMenu::SetupMenuInfo(CMenuHandle& menu) const mi.cbSize = sizeof(mi); mi.fMask = MIM_STYLE; mi.dwStyle = MNS_CHECKORBMP; + UxModeUpdateMenuInfo(mi); return menu.SetMenuInfo(&mi); } return FALSE; @@ -359,11 +458,19 @@ HRESULT CShellBrowseMenu::BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu) ATLASSERT((int)id == ID_ACTION_FIRST + (m_openMenus.GetSize() * 0x1000)); UINT pos = isTopMenu ? m_controller->GetShellMenuAnchor() + 1 : 0; auto idMsg = MessageID::SBM_NONE; - TCHAR szBuff[MAX_PATH]; - szBuff[0] = _T('\0'); if (pFolder) { + TCHAR szBuff[MAX_PATH]; + szBuff[0] = _T('\0'); + + CShellItemIDList idlParent; + CComQIPtr persFolder(pFolder); + if (!persFolder || FAILED(persFolder->GetCurFolder(&idlParent))) + { + ATLTRACE2(_T(__FUNCTION__) _T(" failed to retrieve folder IDL\n")); + } + CShellItemIDList idlChild; ULONG ulFetched = 0; @@ -399,7 +506,14 @@ HRESULT CShellBrowseMenu::BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu) : menuPopup.AppendMenu(MF_STRING, id, szBuff); if (succ) { - auto pData = new ShellItemData; + auto pData = new ShellMenuItemData; + + if (!idlParent.IsNull()) + { + CShellItemIDList absIDL(CShellMgr::ConcatIDLs(idlParent, idlChild)); + pData->iconIndex = CShellMgr::GetIconIndex(absIDL, SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON); + } + pData->caption = szBuff; pData->parentFolder.p = pFolder; pFolder->AddRef(); pData->relativeIDL.CopyFrom(idlChild); @@ -409,9 +523,19 @@ HRESULT CShellBrowseMenu::BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu) pSelf.p->AddRef(); } + //CMenuItemInfo mii; + //mii.fMask = MIIM_DATA | MIIM_BITMAP; + //mii.hbmpItem = HBMMENU_CALLBACK; + //mii.dwItemData = (LONG_PTR)pData; + CMenuItemInfo mii; - mii.fMask = MIIM_DATA | MIIM_BITMAP; - mii.hbmpItem = HBMMENU_CALLBACK; + mii.cch = ::lstrlen(szBuff); + mii.fMask = MIIM_CHECKMARKS | MIIM_DATA | MIIM_ID | MIIM_STATE | MIIM_SUBMENU | MIIM_TYPE; + mii.dwTypeData = szBuff; + + menuPopup.GetMenuItemInfo(pos, TRUE, &mii); + + mii.fType |= MFT_OWNERDRAW; mii.dwItemData = (LONG_PTR)pData; succ = menuPopup.SetMenuItemInfo(pos, TRUE, &mii); @@ -448,7 +572,27 @@ HRESULT CShellBrowseMenu::BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu) menuPopup.AppendMenu(MF_STRING, id, strMsg); menuPopup.EnableMenuItem(id, MF_BYCOMMAND | MF_DISABLED); } + else if (isTopMenu) + { + for (auto i = 0; i <= m_controller->GetShellMenuAnchor(); i++) + { + auto pData = new ShellMenuItemData; + + CMenuItemInfo mii; + mii.fMask = MIIM_CHECKMARKS | MIIM_DATA | MIIM_ID | MIIM_STATE | MIIM_SUBMENU | MIIM_TYPE; + menuPopup.GetMenuItemInfo(i, TRUE, &mii); + if (MFT_SEPARATOR != (mii.fType & MFT_SEPARATOR)) + { + ::GetMenuString(menuPopup, i, pData->caption.GetBufferSetLength(MAX_PATH), MAX_PATH, MF_BYPOSITION); + pData->caption.ReleaseBuffer(); + } + + mii.fType |= MFT_OWNERDRAW; + mii.dwItemData = (LONG_PTR)pData; + menuPopup.SetMenuItemInfo(i, TRUE, &mii); + } + } return hr; } diff --git a/src/app/ShellBrowseMenu.h b/src/app/ShellBrowseMenu.h index 2a7fb3f..aab9d78 100644 --- a/src/app/ShellBrowseMenu.h +++ b/src/app/ShellBrowseMenu.h @@ -7,6 +7,7 @@ #endif class CShellBrowseMenu + : public CUxModeMenuHelper { public: enum MessageID @@ -22,6 +23,14 @@ class CShellBrowseMenu virtual int GetMessageString(MessageID msgID, CString& msg) const = 0; }; + typedef struct ShellMenuItemData + : public ShellItemData + { + int iconIndex{ -1 }; + CString caption; + virtual ~ShellMenuItemData() = default; + } SHELLMENUITEMDATA, * LPSHELLMENUITEMDATA; + struct ShellItemSelection : public ShellItemData { @@ -46,15 +55,12 @@ class CShellBrowseMenu virtual ~FolderSelection() = default; }; - CShellBrowseMenu(const ShellMenuController* controller = NULL) - : m_controller(controller), m_isRendered(false), m_isCtxMenuShowing(false) - { - LoadIconImages(); - } + 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) @@ -85,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()) { @@ -102,7 +109,13 @@ 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 OnInitMenuPopup(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnUninitMenuPopup(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); @@ -113,9 +126,11 @@ class CShellBrowseMenu LRESULT OnDrawItem(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled); LRESULT OnMeasureItem(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + BOOL CustomDrawMenuItem(LPDRAWITEMSTRUCT lpDis); + BOOL MeasureMenuItem(LPMEASUREITEMSTRUCT lpMis); HRESULT LoadIconImages(); - BOOL SetupMenuInfo(CMenuHandle& menu) const; + BOOL SetupMenuInfo(CMenuHandle& menu); HRESULT BuildFolderMenu(LPSHELLFOLDER pFolder, HMENU hMenu); void CleanUpMenuData(HMENU hMenu); diff --git a/src/app/WinPinMenu.cpp b/src/app/WinPinMenu.cpp index 787dfa5..16d4a54 100644 --- a/src/app/WinPinMenu.cpp +++ b/src/app/WinPinMenu.cpp @@ -9,6 +9,7 @@ #include "MainFrm.h" CAppModule _Module; +CUxTheme uxTheme; class CWinPinMenuThreadManager { @@ -36,8 +37,14 @@ class CWinPinMenuThreadManager _RunData* pData = (_RunData*)lpData; CMainFrame wndFrame; - RECT rc{ -1000, -1000, -950, -950 }; - if(wndFrame.CreateEx(NULL, &rc, WS_ICONIC) == NULL) + RECT rc{ -1000, -1000, 0, 0 }; + POINT pt{ 0,0 }; + if (::GetCursorPos(&pt)) + { + rc.left = pt.x; + rc.top = pt.y; + } + if(wndFrame.CreateEx(NULL, &rc, WS_POPUP) == NULL) { ATLTRACE(_T("Frame window creation failed!\n")); return 0; @@ -135,13 +142,14 @@ class CWinPinMenuThreadManager int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow) { - CUxTheme uxTheme; + // TODO: intrusive dark mode doesn't support owner-drawn menus, we need to paint all ourselves uxTheme.SetPreferredAppMode(PreferredAppMode::AllowDark); + ::SetThemeAppProperties(STAP_ALLOW_NONCLIENT | STAP_ALLOW_CONTROLS | STAP_ALLOW_WEBCONTENT); HRESULT hRes = ::CoInitialize(NULL); ATLASSERT(SUCCEEDED(hRes)); - AtlInitCommonControls(ICC_BAR_CLASSES); // add flags to support other controls + AtlInitCommonControls(ICC_STANDARD_CLASSES | ICC_BAR_CLASSES); // add flags to support other controls hRes = _Module.Init(NULL, hInstance); ATLASSERT(SUCCEEDED(hRes)); diff --git a/src/app/WinPinMenu.rc b/src/app/WinPinMenu.rc index 46d110d..f21d593 100644 --- a/src/app/WinPinMenu.rc +++ b/src/app/WinPinMenu.rc @@ -30,6 +30,14 @@ IDR_MAINFRAME ICON "res\\app.ico" IDI_APP_RUNNING ICON "res\\app_running.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +1 RT_MANIFEST "res\\app.manifest" + #endif // Neutral (Default) resources ///////////////////////////////////////////////////////////////////////////// @@ -88,25 +96,26 @@ END // Dialog // -IDD_ABOUTBOX DIALOGEX 0, 0, 242, 121 +IDD_ABOUTBOX DIALOGEX 0, 0, 236, 134 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTERMOUSE | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About" FONT 9, "Segoe UI", 0, 0, 0x0 BEGIN - DEFPUSHBUTTON "OK",IDOK,185,100,50,14 - ICON IDR_MAINFRAME,IDC_STATIC,12,15,20,20 - GROUPBOX "",IDC_STATIC,7,7,228,107 - LTEXT "Product Name",IDC_TXT_PRODUCTNAME,41,15,125,18 - RTEXT "License",IDC_LNK_LICENSE,172,15,57,8 - RTEXT "Company Name",IDC_TXT_COMPANYNAME,172,26,57,8 - LTEXT "File Description",IDC_TXT_FILEDESCRIPTION,12,39,217,23 - RTEXT "Version:",IDC_STATIC,12,68,51,8 - RTEXT "Module Version:",IDC_STATIC,12,79,51,8 - RTEXT "Copyright:",IDC_STATIC,12,89,51,8 - RTEXT "Release Notes",IDC_LNK_RELNOTES,172,68,57,8 - LTEXT "1.0.0.0",IDC_TXT_PRODUCTVERSION,70,68,96,8 - LTEXT "1.0.0.1",IDC_TXT_FILEVERSION,70,79,96,8 - LTEXT "Legal Copyright",IDC_TXT_LEGALCOPYRIGHT,70,89,96,8 + GROUPBOX "",IDC_STATIC,7,7,165,63 + DEFPUSHBUTTON "O&K",IDOK,179,114,50,14 + PUSHBUTTON "&Back To Menu",IDRETRY,122,114,50,14 + ICON IDR_MAINFRAME,IDC_STATIC,12,16,20,20,0,WS_EX_TRANSPARENT + LTEXT "Product Name",IDC_TXT_PRODUCTNAME,41,16,125,18,0,WS_EX_TRANSPARENT + RTEXT "License",IDC_LNK_LICENSE,172,16,57,8,WS_TABSTOP,WS_EX_TRANSPARENT + RTEXT "Company Name",IDC_TXT_COMPANYNAME,172,27,57,8,0,WS_EX_TRANSPARENT + LTEXT "File Description",IDC_TXT_FILEDESCRIPTION,12,40,154,22 + RTEXT "Version:",IDC_STATIC,12,76,51,8,0,WS_EX_TRANSPARENT + RTEXT "Module Version:",IDC_STATIC,12,87,51,8,0,WS_EX_TRANSPARENT + RTEXT "Copyright:",IDC_STATIC,12,97,51,8,0,WS_EX_TRANSPARENT + RTEXT "Release Notes",IDC_LNK_RELNOTES,172,76,57,8,WS_TABSTOP,WS_EX_TRANSPARENT + LTEXT "1.0.0.0",IDC_TXT_PRODUCTVERSION,70,76,96,8,0,WS_EX_TRANSPARENT + LTEXT "1.0.0.1",IDC_TXT_FILEVERSION,70,87,96,8,0,WS_EX_TRANSPARENT + LTEXT "Legal Copyright",IDC_TXT_LEGALCOPYRIGHT,70,97,96,8,0,WS_EX_TRANSPARENT END @@ -121,7 +130,7 @@ BEGIN IDD_ABOUTBOX, DIALOG BEGIN LEFTMARGIN, 7 - RIGHTMARGIN, 235 + RIGHTMARGIN, 229 VERTGUIDE, 12 VERTGUIDE, 63 VERTGUIDE, 70 @@ -129,10 +138,12 @@ BEGIN VERTGUIDE, 172 VERTGUIDE, 229 TOPMARGIN, 7 - BOTTOMMARGIN, 114 + BOTTOMMARGIN, 127 HORZGUIDE, 15 HORZGUIDE, 39 - HORZGUIDE, 68 + HORZGUIDE, 70 + HORZGUIDE, 76 + HORZGUIDE, 120 END END #endif // APSTUDIO_INVOKED diff --git a/src/app/WinPinMenu.vcxproj b/src/app/WinPinMenu.vcxproj index 3e32b0a..3350d44 100644 --- a/src/app/WinPinMenu.vcxproj +++ b/src/app/WinPinMenu.vcxproj @@ -98,31 +98,37 @@ true ..\..\build\$(Configuration)-$(PlatformShortName)\ ..\..\build\$(Configuration)-$(PlatformShortName)\ + false true ..\..\build\$(Configuration)-$(PlatformShortName)\ ..\..\build\$(Configuration)-$(PlatformShortName)\ + false true ..\..\build\$(Configuration)-$(PlatformShortName)\ ..\..\build\$(Configuration)-$(PlatformShortName)\ + false false ..\..\build\$(Configuration)-$(PlatformShortName)\ ..\..\build\$(Configuration)-$(PlatformShortName)\ + false false ..\..\build\$(Configuration)-$(PlatformShortName)\ ..\..\build\$(Configuration)-$(PlatformShortName)\ + false false ..\..\build\$(Configuration)-$(PlatformShortName)\ ..\..\build\$(Configuration)-$(PlatformShortName)\ + false @@ -133,6 +139,8 @@ EnableFastChecks Disabled _WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) + stdcpp17 + ../wtlx Windows @@ -163,6 +171,8 @@ EnableFastChecks Disabled _WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) + stdcpp17 + ../wtlx Windows @@ -194,6 +204,8 @@ EnableFastChecks Disabled WIN32;_WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) + stdcpp17 + ../wtlx Windows @@ -221,12 +233,16 @@ Use Level4 MultiThreaded - + Sync WIN32;_WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) + stdcpp17 + ../wtlx + true Windows + UseLinkTimeCodeGeneration 0x0409 @@ -250,12 +266,16 @@ Use Level4 MultiThreaded - + Sync _WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) + stdcpp17 + ../wtlx + true Windows + UseLinkTimeCodeGeneration 0x0409 @@ -278,14 +298,17 @@ Use Level4 MultiThreaded - - + Sync _WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) + stdcpp17 + ../wtlx + true Windows + UseLinkTimeCodeGeneration 0x0409 @@ -306,7 +329,6 @@ - @@ -322,7 +344,6 @@ - @@ -330,8 +351,6 @@ - - @@ -345,6 +364,14 @@ + + + + + + {7d0481bf-835e-4faa-820c-0ef96d007c7a} + + diff --git a/src/app/WinPinMenu.vcxproj.filters b/src/app/WinPinMenu.vcxproj.filters index f66c35a..ebd54f1 100644 --- a/src/app/WinPinMenu.vcxproj.filters +++ b/src/app/WinPinMenu.vcxproj.filters @@ -33,9 +33,6 @@ Source Files - - Source Files - @@ -53,21 +50,12 @@ Header Files - - Header Files - - - Header Files - Header Files Header Files - - Header Files - Header Files @@ -94,4 +82,9 @@ Resource Files + + + Resource Files + + \ No newline at end of file diff --git a/src/app/app.manifest.in b/src/app/app.manifest.in new file mode 100644 index 0000000..c835987 --- /dev/null +++ b/src/app/app.manifest.in @@ -0,0 +1,35 @@ + + + + ${PRODUCT_DESCRIPTON} + + + + + + + + + + + + + + + PerMonitorV2, system + true + + + + + + + + + + + + + + + diff --git a/src/app/buildnumber.txt b/src/app/buildnumber.txt new file mode 100644 index 0000000..2e66562 --- /dev/null +++ b/src/app/buildnumber.txt @@ -0,0 +1 @@ +49 \ No newline at end of file diff --git a/src/app/productmeta.h b/src/app/productmeta.h index 16c1885..c02e744 100644 --- a/src/app/productmeta.h +++ b/src/app/productmeta.h @@ -3,21 +3,22 @@ #define PRODUCT_VERSION_MAJOR 0 #define PRODUCT_VERSION_MINOR 1 -#define PRODUCT_VERSION_PATCH 0 -#define PRODUCT_VERSION_TWEAK 24 +#define PRODUCT_VERSION_PATCH 1 +#define PRODUCT_VERSION_TWEAK 49 #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") -#define PRODUCT_HOMEPAGE_URL _T("https://winpinmenu.sourceforge.net\0") +#define PRODUCT_HOMEPAGE_URL _T("https://github.com/hatelamers/WinPinMenu\0") #define PRODUCT_VENDOR _T("diVISION\0") #define PRODUCT_LICENSE _T("GNU/GPL\0") #define PRODUCT_LICENSE_URL _T("https://www.gnu.org/licenses/gpl-3.0.en.html\0") #define PRODUCT_COPYRIGHT _T("© 2024, some rights reserved\0") -#define PRODUCT_VERSION 0,1,0 -#define PRODUCT_VERSION_S _T("0.1.0\0") +#define PRODUCT_VERSION 0,1,1 +#define PRODUCT_VERSION_S _T("0.1.1\0") #define FILE_NAME _T("WinPinMenu\0") #define FILE_DESCRIPTION _T("diVISION Pinnable Taskbar Menu For Windows\0") -#define FILE_VERSION 0,1,0,24 -#define FILE_VERSION_S _T("0.1.0.24\0") +#define FILE_VERSION 0,1,1,49 +#define FILE_VERSION_S _T("0.1.1.49\0") diff --git a/src/app/productmeta.h.in b/src/app/productmeta.h.in index b394a93..0f4c464 100644 --- a/src/app/productmeta.h.in +++ b/src/app/productmeta.h.in @@ -6,6 +6,7 @@ #define PRODUCT_VERSION_PATCH ${PRODUCT_VERSION_PATCH} #define PRODUCT_VERSION_TWEAK ${BUILD_NUMBER} #define PRODUCT_NAME _T("${PRODUCT_NAME}\0") +#define PRODUCT_TITLE _T("${PRODUCT_TITLE}\0") #define PRODUCT_DESCRIPTION _T("${PRODUCT_DESCRIPTON}\0") #define PRODUCT_HOMEPAGE_URL _T("${PRODUCT_HOMEPAGE_URL}\0") #define PRODUCT_VENDOR _T("${PRODUCT_VENDOR}\0") diff --git a/src/app/res/app.manifest b/src/app/res/app.manifest new file mode 100644 index 0000000..e204b92 --- /dev/null +++ b/src/app/res/app.manifest @@ -0,0 +1,35 @@ + + + + diVISION Pinnable Taskbar Menu For Windows + + + + + + + + + + + + + + + PerMonitorV2, system + true + + + + + + + + + + + + + + + diff --git a/src/app/resource.h b/src/app/resource.h index f4b4747..2e5d9d4 100644 --- a/src/app/resource.h +++ b/src/app/resource.h @@ -7,6 +7,7 @@ #define IDR_MAINFRAME 128 #define IDI_ICON1 202 #define IDI_APP_RUNNING 202 +#define IDR_RT_MANIFEST1 203 #define IDC_TXT_PRODUCTNAME 1000 #define IDC_LNK_LICENSE 1001 #define IDC_TXT_COMPANYNAME 1002 @@ -15,6 +16,7 @@ #define IDC_TXT_PRODUCTVERSION 1005 #define IDC_TXT_FILEVERSION 1006 #define IDC_TXT_LEGALCOPYRIGHT 1007 +#define IDC_SYSLINK1 1009 #define IDS_EMPTY_FOLDER 32772 #define IDS_INVALID_SOURCE 32773 #define ID_FILE_NOACTIONSOURCE 32775 @@ -23,9 +25,9 @@ // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 203 -#define _APS_NEXT_COMMAND_VALUE 32776 -#define _APS_NEXT_CONTROL_VALUE 1008 +#define _APS_NEXT_RESOURCE_VALUE 205 +#define _APS_NEXT_COMMAND_VALUE 32777 +#define _APS_NEXT_CONTROL_VALUE 1010 #define _APS_NEXT_SYMED_VALUE 102 #endif #endif diff --git a/src/app/stdafx.h b/src/app/stdafx.h index 43da8f6..3eba108 100644 --- a/src/app/stdafx.h +++ b/src/app/stdafx.h @@ -7,6 +7,10 @@ // Change these values to use different versions #include "targetver.h" +#ifndef OEMRESOURCE +#define OEMRESOURCE 1 +#endif // !OEMRESOURCE + #include @@ -34,15 +38,6 @@ extern CAppModule _Module; #include -#if defined _M_IX86 - #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") -#elif defined _M_IA64 - #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") -#elif defined _M_X64 - #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") -#else - #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") -#endif - -#include "taskbarautomation.h" -#include "uxthemehelper.h" +#include +#include +#include diff --git a/src/app/targetver.h b/src/app/targetver.h index 3b6eecc..7390d7e 100644 --- a/src/app/targetver.h +++ b/src/app/targetver.h @@ -1,6 +1,6 @@ #pragma once // Change these values to use different versions -#define WINVER 0x0601 -#define _WIN32_WINNT 0x0601 -#define _WIN32_IE 0x0700 +#define WINVER 0x0A00 +#define _WIN32_WINNT 0x0A00 +#define _WIN32_IE 0x0A00 #define _RICHEDIT_VER 0x0500 diff --git a/src/app/taskbarautomation.h b/src/app/taskbarautomation.h deleted file mode 100644 index 0751dd9..0000000 --- a/src/app/taskbarautomation.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include - -//#pragma comment(lib, "uiautomationcore.lib") - -class CTaskbarAutomation -{ -public: - CTaskbarAutomation() - { - Initialize(); - } - - virtual ~CTaskbarAutomation() = default; - - bool IsReady() const noexcept - { - return m_pFirstElement && m_pSearchConditionButton; - } - - template - HRESULT ForEveryButton(TFunc itemReceiver) - { - if (!IsReady()) - { - ATLTRACE2(_T("CTaskbarAutomation hasn't been initialed\n")); - return E_FAIL; - } - CComPtr pTaskbarButtonElements; - auto hr = m_pFirstElement->FindAll(TreeScope_Descendants, m_pSearchConditionButton, &pTaskbarButtonElements); - if (SUCCEEDED(hr)) - { - auto count = 0; - hr = pTaskbarButtonElements->get_Length(&count); - for (auto i = 0; SUCCEEDED(hr) && i < count; i++) - { - CComPtr pButton; - hr = pTaskbarButtonElements->GetElement(i, &pButton); - if (SUCCEEDED(hr) && !itemReceiver(pButton, i)) - { - hr = S_FALSE; - break; - } - } - } - return hr; - } - -protected: - bool Initialize() - { - if (m_pFirstElement && m_pSearchConditionButton) - return true; - - auto hr = S_OK; - if (!m_pAutomation) - { - hr = m_pAutomation.CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER); - //HRESULT hr = CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER, __uuidof(IUIAutomation), (void**)&m_pAutomation); - if (FAILED(hr)) return false; - } - - CComPtr pRootElement; - hr = m_pAutomation->GetRootElement(&pRootElement); - if (FAILED(hr)) return false; - - CComPtr pTaskbarCondition; - hr = m_pAutomation->CreatePropertyCondition(UIA_ClassNamePropertyId, CComVariant(L"MSTaskListWClass"), &pTaskbarCondition); - if (SUCCEEDED(hr)) - { - hr = pRootElement->FindFirst(TreeScope_Descendants, pTaskbarCondition, &m_pFirstElement); - if (SUCCEEDED(hr) && m_pFirstElement) - { - hr = m_pAutomation->CreatePropertyCondition(UIA_ControlTypePropertyId, CComVariant(UIA_ButtonControlTypeId), &m_pSearchConditionButton); - if (FAILED(hr)) return false; - } - } - - if (!m_pFirstElement) - { - if (pTaskbarCondition) - { - pTaskbarCondition.Release(); - } - hr = m_pAutomation->CreatePropertyCondition(UIA_ClassNamePropertyId, CComVariant(L"Shell_TrayWnd"), &pTaskbarCondition); - if (FAILED(hr)) return false; - - hr = pRootElement->FindFirst(TreeScope_Children, pTaskbarCondition, &m_pFirstElement); - if (FAILED(hr) || !m_pFirstElement) return false; - - - hr = m_pAutomation->CreatePropertyCondition(UIA_ClassNamePropertyId, CComVariant(L"Taskbar.TaskListButtonAutomationPeer"), &m_pSearchConditionButton); - if (FAILED(hr)) return false; - } - - return true; - - } - -protected: - - CComPtr m_pAutomation; - CComPtr m_pFirstElement; - CComPtr m_pSearchConditionButton; - -}; diff --git a/src/app/uxthemehelper.h b/src/app/uxthemehelper.h deleted file mode 100644 index 04927c7..0000000 --- a/src/app/uxthemehelper.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -enum class PreferredAppMode -{ - Default, - AllowDark, - ForceDark, - ForceLight, - Max -}; - -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 - -class CUxTheme -{ -public: - CUxTheme() - : m_hUxtheme(NULL), m_pfnAllowDarkModeForWindow(NULL), m_pfnShouldAppsUseDarkMode(NULL), m_pfnSetPreferredAppMode(NULL) - { - m_hUxtheme = ::LoadLibraryEx(_T("uxtheme.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); - ATLASSERT(m_hUxtheme); - if (m_hUxtheme) - { - m_pfnShouldAppsUseDarkMode = (FnShouldAppsUseDarkMode)::GetProcAddress(m_hUxtheme, MAKEINTRESOURCEA(132)); - ATLASSERT(m_pfnShouldAppsUseDarkMode); - m_pfnAllowDarkModeForWindow = (FnAllowDarkModeForWindow)::GetProcAddress(m_hUxtheme, MAKEINTRESOURCEA(133)); - ATLASSERT(m_pfnAllowDarkModeForWindow); - m_pfnSetPreferredAppMode = (FnSetPreferredAppMode)::GetProcAddress(m_hUxtheme, MAKEINTRESOURCEA(135)); - ATLASSERT(m_pfnSetPreferredAppMode); - } - } - - ~CUxTheme() - { - if (m_hUxtheme) - ::FreeLibrary(m_hUxtheme); - } - - bool ShouldAppsUseDarkMode() const - { - if (m_pfnShouldAppsUseDarkMode) - return m_pfnShouldAppsUseDarkMode(); - return false; - } - - bool AllowDarkModeForWindow(HWND hWnd, bool allow) const - { - if (m_pfnAllowDarkModeForWindow) - return m_pfnAllowDarkModeForWindow(hWnd, allow); - return false; - } - - PreferredAppMode SetPreferredAppMode(PreferredAppMode appMode) const - { - if (m_pfnSetPreferredAppMode) - return m_pfnSetPreferredAppMode(appMode); - return PreferredAppMode::Default; - } - -private: - - HMODULE m_hUxtheme; - FnAllowDarkModeForWindow m_pfnAllowDarkModeForWindow; - FnShouldAppsUseDarkMode m_pfnShouldAppsUseDarkMode; - FnSetPreferredAppMode m_pfnSetPreferredAppMode; -}; - diff --git a/src/wtlx/Draw.cpp b/src/wtlx/Draw.cpp new file mode 100644 index 0000000..e1d0266 --- /dev/null +++ b/src/wtlx/Draw.cpp @@ -0,0 +1,267 @@ +/************************************************************************ + * $Revision: 4 $ + * $Date: 2016-11-25 01:28:53 +0100 (Fri, 25 Nov 2016) $ + ************************************************************************/ +/************************************************************************ + * File: Draw.cpp - implementation file + * Copyright: 2016 diVISION Soft, some rights reserved + * License: CPOL, s. https://en.wikipedia.org/wiki/Code_Project_Open_License + * @author Igor Vigdorchik (http://www.codeproject.com/Members/IgorVigdorchik) + ************************************************************************/ + +#include "pch.h" +//#include "Tools.h" +#include "wtlx/Draw.h" + +/////////////////////////////////////////////////////////////////////////////// +HLSCOLOR RGB2HLS (COLORREF rgb) +{ + unsigned char minval = min(GetRValue(rgb), min(GetGValue(rgb), GetBValue(rgb))); + unsigned char maxval = max(GetRValue(rgb), max(GetGValue(rgb), GetBValue(rgb))); + float mdiff = float(maxval) - float(minval); + float msum = float(maxval) + float(minval); + + float luminance = msum / 510.0f; + float saturation = 0.0f; + float hue = 0.0f; + + if ( maxval != minval ) + { + float rnorm = (maxval - GetRValue(rgb) ) / mdiff; + float gnorm = (maxval - GetGValue(rgb)) / mdiff; + float bnorm = (maxval - GetBValue(rgb) ) / mdiff; + + saturation = (luminance <= 0.5f) ? (mdiff / msum) : (mdiff / (510.0f - msum)); + + if (GetRValue(rgb) == maxval) hue = 60.0f * (6.0f + bnorm - gnorm); + if (GetGValue(rgb) == maxval) hue = 60.0f * (2.0f + rnorm - bnorm); + if (GetBValue(rgb) == maxval) hue = 60.0f * (4.0f + gnorm - rnorm); + if (hue > 360.0f) hue = hue - 360.0f; + } + return HLS ((hue*255)/360, luminance*255, saturation*255); +} + +/////////////////////////////////////////////////////////////////////////////// +static BYTE _ToRGB (float rm1, float rm2, float rh) +{ + if (rh > 360.0f) rh -= 360.0f; + else if (rh < 0.0f) rh += 360.0f; + + if (rh < 60.0f) rm1 = rm1 + (rm2 - rm1) * rh / 60.0f; + else if (rh < 180.0f) rm1 = rm2; + else if (rh < 240.0f) rm1 = rm1 + (rm2 - rm1) * (240.0f - rh) / 60.0f; + + return (BYTE)(rm1 * 255); +} + +/////////////////////////////////////////////////////////////////////////////// +COLORREF HLS2RGB (HLSCOLOR hls) +{ + float hue = ((int)HLS_H(hls)*360)/255.0f; + float luminance = HLS_L(hls)/255.0f; + float saturation = HLS_S(hls)/255.0f; + + if ( saturation == 0.0f ) + { + return RGB (HLS_L(hls), HLS_L(hls), HLS_L(hls)); + } + float rm1, rm2; + + if ( luminance <= 0.5f ) rm2 = luminance + luminance * saturation; + else rm2 = luminance + saturation - luminance * saturation; + rm1 = 2.0f * luminance - rm2; + BYTE red = _ToRGB (rm1, rm2, hue + 120.0f); + BYTE green = _ToRGB (rm1, rm2, hue); + BYTE blue = _ToRGB (rm1, rm2, hue - 120.0f); + + return RGB (red, green, blue); +} + +/////////////////////////////////////////////////////////////////////////////// +COLORREF HLS_TRANSFORM (COLORREF rgb, int percent_L, int percent_S) +{ + HLSCOLOR hls = RGB2HLS (rgb); + BYTE h = HLS_H(hls); + BYTE l = HLS_L(hls); + BYTE s = HLS_S(hls); + + if ( percent_L > 0 ) + { + l = BYTE(l + ((255 - l) * percent_L) / 100); + } + else if ( percent_L < 0 ) + { + l = BYTE((l * (100+percent_L)) / 100); + } + if ( percent_S > 0 ) + { + s = BYTE(s + ((255 - s) * percent_S) / 100); + } + else if ( percent_S < 0 ) + { + s = BYTE((s * (100+percent_S)) / 100); + } + 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; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +CBufferDC::CBufferDC (HDC hDestDC, const CRect& rcPaint) : m_hDestDC (hDestDC) +{ + if ( rcPaint.IsRectEmpty() ) + { + ::GetClipBox (m_hDestDC, m_rect); + } + else + { + m_rect = rcPaint; + } + Attach (::CreateCompatibleDC (m_hDestDC)); + m_bitmap.Attach (::CreateCompatibleBitmap (m_hDestDC, m_rect.right, m_rect.bottom)); + m_hOldBitmap = ::SelectObject (m_hDC, m_bitmap); + + if ( m_rect.top > 0 ) + { + ExcludeClipRect (0, 0, m_rect.right, m_rect.top); + } + if ( m_rect.left > 0 ) + { + ExcludeClipRect (0, m_rect.top, m_rect.left, m_rect.bottom); + } +} + +/////////////////////////////////////////////////////////////////////////////// +CBufferDC::~CBufferDC () +{ + ::BitBlt (m_hDestDC, m_rect.left, m_rect.top, m_rect.Width(), m_rect.Height(), m_hDC, m_rect.left, m_rect.top, SRCCOPY); + ::SelectObject (m_hDC, m_hOldBitmap); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +CPenDC::CPenDC (HDC hDC, COLORREF crColor) : m_hDC (hDC) +{ + m_pen.CreatePen (PS_SOLID, 1, crColor); + m_hOldPen = (HPEN)::SelectObject (m_hDC, m_pen); +} + +/////////////////////////////////////////////////////////////////////////////// +CPenDC::~CPenDC () +{ + ::SelectObject (m_hDC, m_hOldPen); +} + +/////////////////////////////////////////////////////////////////////////////// +void CPenDC::Color (COLORREF crColor) +{ + ::SelectObject (m_hDC, m_hOldPen); + m_pen.DeleteObject(); + m_pen.CreatePen (PS_SOLID, 1, crColor); + m_hOldPen = (HPEN)::SelectObject (m_hDC, m_pen); +} + +/////////////////////////////////////////////////////////////////////////////// +COLORREF CPenDC::Color () const +{ + LOGPEN logPen; + + ((CPenDC*)this)->m_pen.GetLogPen (&logPen); + + return logPen.lopnColor; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +CBrushDC::CBrushDC (HDC hDC, COLORREF crColor) : m_hDC (hDC) +{ + if ( crColor == CLR_NONE ) m_brush.Attach ((HBRUSH)::GetStockObject (NULL_BRUSH)); + else m_brush.CreateSolidBrush (crColor); + m_hOldBrush = (HBRUSH)::SelectObject (m_hDC, m_brush); +} + +/////////////////////////////////////////////////////////////////////////////// +CBrushDC::~CBrushDC () +{ + ::SelectObject (m_hDC, m_hOldBrush); +} + +/////////////////////////////////////////////////////////////////////////////// +void CBrushDC::Color (COLORREF crColor) +{ + ::SelectObject (m_hDC, m_hOldBrush); + m_brush.DeleteObject(); + if ( crColor == CLR_NONE ) m_brush.Attach ((HBRUSH)::GetStockObject (NULL_BRUSH)); + else m_brush.CreateSolidBrush (crColor); + m_hOldBrush = (HBRUSH)::SelectObject (m_hDC, m_brush); +} + +/////////////////////////////////////////////////////////////////////////////// +COLORREF CBrushDC::Color () const +{ + LOGBRUSH logBrush; + + ((CBrushDC*)this)->m_brush.GetLogBrush (&logBrush); + + return logBrush.lbColor; +} + + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +CBoldDC::CBoldDC (HDC hDC, bool bBold) : m_hDC (hDC), m_hDefFont (NULL) +{ + LOGFONT lf; + + CFontHandle ((HFONT)::GetCurrentObject (m_hDC, OBJ_FONT)).GetLogFont (&lf); + + if ( ( bBold && lf.lfWeight != FW_BOLD) || + (!bBold && lf.lfWeight == FW_BOLD) ) + { + lf.lfWeight = bBold ? FW_BOLD : FW_NORMAL; + + m_fontBold.CreateFontIndirect (&lf); + m_hDefFont = (HFONT)::SelectObject (m_hDC, m_fontBold); + } +} + +///////////////////////////////////////////////////////////////////////////// +CBoldDC::~CBoldDC () +{ + if ( m_hDefFont != NULL ) + { + ::SelectObject (m_hDC, m_hDefFont); + } +} + +CFontDC::CFontDC(HDC hDC, HFONT hFont) + : m_font(hFont), m_hDC(hDC) + , m_hOldFont((HFONT)::SelectObject(hDC, hFont)) +{ +} + +CFontDC::~CFontDC() +{ + if (m_hOldFont) + { + ::SelectObject(m_hDC, (HFONT)m_hOldFont); + } +} 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..71bd46d --- /dev/null +++ b/src/wtlx/wtlx.vcxproj @@ -0,0 +1,284 @@ + + + + + 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..6c04d59 --- /dev/null +++ b/src/wtlx/wtlx.vcxproj.filters @@ -0,0 +1,48 @@ + + + + + {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 + + + + + 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/wtlx/wtlx/Draw.h b/src/wtlx/wtlx/Draw.h new file mode 100644 index 0000000..4841153 --- /dev/null +++ b/src/wtlx/wtlx/Draw.h @@ -0,0 +1,113 @@ +/************************************************************************ + * $Revision: 4 $ + * $Date: 2016-11-25 01:28:53 +0100 (Fri, 25 Nov 2016) $ + ************************************************************************/ +/************************************************************************ + * File: Draw.h + * 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 + +/////////////////////////////////////////////////////////////////////////////// +typedef DWORD HLSCOLOR; +#define HLS(h,l,s) ((HLSCOLOR)(((BYTE)(h)|((WORD)((BYTE)(l))<<8))|(((DWORD)(BYTE)(s))<<16))) + +/////////////////////////////////////////////////////////////////////////////// +#define HLS_H(hls) ((BYTE)(hls)) +#define HLS_L(hls) ((BYTE)(((WORD)(hls)) >> 8)) +#define HLS_S(hls) ((BYTE)((hls)>>16)) + +/////////////////////////////////////////////////////////////////////////////// +HLSCOLOR RGB2HLS (COLORREF rgb); +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(); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class CBufferDC : public CDCHandle +{ + HDC m_hDestDC; + CBitmap m_bitmap; // Bitmap in Memory DC + CRect m_rect; + HGDIOBJ m_hOldBitmap; // Previous Bitmap + +public: + CBufferDC (HDC hDestDC, const CRect& rcPaint = CRect(0,0,0,0)); + ~CBufferDC (); +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class CPenDC +{ +protected: + CPen m_pen; + HDC m_hDC; + HPEN m_hOldPen; + +public: + CPenDC (HDC hDC, COLORREF crColor = CLR_NONE); + ~CPenDC (); + + void Color (COLORREF crColor); + COLORREF Color () const; +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class CBrushDC +{ +protected: + CBrush m_brush; + HDC m_hDC; + HBRUSH m_hOldBrush; + +public: + CBrushDC (HDC hDC, COLORREF crColor = CLR_NONE); + ~CBrushDC (); + + void Color (COLORREF crColor); + COLORREF Color () const; +}; + +class CFontDC +{ +protected: + HFONT m_font; + HDC m_hDC; + HFONT m_hOldFont; + +public: + CFontDC(HDC hDC, HFONT hFont); + virtual ~CFontDC(); + + HFONT GetFont() const noexcept + { + return m_font; + } +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class CBoldDC +{ +protected: + CFont m_fontBold; + HDC m_hDC; + HFONT m_hDefFont; + +public: + CBoldDC (HDC hDC, bool bBold); + ~CBoldDC (); +}; 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/wtlx/wtlx/taskbarautomation.h b/src/wtlx/wtlx/taskbarautomation.h new file mode 100644 index 0000000..f902f2e --- /dev/null +++ b/src/wtlx/wtlx/taskbarautomation.h @@ -0,0 +1,131 @@ +#pragma once + +#include + +//#pragma comment(lib, "uiautomationcore.lib") + +class CTaskbarAutomation +{ +public: + CTaskbarAutomation() + { + Initialize(); + } + + virtual ~CTaskbarAutomation() = default; + + bool IsReady() const noexcept + { + return m_pTaskbars && m_pSearchConditionButton; + } + + template + HRESULT ForEveryButton(TFunc itemReceiver) + { + if (!IsReady()) + { + ATLTRACE2(_T("CTaskbarAutomation hasn't been initialed\n")); + return E_FAIL; + } + + auto tbCount = 0; + auto hr = m_pTaskbars->get_Length(&tbCount); + ATLTRACE(__FUNCTION__ _T(" %d taskbars\n"), tbCount); + for (auto i = 0; SUCCEEDED(hr) && i < tbCount; i++) + { + CComPtr pTaskbar; + hr = m_pTaskbars->GetElement(i, &pTaskbar); + if (SUCCEEDED(hr)) + { + CComPtr pTaskbarButtonElements; + hr = pTaskbar->FindAllBuildCache(TreeScope_Descendants, m_pSearchConditionButton, m_pCacheRequestButton, &pTaskbarButtonElements); + if (SUCCEEDED(hr)) + { + auto btnCount = 0; + hr = pTaskbarButtonElements->get_Length(&btnCount); + for (auto j = 0; SUCCEEDED(hr) && j < btnCount; j++) + { + CComPtr pButton; + hr = pTaskbarButtonElements->GetElement(j, &pButton); + if (SUCCEEDED(hr) && !itemReceiver(pButton, i, j)) + { + hr = S_FALSE; + break; + } + } + } + + } + } + + return hr; + } + +protected: + bool Initialize() + { + ATLTRACE(__FUNCTION__ _T(" START\n")); + if (m_pTaskbars && m_pSearchConditionButton) + return true; + + auto hr = S_OK; + if (!m_pAutomation) + { + hr = m_pAutomation.CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER); + if (FAILED(hr)) return false; + } + + CComPtr pRootElement; + hr = m_pAutomation->GetRootElement(&pRootElement); + if (FAILED(hr)) return false; + + CComPtr pPrimTaskbarCondition; + hr = m_pAutomation->CreatePropertyCondition(UIA_ClassNamePropertyId, CComVariant(L"Shell_TrayWnd"), &pPrimTaskbarCondition); + if (FAILED(hr)) return false; + + CComPtr pSecondTaskbarCondition; + hr = m_pAutomation->CreatePropertyCondition(UIA_ClassNamePropertyId, CComVariant(L"Shell_SecondaryTrayWnd"), &pSecondTaskbarCondition); + if (FAILED(hr)) return false; + + CComPtr pTaskbarCondition; + hr = m_pAutomation->CreateOrCondition(pPrimTaskbarCondition, pSecondTaskbarCondition, &pTaskbarCondition); + if (FAILED(hr)) return false; + + hr = pRootElement->FindAll(TreeScope_Children, pTaskbarCondition, &m_pTaskbars); + if (FAILED(hr) || !m_pTaskbars) return false; + + //CComPtr pTaskListCondition; + //hr = m_pAutomation->CreatePropertyCondition(UIA_ClassNamePropertyId, CComVariant(L"MSTaskListWClass"), &pTaskListCondition); + //if (FAILED(hr)) return false; + + + CComPtr pSearchConditionUXButton; + hr = m_pAutomation->CreatePropertyCondition(UIA_ClassNamePropertyId, CComVariant(L"Taskbar.TaskListButtonAutomationPeer"), &pSearchConditionUXButton); + if (FAILED(hr)) return false; + + CComPtr pSearchConditionButton; + hr = m_pAutomation->CreatePropertyCondition(UIA_ControlTypePropertyId, CComVariant(UIA_ButtonControlTypeId), &pSearchConditionButton); + if (FAILED(hr)) return false; + + hr = m_pAutomation->CreateOrCondition(pSearchConditionButton, pSearchConditionUXButton, &m_pSearchConditionButton); + if (FAILED(hr)) return false; + + hr = m_pAutomation->CreateCacheRequest(&m_pCacheRequestButton); + if (FAILED(hr)) return false; + + hr = m_pCacheRequestButton->AddProperty(UIA_BoundingRectanglePropertyId); + if (FAILED(hr)) return false; + + ATLTRACE(__FUNCTION__ _T(" END\n")); + return true; + + } + +protected: + + CComPtr m_pAutomation; + CComPtr m_pTaskbars; + CComPtr m_pSearchConditionButton; + CComPtr m_pCacheRequestButton; + +}; 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));