Skip to content

Commit f81a2ba

Browse files
authored
Merge pull request #119 from adriengivry/feature/new_asset_duplication_naming_system
Unique string generation system
2 parents 5c19e91 + ad3cbfc commit f81a2ba

4 files changed

Lines changed: 70 additions & 53 deletions

File tree

Sources/Overload/OvEditor/src/OvEditor/Core/EditorActions.cpp

Lines changed: 5 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -567,58 +567,16 @@ bool OvEditor::Core::EditorActions::DestroyActor(OvCore::ECS::Actor & p_actor)
567567

568568
std::string FindDuplicatedActorUniqueName(OvCore::ECS::Actor& p_duplicated, OvCore::ECS::Actor& p_newActor, OvCore::SceneSystem::Scene& p_scene)
569569
{
570-
const auto sourceName = p_duplicated.GetName();
571-
auto sourceNameWithoutSuffix = sourceName;
572-
573-
auto suffixOpeningParenthesisPos = std::string::npos;
574-
auto suffixClosingParenthesisPos = std::string::npos;
575-
576-
// Keep track of the current character position when iterating onto `sourceName`
577-
auto currentPos = decltype(std::string::npos){sourceName.length() - 1};
578-
579-
// Here we search for `(` and `)` positions. (Needed to extract the number between those parenthesis)
580-
for (auto it = sourceName.rbegin(); it < sourceName.rend(); ++it, --currentPos)
581-
{
582-
auto c = *it;
583-
584-
if (suffixClosingParenthesisPos == std::string::npos && c == ')') suffixClosingParenthesisPos = currentPos;
585-
if (suffixClosingParenthesisPos != std::string::npos && c == '(') suffixOpeningParenthesisPos = currentPos;
586-
}
587-
588-
// We need to declare our `duplicationCounter` here to store the number between found parenthesis OR 1 (In the case no parenthesis, AKA, suffix, has been found)
589-
auto duplicationCounter = uint32_t{ 1 };
590-
591-
// If the two parenthis have been found AND the closing parenthesis is the last character AND there is a space before the opening parenthesis
592-
if (suffixOpeningParenthesisPos != std::string::npos && suffixClosingParenthesisPos == sourceName.length() - 1 && suffixOpeningParenthesisPos > 0 && sourceName[suffixOpeningParenthesisPos - 1] == ' ')
593-
{
594-
// Extract the string between those parenthesis
595-
const auto between = sourceName.substr(suffixOpeningParenthesisPos + 1, suffixClosingParenthesisPos - suffixOpeningParenthesisPos - 1);
596-
597-
// If the `between` string is composed of digits (AKA, `between` is a number)
598-
if (!between.empty() && std::find_if(between.begin(), between.end(), [](unsigned char c) { return !std::isdigit(c); }) == between.end())
599-
{
600-
duplicationCounter = static_cast<uint32_t>(std::atoi(between.c_str()));
601-
sourceNameWithoutSuffix = sourceName.substr(0, suffixOpeningParenthesisPos - 1);
602-
}
603-
}
604-
605570
const auto parent = p_newActor.GetParent();
606571
const auto adjacentActors = parent ? parent->GetChildren() : p_scene.GetActors();
607572

608-
auto foundName = sourceNameWithoutSuffix;
609-
610-
// Lambda that checks if the current `foundName` is used by the actor given in parameter (Also ensure that the given actor is adjacent to our new actor)
611-
// We call "adjacent" two actors that shares the same hierarchical level. (Ex: If [A] and [B] are both direction children of [C], then, they are adjacents)
612-
const auto isActorNameTaken = [&foundName, parent](auto actor) { return (parent || !actor->GetParent()) && actor->GetName() == foundName; };
613-
614-
// While there is an adjacent actor with the current `foundName`, we keep generating new names
615-
while (std::find_if(adjacentActors.begin(), adjacentActors.end(), isActorNameTaken) != adjacentActors.end())
573+
auto availabilityChecker = [&parent, &adjacentActors](std::string target) -> bool
616574
{
617-
// New names are composed of the `sourceNameWithoutSuffix` (Ex: "Cube (1)" name without suffix is "Cube")
618-
foundName = sourceNameWithoutSuffix + " (" + std::to_string(duplicationCounter++) + ")";
619-
}
575+
const auto isActorNameTaken = [&target, parent](auto actor) { return (parent || !actor->GetParent()) && actor->GetName() == target; };
576+
return std::find_if(adjacentActors.begin(), adjacentActors.end(), isActorNameTaken) == adjacentActors.end();
577+
};
620578

621-
return foundName;
579+
return OvTools::Utils::String::GenerateUnique(p_duplicated.GetName(), availabilityChecker);
622580
}
623581

624582
void OvEditor::Core::EditorActions::DuplicateActor(OvCore::ECS::Actor & p_toDuplicate, OvCore::ECS::Actor* p_forcedParent, bool p_focus)

Sources/Overload/OvEditor/src/OvEditor/Panels/AssetBrowser.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <OvWindowing/Dialogs/OpenFileDialog.h>
2222
#include <OvTools/Utils/SystemCalls.h>
2323
#include <OvTools/Utils/PathParser.h>
24+
#include <OvTools/Utils/String.h>
2425

