Files
PowerToys/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp
moooyo 70e1177a6a [PowerRename] Support using photo metadata to replace in the PowerRename (#41728)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
1. Introduce WIC for power rename and add new class WICMetadataExtractor
to use WIC to extract metadata.
2. Add some patterns for metadata extract.
3. Support XMP and EXIF metadata extract.
4. Add test data for xmp and exif extractor
5. Add attribution for the test data uploader.

UI:
<img width="2052" height="1415" alt="image"
src="https://github.com/user-attachments/assets/9051b12e-4e66-4fdc-a4d4-3bada661c235"
/>
<img width="284" height="170" alt="image"
src="https://github.com/user-attachments/assets/2fd67193-77a7-48f0-a5ac-08a69fe64e55"
/>
<img width="715" height="1160" alt="image"
src="https://github.com/user-attachments/assets/5fa68a8c-d129-44dd-b747-099dfbcded12"
/>

demo:


https://github.com/user-attachments/assets/e90bc206-62e5-4101-ada2-3187ee7e2039



<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #5612
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
2025-11-04 09:27:16 +08:00

127 lines
4.0 KiB
C++

#include "pch.h"
#include "template_item.h"
#include <shellapi.h>
#include "new_utilities.h"
#include <cassert>
#include <thread>
#include <shlobj_core.h>
using namespace Microsoft::WRL;
using namespace newplus;
template_item::template_item(const std::filesystem::path entry)
{
path = entry;
}
std::wstring template_item::get_menu_title(const bool show_extension, const bool show_starting_digits, const bool show_resolved_variables) const
{
std::wstring title = path.filename();
if (!show_starting_digits)
{
// Hide starting digits, spaces, and .
title = remove_starting_digits_from_filename(title);
}
if (show_resolved_variables)
{
title = helpers::variables::resolve_variables_in_filename(title, constants::non_localizable::parent_folder_name_variable);
}
if (show_extension || !path.has_extension())
{
return title;
}
if (!helpers::filesystem::is_directory(path))
{
std::wstring ext = path.extension();
title = title.substr(0, title.length() - ext.length());
}
return title;
}
std::wstring template_item::get_target_filename(const bool include_starting_digits) const
{
std::wstring filename = path.filename();
if (!include_starting_digits)
{
// Remove starting digits, spaces, and .
filename = remove_starting_digits_from_filename(filename);
}
return filename;
}
std::wstring template_item::remove_starting_digits_from_filename(std::wstring filename) const
{
filename.erase(0, std::min(filename.find_first_not_of(L"0123456789"), filename.size()));
filename.erase(0, std::min(filename.find_first_not_of(L" ."), filename.size()));
return filename;
}
std::wstring template_item::get_explorer_icon() const
{
return utilities::get_explorer_icon(path);
}
HICON template_item::get_explorer_icon_handle() const
{
return utilities::get_explorer_icon_handle(path);
}
std::filesystem::path template_item::copy_object_to(const HWND window_handle, const std::filesystem::path destination) const
{
// SHFILEOPSTRUCT wants the from and to paths to be terminated with two NULLs.
wchar_t double_terminated_path_from[MAX_PATH + 1] = { 0 };
wcsncpy_s(double_terminated_path_from, this->path.c_str(), this->path.wstring().length());
double_terminated_path_from[this->path.wstring().length() + 1] = 0;
wchar_t double_terminated_path_to[MAX_PATH + 1] = { 0 };
wcsncpy_s(double_terminated_path_to, destination.c_str(), destination.wstring().length());
double_terminated_path_to[destination.wstring().length() + 1] = 0;
SHFILEOPSTRUCT file_operation_params = { 0 };
file_operation_params.wFunc = FO_COPY;
file_operation_params.hwnd = window_handle;
file_operation_params.pFrom = double_terminated_path_from;
file_operation_params.pTo = double_terminated_path_to;
file_operation_params.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS;
const int result = SHFileOperation(&file_operation_params);
if (result != 0)
{
throw std::runtime_error("Failed to copy template");
}
return destination;
}
void template_item::refresh_target(const std::filesystem::path target_final_fullpath) const
{
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH | SHCNF_FLUSH, target_final_fullpath.wstring().c_str(), NULL);
}
void template_item::enter_rename_mode(const std::filesystem::path target_fullpath) const
{
std::thread thread_for_renaming_workaround(rename_on_other_thread_workaround, target_fullpath);
thread_for_renaming_workaround.detach();
}
void template_item::rename_on_other_thread_workaround(const std::filesystem::path target_fullpath)
{
// Have been unable to have Windows Explorer Shell enter rename mode from the main thread
// Sleep for a bit to only enter rename mode when icon has been drawn.
const std::chrono::milliseconds approx_wait_for_icon_redraw_not_needed{ 50 };
std::this_thread::sleep_for(std::chrono::milliseconds(approx_wait_for_icon_redraw_not_needed));
newplus::utilities::explorer_enter_rename_mode(target_fullpath);
}