diff --git a/Core/GDCore/IDE/Events/BehaviorPropertyRenamer.cpp b/Core/GDCore/IDE/Events/BehaviorPropertyRenamer.cpp new file mode 100644 index 0000000000..a15f0fe27a --- /dev/null +++ b/Core/GDCore/IDE/Events/BehaviorPropertyRenamer.cpp @@ -0,0 +1,21 @@ +#include "BehaviorPropertyRenamer.h" + +#include "GDCore/Events/Instruction.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Extensions/PlatformExtension.h" +#include "GDCore/IDE/WholeProjectRefactorer.h" +#include "GDCore/Project/Behavior.h" +#include "GDCore/Project/Object.h" +#include "GDCore/Project/Project.h" + +namespace gd { + +void BehaviorPropertyRenamer::DoVisitBehavior(gd::Behavior &behavior) { + if (behavior.GetTypeName() == behaviorType) { + behavior.RenameProperty(oldName, newName); + } +}; + +BehaviorPropertyRenamer::~BehaviorPropertyRenamer() {} + +} // namespace gd diff --git a/Core/GDCore/IDE/Events/BehaviorPropertyRenamer.h b/Core/GDCore/IDE/Events/BehaviorPropertyRenamer.h new file mode 100644 index 0000000000..07375571da --- /dev/null +++ b/Core/GDCore/IDE/Events/BehaviorPropertyRenamer.h @@ -0,0 +1,39 @@ +/* + * GDevelop Core + * Copyright 2008-2026 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ + +#pragma once +#include + +#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h" +#include "GDCore/String.h" + +namespace gd { +class Object; +class Behavior; +} // namespace gd + +namespace gd { + +/** + * \brief Rename a property in behaviors of all objects. + */ +class GD_CORE_API BehaviorPropertyRenamer : public ArbitraryObjectsWorker { +public: + BehaviorPropertyRenamer(const gd::String &behaviorType_, + const gd::String &oldName_, + const gd::String &newName_) + : behaviorType(behaviorType_), oldName(oldName_), newName(newName_){}; + virtual ~BehaviorPropertyRenamer(); + +private: + void DoVisitBehavior(gd::Behavior &behavior) override; + + gd::String behaviorType; + gd::String oldName; + gd::String newName; +}; + +}; // namespace gd diff --git a/Core/GDCore/IDE/Events/BehaviorTypeRenamer.h b/Core/GDCore/IDE/Events/BehaviorTypeRenamer.h index d6fa1634dc..ae15da6d0c 100644 --- a/Core/GDCore/IDE/Events/BehaviorTypeRenamer.h +++ b/Core/GDCore/IDE/Events/BehaviorTypeRenamer.h @@ -4,8 +4,7 @@ * reserved. This project is released under the MIT License. */ -#ifndef GDCORE_BEHAVIORTYPERENAMER_H -#define GDCORE_BEHAVIORTYPERENAMER_H +#pragma once #include #include "GDCore/IDE/Project/ArbitraryObjectsWorker.h" @@ -34,5 +33,3 @@ class GD_CORE_API BehaviorTypeRenamer : public ArbitraryObjectsWorker { }; }; // namespace gd - -#endif // GDCORE_BEHAVIORTYPERENAMER_H diff --git a/Core/GDCore/IDE/Events/ObjectPropertyRenamer.cpp b/Core/GDCore/IDE/Events/ObjectPropertyRenamer.cpp new file mode 100644 index 0000000000..72c1988b81 --- /dev/null +++ b/Core/GDCore/IDE/Events/ObjectPropertyRenamer.cpp @@ -0,0 +1,21 @@ +#include "ObjectPropertyRenamer.h" + +#include "GDCore/Events/Instruction.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Extensions/PlatformExtension.h" +#include "GDCore/IDE/WholeProjectRefactorer.h" +#include "GDCore/Project/Behavior.h" +#include "GDCore/Project/Object.h" +#include "GDCore/Project/Project.h" + +namespace gd { + +void ObjectPropertyRenamer::DoVisitObject(gd::Object &object) { + if (object.GetType() == objectType) { + object.GetConfiguration().RenameProperty(oldName, newName); + } +}; + +ObjectPropertyRenamer::~ObjectPropertyRenamer() {} + +} // namespace gd diff --git a/Core/GDCore/IDE/Events/ObjectPropertyRenamer.h b/Core/GDCore/IDE/Events/ObjectPropertyRenamer.h new file mode 100644 index 0000000000..0590ca07c9 --- /dev/null +++ b/Core/GDCore/IDE/Events/ObjectPropertyRenamer.h @@ -0,0 +1,38 @@ +/* + * GDevelop Core + * Copyright 2008-2026 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ + +#pragma once +#include + +#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h" +#include "GDCore/String.h" + +namespace gd { +class Object; +class Behavior; +} // namespace gd + +namespace gd { + +/** + * \brief Rename a property in all custom objects of a given type. + */ +class GD_CORE_API ObjectPropertyRenamer : public ArbitraryObjectsWorker { +public: + ObjectPropertyRenamer(const gd::String &objectType_, + const gd::String &oldName_, const gd::String &newName_) + : objectType(objectType_), oldName(oldName_), newName(newName_){}; + virtual ~ObjectPropertyRenamer(); + +private: + void DoVisitObject(gd::Object &object) override; + + gd::String objectType; + gd::String oldName; + gd::String newName; +}; + +}; // namespace gd diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.cpp b/Core/GDCore/IDE/WholeProjectRefactorer.cpp index 5180f8fa4f..b732d71ef0 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.cpp +++ b/Core/GDCore/IDE/WholeProjectRefactorer.cpp @@ -16,6 +16,7 @@ #include "GDCore/IDE/EventBasedObjectBrowser.h" #include "GDCore/IDE/Events/ArbitraryEventsWorker.h" #include "GDCore/IDE/Events/BehaviorParametersFiller.h" +#include "GDCore/IDE/Events/BehaviorPropertyRenamer.h" #include "GDCore/IDE/Events/BehaviorTypeRenamer.h" #include "GDCore/IDE/Events/CustomObjectTypeRenamer.h" #include "GDCore/IDE/Events/EventsBehaviorRenamer.h" @@ -30,6 +31,7 @@ #include "GDCore/IDE/Events/InstructionsTypeRenamer.h" #include "GDCore/IDE/Events/LinkEventTargetRenamer.h" #include "GDCore/IDE/Events/LeaderboardIdRenamer.h" +#include "GDCore/IDE/Events/ObjectPropertyRenamer.h" #include "GDCore/IDE/Events/ProjectElementRenamer.h" #include "GDCore/IDE/Project/BehaviorObjectTypeRenamer.h" #include "GDCore/IDE/Project/BehaviorsSharedDataBehaviorTypeRenamer.h" @@ -1021,6 +1023,14 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty( auto &properties = eventsBasedBehavior.GetPropertyDescriptors(); if (!properties.Has(oldPropertyName)) return; + const auto &behaviorType = gd::PlatformExtension::GetBehaviorFullType( + eventsFunctionsExtension.GetName(), eventsBasedBehavior.GetName()); + + // Rename the property in behaviors of all objects. + const WholeProjectBrowser projectBrowser; + auto behaviorPropertyRenamer = gd::BehaviorPropertyRenamer( + behaviorType, oldPropertyName, newPropertyName); + projectBrowser.ExposeObjects(project, behaviorPropertyRenamer); if (properties.Get(oldPropertyName).GetType() == "Behavior") { // This is a property representing another behavior that must exist on the @@ -1049,8 +1059,7 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty( gd::ExpressionsRenamer expressionRenamer = gd::ExpressionsRenamer(project.GetCurrentPlatform()); expressionRenamer.SetReplacedBehaviorExpression( - gd::PlatformExtension::GetBehaviorFullType( - eventsFunctionsExtension.GetName(), eventsBasedBehavior.GetName()), + behaviorType, EventsBasedBehavior::GetPropertyExpressionName(oldPropertyName), EventsBasedBehavior::GetPropertyExpressionName(newPropertyName)); gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer); @@ -1173,6 +1182,14 @@ void WholeProjectRefactorer::RenameEventsBasedObjectProperty( auto &properties = eventsBasedObject.GetPropertyDescriptors(); if (!properties.Has(oldPropertyName)) return; + const auto &objectType = gd::PlatformExtension::GetObjectFullType( + eventsFunctionsExtension.GetName(), eventsBasedObject.GetName()); + + // Rename the property in all custom objects of this type. + const WholeProjectBrowser projectBrowser; + auto objectPropertyRenamer = + gd::ObjectPropertyRenamer(objectType, oldPropertyName, newPropertyName); + projectBrowser.ExposeObjects(project, objectPropertyRenamer); // Properties that represent primitive values will be used through // their related actions/conditions/expressions. Rename these. @@ -1185,8 +1202,7 @@ void WholeProjectRefactorer::RenameEventsBasedObjectProperty( gd::ExpressionsRenamer expressionRenamer = gd::ExpressionsRenamer(project.GetCurrentPlatform()); expressionRenamer.SetReplacedObjectExpression( - gd::PlatformExtension::GetObjectFullType( - eventsFunctionsExtension.GetName(), eventsBasedObject.GetName()), + objectType, EventsBasedObject::GetPropertyExpressionName(oldPropertyName), EventsBasedObject::GetPropertyExpressionName(newPropertyName)); gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer); diff --git a/Core/GDCore/Project/BehaviorConfigurationContainer.h b/Core/GDCore/Project/BehaviorConfigurationContainer.h index db871271ec..a3e70d93e2 100644 --- a/Core/GDCore/Project/BehaviorConfigurationContainer.h +++ b/Core/GDCore/Project/BehaviorConfigurationContainer.h @@ -87,6 +87,17 @@ class GD_CORE_API BehaviorConfigurationContainer { return UpdateProperty(content, name, value); }; + /** + * \brief Called when the IDE wants to rename a custom property of the object + * configuration. + * + * \return false if properties can't be renamed + */ + virtual bool RenameProperty(const gd::String &oldName, + const gd::String &newName) { + return RenameProperty(content, oldName, newName); + }; + /** * \brief Called to initialize the content with the default properties * for the behavior. @@ -199,6 +210,18 @@ class GD_CORE_API BehaviorConfigurationContainer { return false; }; + /** + * \brief Called when the IDE wants to rename a custom property of the object + * configuration. + * + * \return false if properties can't be renamed + */ + virtual bool RenameProperty(gd::SerializerElement &behaviorContent, + const gd::String &oldName, + const gd::String &newName) { + return false; + }; + /** * \brief Called to initialize the content with the default properties * for the behavior. diff --git a/Core/GDCore/Project/CustomBehavior.cpp b/Core/GDCore/Project/CustomBehavior.cpp index 0508251114..e46d35ebd5 100644 --- a/Core/GDCore/Project/CustomBehavior.cpp +++ b/Core/GDCore/Project/CustomBehavior.cpp @@ -58,3 +58,17 @@ bool CustomBehavior::UpdateProperty(gd::SerializerElement &behaviorContent, propertyName, newValue); } + +bool CustomBehavior::RenameProperty(gd::SerializerElement &behaviorContent, + const gd::String &oldName, + const gd::String &newName) { + if (!project.HasEventsBasedBehavior(GetTypeName())) { + return false; + } + const auto &eventsBasedBehavior = + project.GetEventsBasedBehavior(GetTypeName()); + const auto &properties = eventsBasedBehavior.GetPropertyDescriptors(); + + return gd::CustomConfigurationHelper::RenameProperty( + properties, behaviorContent, oldName, newName); +} diff --git a/Core/GDCore/Project/CustomBehavior.h b/Core/GDCore/Project/CustomBehavior.h index 52e0c18945..b4ba179958 100644 --- a/Core/GDCore/Project/CustomBehavior.h +++ b/Core/GDCore/Project/CustomBehavior.h @@ -37,6 +37,9 @@ protected: bool UpdateProperty(gd::SerializerElement &behaviorContent, const gd::String &name, const gd::String &value) override; void InitializeContent(gd::SerializerElement &behaviorContent) override; + bool RenameProperty(gd::SerializerElement &behaviorContent, + const gd::String &oldName, + const gd::String &newName) override; private: const Project &project; ///< The project is used to get the diff --git a/Core/GDCore/Project/CustomConfigurationHelper.cpp b/Core/GDCore/Project/CustomConfigurationHelper.cpp index 8880fc8b5f..8701be9a94 100644 --- a/Core/GDCore/Project/CustomConfigurationHelper.cpp +++ b/Core/GDCore/Project/CustomConfigurationHelper.cpp @@ -102,5 +102,19 @@ bool CustomConfigurationHelper::UpdateProperty( element.SetBoolValue(newValue == "1"); } + return true; +} + +bool CustomConfigurationHelper::RenameProperty( + const gd::PropertiesContainer &properties, + gd::SerializerElement &configurationContent, const gd::String &oldName, + const gd::String &newName) { + if (!configurationContent.HasChild(oldName)) { + return false; + } + auto &oldElement = configurationContent.GetChild(oldName); + auto &newElement = configurationContent.AddChild(newName); + newElement = oldElement; + configurationContent.RemoveChild(oldName); return true; } \ No newline at end of file diff --git a/Core/GDCore/Project/CustomConfigurationHelper.h b/Core/GDCore/Project/CustomConfigurationHelper.h index 5f9b57fb43..cdd8e2572f 100644 --- a/Core/GDCore/Project/CustomConfigurationHelper.h +++ b/Core/GDCore/Project/CustomConfigurationHelper.h @@ -3,8 +3,7 @@ * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights * reserved. This project is released under the MIT License. */ -#ifndef GDCORE_CUSTOMCONFIGURATIONHELPER_H -#define GDCORE_CUSTOMCONFIGURATIONHELPER_H +#pragma once #include "GDCore/Project/Behavior.h" #include "GDCore/Project/EventsBasedBehavior.h" @@ -38,7 +37,10 @@ public: gd::SerializerElement &behaviorContent, const gd::String &name, const gd::String &value); + + static bool RenameProperty(const gd::PropertiesContainer &properties, + gd::SerializerElement &behaviorContent, + const gd::String &oldName, + const gd::String &newName); }; } // namespace gd - -#endif // GDCORE_CUSTOMCONFIGURATIONHELPER_H \ No newline at end of file diff --git a/Core/GDCore/Project/CustomObjectConfiguration.cpp b/Core/GDCore/Project/CustomObjectConfiguration.cpp index 4a03bd8e32..a9f252b209 100644 --- a/Core/GDCore/Project/CustomObjectConfiguration.cpp +++ b/Core/GDCore/Project/CustomObjectConfiguration.cpp @@ -131,6 +131,20 @@ bool CustomObjectConfiguration::UpdateProperty(const gd::String& propertyName, newValue); } +bool CustomObjectConfiguration::RenameProperty(const gd::String& oldName, const gd::String& newName) { + if (!project->HasEventsBasedObject(GetType())) { + return false; + } + const auto &eventsBasedObject = project->GetEventsBasedObject(GetType()); + const auto &properties = eventsBasedObject.GetPropertyDescriptors(); + + return gd::CustomConfigurationHelper::RenameProperty( + properties, + objectContent, + oldName, + newName); +}; + std::map CustomObjectConfiguration::GetInitialInstanceProperties( const gd::InitialInstance &initialInstance) { diff --git a/Core/GDCore/Project/CustomObjectConfiguration.h b/Core/GDCore/Project/CustomObjectConfiguration.h index ccbc532a81..4152027fca 100644 --- a/Core/GDCore/Project/CustomObjectConfiguration.h +++ b/Core/GDCore/Project/CustomObjectConfiguration.h @@ -57,6 +57,7 @@ public: std::map GetProperties() const override; bool UpdateProperty(const gd::String& name, const gd::String& value) override; + bool RenameProperty(const gd::String& oldName, const gd::String& newName) override; std::map GetInitialInstanceProperties( const gd::InitialInstance& instance) override; @@ -66,6 +67,7 @@ public: void ExposeResources(gd::ArbitraryResourceWorker& worker) override; + /** * \brief Get the name of the events-based object variant used by this custom object. */ diff --git a/Core/GDCore/Project/ObjectConfiguration.h b/Core/GDCore/Project/ObjectConfiguration.h index ed86abded7..3ef95d9abf 100644 --- a/Core/GDCore/Project/ObjectConfiguration.h +++ b/Core/GDCore/Project/ObjectConfiguration.h @@ -99,6 +99,17 @@ class GD_CORE_API ObjectConfiguration { virtual bool UpdateProperty(const gd::String& name, const gd::String& value) { return false; }; + + /** + * \brief Called when the IDE wants to rename a custom property of the object + * configuration. + * + * \return false if properties can't be renamed + */ + virtual bool RenameProperty(const gd::String &oldName, + const gd::String &newName) { + return false; + }; ///@} /** \name Drawing and editing initial instances diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index 92c3d85098..9685a6809f 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -1053,16 +1053,19 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) { // Add a property: eventsBasedBehavior.GetPropertyDescriptors() .InsertNew("MyProperty", 0) - .SetType("Number"); + .SetType("Number") + .SetValue("0"); // Add a shared property: eventsBasedBehavior.GetSharedPropertyDescriptors() .InsertNew("MySharedProperty", 0) - .SetType("Number"); + .SetType("Number") + .SetValue("0"); // The same name is used for another shared property to ensure there is no name // collision. eventsBasedBehavior.GetSharedPropertyDescriptors() .InsertNew("MyProperty", 0) - .SetType("Number"); + .SetType("Number") + .SetValue("0"); } // Add a events based object @@ -3660,6 +3663,37 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyRenamedProperty])"); } + SECTION("(Events based behavior) property renamed (in objects)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedBehavior = + eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior"); + REQUIRE(eventsBasedBehavior.GetPropertyDescriptors().Has("MyProperty")); + auto &property = + eventsBasedBehavior.GetPropertyDescriptors().Get("MyProperty"); + + auto &layout = project.GetLayout("Scene"); + auto &object = layout.GetObjects().GetObject("ObjectWithMyBehavior"); + auto &behavior = object.GetBehavior("MyBehavior"); + behavior.UpdateProperty("MyProperty", "123"); + { + auto properties = behavior.GetProperties(); + REQUIRE(properties.find("MyProperty") != properties.end()); + REQUIRE(properties.at("MyProperty").GetValue() == "123"); + } + + gd::WholeProjectRefactorer::RenameEventsBasedBehaviorProperty( + project, eventsExtension, eventsBasedBehavior, "MyProperty", + "MyRenamedProperty"); + property.SetName("MyRenamedProperty"); + + auto properties = behavior.GetProperties(); + REQUIRE(properties.find("MyRenamedProperty") != properties.end()); + REQUIRE(properties.at("MyRenamedProperty").GetValue() == "123"); + } + SECTION("(Events based behavior) property renamed (in variable setter)") { gd::Project project; gd::Platform platform; @@ -3924,6 +3958,37 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyRenamedProperty])"); } + SECTION("(Events based object) property renamed (in objects)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedObject = + eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject"); + REQUIRE(eventsBasedObject.GetPropertyDescriptors().Has("MyProperty")); + auto &property = + eventsBasedObject.GetPropertyDescriptors().Get("MyProperty"); + + auto &layout = project.GetLayout("Scene"); + auto &object = layout.GetObjects().GetObject("MyCustomObject"); + auto &objectConfiguration = object.GetConfiguration(); + objectConfiguration.UpdateProperty("MyProperty", "123"); + { + auto properties = objectConfiguration.GetProperties(); + REQUIRE(properties.find("MyProperty") != properties.end()); + REQUIRE(properties.at("MyProperty").GetValue() == "123"); + } + + gd::WholeProjectRefactorer::RenameEventsBasedObjectProperty( + project, eventsExtension, eventsBasedObject, "MyProperty", + "MyRenamedProperty"); + property.SetName("MyRenamedProperty"); + + auto properties = objectConfiguration.GetProperties(); + REQUIRE(properties.find("MyRenamedProperty") != properties.end()); + REQUIRE(properties.at("MyRenamedProperty").GetValue() == "123"); + } + SECTION("(Events based object) property renamed (in variable setter)") { gd::Project project; gd::Platform platform;