mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
[New+]Support for variables in template filenames (#37074)
* Add variable support - initial version without UI * Add variable in template filename support in New+ * Fix XAML style * Addressed code review feedback
This commit is contained in:
committed by
GitHub
parent
1d358af600
commit
1f81d14000
@@ -83,6 +83,8 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\constants.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_filesystem.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_variables.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\new_utilities.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\settings.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\shell_context_sub_menu.h" />
|
||||
@@ -97,6 +99,7 @@
|
||||
<ClInclude Include="shell_context_menu_win10.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp" />
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\new_utilities.cpp" />
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\powertoys_module.cpp" />
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\settings.cpp" />
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\settings.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_filesystem.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_variables.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
@@ -92,6 +98,9 @@
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\new_utilities.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Generated Files\new.rc">
|
||||
|
||||
@@ -32,7 +32,7 @@ IFACEMETHODIMP shell_context_menu_win10::Initialize(PCIDLIST_ABSOLUTE, IDataObje
|
||||
IFACEMETHODIMP shell_context_menu_win10::QueryContextMenu(HMENU menu_handle, UINT menu_index, UINT menu_first_cmd_id, UINT, UINT menu_flags)
|
||||
{
|
||||
if (!NewSettingsInstance().GetEnabled()
|
||||
|| package::IsWin11OrGreater()
|
||||
|| package::IsWin11OrGreater()
|
||||
)
|
||||
{
|
||||
return E_FAIL;
|
||||
@@ -47,7 +47,7 @@ IFACEMETHODIMP shell_context_menu_win10::QueryContextMenu(HMENU menu_handle, UIN
|
||||
{
|
||||
// Create the initial context popup menu containing the list of templates and open templates action
|
||||
int menu_id = menu_first_cmd_id;
|
||||
MENUITEMINFO newplus_main_context_menu_item;
|
||||
MENUITEMINFO newplus_main_context_menu_item = { 0 };
|
||||
HMENU sub_menu_of_templates = CreatePopupMenu();
|
||||
int sub_menu_index = 0;
|
||||
|
||||
@@ -142,7 +142,7 @@ void shell_context_menu_win10::add_open_templates_to_context_menu(HMENU sub_menu
|
||||
wchar_t menu_name_open[256] = { 0 };
|
||||
wcscpy_s(menu_name_open, ARRAYSIZE(menu_name_open), localized_context_menu_item_open_templates.c_str());
|
||||
const auto open_folder_item = Make<template_folder_context_menu_item>(template_folder_root);
|
||||
MENUITEMINFO newplus_menu_item_open_templates;
|
||||
MENUITEMINFO newplus_menu_item_open_templates = { 0 };
|
||||
newplus_menu_item_open_templates.cbSize = sizeof(MENUITEMINFO);
|
||||
newplus_menu_item_open_templates.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID;
|
||||
newplus_menu_item_open_templates.wID = menu_id;
|
||||
@@ -174,7 +174,7 @@ void shell_context_menu_win10::add_open_templates_to_context_menu(HMENU sub_menu
|
||||
|
||||
void shell_context_menu_win10::add_separator_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index)
|
||||
{
|
||||
MENUITEMINFO menu_item_separator;
|
||||
MENUITEMINFO menu_item_separator = { 0 };
|
||||
menu_item_separator.cbSize = sizeof(MENUITEMINFO);
|
||||
menu_item_separator.fMask = MIIM_FTYPE;
|
||||
menu_item_separator.fType = MFT_SEPARATOR;
|
||||
@@ -184,8 +184,11 @@ void shell_context_menu_win10::add_separator_to_context_menu(HMENU sub_menu_of_t
|
||||
void shell_context_menu_win10::add_template_item_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, newplus::template_item* const template_item, int menu_id, int index)
|
||||
{
|
||||
wchar_t menu_name[256] = { 0 };
|
||||
wcscpy_s(menu_name, ARRAYSIZE(menu_name), template_item->get_menu_title(!utilities::get_newplus_setting_hide_extension(), !utilities::get_newplus_setting_hide_starting_digits()).c_str());
|
||||
MENUITEMINFO newplus_menu_item_template;
|
||||
wcscpy_s(menu_name, ARRAYSIZE(menu_name), template_item->get_menu_title(
|
||||
!utilities::get_newplus_setting_hide_extension(),
|
||||
!utilities::get_newplus_setting_hide_starting_digits(),
|
||||
utilities::get_newplus_setting_resolve_variables()).c_str());
|
||||
MENUITEMINFO newplus_menu_item_template = { 0 };
|
||||
newplus_menu_item_template.cbSize = sizeof(MENUITEMINFO);
|
||||
newplus_menu_item_template.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_DATA;
|
||||
newplus_menu_item_template.wID = menu_id;
|
||||
|
||||
@@ -114,6 +114,8 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="dll_main.h" />
|
||||
<ClInclude Include="helpers_filesystem.h" />
|
||||
<ClInclude Include="helpers_variables.h" />
|
||||
<ClInclude Include="shell_context_menu.h" />
|
||||
<ClInclude Include="shell_context_sub_menu.h" />
|
||||
<ClInclude Include="shell_context_sub_menu_item.h" />
|
||||
@@ -128,6 +130,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
|
||||
<ClInclude Include="template_item.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp" />
|
||||
<ClCompile Include="new_utilities.cpp" />
|
||||
<ClCompile Include="shell_context_menu.cpp" />
|
||||
<ClCompile Include="shell_context_sub_menu.cpp" />
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
<ClCompile Include="new_utilities.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="template_folder.h">
|
||||
@@ -75,6 +78,12 @@
|
||||
<ClInclude Include="resource.base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="helpers_filesystem.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="helpers_variables.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace newplus::constants::non_localizable
|
||||
|
||||
constexpr WCHAR settings_json_key_hide_starting_digits[] = L"HideStartingDigits";
|
||||
|
||||
constexpr WCHAR settings_json_key_replace_variables[] = L"ReplaceVariables";
|
||||
|
||||
constexpr WCHAR settings_json_key_template_location[] = L"TemplateLocation";
|
||||
|
||||
constexpr WCHAR context_menu_package_name[] = L"NewPlusContextMenu";
|
||||
@@ -30,5 +32,5 @@ namespace newplus::constants::non_localizable
|
||||
|
||||
constexpr WCHAR open_templates_icon_dark_resource_relative_path[] = L"\\Assets\\NewPlus\\Open_templates_dark.ico";
|
||||
|
||||
constexpr WCHAR desktop_ini_filename[] = L"desktop.ini";
|
||||
constexpr WCHAR parent_folder_name_variable[] = L"$PARENT_FOLDER_NAME";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "helpers_variables.h"
|
||||
|
||||
namespace newplus::helpers::filesystem
|
||||
{
|
||||
namespace constants::non_localizable
|
||||
{
|
||||
constexpr WCHAR desktop_ini_filename[] = L"desktop.ini";
|
||||
}
|
||||
|
||||
inline bool is_hidden(const std::filesystem::path path)
|
||||
{
|
||||
const std::filesystem::path::string_type name = path.filename();
|
||||
if (name == constants::non_localizable::desktop_ini_filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool is_directory(const std::filesystem::path path)
|
||||
{
|
||||
const auto entry = std::filesystem::directory_entry(path);
|
||||
return entry.is_directory();
|
||||
}
|
||||
|
||||
inline std::wstring make_valid_filename(const std::wstring& string, const wchar_t replace_with = L' ')
|
||||
{
|
||||
// replace all non-filename-valid chars with replace_with wchar
|
||||
std::wstring valid_filename = string;
|
||||
|
||||
std::replace_if(valid_filename.begin(), valid_filename.end(), [](wchar_t c) { return c == L'/' || c == L'\\' || c == L':' || c == L'*' || c == L'?' || c == L'"' || c == L'<' || c == L'>' || c == L'|'; }, replace_with);
|
||||
|
||||
return valid_filename;
|
||||
}
|
||||
|
||||
inline std::wstring make_unique_path_name(const std::wstring& initial_path)
|
||||
{
|
||||
std::filesystem::path folder_path(initial_path);
|
||||
std::filesystem::path path_based_on(initial_path);
|
||||
|
||||
int counter = 1;
|
||||
|
||||
while (std::filesystem::exists(folder_path))
|
||||
{
|
||||
std::wstring new_filename = path_based_on.stem().wstring() + L" (" + std::to_wstring(counter) + L")";
|
||||
if (path_based_on.has_extension())
|
||||
{
|
||||
new_filename += path_based_on.extension().wstring();
|
||||
}
|
||||
folder_path = path_based_on.parent_path() / new_filename;
|
||||
counter++;
|
||||
}
|
||||
|
||||
return folder_path.wstring();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
|
||||
#include <regex>
|
||||
#include "..\..\powerrename\lib\Helpers.h"
|
||||
#include "helpers_filesystem.h"
|
||||
|
||||
#pragma comment(lib, "Pathcch.lib")
|
||||
|
||||
namespace newplus::helpers::variables
|
||||
{
|
||||
inline std::wstring resolve_an_environment_variable(const std::wstring& string)
|
||||
{
|
||||
std::wstring return_string = string;
|
||||
wchar_t* env_variable = nullptr;
|
||||
|
||||
_wdupenv_s(&env_variable, nullptr, return_string.c_str());
|
||||
|
||||
if (env_variable != nullptr)
|
||||
{
|
||||
return_string = env_variable;
|
||||
free(env_variable);
|
||||
}
|
||||
|
||||
return return_string;
|
||||
}
|
||||
|
||||
inline std::wstring resolve_date_time_variables(const std::wstring& string)
|
||||
{
|
||||
SYSTEMTIME local_now = { 0 };
|
||||
GetLocalTime(&local_now);
|
||||
wchar_t resolved_filename[MAX_PATH] = { 0 };
|
||||
GetDatedFileName(resolved_filename, ARRAYSIZE(resolved_filename), string.c_str(), local_now);
|
||||
|
||||
return resolved_filename;
|
||||
}
|
||||
|
||||
inline std::wstring replace_all_occurrences(const std::wstring& string, const std::wstring& search_for, const std::wstring& replacement)
|
||||
{
|
||||
std::wstring return_string = string;
|
||||
size_t pos = 0;
|
||||
|
||||
while ((pos = return_string.find(search_for, pos)) != std::wstring::npos)
|
||||
{
|
||||
return_string.replace(pos, search_for.length(), replacement);
|
||||
pos += replacement.length();
|
||||
}
|
||||
|
||||
return return_string;
|
||||
}
|
||||
|
||||
inline std::wstring resolve_environment_variables(const std::wstring& string)
|
||||
{
|
||||
// Do case-insensitive string replacement of environment variables being consistent with normal %eNV_VaR% behavior
|
||||
std::wstring return_string = string;
|
||||
const std::wregex reg_expression(L"%([^%]+)%");
|
||||
std::wsmatch match;
|
||||
|
||||
size_t start = 0;
|
||||
while (std::regex_search(return_string.cbegin() + start, return_string.cend(), match, reg_expression))
|
||||
{
|
||||
std::wstring env_var_name = match[1].str();
|
||||
std::wstring env_var_value = resolve_an_environment_variable(env_var_name);
|
||||
if (!env_var_value.empty())
|
||||
{
|
||||
size_t match_position = match.position(0) + start;
|
||||
return_string.replace(match_position, match.length(0), env_var_value);
|
||||
start = match_position + env_var_value.length();
|
||||
}
|
||||
else
|
||||
{
|
||||
start += match.position(0) + match.length(0);
|
||||
}
|
||||
}
|
||||
|
||||
return return_string;
|
||||
}
|
||||
|
||||
inline std::wstring resolve_parent_folder(const std::wstring& string, const std::wstring& parent_folder_name)
|
||||
{
|
||||
// Do case-sensitive string replacement, for consistency on variables designated with $
|
||||
std::wstring result = replace_all_occurrences(string, constants::non_localizable::parent_folder_name_variable, parent_folder_name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::filesystem::path resolve_variables_in_filename(const std::wstring& filename, const std::wstring& parent_folder_name)
|
||||
{
|
||||
std::wstring result;
|
||||
|
||||
result = resolve_date_time_variables(filename);
|
||||
result = resolve_environment_variables(result);
|
||||
if (!parent_folder_name.empty())
|
||||
{
|
||||
result = resolve_parent_folder(result, parent_folder_name);
|
||||
}
|
||||
result = newplus::helpers::filesystem::make_valid_filename(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::filesystem::path resolve_variables_in_path(const std::filesystem::path& path)
|
||||
{
|
||||
// Need to resolve the whole path top-down (root to leaf), because of the support for $PARENT_FOLDER_NAME
|
||||
std::filesystem::path result;
|
||||
std::wstring previous_section;
|
||||
std::wstring current_section;
|
||||
auto path_section = path.begin();
|
||||
int level = 0;
|
||||
|
||||
while (path_section != path.end())
|
||||
{
|
||||
previous_section = current_section;
|
||||
current_section = path_section->wstring();
|
||||
|
||||
if (level <= 1)
|
||||
{
|
||||
// Up to and including L"x:\\"
|
||||
result /= current_section;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Past L"x:\\", e.g. L"x:\\level1" and beyond
|
||||
result /= resolve_variables_in_filename(current_section, previous_section);
|
||||
}
|
||||
path_section++;
|
||||
level++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void resolve_variables_in_filename_and_rename_files(const std::filesystem::path& path, const bool do_rename = true)
|
||||
{
|
||||
// Depth first recursion, so that we start renaming the leaves, and avoid having to rescan
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path))
|
||||
{
|
||||
if (std::filesystem::is_directory(entry.status()))
|
||||
{
|
||||
resolve_variables_in_filename_and_rename_files(entry.path(), do_rename);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the actual rename
|
||||
for (const auto& current : std::filesystem::directory_iterator(path))
|
||||
{
|
||||
if (!newplus::helpers::filesystem::is_hidden(current))
|
||||
{
|
||||
const std::filesystem::path resolved_path = resolve_variables_in_path(current.path());
|
||||
|
||||
// Only rename if the filename is actually different
|
||||
const std::wstring non_resolved_leaf = current.path().filename();
|
||||
const std::wstring resolved_leaf = resolved_path.filename();
|
||||
|
||||
if (StrCmpIW(non_resolved_leaf.c_str(), resolved_leaf.c_str()) != 0)
|
||||
{
|
||||
const std::wstring org_name = current.path();
|
||||
const std::wstring new_name = current.path().parent_path() / resolved_leaf;
|
||||
const std::wstring really_new_name = helpers::filesystem::make_unique_path_name(new_name);
|
||||
|
||||
// To aid with testing, only conditionally rename
|
||||
if (do_rename)
|
||||
{
|
||||
std::filesystem::rename(org_name, really_new_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "settings.h"
|
||||
#include "template_item.h"
|
||||
#include "trace.h"
|
||||
#include "helpers_variables.h"
|
||||
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
|
||||
@@ -72,23 +73,6 @@ namespace newplus::utilities
|
||||
return hIcon;
|
||||
}
|
||||
|
||||
inline bool is_hidden(const std::filesystem::path path)
|
||||
{
|
||||
const std::filesystem::path::string_type name = path.filename();
|
||||
if (name == constants::non_localizable::desktop_ini_filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool is_directory(const std::filesystem::path path)
|
||||
{
|
||||
const auto entry = std::filesystem::directory_entry(path);
|
||||
return entry.is_directory();
|
||||
}
|
||||
|
||||
inline bool wstring_same_when_comparing_ignore_case(std::wstring stringA, std::wstring stringB)
|
||||
{
|
||||
transform(stringA.begin(), stringA.end(), stringA.begin(), towupper);
|
||||
@@ -97,20 +81,6 @@ namespace newplus::utilities
|
||||
return (stringA == stringB);
|
||||
}
|
||||
|
||||
inline void process_pending_window_messages(HWND window_handle = NULL)
|
||||
{
|
||||
if (window_handle == NULL)
|
||||
{
|
||||
window_handle = GetActiveWindow();
|
||||
}
|
||||
|
||||
MSG current_message;
|
||||
while (PeekMessage(¤t_message, window_handle, NULL, NULL, PM_REMOVE))
|
||||
{
|
||||
DispatchMessage(¤t_message);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::wstring get_new_template_folder_location()
|
||||
{
|
||||
return NewSettingsInstance().GetTemplateLocation();
|
||||
@@ -126,6 +96,11 @@ namespace newplus::utilities
|
||||
return NewSettingsInstance().GetHideStartingDigits();
|
||||
}
|
||||
|
||||
inline bool get_newplus_setting_resolve_variables()
|
||||
{
|
||||
return NewSettingsInstance().GetReplaceVariables();
|
||||
}
|
||||
|
||||
inline void create_folder_if_not_exist(const std::filesystem::path path)
|
||||
{
|
||||
std::filesystem::create_directory(path);
|
||||
@@ -259,6 +234,7 @@ namespace newplus::utilities
|
||||
{
|
||||
ComPtr<IWebBrowserApp> web_browser_app;
|
||||
VARIANT v;
|
||||
VariantInit(&v);
|
||||
V_VT(&v) = VT_I4;
|
||||
V_I4(&v) = i;
|
||||
hr = shell_windows->Item(v, &shell_window);
|
||||
@@ -382,15 +358,30 @@ namespace newplus::utilities
|
||||
std::filesystem::path source_fullpath = template_entry->path;
|
||||
std::filesystem::path target_fullpath = std::wstring(target_path_name);
|
||||
|
||||
// Only append name to target if source is not a directory
|
||||
if (!utilities::is_directory(source_fullpath))
|
||||
// Get target name without starting digits as appropriate
|
||||
const std::wstring target_name = template_entry->get_target_filename(!utilities::get_newplus_setting_hide_starting_digits());
|
||||
|
||||
// Get initial resolved name
|
||||
target_fullpath /= target_name;
|
||||
|
||||
// Expand variables in name of the target path
|
||||
if (utilities::get_newplus_setting_resolve_variables())
|
||||
{
|
||||
target_fullpath.append(template_entry->get_target_filename(!utilities::get_newplus_setting_hide_starting_digits()));
|
||||
target_fullpath = helpers::variables::resolve_variables_in_path(target_fullpath);
|
||||
}
|
||||
|
||||
// Copy file and determine final filename
|
||||
// See if our target already exist, and if so then generate a unique name
|
||||
target_fullpath = helpers::filesystem::make_unique_path_name(target_fullpath);
|
||||
|
||||
// Finally copy file/folder/subfolders
|
||||
std::filesystem::path target_final_fullpath = template_entry->copy_object_to(GetActiveWindow(), target_fullpath);
|
||||
|
||||
// Resolve variables and rename files in newly copied folders and subfolders and files
|
||||
if (utilities::get_newplus_setting_resolve_variables() && helpers::filesystem::is_directory(target_final_fullpath))
|
||||
{
|
||||
helpers::variables::resolve_variables_in_filename_and_rename_files(target_final_fullpath);
|
||||
}
|
||||
|
||||
// Touch all files and set last modified to "now"
|
||||
update_last_write_time(target_final_fullpath);
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ void NewSettings::Save()
|
||||
|
||||
values.add_property(newplus::constants::non_localizable::settings_json_key_hide_file_extension, new_settings.hide_file_extension);
|
||||
values.add_property(newplus::constants::non_localizable::settings_json_key_hide_starting_digits, new_settings.hide_starting_digits);
|
||||
values.add_property(newplus::constants::non_localizable::settings_json_key_replace_variables, new_settings.replace_variables);
|
||||
values.add_property(newplus::constants::non_localizable::settings_json_key_template_location, new_settings.template_location);
|
||||
|
||||
values.save_to_settings_file();
|
||||
@@ -70,6 +71,9 @@ void NewSettings::InitializeWithDefaultSettings()
|
||||
// Currently a similar defaulting logic is also in InitializeWithDefaultSettings in NewViewModel.cs
|
||||
SetHideFileExtension(true);
|
||||
|
||||
// By default Replace Variables is turned off
|
||||
SetReplaceVariables(false);
|
||||
|
||||
SetTemplateLocation(GetTemplateLocationDefaultPath());
|
||||
}
|
||||
|
||||
@@ -139,6 +143,12 @@ void NewSettings::ParseJson()
|
||||
new_settings.hide_starting_digits = hideStartingDigitsValue.value();
|
||||
}
|
||||
|
||||
auto resolveVariables = settings.get_bool_value(newplus::constants::non_localizable::settings_json_key_replace_variables);
|
||||
if (resolveVariables.has_value())
|
||||
{
|
||||
new_settings.replace_variables = resolveVariables.value();
|
||||
}
|
||||
|
||||
GetSystemTimeAsFileTime(&new_settings_last_loaded_timestamp);
|
||||
}
|
||||
|
||||
@@ -163,11 +173,8 @@ bool NewSettings::GetEnabled()
|
||||
|
||||
bool NewSettings::GetHideFileExtension() const
|
||||
{
|
||||
auto gpoSetting = powertoys_gpo::getConfiguredNewPlusHideTemplateFilenameExtensionValue();
|
||||
if (gpoSetting == powertoys_gpo::gpo_rule_configured_enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const auto gpoSetting = powertoys_gpo::getConfiguredNewPlusHideTemplateFilenameExtensionValue();
|
||||
|
||||
if (gpoSetting == powertoys_gpo::gpo_rule_configured_disabled)
|
||||
{
|
||||
return false;
|
||||
@@ -191,6 +198,23 @@ void NewSettings::SetHideStartingDigits(const bool hide_starting_digits)
|
||||
new_settings.hide_starting_digits = hide_starting_digits;
|
||||
}
|
||||
|
||||
bool NewSettings::GetReplaceVariables() const
|
||||
{
|
||||
const auto gpoSetting = powertoys_gpo::getConfiguredNewPlusReplaceVariablesValue();
|
||||
|
||||
if (gpoSetting == powertoys_gpo::gpo_rule_configured_disabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return new_settings.replace_variables;
|
||||
}
|
||||
|
||||
void NewSettings::SetReplaceVariables(const bool replace_variables)
|
||||
{
|
||||
new_settings.replace_variables = replace_variables;
|
||||
}
|
||||
|
||||
std::wstring NewSettings::GetTemplateLocation() const
|
||||
{
|
||||
return new_settings.template_location;
|
||||
@@ -201,7 +225,7 @@ void NewSettings::SetTemplateLocation(const std::wstring template_location)
|
||||
new_settings.template_location = template_location;
|
||||
}
|
||||
|
||||
std::wstring NewSettings::GetTemplateLocationDefaultPath()
|
||||
std::wstring NewSettings::GetTemplateLocationDefaultPath() const
|
||||
{
|
||||
static const std::wstring default_template_sub_folder_name =
|
||||
GET_RESOURCE_STRING_FALLBACK(
|
||||
|
||||
@@ -12,6 +12,8 @@ public:
|
||||
void SetHideFileExtension(const bool hide_file_extension);
|
||||
bool GetHideStartingDigits() const;
|
||||
void SetHideStartingDigits(const bool hide_starting_digits);
|
||||
bool GetReplaceVariables() const;
|
||||
void SetReplaceVariables(const bool resolve_variables);
|
||||
std::wstring GetTemplateLocation() const;
|
||||
void SetTemplateLocation(const std::wstring template_location);
|
||||
|
||||
@@ -25,12 +27,13 @@ private:
|
||||
bool enabled{ false };
|
||||
bool hide_file_extension{ true };
|
||||
bool hide_starting_digits{ true };
|
||||
bool replace_variables{ true };
|
||||
std::wstring template_location;
|
||||
};
|
||||
|
||||
void RefreshEnabledState();
|
||||
void InitializeWithDefaultSettings();
|
||||
std::wstring GetTemplateLocationDefaultPath();
|
||||
std::wstring GetTemplateLocationDefaultPath() const;
|
||||
|
||||
void Reload();
|
||||
void ParseJson();
|
||||
|
||||
@@ -69,8 +69,21 @@ IFACEMETHODIMP shell_context_menu::GetFlags(_Out_ EXPCMDFLAGS* returned_menu_ite
|
||||
|
||||
IFACEMETHODIMP shell_context_menu::EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** returned_enum_commands)
|
||||
{
|
||||
auto e = Make<shell_context_sub_menu>(site_of_folder);
|
||||
return e->QueryInterface(IID_PPV_ARGS(returned_enum_commands));
|
||||
try
|
||||
{
|
||||
auto e = Make<shell_context_sub_menu>(site_of_folder);
|
||||
return e->QueryInterface(IID_PPV_ARGS(returned_enum_commands));
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
Logger::error("New+ create submenu error: {}", ex.what());
|
||||
return E_FAIL;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("New+ create submenu error");
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
@@ -80,8 +93,8 @@ IFACEMETHODIMP shell_context_menu::SetSite(_In_ IUnknown* site) noexcept
|
||||
this->site_of_folder = site;
|
||||
return S_OK;
|
||||
}
|
||||
IFACEMETHODIMP shell_context_menu::GetSite(_In_ REFIID riid, _COM_Outptr_ void** returned_site) noexcept
|
||||
IFACEMETHODIMP shell_context_menu::GetSite(_In_ REFIID interface_type, _COM_Outptr_ void** returned_site) noexcept
|
||||
{
|
||||
return this->site_of_folder.CopyTo(riid, returned_site);
|
||||
return this->site_of_folder.CopyTo(interface_type, returned_site);
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
|
||||
#pragma region IObjectWithSite
|
||||
IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept;
|
||||
IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept;
|
||||
IFACEMETHODIMP GetSite(_In_ REFIID interface_type, _COM_Outptr_ void** site) noexcept;
|
||||
#pragma endregion
|
||||
|
||||
protected:
|
||||
|
||||
@@ -22,7 +22,8 @@ IFACEMETHODIMP shell_context_sub_menu_item::GetTitle(_In_opt_ IShellItemArray* i
|
||||
{
|
||||
return SHStrDup(this->template_entry->get_menu_title(
|
||||
!utilities::get_newplus_setting_hide_extension(),
|
||||
!utilities::get_newplus_setting_hide_starting_digits()
|
||||
!utilities::get_newplus_setting_hide_starting_digits(),
|
||||
utilities::get_newplus_setting_resolve_variables()
|
||||
).c_str(), title);
|
||||
}
|
||||
|
||||
@@ -95,6 +96,7 @@ IFACEMETHODIMP separator_context_menu_item::GetIcon(_In_opt_ IShellItemArray*, _
|
||||
|
||||
IFACEMETHODIMP separator_context_menu_item::GetFlags(_Out_ EXPCMDFLAGS* returned_flags)
|
||||
{
|
||||
// Separators no longer work on Windows 11 regular context menu. They do still work on the extended context menu.
|
||||
*returned_flags = ECF_ISSEPARATOR;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ void template_folder::rescan_template_folder()
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!utilities::is_hidden(entry.path()))
|
||||
if (!helpers::filesystem::is_hidden(entry.path()))
|
||||
{
|
||||
files.push_back({ entry.path().wstring(), new template_item(entry) });
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "new_utilities.h"
|
||||
#include <cassert>
|
||||
#include <thread>
|
||||
#include <shellapi.h>
|
||||
#include <shlobj_core.h>
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
@@ -17,7 +16,7 @@ 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
|
||||
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();
|
||||
|
||||
@@ -27,13 +26,21 @@ std::wstring template_item::get_menu_title(const bool show_extension, const bool
|
||||
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;
|
||||
}
|
||||
|
||||
std::wstring ext = path.extension();
|
||||
title = title.substr(0, title.length() - ext.length());
|
||||
if (!helpers::filesystem::is_directory(path))
|
||||
{
|
||||
std::wstring ext = path.extension();
|
||||
title = title.substr(0, title.length() - ext.length());
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
@@ -53,7 +60,8 @@ std::wstring template_item::get_target_filename(const bool include_starting_digi
|
||||
|
||||
std::wstring template_item::remove_starting_digits_from_filename(std::wstring filename) const
|
||||
{
|
||||
filename.erase(0, min(filename.find_first_not_of(L"0123456789 ."), filename.size()));
|
||||
filename.erase(0, min(filename.find_first_not_of(L"0123456789"), filename.size()));
|
||||
filename.erase(0, min(filename.find_first_not_of(L" ."), filename.size()));
|
||||
|
||||
return filename;
|
||||
}
|
||||
@@ -70,7 +78,7 @@ HICON template_item::get_explorer_icon_handle() const
|
||||
|
||||
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,
|
||||
// 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;
|
||||
@@ -84,37 +92,16 @@ std::filesystem::path template_item::copy_object_to(const HWND window_handle, co
|
||||
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_RENAMEONCOLLISION | FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS | FOF_WANTMAPPINGHANDLE;
|
||||
file_operation_params.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS;
|
||||
|
||||
const int result = SHFileOperation(&file_operation_params);
|
||||
|
||||
if (!file_operation_params.hNameMappings)
|
||||
if (result != 0)
|
||||
{
|
||||
// No file name collision on copy
|
||||
if (utilities::is_directory(this->path))
|
||||
{
|
||||
// Append dir for consistency on directory naming inclusion for with and without collision
|
||||
std::filesystem::path with_dir = destination;
|
||||
with_dir /= this->path.filename();
|
||||
return with_dir;
|
||||
}
|
||||
|
||||
return destination;
|
||||
throw std::runtime_error("Failed to copy template");
|
||||
}
|
||||
|
||||
struct file_operation_collision_mapping
|
||||
{
|
||||
int index;
|
||||
SHNAMEMAPPING* mapping;
|
||||
};
|
||||
|
||||
file_operation_collision_mapping* mapping = static_cast<file_operation_collision_mapping*>(file_operation_params.hNameMappings);
|
||||
SHNAMEMAPPING* map = &mapping->mapping[0];
|
||||
std::wstring final_path(map->pszNewPath);
|
||||
|
||||
SHFreeNameMappings(file_operation_params.hNameMappings);
|
||||
|
||||
return final_path;
|
||||
return destination;
|
||||
}
|
||||
|
||||
void template_item::refresh_target(const std::filesystem::path target_final_fullpath) const
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace newplus
|
||||
public:
|
||||
template_item(const std::filesystem::path entry);
|
||||
|
||||
std::wstring get_menu_title(const bool show_extension, const bool show_starting_digits) const;
|
||||
std::wstring get_menu_title(const bool show_extension, const bool show_starting_digits, const bool show_resolved_variables) const;
|
||||
|
||||
std::wstring get_target_filename(const bool include_starting_digits) const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user