diff --git a/src/webots/gui/WbMainWindow.cpp b/src/webots/gui/WbMainWindow.cpp index 91638c2a306..f4dabfbda85 100644 --- a/src/webots/gui/WbMainWindow.cpp +++ b/src/webots/gui/WbMainWindow.cpp @@ -38,6 +38,7 @@ #include "WbNewProjectWizard.hpp" #include "WbNewProtoWizard.hpp" #include "WbNewWorldWizard.hpp" +#include "WbNodeEditor.hpp" #include "WbNodeOperations.hpp" #include "WbNodeUtilities.hpp" #include "WbOdeDebugger.hpp" @@ -1343,6 +1344,10 @@ void WbMainWindow::updateAfterWorldLoading(bool reloading, bool firstLoad) { if (world->fileName() != WbProject::newWorldPath()) mRecentFiles->makeRecent(world->fileName()); + connect(this, &WbMainWindow::worldCreated, WbNodeEditor::instance(), &WbNodeEditor::tryConnectToWorld); + emit worldCreated(); + disconnect(this, &WbMainWindow::worldCreated, WbNodeEditor::instance(), &WbNodeEditor::tryConnectToWorld); + mSimulationView->setWorld(WbSimulationWorld::instance()); // update 'view' menu diff --git a/src/webots/gui/WbMainWindow.hpp b/src/webots/gui/WbMainWindow.hpp index fe7f2a88660..cfff58e8699 100644 --- a/src/webots/gui/WbMainWindow.hpp +++ b/src/webots/gui/WbMainWindow.hpp @@ -74,6 +74,7 @@ class WbMainWindow : public QMainWindow { signals: void restartRequested(); void splashScreenCloseRequested(); + void worldCreated(); public slots: void loadDifferentWorld(const QString &fileName); diff --git a/src/webots/nodes/utils/WbWorld.cpp b/src/webots/nodes/utils/WbWorld.cpp index d7f886c4234..63f2afed9a0 100644 --- a/src/webots/nodes/utils/WbWorld.cpp +++ b/src/webots/nodes/utils/WbWorld.cpp @@ -225,17 +225,21 @@ bool WbWorld::needSaving() const { } void WbWorld::setModifiedFromSceneTree() { - if (!mIsModifiedFromSceneTree) { - mIsModifiedFromSceneTree = true; - setModified(); + mIsModifiedFromSceneTree = true; + + if (isModifiedFromSceneTreeRestarted) { + mIsModifiedFromSceneTree = false; + isModifiedFromSceneTreeRestarted = false; } } +void WbWorld::resetModifiedFromSceneTree() { + mIsModifiedFromSceneTree = false; + isModifiedFromSceneTreeRestarted = true; +} + void WbWorld::setModified(bool isModified) { - if (mIsModified != isModified) { - mIsModified = isModified; - emit modificationChanged(isModified); - } + emit checkDefDiff(); } bool WbWorld::saveAs(const QString &fileName) { diff --git a/src/webots/nodes/utils/WbWorld.hpp b/src/webots/nodes/utils/WbWorld.hpp index f84d4054152..2a2a45de854 100644 --- a/src/webots/nodes/utils/WbWorld.hpp +++ b/src/webots/nodes/utils/WbWorld.hpp @@ -158,12 +158,14 @@ class WbWorld : public QObject { void robotAdded(WbRobot *robot); void robotRemoved(WbRobot *robot); void resetRequested(bool restartControllers); + void checkDefDiff(); public slots: void awake(); void updateVideoRecordingStatus(int status) { mIsVideoRecording = (status == WB_SUPERVISOR_MOVIE_RECORDING || status == WB_SUPERVISOR_MOVIE_SAVING); } + void resetModifiedFromSceneTree(); protected: // collecting contact and immersion geometries @@ -199,6 +201,7 @@ protected slots: bool mIsLoading; bool mIsCleaning; bool mIsVideoRecording; + bool isModifiedFromSceneTreeRestarted = false; void checkPresenceOfMandatoryNodes(); WbNode *findTopLevelNode(const QString &modelName, int preferredPosition) const; diff --git a/src/webots/scene_tree/WbFieldEditor.cpp b/src/webots/scene_tree/WbFieldEditor.cpp index 1d72afafb74..97e924af7c0 100644 --- a/src/webots/scene_tree/WbFieldEditor.cpp +++ b/src/webots/scene_tree/WbFieldEditor.cpp @@ -87,9 +87,18 @@ WbFieldEditor::WbFieldEditor(QWidget *parent) : connect(stringEditor, &WbExtendedStringEditor::editRequested, this, &WbFieldEditor::editRequested); WbNodePane *const nodePane = new WbNodePane(this); - const WbNodeEditor *nodeEditor = nodePane->nodeEditor(); + const WbNodeEditor *nodeEditor = WbNodeEditor::instance(nodePane); connect(nodeEditor, &WbNodeEditor::dictionaryUpdateRequested, this, &WbFieldEditor::dictionaryUpdateRequested); - + connect(nodeEditor, &WbNodeEditor::defNameChanged, WbActionManager::instance()->action(WbAction::SAVE_WORLD), + &QAction::setEnabled); + connect(WbActionManager::instance()->action(WbAction::SAVE_WORLD), &QAction::setEnabled, nodeEditor, + &WbNodeEditor::resetDefNamesToInitial); + connect(WbActionManager::instance()->action(WbAction::SAVE_WORLD_AS), &QAction::setEnabled, nodeEditor, + &WbNodeEditor::resetDefNamesToInitial); + connect(WbActionManager::instance()->action(WbAction::SAVE_WORLD), &QAction::triggered, nodeEditor, + &WbNodeEditor::switchInitialCurrentDef); + connect(WbActionManager::instance()->action(WbAction::SAVE_WORLD_AS), &QAction::triggered, nodeEditor, + &WbNodeEditor::switchInitialCurrentDef); // create editors mEditors.insert(WB_NO_FIELD, new WbEmptyEditor(this)); mEditors.insert(WB_SF_BOOL, new WbBoolEditor(this)); diff --git a/src/webots/scene_tree/WbNodeEditor.cpp b/src/webots/scene_tree/WbNodeEditor.cpp index 1950c6e5dbb..1dbbe07ba44 100644 --- a/src/webots/scene_tree/WbNodeEditor.cpp +++ b/src/webots/scene_tree/WbNodeEditor.cpp @@ -14,6 +14,7 @@ #include "WbNodeEditor.hpp" +#include "WbActionManager.hpp" #include "WbBaseNode.hpp" #include "WbField.hpp" #include "WbFieldLineEdit.hpp" @@ -32,9 +33,11 @@ #include "WbTransform.hpp" #include "WbViewpoint.hpp" #include "WbVrmlNodeUtilities.hpp" +#include "WbWorld.hpp" #include "WbWorldInfo.hpp" #include +#include #include #include #include @@ -43,6 +46,14 @@ #include #include +WbNodeEditor *WbNodeEditor::cInstance = nullptr; + +WbNodeEditor *WbNodeEditor::instance(QWidget *parent) { + if (!cInstance) + cInstance = new WbNodeEditor(parent); + return cInstance; +} + WbNodeEditor::WbNodeEditor(QWidget *parent) : WbValueEditor(parent), mNode(NULL), @@ -87,6 +98,14 @@ WbNodeEditor::WbNodeEditor(QWidget *parent) : connect(mPrintUrl, &QPushButton::pressed, this, &WbNodeEditor::printUrl); connect(mShowResizeHandlesCheckBox, &QAbstractButton::toggled, WbSelection::instance(), &WbSelection::showResizeManipulatorFromSceneTree, Qt::UniqueConnection); + state = WbSimulationState::instance(); +} + +void WbNodeEditor::tryConnectToWorld() { + world = WbWorld::instance(); + connect(world, &WbWorld::checkDefDiff, this, &WbNodeEditor::resetDefNamesToInitial); + connect(this, &WbNodeEditor::resetModifiedFromSceneTree, world, &WbWorld::resetModifiedFromSceneTree); + mInitialCurrentDefMap.clear(); } void WbNodeEditor::printUrl() { @@ -131,6 +150,9 @@ void WbNodeEditor::edit(bool copyOriginalValue) { mShowResizeHandlesCheckBox->setChecked(g->isResizeManipulatorAttached()); } } + + if (mNode && !mInitialCurrentDefMap.contains(mNode)) + mInitialCurrentDefMap[mNode] = QPair(mNode->defName(), QString()); } update(); @@ -198,6 +220,12 @@ void WbNodeEditor::apply() { QString newDef = mDefEdit->text(); const QString &previousDef = mNode->defName(); + mInitialCurrentDefMap[mNode].second = newDef; + + bool hasStarted = state->hasStarted(); + if (!hasStarted) + this->compareInitialCurrentDef(); + if (newDef == previousDef) return; @@ -259,3 +287,74 @@ void WbNodeEditor::apply() { if (dictionaryUpdateRequest) emit dictionaryUpdateRequested(); } + +void WbNodeEditor::compareInitialCurrentDef() { + if (!mInitialCurrentDefMap.isEmpty()) { + bool foundDifference = false; + // Iterate through the QMap + for (auto it = mInitialCurrentDefMap.constBegin(); it != mInitialCurrentDefMap.constEnd(); ++it) { + const QString &initialDef = it.value().first; // First QString (initial) + const QString ¤tDef = it.value().second; // Second QString (current) + + // Compare the two QStrings + if (initialDef != currentDef) { + foundDifference = true; // Mark that a difference is found + break; + } + } + if (foundDifference) + emit defNameChanged(true); // Emit true if any difference is found + else { + emit resetModifiedFromSceneTree(); + emit defNameChanged(false); // Emit false if no differences were found + } + } else + emit defNameChanged(false); // If all QStrings are the same, return false +} + +void WbNodeEditor::resetDefNamesToInitial() { + // Check if the map is empty + if (mInitialCurrentDefMap.isEmpty()) { + emit defNameChanged(false); + return; + } + + // Iterate through the map and reset each node's DEF name to its initial value + for (auto it = mInitialCurrentDefMap.begin(); it != mInitialCurrentDefMap.end(); ++it) { + WbNode *node = it.key(); + const QString &initialDef = it.value().first; // Access initial DEF name + + // Only reset if node exists and the current DEF differs from the initial one + if (node && node->defName() != initialDef) + node->setDefName(initialDef); // Set the DEF name back to the initial one + } + + update(); + + emit defNameChanged(false); + emit resetModifiedFromSceneTree(); +} + +void WbNodeEditor::switchInitialCurrentDef() { + // Check if the map is empty + if (mInitialCurrentDefMap.isEmpty()) { + emit defNameChanged(false); + return; + } + + // Iterate through the map and switch the initial DEF to the current one + for (auto it = mInitialCurrentDefMap.begin(); it != mInitialCurrentDefMap.end(); ++it) { + const WbNode *node = it.key(); + + // Switch the initial DEF to the current DEF + if (node) { + QString &initialDef = it.value().first; // Reference to initial DEF name + const QString ¤tDef = node->defName(); // Get the current DEF name of the node + initialDef = currentDef; // Update the initial DEF with the current one + } + } + + update(); + + emit defNameChanged(false); +} diff --git a/src/webots/scene_tree/WbNodeEditor.hpp b/src/webots/scene_tree/WbNodeEditor.hpp index 8c1932a1b40..a4e781f4b2c 100644 --- a/src/webots/scene_tree/WbNodeEditor.hpp +++ b/src/webots/scene_tree/WbNodeEditor.hpp @@ -19,7 +19,9 @@ // Description: editor for editing a WbSFNode or a WbMFNode item // +#include "WbSimulationState.hpp" #include "WbValueEditor.hpp" +#include "WbWorld.hpp" class WbFieldLineEdit; class WbNode; @@ -33,7 +35,7 @@ class WbNodeEditor : public WbValueEditor { Q_OBJECT public: - explicit WbNodeEditor(QWidget *parent = NULL); + static WbNodeEditor *instance(QWidget *parent = nullptr); void recursiveBlockSignals(bool block) override; @@ -47,10 +49,15 @@ class WbNodeEditor : public WbValueEditor { signals: void dictionaryUpdateRequested(); + void defNameChanged(bool changed); + void resetModifiedFromSceneTree(); public slots: void apply() override; void cleanValue() override; + void resetDefNamesToInitial(); + void switchInitialCurrentDef(); + void tryConnectToWorld(); protected: enum PaneType { DEF_PANE, EMPTY_PANE }; @@ -63,14 +70,20 @@ public slots: QLabel *mNbTriangles; QStackedWidget *mStackedWidget; bool mMessageBox; + QMap> mInitialCurrentDefMap; + const WbWorld *world = nullptr; + WbSimulationState *state = nullptr; + static WbNodeEditor *cInstance; // actions buttons QLabel *mShowResizeHandlesLabel; QCheckBox *mShowResizeHandlesCheckBox; + explicit WbNodeEditor(QWidget *parent = nullptr); void setTransformActionVisibile(bool visible); void takeKeyboardFocus() override {} void printUrl(); + void compareInitialCurrentDef(); }; #endif diff --git a/src/webots/scene_tree/WbNodePane.cpp b/src/webots/scene_tree/WbNodePane.cpp index acc58332b58..4ed05d0d10f 100644 --- a/src/webots/scene_tree/WbNodePane.cpp +++ b/src/webots/scene_tree/WbNodePane.cpp @@ -33,7 +33,7 @@ static const QStringList cTabNames = QStringList() << "Node" WbNodePane::WbNodePane(QWidget *parent) : WbValueEditor(parent), mTabs(new QTabWidget(this)), - mNodeEditor(new WbNodeEditor()), + mNodeEditor(WbNodeEditor::instance()), mPhysicsViewer(new WbPhysicsViewer()), mPositionViewer(new WbPositionViewer()), mVelocityViewer(new WbVelocityViewer()),