2526
#include <OvCore/Global/ServiceLocator.h>
2627
#include <OvCore/ResourceManagement/ModelManager.h>
@@ -593,12 +594,14 @@ class FileContextualMenu : public BrowserItemContextualMenu
593594
if (size_t pos = filePathWithoutExtension.rfind('.'); pos != std::string::npos)
594595
filePathWithoutExtension = filePathWithoutExtension.substr(0, pos);
595596

596-
std::string newNameWithoutExtension = filePathWithoutExtension + " (Copy)";
597597
std::string extension = "." + OvTools::Utils::PathParser::GetExtension(filePath);
598-
while (std::filesystem::exists(newNameWithoutExtension + extension))
599-
{
600-
newNameWithoutExtension += " (Copy)";
601-
}
598+
599+
auto filenameAvailable = [&extension](const std::string& target)
600+
{
601+
return !std::filesystem::exists(target + extension);
602+
};
603+
604+
const auto newNameWithoutExtension = OvTools::Utils::String::GenerateUnique(filePathWithoutExtension, filenameAvailable);
602605

603606
std::string finalPath = newNameWithoutExtension + extension;
604607
std::filesystem::copy(filePath, finalPath);

Sources/Overload/OvTools/include/OvTools/Utils/String.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#pragma once
88

99
#include <string>
10+
#include <functional>
1011

1112
#include "OvTools/API/Export.h"
1213

@@ -38,5 +39,12 @@ namespace OvTools::Utils
3839
* @param p_to
3940
*/
4041
static void ReplaceAll(std::string& p_target, const std::string& p_from, const std::string& p_to);
42+
43+
/**
44+
* Generate a unique string satisfying the availability predicate
45+
* @param p_source
46+
* @param p_isAvailable (A callback that must returning true if the input string is available)
47+
*/
48+
static std::string GenerateUnique(const std::string& p_source, std::function<bool(std::string)> p_isAvailable);
4149
};
4250
}

Sources/Overload/OvTools/src/OvTools/Utils/String.cpp

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,52 @@ void OvTools::Utils::String::ReplaceAll(std::string& p_target, const std::string
2929
p_target.replace(start_pos, p_from.length(), p_to);
3030
start_pos += p_to.length();
3131
}
32-
}
32+
}
33+
34+
std::string OvTools::Utils::String::GenerateUnique(const std::string& p_source, std::function<bool(std::string)> p_isAvailable)
35+
{
36+
auto suffixlessSource = p_source;
37+
38+
auto suffixOpeningParenthesisPos = std::string::npos;
39+
auto suffixClosingParenthesisPos = std::string::npos;
40+
41+
// Keep track of the current character position when iterating onto `p_source`
42+
auto currentPos = decltype(std::string::npos){p_source.length() - 1};
43+
44+
// Here we search for `(` and `)` positions. (Needed to extract the number between those parenthesis)
45+
for (auto it = p_source.rbegin(); it < p_source.rend(); ++it, --currentPos)
46+
{
47+
const auto c = *it;
48+
49+
if (suffixClosingParenthesisPos == std::string::npos && c == ')') suffixClosingParenthesisPos = currentPos;
50+
if (suffixClosingParenthesisPos != std::string::npos && c == '(') suffixOpeningParenthesisPos = currentPos;
51+
}
52+
53+
// We need to declare our `counter` here to store the number between found parenthesis OR 1 (In the case no parenthesis, AKA, suffix, has been found)
54+
auto counter = uint32_t{ 1 };
55+
56+
// If the two parenthis have been found AND the closing parenthesis is the last character AND there is a space before the opening parenthesis
57+
if (suffixOpeningParenthesisPos != std::string::npos && suffixClosingParenthesisPos == p_source.length() - 1 && suffixOpeningParenthesisPos > 0 && p_source[suffixOpeningParenthesisPos - 1] == ' ')
58+
{
59+
// Extract the string between those parenthesis
60+
const auto between = p_source.substr(suffixOpeningParenthesisPos + 1, suffixClosingParenthesisPos - suffixOpeningParenthesisPos - 1);
61+
62+
// If the `between` string is composed of digits (AKA, `between` is a number)
63+
if (!between.empty() && std::find_if(between.begin(), between.end(), [](unsigned char c) { return !std::isdigit(c); }) == between.end())
64+
{
65+
counter = static_cast<uint32_t>(std::atoi(between.c_str()));
66+
suffixlessSource = p_source.substr(0, suffixOpeningParenthesisPos - 1);
67+
}
68+
}
69+
70+
auto result = suffixlessSource;
71+
72+
// While `result` isn't available, we keep generating new strings
73+
while (!p_isAvailable(result))
74+
{
75+
// New strings are composed of the `suffixlessSource` (Ex: "Foo (1)" without suffix is "Foo")
76+
result = suffixlessSource + " (" + std::to_string(counter++) + ")";
77+
}
78+
79+
return result;
80+
}

0 commit comments

Comments
 (0)