diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 562ce51cf..5dbd6fafa 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -7,6 +7,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ abgr abi ABlocked +ABOUTBOX Abug accctrl Acceleratorkeys @@ -1048,6 +1049,7 @@ IWeb IWIC IWindows IWork +IXaml IXml ixx IYUV @@ -1157,6 +1159,7 @@ lmcons LMEM LMENU lnk +LOADSTRING LOCALAPPDATA LOCALDISPLAY localhost @@ -1178,6 +1181,7 @@ lowlevel LOWORD lparam LPBYTE +LPCITEMIDLIST LPCMINVOKECOMMANDINFO LPCREATESTRUCT LPCTSTR @@ -1224,6 +1228,7 @@ LVS LVSIL LWA lwin +LZero lzw mailto MAINICON @@ -1579,6 +1584,7 @@ phwnd pici pid pidl +PIDLIST PINDIR pinfo pinvoke @@ -1611,6 +1617,7 @@ powerlauncher powerpreview powerrename POWERRENAMETEST +POWERRENAMEUIHOST powershell powertoy powertoysinterop diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 8f4b0da2f..22f6cf723 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -172,6 +172,8 @@ build: - 'modules\launcher\Wox.Plugin.dll' - 'modules\MouseUtils\FindMyMouse.dll' - 'modules\PowerRename\PowerRenameExt.dll' + - 'modules\PowerRename\PowerRenameUILib.dll' + - 'modules\PowerRename\PowerRename.exe' - 'modules\ShortcutGuide\ShortcutGuide\PowerToys.ShortcutGuide.exe' - 'modules\ShortcutGuide\ShortcutGuideModuleInterface\ShortcutGuideModuleInterface.dll' - 'modules\VideoConference\VideoConferenceModule.dll' diff --git a/Cpp.Build.props b/Cpp.Build.props index b696fe747..1f7d2a393 100644 --- a/Cpp.Build.props +++ b/Cpp.Build.props @@ -76,7 +76,7 @@ - 10.0.17134.0 + 10.0.18362.0 @@ -84,7 +84,6 @@ v142 $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\ Unicode - Spectre diff --git a/PowerToys.sln b/PowerToys.sln index 17e285653..ad3f59a4e 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -5,7 +5,6 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}" ProjectSection(ProjectDependencies) = postProject {217DF501-135C-4E38-BFC8-99D4821032EA} = {217DF501-135C-4E38-BFC8-99D4821032EA} - {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798} {48804216-2A0E-4168-A6D8-9CD068D14227} = {48804216-2A0E-4168-A6D8-9CD068D14227} {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB} = {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB} @@ -57,26 +56,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "powerrename", "powerrename" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameExt", "src\modules\powerrename\dll\PowerRenameExt.vcxproj", "{B25AC7A5-FB9F-4789-B392-D5C85E948670}" ProjectSection(ProjectDependencies) = postProject - {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798} {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameLib", "src\modules\powerrename\lib\PowerRenameLib.vcxproj", "{51920F1F-C28C-4ADF-8660-4238766796C2}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUI", "src\modules\powerrename\ui\PowerRenameUI.vcxproj", "{0E072714-D127-460B-AFAD-B4C40B412798}" - ProjectSection(ProjectDependencies) = postProject - {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} - EndProjectSection -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameTest", "src\modules\powerrename\testapp\PowerRenameTest.vcxproj", "{A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}" ProjectSection(ProjectDependencies) = postProject - {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798} {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUnitTests", "src\modules\powerrename\unittests\PowerRenameLibUnitTests.vcxproj", "{2151F984-E006-4A9F-92EF-C6DDE3DC8413}" ProjectSection(ProjectDependencies) = postProject - {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798} {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {B25AC7A5-FB9F-4789-B392-D5C85E948670} EndProjectSection @@ -84,9 +75,6 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ModuleTemplateCompileTest", "tools\project_template\ModuleTemplate\ModuleTemplateCompileTest.vcxproj", "{64A80062-4D8B-4229-8A38-DFA1D7497749}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUWPUI", "src\modules\powerrename\UWPui\PowerRenameUWPUI.vcxproj", "{0485F45C-EA7A-4BB5-804B-3E8D14699387}" - ProjectSection(ProjectDependencies) = postProject - {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798} - EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManager", "src\modules\keyboardmanager\dll\KeyboardManager.vcxproj", "{89F34AF7-1C34-4A72-AA6E-534BCF972BD9}" EndProject @@ -194,6 +182,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject src\.editorconfig = src\.editorconfig src\tests\win-app-driver\packages.config = src\tests\win-app-driver\packages.config + Solution.props = Solution.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Settings.UI.Library", "src\settings-ui\Microsoft.PowerToys.Settings.UI.Library\Microsoft.PowerToys.Settings.UI.Library.csproj", "{B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}" @@ -283,6 +272,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643 src\common\utils\EventLocker.h = src\common\utils\EventLocker.h src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h src\common\utils\exec.h = src\common\utils\exec.h + src\common\utils\HDropIterator.h = src\common\utils\HDropIterator.h src\common\utils\HttpClient.h = src\common\utils\HttpClient.h src\common\utils\json.h = src\common\utils\json.h src\common\utils\logger_helper.h = src\common\utils\logger_helper.h @@ -372,6 +362,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests.csproj", "{4ED320BC-BA04-4D42-8D15-CBE62151F08B}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUIHost", "src\modules\powerrename\PowerRenameUIHost\PowerRenameUIHost.vcxproj", "{F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}" + ProjectSection(ProjectDependencies) = postProject + {4642D596-723F-4BFC-894C-46811219AC4A} = {4642D596-723F-4BFC-894C-46811219AC4A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUILib", "src\modules\powerrename\PowerRenameUILib\PowerRenameUILib.vcxproj", "{4642D596-723F-4BFC-894C-46811219AC4A}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MouseUtils", "MouseUtils", "{322566EF-20DC-43A6-B9F8-616AF942579A}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FindMyMouse", "src\modules\MouseUtils\FindMyMouse\FindMyMouse.vcxproj", "{E94FD11C-0591-456F-899F-EFC0CA548336}" @@ -426,12 +423,6 @@ Global {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x64.ActiveCfg = Release|x64 {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x64.Build.0 = Release|x64 {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x86.ActiveCfg = Release|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x64.ActiveCfg = Debug|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x64.Build.0 = Debug|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x86.ActiveCfg = Debug|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x64.ActiveCfg = Release|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x64.Build.0 = Release|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x86.ActiveCfg = Release|x64 {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x64.ActiveCfg = Debug|x64 {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x64.Build.0 = Debug|x64 {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x86.ActiveCfg = Debug|x64 @@ -985,6 +976,18 @@ Global {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x64.Build.0 = Release|x64 {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x86.ActiveCfg = Release|Any CPU {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x86.Build.0 = Release|Any CPU + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Debug|x64.ActiveCfg = Debug|x64 + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Debug|x64.Build.0 = Debug|x64 + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Debug|x86.ActiveCfg = Debug|x64 + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Release|x64.ActiveCfg = Release|x64 + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Release|x64.Build.0 = Release|x64 + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Release|x86.ActiveCfg = Release|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Debug|x64.ActiveCfg = Debug|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Debug|x64.Build.0 = Debug|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Debug|x86.ActiveCfg = Debug|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Release|x64.ActiveCfg = Release|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Release|x64.Build.0 = Release|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Release|x86.ActiveCfg = Release|x64 {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x64.ActiveCfg = Debug|x64 {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x64.Build.0 = Debug|x64 {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x86.ActiveCfg = Debug|x64 @@ -1005,7 +1008,6 @@ Global {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {51920F1F-C28C-4ADF-8660-4238766796C2} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} - {0E072714-D127-460B-AFAD-B4C40B412798} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {2151F984-E006-4A9F-92EF-C6DDE3DC8413} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {0485F45C-EA7A-4BB5-804B-3E8D14699387} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} @@ -1108,6 +1110,8 @@ Global {F40C3397-1834-4530-B2D9-8F8B8456BCDF} = {2F305555-C296-497E-AC20-5FA1B237996A} {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} = {4AFC9975-2456-4C70-94A4-84073C1CED93} {4ED320BC-BA04-4D42-8D15-CBE62151F08B} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} + {4642D596-723F-4BFC-894C-46811219AC4A} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {322566EF-20DC-43A6-B9F8-616AF942579A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {E94FD11C-0591-456F-899F-EFC0CA548336} = {322566EF-20DC-43A6-B9F8-616AF942579A} EndGlobalSection diff --git a/Solution.props b/Solution.props new file mode 100644 index 000000000..1598df055 --- /dev/null +++ b/Solution.props @@ -0,0 +1,6 @@ + + + + $(IntDir)Generated Files\ + + diff --git a/doc/devdocs/modules/powerrename.md b/doc/devdocs/modules/powerrename.md index b22984e6f..79a72002f 100644 --- a/doc/devdocs/modules/powerrename.md +++ b/doc/devdocs/modules/powerrename.md @@ -21,6 +21,3 @@ TODO #### [`trace.cpp`](/src/modules/powerrename/lib/trace.cpp) TODO - -#### [`PowerRenameUI.cpp`](/src/modules/powerrename/ui/PowerRenameUI.cpp) -TODO \ No newline at end of file diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 32df99d25..add2df8ec 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -236,7 +236,9 @@ - + + + @@ -694,7 +696,15 @@ - + + + + + + + + + @@ -707,6 +717,12 @@ + + + + + + @@ -1011,6 +1027,7 @@ + diff --git a/src/common/interop/pch.h b/src/common/interop/pch.h index 409d905e3..2f415a620 100644 --- a/src/common/interop/pch.h +++ b/src/common/interop/pch.h @@ -10,5 +10,6 @@ #define WIN32_LEAN_AND_MEAN // add headers that you want to pre-compile here #include +#include #endif //PCH_H diff --git a/src/common/utils/HDropIterator.h b/src/common/utils/HDropIterator.h new file mode 100644 index 000000000..7fbc0d717 --- /dev/null +++ b/src/common/utils/HDropIterator.h @@ -0,0 +1,58 @@ +#pragma once +#include + +class HDropIterator +{ +public: + HDropIterator(IDataObject* pDataObject) + { + _current = 0; + + FORMATETC formatetc = { + CF_HDROP, + NULL, + DVASPECT_CONTENT, + -1, + TYMED_HGLOBAL + }; + + pDataObject->GetData(&formatetc, &m_medium); + + _listCount = DragQueryFile((HDROP)m_medium.hGlobal, 0xFFFFFFFF, NULL, 0); + } + + ~HDropIterator() + { + ReleaseStgMedium(&m_medium); + } + + void First() + { + _current = 0; + } + + void Next() + { + _current++; + } + + bool IsDone() const + { + return _current >= _listCount; + } + + LPTSTR CurrentItem() const + { + UINT cch = DragQueryFile((HDROP)m_medium.hGlobal, _current, NULL, 0) + 1; + LPTSTR pszPath = (LPTSTR)malloc(sizeof(TCHAR) * cch); + + DragQueryFile((HDROP)m_medium.hGlobal, _current, pszPath, cch); + + return pszPath; + } + +private: + UINT _listCount; + STGMEDIUM m_medium; + UINT _current; +}; diff --git a/src/modules/imageresizer/dll/ContextMenuHandler.cpp b/src/modules/imageresizer/dll/ContextMenuHandler.cpp index 6674cffa5..681dabce7 100644 --- a/src/modules/imageresizer/dll/ContextMenuHandler.cpp +++ b/src/modules/imageresizer/dll/ContextMenuHandler.cpp @@ -2,11 +2,11 @@ #include "pch.h" #include "ContextMenuHandler.h" -#include "HDropIterator.h" #include "Settings.h" #include #include #include +#include #include "trace.h" diff --git a/src/modules/imageresizer/dll/HDropIterator.cpp b/src/modules/imageresizer/dll/HDropIterator.cpp deleted file mode 100644 index bfc23b39e..000000000 --- a/src/modules/imageresizer/dll/HDropIterator.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "pch.h" -#include "HDropIterator.h" - -HDropIterator::HDropIterator(IDataObject* pdtobj) -{ - _current = 0; - - FORMATETC formatetc = { - CF_HDROP, - NULL, - DVASPECT_CONTENT, - -1, - TYMED_HGLOBAL - }; - - pdtobj->GetData(&formatetc, &m_medium); - - _listCount = DragQueryFile((HDROP)m_medium.hGlobal, 0xFFFFFFFF, NULL, 0); -} - -HDropIterator::~HDropIterator() -{ - ReleaseStgMedium(&m_medium); -} - -void HDropIterator::First() -{ - _current = 0; -} - -void HDropIterator::Next() -{ - _current++; -} - -bool HDropIterator::IsDone() const -{ - return _current >= _listCount; -} - -LPTSTR HDropIterator::CurrentItem() const -{ - UINT cch = DragQueryFile((HDROP)m_medium.hGlobal, _current, NULL, 0) + 1; - LPTSTR pszPath = (LPTSTR)malloc(sizeof(TCHAR) * cch); - - DragQueryFile((HDROP)m_medium.hGlobal, _current, pszPath, cch); - - return pszPath; -} diff --git a/src/modules/imageresizer/dll/HDropIterator.h b/src/modules/imageresizer/dll/HDropIterator.h deleted file mode 100644 index 8f1d26151..000000000 --- a/src/modules/imageresizer/dll/HDropIterator.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -class HDropIterator -{ -public: - HDropIterator(IDataObject *pDataObject); - ~HDropIterator(); - void First(); - void Next(); - bool IsDone() const; - LPTSTR CurrentItem() const; - -private: - UINT _listCount; - STGMEDIUM m_medium; - UINT _current; -}; diff --git a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj index c5b18627e..d97f61e4a 100644 --- a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj +++ b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj @@ -77,7 +77,6 @@ - false @@ -97,7 +96,6 @@ - diff --git a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters index bbd3a2437..dd6a23aa9 100644 --- a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters +++ b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters @@ -28,9 +28,6 @@ Source Files - - Source Files - Source Files @@ -57,9 +54,6 @@ Header Files - - Header Files - Header Files diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp new file mode 100644 index 000000000..28c85ab5a --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp @@ -0,0 +1,917 @@ +// PowerRenameUIHost.cpp : Defines the entry point for the application. +// +#include "pch.h" + +#include "PowerRenameUIHost.h" +#include +#include +#include +#include +#include +#include + +#define MAX_LOADSTRING 100 + +const wchar_t c_WindowClass[] = L"PowerRename"; +HINSTANCE g_hostHInst; + +int AppWindow::Show(HINSTANCE hInstance, std::vector files) +{ + auto window = AppWindow(hInstance, files); + window.CreateAndShowWindow(); + return window.MessageLoop(window.m_accelerators.get()); +} + +LRESULT AppWindow::MessageHandler(UINT message, WPARAM wParam, LPARAM lParam) noexcept +{ + switch (message) + { + HANDLE_MSG(WindowHandle(), WM_CREATE, OnCreate); + HANDLE_MSG(WindowHandle(), WM_COMMAND, OnCommand); + HANDLE_MSG(WindowHandle(), WM_DESTROY, OnDestroy); + HANDLE_MSG(WindowHandle(), WM_SIZE, OnResize); + default: + return base_type::MessageHandler(message, wParam, lParam); + } + + return base_type::MessageHandler(message, wParam, lParam); +} + +AppWindow::AppWindow(HINSTANCE hInstance, std::vector files) noexcept : + m_instance{ hInstance }, m_managerEvents{ this } +{ + HRESULT hr = CPowerRenameManager::s_CreateInstance(&m_prManager); + // Create the factory for our items + CComPtr prItemFactory; + hr = CPowerRenameItem::s_CreateInstance(nullptr, IID_PPV_ARGS(&prItemFactory)); + hr = m_prManager->PutRenameItemFactory(prItemFactory); + hr = m_prManager->Advise(&m_managerEvents, &m_cookie); + + if (SUCCEEDED(hr)) + { + CComPtr shellItemArray; + // To test PowerRenameUIHost uncomment this line and update the path to + // your local (absolute or relative) path which you want to see in PowerRename + //files.push_back(L""); + + if (!files.empty()) + { + hr = CreateShellItemArrayFromPaths(files, &shellItemArray); + if (SUCCEEDED(hr)) + { + CComPtr enumShellItems; + hr = shellItemArray->EnumItems(&enumShellItems); + if (SUCCEEDED(hr)) + { + EnumerateShellItems(enumShellItems); + } + } + } + } +} + +void AppWindow::CreateAndShowWindow() +{ + m_accelerators.reset(LoadAcceleratorsW(m_instance, MAKEINTRESOURCE(IDC_POWERRENAMEUIHOST))); + + WNDCLASSEXW wcex = { sizeof(wcex) }; + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.hInstance = m_instance; + wcex.hIcon = LoadIconW(m_instance, MAKEINTRESOURCE(IDC_POWERRENAMEUIHOST)); + wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wcex.hbrBackground = reinterpret_cast(COLOR_WINDOW + 1); + wcex.lpszClassName = c_WindowClass; + wcex.hIconSm = LoadIconW(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); + RegisterClassExW(&wcex); // don't test result, handle error at CreateWindow + + wchar_t title[64]; + LoadStringW(m_instance, IDS_APP_TITLE, title, ARRAYSIZE(title)); + + m_window = CreateWindowW(c_WindowClass, title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, m_instance, this); + THROW_LAST_ERROR_IF(!m_window); + + ShowWindow(m_window, SW_SHOWNORMAL); + UpdateWindow(m_window); + SetFocus(m_window); +} + +bool AppWindow::OnCreate(HWND, LPCREATESTRUCT) noexcept +{ + m_mainUserControl = winrt::PowerRenameUILib::MainWindow(); + m_xamlIsland = CreateDesktopWindowsXamlSource(WS_TABSTOP, m_mainUserControl); + + PopulateExplorerItems(); + SetHandlers(); + ReadSettings(); + + m_mainUserControl.UIUpdatesItem().ButtonRenameEnabled(false); + InitAutoComplete(); + SearchReplaceChanged(); + return true; +} + +void AppWindow::OnCommand(HWND, int id, HWND hwndControl, UINT codeNotify) noexcept +{ + switch (id) + { + case IDM_ABOUT: + DialogBoxW(m_instance, MAKEINTRESOURCE(IDD_ABOUTBOX), WindowHandle(), [](HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -> INT_PTR { + switch (message) + { + case WM_INITDIALOG: + return TRUE; + + case WM_COMMAND: + if ((LOWORD(wParam) == IDOK) || (LOWORD(wParam) == IDCANCEL)) + { + EndDialog(hDlg, LOWORD(wParam)); + return TRUE; + } + break; + } + return FALSE; + }); + break; + + case IDM_EXIT: + PostQuitMessage(0); + break; + } +} + +void AppWindow::OnDestroy(HWND hwnd) noexcept +{ + base_type::OnDestroy(hwnd); +} + +void AppWindow::OnResize(HWND, UINT state, int cx, int cy) noexcept +{ + SetWindowPos(m_xamlIsland, NULL, 0, 0, cx, cy, SWP_SHOWWINDOW); +} + +HRESULT AppWindow::CreateShellItemArrayFromPaths( + std::vector files, + IShellItemArray** shellItemArray) +{ + *shellItemArray = nullptr; + PIDLIST_ABSOLUTE* itemList = nullptr; + itemList = new (std::nothrow) PIDLIST_ABSOLUTE[files.size()]; + HRESULT hr = itemList ? S_OK : E_OUTOFMEMORY; + UINT itemsCnt = 0; + for (const auto& file : files) + { + const DWORD BUFSIZE = 4096; + TCHAR buffer[BUFSIZE] = TEXT(""); + auto retval = GetFullPathName(file.c_str(), BUFSIZE, buffer, NULL); + if (retval != 0 && PathFileExists(buffer)) + { + hr = SHParseDisplayName(buffer, nullptr, &itemList[itemsCnt], 0, nullptr); + ++itemsCnt; + } + } + if (SUCCEEDED(hr) && itemsCnt > 0) + { + hr = SHCreateShellItemArrayFromIDLists(itemsCnt, const_cast(itemList), shellItemArray); + + for (UINT i = 0; i < itemsCnt; i++) + { + CoTaskMemFree(itemList[i]); + } + } + else + { + hr = E_FAIL; + } + + delete[] itemList; + return hr; +} + +void AppWindow::PopulateExplorerItems() +{ + UINT count = 0; + m_prManager->GetVisibleItemCount(&count); + + UINT currDepth = 0; + std::stack parents{}; + UINT prevId = 0; + parents.push(0); + + for (UINT i = 0; i < count; ++i) + { + CComPtr renameItem; + if (SUCCEEDED(m_prManager->GetVisibleItemByIndex(i, &renameItem))) + { + int id = 0; + renameItem->GetId(&id); + + PWSTR originalName = nullptr; + renameItem->GetOriginalName(&originalName); + PWSTR newName = nullptr; + renameItem->GetNewName(&newName); + + bool selected; + renameItem->GetSelected(&selected); + + UINT depth = 0; + renameItem->GetDepth(&depth); + + bool isFolder = false; + bool isSubFolderContent = false; + winrt::check_hresult(renameItem->GetIsFolder(&isFolder)); + + if (depth > currDepth) + { + parents.push(prevId); + currDepth = depth; + } + else + { + while (currDepth > depth) + { + parents.pop(); + currDepth--; + } + currDepth = depth; + } + m_mainUserControl.AddExplorerItem( + id, originalName, newName == nullptr ? hstring{} : hstring{ newName }, isFolder ? 0 : 1, parents.top(), selected); + prevId = id; + } + } +} + +HRESULT AppWindow::InitAutoComplete() +{ + HRESULT hr = S_OK; + if (CSettingsInstance().GetMRUEnabled()) + { + hr = CPowerRenameMRU::CPowerRenameMRUSearch_CreateInstance(&m_searchMRU); + if (SUCCEEDED(hr)) + { + for (const auto& item : m_searchMRU->GetMRUStrings()) + { + if (!item.empty()) + { + m_mainUserControl.AppendSearchMRU(item); + } + } + } + + if (SUCCEEDED(hr)) + { + hr = CPowerRenameMRU::CPowerRenameMRUReplace_CreateInstance(&m_replaceMRU); + if (SUCCEEDED(hr)) + { + for (const auto& item : m_replaceMRU->GetMRUStrings()) + { + if (!item.empty()) + { + m_mainUserControl.AppendReplaceMRU(item); + } + } + } + } + } + + return hr; +} + +HRESULT AppWindow::EnumerateShellItems(_In_ IEnumShellItems* enumShellItems) +{ + HRESULT hr = S_OK; + // Enumerate the data object and populate the manager + if (m_prManager) + { + m_disableCountUpdate = true; + + // Ensure we re-create the enumerator + m_prEnum = nullptr; + hr = CPowerRenameEnum::s_CreateInstance(nullptr, m_prManager, IID_PPV_ARGS(&m_prEnum)); + if (SUCCEEDED(hr)) + { + hr = m_prEnum->Start(enumShellItems); + } + + m_disableCountUpdate = false; + } + + return hr; +} + +void AppWindow::SearchReplaceChanged(bool forceRenaming) +{ + // Pass updated search and replace terms to the IPowerRenameRegEx handler + CComPtr prRegEx; + if (m_prManager && SUCCEEDED(m_prManager->GetRenameRegEx(&prRegEx))) + { + winrt::hstring searchTerm = m_mainUserControl.AutoSuggestBoxSearch().Text(); + prRegEx->PutSearchTerm(searchTerm.c_str(), forceRenaming); + + winrt::hstring replaceTerm = m_mainUserControl.AutoSuggestBoxReplace().Text(); + prRegEx->PutReplaceTerm(replaceTerm.c_str(), forceRenaming); + } +} + +void AppWindow::ValidateFlags(PowerRenameFlags flag) +{ + if (flag == Uppercase) + { + if (m_mainUserControl.ToggleButtonUpperCase().IsChecked()) + { + m_mainUserControl.ToggleButtonLowerCase().IsChecked(false); + m_mainUserControl.ToggleButtonTitleCase().IsChecked(false); + m_mainUserControl.ToggleButtonCapitalize().IsChecked(false); + } + } + else if (flag == Lowercase) + { + if (m_mainUserControl.ToggleButtonLowerCase().IsChecked()) + { + m_mainUserControl.ToggleButtonUpperCase().IsChecked(false); + m_mainUserControl.ToggleButtonTitleCase().IsChecked(false); + m_mainUserControl.ToggleButtonCapitalize().IsChecked(false); + } + } + else if (flag == Titlecase) + { + if (m_mainUserControl.ToggleButtonTitleCase().IsChecked()) + { + m_mainUserControl.ToggleButtonUpperCase().IsChecked(false); + m_mainUserControl.ToggleButtonLowerCase().IsChecked(false); + m_mainUserControl.ToggleButtonCapitalize().IsChecked(false); + } + } + else if (flag == Capitalized) + { + if (m_mainUserControl.ToggleButtonCapitalize().IsChecked()) + { + m_mainUserControl.ToggleButtonUpperCase().IsChecked(false); + m_mainUserControl.ToggleButtonLowerCase().IsChecked(false); + m_mainUserControl.ToggleButtonTitleCase().IsChecked(false); + } + } + + m_flagValidationInProgress = true; +} + +void AppWindow::UpdateFlag(PowerRenameFlags flag, UpdateFlagCommand command) +{ + DWORD flags{}; + m_prManager->GetFlags(&flags); + + if (command == UpdateFlagCommand::Set) + { + flags |= flag; + } + else if (command == UpdateFlagCommand::Reset) + { + flags &= ~flag; + } + + // Ensure we update flags + if (m_prManager) + { + m_prManager->PutFlags(flags); + } +} + +void AppWindow::SetHandlers() +{ + m_mainUserControl.UIUpdatesItem().PropertyChanged([&](winrt::Windows::Foundation::IInspectable const& sender, Data::PropertyChangedEventArgs const& e) { + std::wstring property{ e.PropertyName() }; + if (property == L"ShowAll") + { + SwitchView(); + } + else if (property == L"ChangedItemId") + { + ToggleItem(m_mainUserControl.UIUpdatesItem().ChangedExplorerItemId(), m_mainUserControl.UIUpdatesItem().Checked()); + } + else if (property == L"ToggleAll") + { + ToggleAll(); + } + else if (property == L"Rename") + { + Rename(m_mainUserControl.UIUpdatesItem().CloseUIWindow()); + } + }); + + // AutoSuggestBox Search + m_mainUserControl.AutoSuggestBoxSearch().TextChanged([&](winrt::Windows::Foundation::IInspectable const& sender, AutoSuggestBoxTextChangedEventArgs const&) { + SearchReplaceChanged(); + }); + + // AutoSuggestBox Replace + m_mainUserControl.AutoSuggestBoxReplace().TextChanged([&](winrt::Windows::Foundation::IInspectable const& sender, AutoSuggestBoxTextChangedEventArgs const&) { + SearchReplaceChanged(); + }); + + // ToggleButton UpperCase + m_mainUserControl.ToggleButtonUpperCase().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(Uppercase); + UpdateFlag(Uppercase, UpdateFlagCommand::Set); + }); + m_mainUserControl.ToggleButtonUpperCase().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(Uppercase, UpdateFlagCommand::Reset); + }); + + // ToggleButton LowerCase + m_mainUserControl.ToggleButtonLowerCase().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(Lowercase); + UpdateFlag(Lowercase, UpdateFlagCommand::Set); + }); + m_mainUserControl.ToggleButtonLowerCase().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(Lowercase, UpdateFlagCommand::Reset); + }); + + // ToggleButton TitleCase + m_mainUserControl.ToggleButtonTitleCase().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(Titlecase); + UpdateFlag(Titlecase, UpdateFlagCommand::Set); + }); + m_mainUserControl.ToggleButtonTitleCase().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(Titlecase, UpdateFlagCommand::Reset); + }); + + // ToggleButton Capitalize + m_mainUserControl.ToggleButtonCapitalize().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(Capitalized); + UpdateFlag(Capitalized, UpdateFlagCommand::Set); + }); + m_mainUserControl.ToggleButtonCapitalize().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(Capitalized, UpdateFlagCommand::Reset); + }); + + // CheckBox Regex + m_mainUserControl.CheckBoxRegex().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(UseRegularExpressions); + UpdateFlag(UseRegularExpressions, UpdateFlagCommand::Set); + }); + m_mainUserControl.CheckBoxRegex().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(UseRegularExpressions, UpdateFlagCommand::Reset); + }); + + // CheckBox CaseSensitive + m_mainUserControl.CheckBoxCaseSensitive().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(CaseSensitive); + UpdateFlag(CaseSensitive, UpdateFlagCommand::Set); + }); + m_mainUserControl.CheckBoxCaseSensitive().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(CaseSensitive, UpdateFlagCommand::Reset); + }); + + // ComboBox RenameParts + m_mainUserControl.ComboBoxRenameParts().SelectionChanged([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + if (m_mainUserControl.ComboBoxRenameParts().SelectedIndex() == 0) + { // Filename + extension + UpdateFlag(NameOnly, UpdateFlagCommand::Reset); + UpdateFlag(ExtensionOnly, UpdateFlagCommand::Reset); + } + else if (m_mainUserControl.ComboBoxRenameParts().SelectedIndex() == 1) // Filename Only + { + ValidateFlags(NameOnly); + UpdateFlag(ExtensionOnly, UpdateFlagCommand::Reset); + UpdateFlag(NameOnly, UpdateFlagCommand::Set); + } + else if (m_mainUserControl.ComboBoxRenameParts().SelectedIndex() == 2) // Extension Only + { + ValidateFlags(ExtensionOnly); + UpdateFlag(NameOnly, UpdateFlagCommand::Reset); + UpdateFlag(ExtensionOnly, UpdateFlagCommand::Set); + } + }); + + // CheckBox MatchAllOccurences + m_mainUserControl.CheckBoxMatchAll().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(MatchAllOccurences); + UpdateFlag(MatchAllOccurences, UpdateFlagCommand::Set); + }); + m_mainUserControl.CheckBoxMatchAll().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(MatchAllOccurences, UpdateFlagCommand::Reset); + }); + + // ToggleButton IncludeFiles + m_mainUserControl.ToggleButtonIncludeFiles().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(ExcludeFiles); + UpdateFlag(ExcludeFiles, UpdateFlagCommand::Reset); + }); + m_mainUserControl.ToggleButtonIncludeFiles().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(ExcludeFiles, UpdateFlagCommand::Set); + }); + + // ToggleButton IncludeFolders + m_mainUserControl.ToggleButtonIncludeFolders().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(ExcludeFolders); + UpdateFlag(ExcludeFolders, UpdateFlagCommand::Reset); + }); + m_mainUserControl.ToggleButtonIncludeFolders().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(ExcludeFolders, UpdateFlagCommand::Set); + }); + + // ToggleButton IncludeSubfolders + m_mainUserControl.ToggleButtonIncludeSubfolders().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(ExcludeSubfolders); + UpdateFlag(ExcludeSubfolders, UpdateFlagCommand::Reset); + }); + m_mainUserControl.ToggleButtonIncludeSubfolders().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(ExcludeSubfolders, UpdateFlagCommand::Set); + }); + + // CheckBox EnumerateItems + m_mainUserControl.ToggleButtonEnumerateItems().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(EnumerateItems); + UpdateFlag(EnumerateItems, UpdateFlagCommand::Set); + }); + m_mainUserControl.ToggleButtonEnumerateItems().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(EnumerateItems, UpdateFlagCommand::Reset); + }); + + // ButtonSettings + m_mainUserControl.ButtonSettings().Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + OpenSettingsApp(); + }); +} + +void AppWindow::ToggleItem(int32_t id, bool checked) +{ + CComPtr spItem; + m_prManager->GetItemById(id, &spItem); + spItem->PutSelected(checked); + UpdateCounts(); +} + +void AppWindow::ToggleAll() +{ + UINT itemCount = 0; + m_prManager->GetItemCount(&itemCount); + bool selected = m_mainUserControl.CheckBoxSelectAll().IsChecked().GetBoolean(); + for (UINT i = 0; i < itemCount; i++) + { + CComPtr spItem; + if (SUCCEEDED(m_prManager->GetItemByIndex(i, &spItem))) + { + spItem->PutSelected(selected); + } + } + UpdateCounts(); +} + +void AppWindow::SwitchView() +{ + m_prManager->SwitchFilter(0); + PopulateExplorerItems(); +} + +void AppWindow::Rename(bool closeWindow) +{ + if (m_prManager) + { + m_prManager->Rename(m_window, closeWindow); + } + + // Persist the current settings. We only do this when + // a rename is actually performed. Not when the user + // closes/cancels the dialog. + WriteSettings(); +} + +HRESULT AppWindow::ReadSettings() +{ + // Check if we should read flags from settings + // or the defaults from the manager. + DWORD flags = 0; + if (CSettingsInstance().GetPersistState()) + { + flags = CSettingsInstance().GetFlags(); + + m_mainUserControl.AutoSuggestBoxSearch().Text(CSettingsInstance().GetSearchText().c_str()); + m_mainUserControl.AutoSuggestBoxReplace().Text(CSettingsInstance().GetReplaceText().c_str()); + } + else + { + m_prManager->GetFlags(&flags); + } + + m_prManager->PutFlags(flags); + SetCheckboxesFromFlags(flags); + + return S_OK; +} + +HRESULT AppWindow::WriteSettings() +{ + // Check if we should store our settings + if (CSettingsInstance().GetPersistState()) + { + DWORD flags = 0; + m_prManager->GetFlags(&flags); + CSettingsInstance().SetFlags(flags); + + winrt::hstring searchTerm = m_mainUserControl.AutoSuggestBoxSearch().Text(); + CSettingsInstance().SetSearchText(std::wstring{ searchTerm }); + + if (CSettingsInstance().GetMRUEnabled() && m_searchMRU) + { + CComPtr spSearchMRU; + if (SUCCEEDED(m_searchMRU->QueryInterface(IID_PPV_ARGS(&spSearchMRU)))) + { + spSearchMRU->AddMRUString(searchTerm.c_str()); + } + } + + winrt::hstring replaceTerm = m_mainUserControl.AutoSuggestBoxReplace().Text(); + CSettingsInstance().SetReplaceText(std::wstring{ replaceTerm }); + + if (CSettingsInstance().GetMRUEnabled() && m_replaceMRU) + { + CComPtr spReplaceMRU; + if (SUCCEEDED(m_replaceMRU->QueryInterface(IID_PPV_ARGS(&spReplaceMRU)))) + { + spReplaceMRU->AddMRUString(replaceTerm.c_str()); + } + } + + Trace::SettingsChanged(); + } + + return S_OK; +} + +HRESULT AppWindow::OpenSettingsApp() +{ + std::wstring path = get_module_folderpath(g_hostHInst); + path += L"\\..\\..\\PowerToys.exe"; + + std::wstring openSettings = L"--open-settings=PowerRename"; + + CString commandLine; + commandLine.Format(_T("\"%s\""), path.c_str()); + commandLine.AppendFormat(_T(" %s"), openSettings.c_str()); + + int nSize = commandLine.GetLength() + 1; + LPTSTR lpszCommandLine = new TCHAR[nSize]; + _tcscpy_s(lpszCommandLine, nSize, commandLine); + + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); + startupInfo.cb = sizeof(STARTUPINFO); + startupInfo.wShowWindow = SW_SHOWNORMAL; + + PROCESS_INFORMATION processInformation; + + // Start the resizer + CreateProcess( + NULL, + lpszCommandLine, + NULL, + NULL, + TRUE, + 0, + NULL, + NULL, + &startupInfo, + &processInformation); + + delete[] lpszCommandLine; + + if (!CloseHandle(processInformation.hProcess)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + if (!CloseHandle(processInformation.hThread)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + return S_OK; +} + +void AppWindow::SetCheckboxesFromFlags(DWORD flags) +{ + if (flags & CaseSensitive) + { + m_mainUserControl.CheckBoxCaseSensitive().IsChecked(true); + } + if (flags & MatchAllOccurences) + { + m_mainUserControl.CheckBoxMatchAll().IsChecked(true); + } + if (flags & UseRegularExpressions) + { + m_mainUserControl.CheckBoxRegex().IsChecked(true); + } + if (flags & EnumerateItems) + { + m_mainUserControl.ToggleButtonEnumerateItems().IsChecked(true); + } + if (flags & ExcludeFiles) + { + m_mainUserControl.ToggleButtonIncludeFiles().IsChecked(false); + } + if (flags & ExcludeFolders) + { + m_mainUserControl.ToggleButtonIncludeFolders().IsChecked(false); + } + if (flags & ExcludeSubfolders) + { + m_mainUserControl.ToggleButtonIncludeSubfolders().IsChecked(false); + } + if (flags & NameOnly) + { + m_mainUserControl.ComboBoxRenameParts().SelectedIndex(1); + } + else if (flags & ExtensionOnly) + { + m_mainUserControl.ComboBoxRenameParts().SelectedIndex(2); + } + if (flags & Uppercase) + { + m_mainUserControl.ToggleButtonUpperCase().IsChecked(true); + } + else if (flags & Lowercase) + { + m_mainUserControl.ToggleButtonLowerCase().IsChecked(true); + } + else if (flags & Titlecase) + { + m_mainUserControl.ToggleButtonTitleCase().IsChecked(true); + } + else if (flags & Capitalized) + { + m_mainUserControl.ToggleButtonCapitalize().IsChecked(true); + } +} + +void AppWindow::UpdateCounts() +{ + // This method is CPU intensive. We disable it during certain operations + // for performance reasons. + if (m_disableCountUpdate) + { + return; + } + + UINT selectedCount = 0; + UINT renamingCount = 0; + if (m_prManager) + { + m_prManager->GetSelectedItemCount(&selectedCount); + m_prManager->GetRenameItemCount(&renamingCount); + } + + if (m_selectedCount != selectedCount || + m_renamingCount != renamingCount) + { + m_selectedCount = selectedCount; + m_renamingCount = renamingCount; + + // Update counts UI elements if/when added + + // Update Rename button state + m_mainUserControl.UIUpdatesItem().ButtonRenameEnabled(renamingCount > 0); + } +} + +HRESULT AppWindow::OnItemAdded(_In_ IPowerRenameItem* renameItem) +{ + return S_OK; +} + +HRESULT AppWindow::OnUpdate(_In_ IPowerRenameItem* renameItem) +{ + int id; + HRESULT hr = renameItem->GetId(&id); + if (SUCCEEDED(hr)) + { + PWSTR newName = nullptr; + hr = renameItem->GetNewName(&newName); + if (SUCCEEDED(hr)) + { + hstring newNameStr = newName == nullptr ? hstring{} : newName; + m_mainUserControl.UpdateExplorerItem(id, newNameStr); + } + } + + UpdateCounts(); + return S_OK; +} + +HRESULT AppWindow::OnRename(_In_ IPowerRenameItem* renameItem) +{ + int id; + HRESULT hr = renameItem->GetId(&id); + if (SUCCEEDED(hr)) + { + PWSTR newName = nullptr; + hr = renameItem->GetOriginalName(&newName); + if (SUCCEEDED(hr)) + { + hstring newNameStr = newName == nullptr ? hstring{} : newName; + m_mainUserControl.UpdateRenamedExplorerItem(id, newNameStr); + } + } + + UpdateCounts(); + return S_OK; +} + +HRESULT AppWindow::OnError(_In_ IPowerRenameItem* renameItem) +{ + return S_OK; +} + +HRESULT AppWindow::OnRegExStarted(_In_ DWORD threadId) +{ + return S_OK; +} + +HRESULT AppWindow::OnRegExCanceled(_In_ DWORD threadId) +{ + return S_OK; +} + +HRESULT AppWindow::OnRegExCompleted(_In_ DWORD threadId) +{ + if (m_flagValidationInProgress) + { + m_flagValidationInProgress = false; + } + else + { + DWORD filter = 0; + m_prManager->GetFilter(&filter); + if (filter == PowerRenameFilters::ShouldRename) + { + m_mainUserControl.ExplorerItems().Clear(); + PopulateExplorerItems(); + } + } + + return S_OK; +} + +HRESULT AppWindow::OnRenameStarted() +{ + return S_OK; +} + +HRESULT AppWindow::OnRenameCompleted(bool closeUIWindowAfterRenaming) +{ + if (closeUIWindowAfterRenaming) + { + // Close the window + PostMessage(m_window, WM_CLOSE, (WPARAM)0, (LPARAM)0); + } + else + { + // Force renaming work to start so newly renamed items are processed right away + SearchReplaceChanged(true); + } + return S_OK; +} + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ +#define BUFSIZE 4096 * 4 + + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + if (hStdin == INVALID_HANDLE_VALUE) + ExitProcess(1); + BOOL bSuccess; + WCHAR chBuf[BUFSIZE]; + DWORD dwRead; + std::vector files; + for (;;) + { + // Read from standard input and stop on error or no data. + bSuccess = ReadFile(hStdin, chBuf, BUFSIZE * sizeof(wchar_t), &dwRead, NULL); + + if (!bSuccess || dwRead == 0) + break; + + std::wstring inputBatch{ chBuf, dwRead / sizeof(wchar_t) }; + + std::wstringstream ss(inputBatch); + std::wstring item; + wchar_t delimiter = '?'; + while (std::getline(ss, item, delimiter)) + { + files.push_back(item); + } + + if (!bSuccess) + break; + } + + g_hostHInst = hInstance; + winrt::init_apartment(winrt::apartment_type::single_threaded); + + winrt::PowerRenameUILib::App app; + const auto result = AppWindow::Show(hInstance, files); + app.Close(); +} diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.h b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.h new file mode 100644 index 000000000..80b16cf6b --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.h @@ -0,0 +1,153 @@ +#pragma once +#include "pch.h" + +#include "resource.h" +#include "XamlBridge.h" + +#include +#include +#include +#include +#include + +#include +#include +#pragma push_macro("GetCurrentTime") +#undef GetCurrentTime +#include +#pragma pop_macro("GetCurrentTime") +#include +#include +#include +#include +#include +#include +#include + +using namespace winrt; +using namespace Windows::UI; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Composition; +using namespace Windows::UI::Xaml::Hosting; +using namespace Windows::Foundation::Numerics; +using namespace Windows::UI::Xaml::Controls; + +class AppWindow : public DesktopWindowT +{ +public: + // Proxy class to Advise() PRManager, as AppWindow can't implement IPowerRenameManagerEvents + class UIHostPowerRenameManagerEvents : public IPowerRenameManagerEvents + { + public: + UIHostPowerRenameManagerEvents(AppWindow* app) : + m_refCount{ 1 }, m_app{ app } + { + } + + IFACEMETHODIMP_(ULONG) + AddRef() + { + return InterlockedIncrement(&m_refCount); + } + + IFACEMETHODIMP_(ULONG) + Release() + { + long refCount = InterlockedDecrement(&m_refCount); + + if (refCount == 0) + { + delete this; + } + return refCount; + } + + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _Outptr_ void** ppv) + { + static const QITAB qit[] = { + QITABENT(UIHostPowerRenameManagerEvents, IPowerRenameManagerEvents), + { 0 } + }; + return QISearch(this, qit, riid, ppv); + } + + HRESULT OnItemAdded(_In_ IPowerRenameItem* renameItem) override { return m_app->OnItemAdded(renameItem); } + HRESULT OnUpdate(_In_ IPowerRenameItem* renameItem) override { return m_app->OnUpdate(renameItem); } + HRESULT OnRename(_In_ IPowerRenameItem* renameItem) override { return m_app->OnRename(renameItem); } + HRESULT OnError(_In_ IPowerRenameItem* renameItem) override { return m_app->OnError(renameItem); } + HRESULT OnRegExStarted(_In_ DWORD threadId) override { return m_app->OnRegExStarted(threadId); } + HRESULT OnRegExCanceled(_In_ DWORD threadId) override { return m_app->OnRegExCanceled(threadId); } + HRESULT OnRegExCompleted(_In_ DWORD threadId) override { return m_app->OnRegExCompleted(threadId); } + HRESULT OnRenameStarted() override { return m_app->OnRenameStarted(); } + HRESULT OnRenameCompleted(bool closeUIWindowAfterRenaming) override { return m_app->OnRenameCompleted(closeUIWindowAfterRenaming); } + + private: + long m_refCount; + + AppWindow* m_app; + }; + + static int Show(HINSTANCE hInstance, std::vector files); + LRESULT MessageHandler(UINT message, WPARAM wParam, LPARAM lParam) noexcept; + +private: + enum class UpdateFlagCommand + { + Set = 0, + Reset + }; + + AppWindow(HINSTANCE hInstance, std::vector files) noexcept; + void CreateAndShowWindow(); + bool OnCreate(HWND, LPCREATESTRUCT) noexcept; + void OnCommand(HWND, int id, HWND hwndControl, UINT codeNotify) noexcept; + void OnDestroy(HWND hwnd) noexcept; + void OnResize(HWND, UINT state, int cx, int cy) noexcept; + HRESULT CreateShellItemArrayFromPaths(std::vector files, IShellItemArray** shellItemArray); + + void PopulateExplorerItems(); + HRESULT InitAutoComplete(); + HRESULT EnumerateShellItems(_In_ IEnumShellItems* enumShellItems); + void SearchReplaceChanged(bool forceRenaming = false); + void ValidateFlags(PowerRenameFlags flag); + void UpdateFlag(PowerRenameFlags flag, UpdateFlagCommand command); + void SetHandlers(); + void ToggleItem(int32_t id, bool checked); + void ToggleAll(); + void SwitchView(); + void Rename(bool closeWindow); + HRESULT ReadSettings(); + HRESULT WriteSettings(); + HRESULT OpenSettingsApp(); + void SetCheckboxesFromFlags(DWORD flags); + void UpdateCounts(); + + HRESULT OnItemAdded(_In_ IPowerRenameItem* renameItem); + HRESULT OnUpdate(_In_ IPowerRenameItem* renameItem); + HRESULT OnRename(_In_ IPowerRenameItem* renameItem); + HRESULT OnError(_In_ IPowerRenameItem* renameItem); + HRESULT OnRegExStarted(_In_ DWORD threadId); + HRESULT OnRegExCanceled(_In_ DWORD threadId); + HRESULT OnRegExCompleted(_In_ DWORD threadId); + HRESULT OnRenameStarted(); + HRESULT OnRenameCompleted(bool closeUIWindowAfterRenaming); + + wil::unique_haccel m_accelerators; + const HINSTANCE m_instance; + HWND m_xamlIsland{}; + HWND m_window{}; + winrt::PowerRenameUILib::MainWindow m_mainUserControl{ nullptr }; + + bool m_disableCountUpdate = false; + CComPtr m_prManager; + CComPtr m_dataSource; + CComPtr m_prEnum; + UIHostPowerRenameManagerEvents m_managerEvents; + DWORD m_cookie = 0; + CComPtr m_searchMRU; + CComPtr m_replaceMRU; + UINT m_selectedCount = 0; + UINT m_renamingCount = 0; + + bool m_flagValidationInProgress = false; +}; diff --git a/src/modules/powerrename/ui/PowerRename.ico b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.ico similarity index 100% rename from src/modules/powerrename/ui/PowerRename.ico rename to src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.ico diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.rc b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.rc new file mode 100644 index 000000000..c2ed5aca3 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.rc @@ -0,0 +1,150 @@ +//Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE resource. +// +#ifndef APSTUDIO_INVOKED +#include "targetver.h" +#endif +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. + +IDI_POWERRENAMEUIHOST ICON "PowerRenameUIHost.ico" +IDI_SMALL ICON "small.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDC_POWERRENAMEUIHOST MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "E&xit", IDM_EXIT + END + POPUP "&Help" + BEGIN + MENUITEM "&About ...", IDM_ABOUT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDC_POWERRENAMEUIHOST ACCELERATORS +BEGIN + "?", IDM_ABOUT, ASCII, ALT + "/", IDM_ABOUT, ASCII, ALT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About PowerRenameUIHost" +FONT 8, "MS Shell Dlg" +BEGIN + ICON IDR_MAINFRAME,IDC_STATIC,14,14,21,20 + LTEXT "PowerRenameUIHost, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX + LTEXT "Copyright (c) 2021",IDC_STATIC,42,26,114,8 + DEFPUSHBUTTON "OK",IDOK,113,41,50,14,WS_GROUP +END + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_ABOUTBOX, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 163 + TOPMARGIN, 7 + BOTTOMMARGIN, 55 + END +END +#endif // APSTUDIO_INVOKED + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#ifndef APSTUDIO_INVOKED\r\n" + "#include ""targetver.h""\r\n" + "#endif\r\n" + "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""windows.h""\r\n" + "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDC_POWERRENAMEUIHOST "POWERRENAMEUIHOST" + IDS_APP_TITLE "PowerRename" +END + +#endif +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE resource. +// + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj new file mode 100644 index 000000000..483078fe1 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj @@ -0,0 +1,165 @@ + + + + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {f7ec4e6c-19ca-4fbd-9918-b8ac5fef4f63} + PowerRenameUIHost + 10.0.18362.0 + $(WindowsTargetPlatformVersion) + + + + Application + true + v142 + Unicode + false + + + Application + false + v142 + true + Unicode + false + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\modules\PowerRename\ + PowerRename + + + false + $(SolutionDir)$(Platform)\$(Configuration)\modules\PowerRename\ + PowerRename + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\..\;$(ProjectDir)..\..\..\common\Telemetry;$(ProjectDir)..\lib;%(AdditionalIncludeDirectories) + + + Windows + true + + + PerMonitorHighDPIAware + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + true + $(ProjectDir)..\..\..\;$(ProjectDir)..\..\..\common\Telemetry;$(ProjectDir)..\lib;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + + + PerMonitorHighDPIAware + + + + + + + + + + + + + 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}. + + + + + + + + + + + + + + PowerRenameUILib + + + $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\;$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\Generated Files\; + + + + + {51920f1f-c28c-4adf-8660-4238766796c2} + + + \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj.filters b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj.filters new file mode 100644 index 000000000..8be01112e --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj.filters @@ -0,0 +1,67 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + Resource Files + + + Resource Files + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUIHost/Resource.h b/src/modules/powerrename/PowerRenameUIHost/Resource.h new file mode 100644 index 000000000..3f2ed88ba --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/Resource.h @@ -0,0 +1,30 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by PowerRenameUIHost.rc + +#define IDS_APP_TITLE 103 + +#define IDR_MAINFRAME 128 +#define IDD_POWERRENAMEUIHOST_DIALOG 102 +#define IDD_ABOUTBOX 103 +#define IDM_ABOUT 104 +#define IDM_EXIT 105 +#define IDI_POWERRENAMEUIHOST 107 +#define IDI_SMALL 108 +#define IDC_POWERRENAMEUIHOST 109 +#define IDC_MYICON 2 +#ifndef IDC_STATIC +#define IDC_STATIC -1 +#endif +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS + +#define _APS_NO_MFC 130 +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif diff --git a/src/modules/powerrename/PowerRenameUIHost/XamlBridge.cpp b/src/modules/powerrename/PowerRenameUIHost/XamlBridge.cpp new file mode 100644 index 000000000..f5b551e5e --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/XamlBridge.cpp @@ -0,0 +1,249 @@ +#include "pch.h" + +#include "XamlBridge.h" +#include "Resource.h" +#include + +bool DesktopWindow::FilterMessage(const MSG* msg) +{ + // When multiple child windows are present it is needed to pre dispatch messages to all + // DesktopWindowXamlSource instances so keyboard accelerators and + // keyboard focus work correctly. + for (auto& xamlSource : m_xamlSources) + { + BOOL xamlSourceProcessedMessage = FALSE; + winrt::check_hresult(xamlSource.as()->PreTranslateMessage(msg, &xamlSourceProcessedMessage)); + if (xamlSourceProcessedMessage != FALSE) + { + return true; + } + } + + return false; +} + +const auto static invalidReason = static_cast(-1); + +winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason GetReasonFromKey(WPARAM key) +{ + auto reason = invalidReason; + if (key == VK_TAB) + { + byte keyboardState[256] = {}; + WINRT_VERIFY(::GetKeyboardState(keyboardState)); + reason = (keyboardState[VK_SHIFT] & 0x80) ? + winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Last : + winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First; + } + else if (key == VK_LEFT) + { + reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Left; + } + else if (key == VK_RIGHT) + { + reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right; + } + else if (key == VK_UP) + { + reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Up; + } + else if (key == VK_DOWN) + { + reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down; + } + return reason; +} + +winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource DesktopWindow::GetNextFocusedIsland(const MSG* msg) +{ + if (msg->message == WM_KEYDOWN) + { + const auto key = msg->wParam; + auto reason = GetReasonFromKey(key); + if (reason != invalidReason) + { + const BOOL previous = + (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First || + reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down || + reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) ? + false : + true; + + const auto currentFocusedWindow = ::GetFocus(); + const auto nextElement = ::GetNextDlgTabItem(m_window.get(), currentFocusedWindow, previous); + for (auto& xamlSource : m_xamlSources) + { + HWND islandWnd{}; + winrt::check_hresult(xamlSource.as()->get_WindowHandle(&islandWnd)); + if (nextElement == islandWnd) + { + return xamlSource; + } + } + } + } + + return nullptr; +} + +winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource DesktopWindow::GetFocusedIsland() +{ + for (auto& xamlSource : m_xamlSources) + { + if (xamlSource.HasFocus()) + { + return xamlSource; + } + } + return nullptr; +} + +bool DesktopWindow::NavigateFocus(MSG* msg) +{ + if (const auto nextFocusedIsland = GetNextFocusedIsland(msg)) + { + const auto previousFocusedWindow = ::GetFocus(); + RECT rect = {}; + WINRT_VERIFY(::GetWindowRect(previousFocusedWindow, &rect)); + const auto nativeIsland = nextFocusedIsland.as(); + HWND islandWnd = nullptr; + winrt::check_hresult(nativeIsland->get_WindowHandle(&islandWnd)); + POINT pt = { rect.left, rect.top }; + SIZE size = { rect.right - rect.left, rect.bottom - rect.top }; + ::ScreenToClient(islandWnd, &pt); + const auto hintRect = winrt::Windows::Foundation::Rect({ static_cast(pt.x), static_cast(pt.y), static_cast(size.cx), static_cast(size.cy) }); + const auto reason = GetReasonFromKey(msg->wParam); + const auto request = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationRequest(reason, hintRect); + m_lastFocusRequestId = request.CorrelationId(); + const auto result = nextFocusedIsland.NavigateFocus(request); + return result.WasFocusMoved(); + } + else + { + const bool islandIsFocused = GetFocusedIsland() != nullptr; + byte keyboardState[256] = {}; + WINRT_VERIFY(::GetKeyboardState(keyboardState)); + const bool isMenuModifier = keyboardState[VK_MENU] & 0x80; + if (islandIsFocused && !isMenuModifier) + { + return false; + } + const bool isDialogMessage = !!IsDialogMessage(m_window.get(), msg); + return isDialogMessage; + } +} + +int DesktopWindow::MessageLoop(HACCEL accelerators) +{ + MSG msg = {}; + while (GetMessage(&msg, nullptr, 0, 0)) + { + const bool processedMessage = FilterMessage(&msg); + if (!processedMessage && !TranslateAcceleratorW(msg.hwnd, accelerators, &msg)) + { + if (!NavigateFocus(&msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + return static_cast(msg.wParam); +} + +static const WPARAM invalidKey = (WPARAM)-1; + +WPARAM GetKeyFromReason(winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason reason) +{ + auto key = invalidKey; + if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Last || reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First) + { + key = VK_TAB; + } + else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Left) + { + key = VK_LEFT; + } + else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) + { + key = VK_RIGHT; + } + else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Up) + { + key = VK_UP; + } + else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down) + { + key = VK_DOWN; + } + return key; +} + +void DesktopWindow::OnTakeFocusRequested(winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource const& sender, winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSourceTakeFocusRequestedEventArgs const& args) +{ + if (args.Request().CorrelationId() != m_lastFocusRequestId) + { + const auto reason = args.Request().Reason(); + const BOOL previous = + (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First || + reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down || + reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) ? + false : + true; + + const auto nativeXamlSource = sender.as(); + HWND senderHwnd = nullptr; + winrt::check_hresult(nativeXamlSource->get_WindowHandle(&senderHwnd)); + + MSG msg = {}; + msg.hwnd = senderHwnd; + msg.message = WM_KEYDOWN; + msg.wParam = GetKeyFromReason(reason); + if (!NavigateFocus(&msg)) + { + const auto nextElement = ::GetNextDlgTabItem(m_window.get(), senderHwnd, previous); + ::SetFocus(nextElement); + } + } + else + { + const auto request = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationRequest(winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Restore); + m_lastFocusRequestId = request.CorrelationId(); + sender.NavigateFocus(request); + } +} + +HWND DesktopWindow::CreateDesktopWindowsXamlSource(DWORD extraWindowStyles, const winrt::Windows::UI::Xaml::UIElement& content) +{ + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource desktopSource; + + auto interop = desktopSource.as(); + // Parent the DesktopWindowXamlSource object to current window + winrt::check_hresult(interop->AttachToWindow(m_window.get())); + HWND xamlSourceWindow{}; // Lifetime controlled desktopSource + winrt::check_hresult(interop->get_WindowHandle(&xamlSourceWindow)); + const DWORD style = GetWindowLongW(xamlSourceWindow, GWL_STYLE) | extraWindowStyles; + SetWindowLongW(xamlSourceWindow, GWL_STYLE, style); + + desktopSource.Content(content); + + m_takeFocusEventRevokers.push_back(desktopSource.TakeFocusRequested(winrt::auto_revoke, { this, &DesktopWindow::OnTakeFocusRequested })); + m_xamlSources.push_back(desktopSource); + + return xamlSourceWindow; +} + +void DesktopWindow::ClearXamlIslands() +{ + for (auto& takeFocusRevoker : m_takeFocusEventRevokers) + { + takeFocusRevoker.revoke(); + } + m_takeFocusEventRevokers.clear(); + + for (auto& xamlSource : m_xamlSources) + { + xamlSource.Close(); + } + m_xamlSources.clear(); +} diff --git a/src/modules/powerrename/PowerRenameUIHost/XamlBridge.h b/src/modules/powerrename/PowerRenameUIHost/XamlBridge.h new file mode 100644 index 000000000..24056ec28 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/XamlBridge.h @@ -0,0 +1,108 @@ +#pragma once + +#include // To enable support for non-WinRT interfaces, unknwn.h must be included before any C++/WinRT headers. +#include +#include +#include +#pragma push_macro("GetCurrentTime") +#undef GetCurrentTime +#include +#pragma pop_macro("GetCurrentTime") +#include +#include +#include +#include + +class DesktopWindow +{ +protected: + int MessageLoop(HACCEL accelerators); + HWND CreateDesktopWindowsXamlSource(DWORD extraStyles, const winrt::Windows::UI::Xaml::UIElement& content); + void ClearXamlIslands(); + + HWND WindowHandle() const + { + return m_window.get(); + } + + static void OnNCCreate(HWND window, LPARAM lparam) noexcept + { + auto cs = reinterpret_cast(lparam); + auto that = static_cast(cs->lpCreateParams); + that->m_window.reset(window); // take ownership of the window + SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast(that)); + } + +private: + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource GetFocusedIsland(); + bool FilterMessage(const MSG* msg); + void OnTakeFocusRequested(winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource const& sender, winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSourceTakeFocusRequestedEventArgs const& args); + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource GetNextFocusedIsland(const MSG* msg); + bool NavigateFocus(MSG* msg); + + wil::unique_hwnd m_window; + winrt::guid m_lastFocusRequestId; + std::vector m_takeFocusEventRevokers; + std::vector m_xamlSources; +}; + +template +struct DesktopWindowT : public DesktopWindow +{ +protected: + using base_type = DesktopWindowT; + + static LRESULT __stdcall WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept + { + if (message == WM_NCCREATE) + { + OnNCCreate(window, lparam); + } + else if (message == WM_NCDESTROY) + { + SetWindowLongPtrW(window, GWLP_USERDATA, 0); + } + else if (auto that = reinterpret_cast(GetWindowLongPtrW(window, GWLP_USERDATA))) + { + return that->MessageHandler(message, wparam, lparam); + } + + return DefWindowProcW(window, message, wparam, lparam); + } + + LRESULT MessageHandler(UINT message, WPARAM wParam, LPARAM lParam) noexcept + { + switch (message) + { + HANDLE_MSG(WindowHandle(), WM_DESTROY, OnDestroy); + HANDLE_MSG(WindowHandle(), WM_ACTIVATE, OnActivate); + HANDLE_MSG(WindowHandle(), WM_SETFOCUS, OnSetFocus); + } + return DefWindowProcW(WindowHandle(), message, wParam, lParam); + } + + void OnDestroy(HWND) + { + ClearXamlIslands(); + PostQuitMessage(0); + } + +private: + void OnActivate(HWND, UINT state, HWND hwndActDeact, BOOL fMinimized) + { + if (state == WA_INACTIVE) + { + m_hwndLastFocus = GetFocus(); + } + } + + void OnSetFocus(HWND, HWND hwndOldFocus) + { + if (m_hwndLastFocus) + { + SetFocus(m_hwndLastFocus); + } + } + + HWND m_hwndLastFocus = nullptr; +}; diff --git a/src/modules/powerrename/PowerRenameUIHost/app.manifest b/src/modules/powerrename/PowerRenameUIHost/app.manifest new file mode 100644 index 000000000..1b5754681 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/app.manifest @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUIHost/framework.h b/src/modules/powerrename/PowerRenameUIHost/framework.h new file mode 100644 index 000000000..ce362d71f --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/framework.h @@ -0,0 +1,15 @@ +// header.h : include file for standard system include files, +// or project specific include files +// + +#pragma once + +#include "targetver.h" +// #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include +// C RunTime Header Files +#include +#include +#include +#include diff --git a/src/modules/powerrename/PowerRenameUIHost/packages.config b/src/modules/powerrename/PowerRenameUIHost/packages.config new file mode 100644 index 000000000..cb4fc6898 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/pch.cpp b/src/modules/powerrename/PowerRenameUIHost/pch.cpp similarity index 100% rename from src/modules/powerrename/ui/pch.cpp rename to src/modules/powerrename/PowerRenameUIHost/pch.cpp diff --git a/src/modules/powerrename/PowerRenameUIHost/pch.h b/src/modules/powerrename/PowerRenameUIHost/pch.h new file mode 100644 index 000000000..61ca50ecd --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/pch.h @@ -0,0 +1,5 @@ +#pragma once + +#include "framework.h" +#include +#include diff --git a/src/modules/powerrename/PowerRenameUIHost/small.ico b/src/modules/powerrename/PowerRenameUIHost/small.ico new file mode 100644 index 000000000..4f111feaa Binary files /dev/null and b/src/modules/powerrename/PowerRenameUIHost/small.ico differ diff --git a/src/modules/powerrename/ui/targetver.h b/src/modules/powerrename/PowerRenameUIHost/targetver.h similarity index 100% rename from src/modules/powerrename/ui/targetver.h rename to src/modules/powerrename/PowerRenameUIHost/targetver.h diff --git a/src/modules/powerrename/PowerRenameUILib/App.cpp b/src/modules/powerrename/PowerRenameUILib/App.cpp new file mode 100644 index 000000000..f1840cbe1 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUILib/App.cpp @@ -0,0 +1,18 @@ +#include "pch.h" +#include "App.h" +#include "App.g.cpp" +using namespace winrt; +using namespace Windows::UI::Xaml; +namespace winrt::PowerRenameUILib::implementation +{ + App::App() + { + Initialize(); + AddRef(); + m_inner.as<::IUnknown>()->Release(); + } + App::~App() + { + Close(); + } +} diff --git a/src/modules/powerrename/PowerRenameUILib/App.h b/src/modules/powerrename/PowerRenameUILib/App.h new file mode 100644 index 000000000..2ba215af1 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUILib/App.h @@ -0,0 +1,18 @@ +#pragma once +#include "App.g.h" +#include "App.base.h" +namespace winrt::PowerRenameUILib::implementation +{ + class App : public AppT2 + { + public: + App(); + ~App(); + }; +} +namespace winrt::PowerRenameUILib::factory_implementation +{ + class App : public AppT + { + }; +} diff --git a/src/modules/powerrename/PowerRenameUILib/App.idl b/src/modules/powerrename/PowerRenameUILib/App.idl new file mode 100644 index 000000000..648b2544a --- /dev/null +++ b/src/modules/powerrename/PowerRenameUILib/App.idl @@ -0,0 +1,7 @@ +namespace PowerRenameUILib +{ + [default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication + { + App(); + } +} \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUILib/App.xaml b/src/modules/powerrename/PowerRenameUILib/App.xaml new file mode 100644 index 000000000..7795f1b7b --- /dev/null +++ b/src/modules/powerrename/PowerRenameUILib/App.xaml @@ -0,0 +1,1066 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Visible + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Filename + extension + Filename only + Extension only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +