Fix property value loss when renaming a property in the extension editor (#8211)

This commit is contained in:
D8H
2026-02-05 19:30:53 +01:00
committed by GitHub
parent c9ab6abb94
commit 080fc35c14
15 changed files with 295 additions and 15 deletions
@@ -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
@@ -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 <set>
#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
+1 -4
View File
@@ -4,8 +4,7 @@
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_BEHAVIORTYPERENAMER_H
#define GDCORE_BEHAVIORTYPERENAMER_H
#pragma once
#include <set>
#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h"
@@ -34,5 +33,3 @@ class GD_CORE_API BehaviorTypeRenamer : public ArbitraryObjectsWorker {
};
}; // namespace gd
#endif // GDCORE_BEHAVIORTYPERENAMER_H
@@ -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
@@ -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 <set>
#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
+20 -4
View File
@@ -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);
@@ -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.
+14
View File
@@ -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);
}
+3
View File
@@ -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
@@ -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;
}
@@ -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
@@ -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<gd::String, gd::PropertyDescriptor>
CustomObjectConfiguration::GetInitialInstanceProperties(
const gd::InitialInstance &initialInstance) {
@@ -57,6 +57,7 @@ public:
std::map<gd::String, gd::PropertyDescriptor> 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<gd::String, gd::PropertyDescriptor> 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.
*/
+11
View File
@@ -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
+68 -3
View File
@@ -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;