diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c076b6f..bfd9cbd4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
it returns only the modules that are part of the data model of the current project. - #630
- Fixed a crash when creating a calculator with missing author information in a Python Task - #627
+### Changed
+ - Python Task and Calculator wizards now automatically transfer changes from the editor to
+ the corresponding data model object whenever the wizard loses focus or is closed.
+ - Removed the `Save` and `Cancel` buttons as well as the "Do you want to save..." message box,
+ since manual saving is no longer required. All changes can be undone via GTlab's Undo/Redo system. - #86
+ - The script editor in the wizard now updates automatically when Undo/Redo is performed in the main GUI.
+
## [1.8.0] - 2025-07-11
### Fixed
diff --git a/src/module/processcomponents/gtpy_abstractscriptcomponent.cpp b/src/module/processcomponents/gtpy_abstractscriptcomponent.cpp
index 7ab7005c..1ea595c4 100644
--- a/src/module/processcomponents/gtpy_abstractscriptcomponent.cpp
+++ b/src/module/processcomponents/gtpy_abstractscriptcomponent.cpp
@@ -226,6 +226,12 @@ GtpyAbstractScriptComponent::outputArg(const QString& argName) const
}
#endif
+const GtStringProperty&
+GtpyAbstractScriptComponent::scriptProp() const
+{
+ return m_script;
+}
+
bool
GtpyAbstractScriptComponent::evalScript(int contextId)
{
diff --git a/src/module/processcomponents/gtpy_abstractscriptcomponent.h b/src/module/processcomponents/gtpy_abstractscriptcomponent.h
index 53e88635..fc603984 100644
--- a/src/module/processcomponents/gtpy_abstractscriptcomponent.h
+++ b/src/module/processcomponents/gtpy_abstractscriptcomponent.h
@@ -108,6 +108,12 @@ class GtpyAbstractScriptComponent
QVariant outputArg(const QString& argName) const;
#endif
+ /**
+ * @brief Returns the script property.
+ * @return Constant reference to the script property.
+ */
+ const GtStringProperty& scriptProp() const;
+
protected:
/**
* @brief GtpyComponentAssistant
diff --git a/src/module/wizards/gtpy_abstractscriptingwizardpage.cpp b/src/module/wizards/gtpy_abstractscriptingwizardpage.cpp
index 0064e00c..c9de4bfa 100644
--- a/src/module/wizards/gtpy_abstractscriptingwizardpage.cpp
+++ b/src/module/wizards/gtpy_abstractscriptingwizardpage.cpp
@@ -34,13 +34,13 @@
#include "gtpy_editorsettingsdialog.h"
#include "gtpy_packageiteration.h"
#include "gtpy_transfer.h"
+#include "gtpy_abstractscriptcomponent.h"
// GTlab framework includes
#include "gt_object.h"
#include "gt_project.h"
#include "gt_datamodel.h"
#include "gt_filedialog.h"
-#include "gt_saveprojectmessagebox.h"
#include "gt_pyhighlighter.h"
#include "gt_searchwidget.h"
@@ -57,7 +57,6 @@ GtpyAbstractScriptingWizardPage::GtpyAbstractScriptingWizardPage(
m_editorSettings(nullptr),
m_isEvaluating(false),
m_runnable(nullptr),
- m_savingEnabled(true),
m_componentUuid(QString())
{
setTitle(tr("Python Script Editor"));
@@ -156,7 +155,6 @@ GtpyAbstractScriptingWizardPage::GtpyAbstractScriptingWizardPage(
fontClearOutput.setItalic(true);
fontClearOutput.setPointSize(7);
shortCutClearOutput->setFont(fontClearOutput);
- shortCutClearOutput->installEventFilter(this);
QVBoxLayout* clearButtonLay = new QVBoxLayout;
@@ -185,25 +183,6 @@ GtpyAbstractScriptingWizardPage::GtpyAbstractScriptingWizardPage(
toolBarLayout->addStretch(1);
- //Save Button
- m_shortCutSave = new QLabel(" Ctrl+S");
- QFont fontSave = m_shortCutSave->font();
- fontSave.setItalic(true);
- fontSave.setPointSize(7);
- m_shortCutSave->setFont(fontSave);
-
- m_saveButton = new QPushButton;
- m_saveButton->setIcon(GTPY_ICON(save));
- m_saveButton->setToolTip(tr("Save Script"));
-
- QVBoxLayout* saveButtonLay = new QVBoxLayout;
- saveButtonLay->addWidget(m_saveButton);
- saveButtonLay->addWidget(m_shortCutSave);
-
- toolBarLayout->addLayout(saveButtonLay);
-
- enableSaveButton(false);
-
//Import Button
QLabel* shortCutImport = new QLabel("");
QFont fontImport = shortCutImport->font();
@@ -311,12 +290,11 @@ void
GtpyAbstractScriptingWizardPage::initializePage()
{
// we want to react, when the wizard should be closed
- if (wizard()) wizard()->installEventFilter(this);
-
- /// Can not be connected in the constructor because onSaveButtonClicked()
- /// calls the pure virtual function saveScript()
- connect(m_saveButton, SIGNAL(clicked(bool)), this,
- SLOT(onSaveButtonClicked()));
+ if (auto* wiz = wizard())
+ {
+ wiz->setOption(QWizard::NoCancelButton, true);
+ wiz->installEventFilter(this);
+ }
setWizardNonModal();
@@ -332,20 +310,22 @@ GtpyAbstractScriptingWizardPage::initializePage()
loadPackages();
- GtObject* component = gtDataModel->objectByUuid(m_componentUuid);
+ GtObject* obj = gtDataModel->objectByUuid(m_componentUuid);
+ if (!obj) return;
- if (!component)
- {
- enableSaving(false);
- }
- else
- {
- setTitle(component->objectName());
- connect(component, SIGNAL(objectNameChanged(QString)), this,
- SLOT(componentRenamed(QString)));
- }
+ setTitle(obj->objectName());
+
+ connect(obj, &GtObject::objectNameChanged, this,
+ &GtpyAbstractScriptingWizardPage::componentRenamed);
+
+ auto* comp = dynamic_cast(obj);
+ if (!comp) return;
- connect(m_editor, SIGNAL(textChanged()), this, SLOT(onTextChanged()));
+ using DataChanged = void (GtObject::*)(GtObject*, GtAbstractProperty*);
+ connect(obj, static_cast(&GtObject::dataChanged),
+ this, [this, c = comp](GtObject* obj, GtAbstractProperty* prop) {
+ if (c && prop == &c->scriptProp()) setPlainTextToEditor(c->script());
+ });
}
bool
@@ -365,16 +345,6 @@ GtpyAbstractScriptingWizardPage::keyPressEvent(QKeyEvent* e)
// Ignore return
return;
- case Qt::Key_Escape:
-
- if (m_saveButton->isEnabled())
- {
- saveMessageBox();
- return;
- }
-
- break;
-
default:
break;
}
@@ -400,14 +370,6 @@ GtpyAbstractScriptingWizardPage::keyPressEvent(QKeyEvent* e)
return;
}
- // Save shortcut
- if (((e->modifiers() & Qt::ControlModifier) &&
- e->key() == Qt::Key_S))
- {
- onSaveButtonClicked();
- return;
- }
-
GtProcessWizardPage::keyPressEvent(e);
}
@@ -457,16 +419,16 @@ GtpyAbstractScriptingWizardPage::defaultFrameStyle()
void
GtpyAbstractScriptingWizardPage::setPlainTextToEditor(const QString& text)
{
- if (!m_editor)
- {
- return;
- }
+ assert(m_editor);
+ assert(m_searchWidget);
m_editor->setPlainText(text);
+ m_editor->setExtraSelections({});
+ m_editor->searchHighlighting(m_searchWidget->text());
}
QString
-GtpyAbstractScriptingWizardPage::editorText()
+GtpyAbstractScriptingWizardPage::editorText() const
{
if (!m_editor)
{
@@ -710,21 +672,6 @@ GtpyAbstractScriptingWizardPage::onEvalShortCutTriggered()
}
}
-void
-GtpyAbstractScriptingWizardPage::onSaveButtonClicked()
-{
- connect(m_editor, SIGNAL(textChanged()), this, SLOT(onTextChanged()));
- saveScript();
- enableSaveButton(false);
-}
-
-void
-GtpyAbstractScriptingWizardPage::onTextChanged()
-{
- enableSaveButton(true);
- disconnect(m_editor, SIGNAL(textChanged()), this, SLOT(onTextChanged()));
-}
-
void
GtpyAbstractScriptingWizardPage::initialization()
{
@@ -738,66 +685,26 @@ GtpyAbstractScriptingWizardPage::validation()
}
void
-GtpyAbstractScriptingWizardPage::enableSaveButton(bool enable)
+GtpyAbstractScriptingWizardPage::saveScript()
{
- if (!m_savingEnabled)
- {
- enable = false;
- }
-
- m_saveButton->setEnabled(enable);
-
- if (enable)
- {
- m_shortCutSave->setText(" Ctrl+S");
- }
- else
- {
- m_shortCutSave->setText("");
- }
+ storeScriptInDataModel();
}
-int
-GtpyAbstractScriptingWizardPage::saveMessageBox()
+void
+GtpyAbstractScriptingWizardPage::storeScriptInDataModel() const
{
- QWidget* wiz = wizard();
-
- if (!wiz)
- {
- return QMessageBox::Cancel;
- }
-
- QString text =
- tr("Do you want to ") +
- tr("save your changes before closing the wizard?");
-
- GtSaveProjectMessageBox mb(text);
-
- mb.setWindowFlags(mb.windowFlags() | Qt::WindowStaysOnTopHint);
+ GtObject* obj = gtDataModel->objectByUuid(componentUuid());
+ if (!obj) return;
- int ret;
+ auto* comp = dynamic_cast(obj);
+ if (!comp) return;
- ret = mb.exec();
+ const QString script = editorText();
+ if (comp->script() == script) return;
- switch (ret)
- {
- case QMessageBox::Yes:
- onSaveButtonClicked();
- wiz->close();
- break;
-
- case QMessageBox::No:
- wiz->close();
- break;
-
- case QMessageBox::Cancel:
- break;
-
- default:
- break;
- }
-
- return ret;
+ auto _ = gtApp->makeCommand(obj, tr("%1 code changed")
+ .arg(obj->objectName()));
+ comp->setScript(script);
}
void
@@ -961,24 +868,26 @@ GtpyAbstractScriptingWizardPage::cursorToNewLine()
m_editor->setTextCursor(cur);
}
-void
-GtpyAbstractScriptingWizardPage::enableSaving(bool enable)
-{
- m_savingEnabled = enable;
-}
-
-
bool
-GtpyAbstractScriptingWizardPage::eventFilter(QObject *watched, QEvent *event)
+GtpyAbstractScriptingWizardPage::eventFilter(QObject* watched, QEvent* event)
{
- if (event && event->type() == QEvent::Close && m_saveButton->isEnabled())
+ if (!watched || !event)
+ {
+ return GtProcessWizardPage::eventFilter(watched, event);
+ }
+
+ switch (event->type())
{
- if (saveMessageBox() == QMessageBox::Cancel) event->ignore();
+ // this event occurs when the wizard loses focus
+ case QEvent::WindowDeactivate:
+ if (watched == wizard()) storeScriptInDataModel();
+ break;
- return true;
+ default:
+ break;
}
- return QObject::eventFilter(watched, event);
+ return GtProcessWizardPage::eventFilter(watched, event);
}
void
diff --git a/src/module/wizards/gtpy_abstractscriptingwizardpage.h b/src/module/wizards/gtpy_abstractscriptingwizardpage.h
index a0fbe72c..fb59c0ca 100644
--- a/src/module/wizards/gtpy_abstractscriptingwizardpage.h
+++ b/src/module/wizards/gtpy_abstractscriptingwizardpage.h
@@ -107,7 +107,7 @@ class GT_PYTHON_EXPORT GtpyAbstractScriptingWizardPage :
* @brief Returns current text in editor widget.
* @return current text in editor widget
*/
- QString editorText();
+ QString editorText() const;
/**
* @brief Replaces the calculator settings between header and caption with
@@ -198,20 +198,13 @@ class GT_PYTHON_EXPORT GtpyAbstractScriptingWizardPage :
*/
void cursorToNewLine();
- /**
- * @brief Enable or disable the saving functionality.
- * @param enable Whether the saving functionality should be enabled or
- * disabled.
- */
- void enableSaving(bool enable = true);
-
/// Python Context id
int m_contextId;
/**
* To intercept closing events
*/
- bool eventFilter(QObject *watched, QEvent *event) override;
+ bool eventFilter(QObject* watched, QEvent* event) override;
/// Python Context type
GtpyContextManager::Context m_contextType;
@@ -242,10 +235,14 @@ protected slots:
virtual bool validation();
/**
- * @brief In this pure virtual function the routine for saving a script
- * must be implemented.
+ * @brief Calls storeScriptInDataModel() to apply the current script
+ * from the editor to the data model.
+ *
+ * This method is retained for backward compatibility.
+ * Use storeScriptInDataModel() directly instead.
*/
- virtual void saveScript() = 0;
+ [[deprecated("Use storeScriptInDataModel() instead")]]
+ virtual void saveScript() final;
/**
* @brief This pure virtual function must return the uuid of the restored
@@ -276,16 +273,9 @@ protected slots:
virtual void saveSettings(GtpyEditorSettings* pref) = 0;
/**
- * @brief Enables of disables the save button.
- * @param enable If true, the Save button is enabled, otherwise it is
- * disabled.
- */
- void enableSaveButton(bool enable = true);
-
- /**
- * @brief saveMesssageBox
+ * @brief Applies the current script from the editor to the data model.
*/
- int saveMessageBox();
+ void storeScriptInDataModel() const;
/**
* @brief Sets the window modality of the wizard to non modal. This allows
@@ -341,12 +331,6 @@ protected slots:
/// Interrupt shortcut lable
QLabel* m_shortCutInterrupt;
- /// Save Button
- QPushButton* m_saveButton;
-
- /// Save shortcut label
- QLabel* m_shortCutSave;
-
/// Console Clear Button
QPushButton* m_consoleClearButton;
@@ -373,9 +357,6 @@ protected slots:
/// Script runnable for evaluation
QPointer m_runnable;
- /// Saving the script
- bool m_savingEnabled;
-
/// Process component uuid
QString m_componentUuid;
@@ -443,16 +424,6 @@ private slots:
*/
void onEvalShortCutTriggered();
- /**
- * @brief It saves the current script.
- */
- void onSaveButtonClicked();
-
- /**
- * @brief Enables the save button.
- */
- void onTextChanged();
-
/**
* @brief Checks if the evaluation was successful.
*/
diff --git a/src/module/wizards/python_task/gtpy_taskwizardpage.cpp b/src/module/wizards/python_task/gtpy_taskwizardpage.cpp
index c8e3ea0b..b7384245 100644
--- a/src/module/wizards/python_task/gtpy_taskwizardpage.cpp
+++ b/src/module/wizards/python_task/gtpy_taskwizardpage.cpp
@@ -38,7 +38,6 @@
#include "gt_project.h"
#include "gt_calculatorprovider.h"
#include "gt_processwizard.h"
-#include "gt_command.h"
#include "gt_application.h"
#include "gt_deleteitemmessagebox.h"
#include "gt_calculator.h"
@@ -156,11 +155,6 @@ GtpyTaskWizardPage::initialization()
gtpy::transfer::propStructToPython(m_contextId, m_task->outputArgs());
#endif
- if (!gtDataModel->objectByUuid(m_task->uuid()))
- {
- enableSaving(false);
- }
-
enableCalculators(m_task);
m_task->setFlag(GtObject::NewlyCreated, false);
@@ -235,36 +229,6 @@ GtpyTaskWizardPage::validation()
return true;
}
-void
-GtpyTaskWizardPage::saveScript()
-{
- if (!m_task)
- {
- return;
- }
-
- m_task->setScript(editorText());
-
- GtObjectMemento memento = m_task->toMemento();
-
- if (memento.isNull())
- {
- return;
- }
-
- GtObject* obj = gtDataModel->objectByUuid(m_task->uuid());
-
- if (GtTask* task = qobject_cast(obj))
- {
- GtCommand command =
- gtApp->startCommand(gtApp->currentProject(),
- task->objectName() +
- tr(" configuration changed"));
- task->fromMemento(memento);
- gtApp->endCommand(command);
- }
-}
-
QString
GtpyTaskWizardPage::componentUuid() const
{
diff --git a/src/module/wizards/python_task/gtpy_taskwizardpage.h b/src/module/wizards/python_task/gtpy_taskwizardpage.h
index fefdded4..16f7b97f 100644
--- a/src/module/wizards/python_task/gtpy_taskwizardpage.h
+++ b/src/module/wizards/python_task/gtpy_taskwizardpage.h
@@ -57,11 +57,6 @@ class GT_PYTHON_EXPORT GtpyTaskWizardPage :
*/
virtual bool validation() override;
- /**
- * @brief Saves the script into the task instance.
- */
- virtual void saveScript() override;
-
/**
* @brief Returns the uuid of the restored GtpyScriptCalculator.
* @return uuid of the restored GtpyScriptCalculator
diff --git a/src/module/wizards/script_calculator/gtpy_scriptcalculatorwizardpage.cpp b/src/module/wizards/script_calculator/gtpy_scriptcalculatorwizardpage.cpp
index f6f09ef3..3c1444e5 100644
--- a/src/module/wizards/script_calculator/gtpy_scriptcalculatorwizardpage.cpp
+++ b/src/module/wizards/script_calculator/gtpy_scriptcalculatorwizardpage.cpp
@@ -10,10 +10,6 @@
// GTlab framework includes
-#include "gt_datamodel.h"
-#include "gt_command.h"
-#include "gt_application.h"
-#include "gt_project.h"
#include "gt_processfactory.h"
#include "gt_objectmemento.h"
#include "gt_abstractprocessprovider.h"
@@ -58,11 +54,6 @@ GtpyScriptCalculatorWizardPage::initialization()
gtpy::transfer::propStructToPython(m_contextId, m_calc->outputArgs());
#endif
- if (!gtDataModel->objectByUuid(m_calc->uuid()))
- {
- enableSaving(false);
- }
-
setPlainTextToEditor(m_calc->script());
}
@@ -80,36 +71,6 @@ GtpyScriptCalculatorWizardPage::validation()
return true;
}
-void
-GtpyScriptCalculatorWizardPage::saveScript()
-{
- if (!m_calc)
- {
- return;
- }
-
- m_calc->setScript(editorText());
-
- GtObjectMemento memento = m_calc->toMemento();
-
- if (memento.isNull())
- {
- return;
- }
-
- auto* obj = gtDataModel->objectByUuid(m_calc->uuid());
-
- if (auto* calc = qobject_cast(obj))
- {
- GtCommand command =
- gtApp->startCommand(gtApp->currentProject(),
- calc->objectName() +
- tr(" configuration changed"));
- calc->fromMemento(memento);
- gtApp->endCommand(command);
- }
-}
-
QString
GtpyScriptCalculatorWizardPage::componentUuid() const
{
diff --git a/src/module/wizards/script_calculator/gtpy_scriptcalculatorwizardpage.h b/src/module/wizards/script_calculator/gtpy_scriptcalculatorwizardpage.h
index 3f304279..068057d7 100644
--- a/src/module/wizards/script_calculator/gtpy_scriptcalculatorwizardpage.h
+++ b/src/module/wizards/script_calculator/gtpy_scriptcalculatorwizardpage.h
@@ -39,11 +39,6 @@ class GtpyScriptCalculatorWizardPage : public GtpyAbstractScriptingWizardPage
*/
virtual bool validation() override;
- /**
- * @brief Saves the script into the calculator instance.
- */
- virtual void saveScript() override;
-
/**
* @brief Returns the uuid of the restored GtpyScriptCalculator.
* @return uuid of the restored GtpyScriptCalculator