Release QObject connections on refresh.

This commit is contained in:
Shaun Reed 2025-03-23 17:29:56 -04:00
parent 48598de9c8
commit 8eaebee2c6
3 changed files with 135 additions and 69 deletions

View File

@ -17,11 +17,10 @@
using namespace Qtk; using namespace Qtk;
ToolBox::ToolBox(QWidget * parent) : ToolBox::ToolBox(QWidget * parent) :
QDockWidget(parent), objectDetails_(this), QDockWidget(parent), objectDetails_(this), transformPanel_(this),
transformPanel_(this, "Transform:"), scalePanel_(this, "Scale:"), scalePanel_(this), vertex_(this, "Vertex Shader:"),
vertex_(this, "Vertex Shader:"), fragment_(this, "Fragment Shader:"), fragment_(this, "Fragment Shader:"), properiesForm_(new QFormLayout),
properiesForm_(new QFormLayout), shaderForm_(new QFormLayout), shaderForm_(new QFormLayout), ui(new Ui::ToolBox)
ui(new Ui::ToolBox)
{ {
ui->setupUi(this); ui->setupUi(this);
setMinimumWidth(350); setMinimumWidth(350);
@ -51,17 +50,60 @@ void ToolBox::updateFocus(const QString & name)
} }
} }
ToolBox::SpinBox3D::SpinBox3D(QLayout * layout, const char * l) : ToolBox::SpinBox3D::SpinBox3D(QWidget * parent, const char * l) :
label(new QLabel(tr(l))), x(new QDoubleSpinBox), y(new QDoubleSpinBox), QWidget(parent), layout(new QHBoxLayout(this)), label(new QLabel(tr(l)))
z(new QDoubleSpinBox), fields({x, y, z})
{ {
// The layout owns the widget and will clean it up on destruction. // The layout owns the widget and will clean it up on destruction.
layout->addWidget(label); layout->addWidget(label);
for (const auto & f : fields) { for (const auto & f : fields) {
layout->addWidget(f); layout->addWidget(f->spinBox);
f->setMinimum(std::numeric_limits<double>::lowest()); f->spinBox->setMinimum(std::numeric_limits<double>::lowest());
f->setSingleStep(0.1); f->spinBox->setSingleStep(0.1);
f->setFixedWidth(75); f->spinBox->setFixedWidth(75);
}
}
void ToolBox::SpinBox::disconnect() const
{
Object::disconnect(connection);
}
void ToolBox::TransformPanel::setObject(const Qtk::Object * object)
{
// Reconnect translation panel controls to the new object.
const std::vector binds = {&Object::setTranslationX,
&Object::setTranslationY,
&Object::setTranslationZ};
for (size_t i = 0; i < spinBox3D.fields.size(); i++) {
auto * f = spinBox3D.fields[i];
// Disconnect before changing spin box value.
f->disconnect();
// Set the values in the spin box to the object's current X,Y,Z
f->spinBox->setValue(object->getTransform().getTranslation()[i]);
// Reconnect to bind spin box value to the new object's position.
f->connection =
connect(f->spinBox, &QDoubleSpinBox::valueChanged, object, binds[i]);
}
}
void ToolBox::ScalePanel::setObject(const Qtk::Object * object)
{
// Reconnect scale panel controls to the new object.
const std::vector binds = {
&Object::setScaleX, &Object::setScaleY, &Object::setScaleZ};
for (size_t i = 0; i < spinBox3D.fields.size(); i++) {
auto * f = spinBox3D.fields[i];
// Disconnect before changing spin box value.
f->disconnect();
// Set the values in the spin box to the object's current X,Y,Z
f->spinBox->setValue(object->getTransform().getScale()[i]);
// Reconnect to bind spin box value to the new object's scale.
f->connection =
connect(f->spinBox, &QDoubleSpinBox::valueChanged, object, binds[i]);
} }
} }
@ -72,44 +114,11 @@ ToolBox::~ToolBox()
void ToolBox::refreshProperties(const Object * object) void ToolBox::refreshProperties(const Object * object)
{ {
objectDetails_.setDetails(object); // Refresh to show the new object's details.
objectDetails_.setObject(object);
// Reconnect transform panel controls to the new object. // Reconnect transform panel controls to the new object.
connect(transformPanel_.spinBox.x, transformPanel_.setObject(object);
&QDoubleSpinBox::valueChanged, scalePanel_.setObject(object);
object,
&Object::setTranslationX);
connect(transformPanel_.spinBox.y,
&QDoubleSpinBox::valueChanged,
object,
&Object::setTranslationY);
connect(transformPanel_.spinBox.z,
&QDoubleSpinBox::valueChanged,
object,
&Object::setTranslationZ);
// Set the values in the spin box to the object's current X,Y,Z position.
auto transform = object->getTransform();
for (size_t i = 0; i < 3; i++) {
transformPanel_.spinBox.fields[i]->setValue(transform.getTranslation()[i]);
}
// Reconnect scale panel controls to the new object.
connect(scalePanel_.spinBox.x,
&QDoubleSpinBox::valueChanged,
object,
&Object::setScaleX);
connect(scalePanel_.spinBox.y,
&QDoubleSpinBox::valueChanged,
object,
&Object::setScaleY);
connect(scalePanel_.spinBox.z,
&QDoubleSpinBox::valueChanged,
object,
&Object::setScaleZ);
// Set the values in the spin box to the object's current X,Y,Z scale.
for (size_t i = 0; i < 3; i++) {
scalePanel_.spinBox.fields[i]->setValue(transform.getScale()[i]);
}
} }
void ToolBox::refreshShaders(const Object * object) void ToolBox::refreshShaders(const Object * object)

View File

@ -84,7 +84,8 @@ namespace Qtk
{ {
} }
void setDetails(const Qtk::Object * object) /// Refresh to display the new object's details
void setObject(const Qtk::Object * object)
{ {
name.setItem(tr("Name:"), object->getName()); name.setItem(tr("Name:"), object->getName());
objectType.setItem( objectType.setItem(
@ -96,32 +97,67 @@ namespace Qtk
}; };
ObjectDetails objectDetails_; ObjectDetails objectDetails_;
/// Spinbox with 3 fields and label. /// Structure to associate a QSpinBox with a connection.
struct SpinBox3D { struct SpinBox {
/// We pass a layout to ensure Qt will clean up resources. /**
explicit SpinBox3D(QLayout * layout, const char * l = "SpinBox3D:"); * Default constructor passes no parent to the QSpinBox.
* It must be added to a layout for Qt to clean up the resources.
*/
SpinBox() : spinBox(new QDoubleSpinBox) {}
QLabel * label; /// Disconnect the associated connection.
QDoubleSpinBox * x; void disconnect() const;
QDoubleSpinBox * y;
QDoubleSpinBox * z; QDoubleSpinBox * spinBox;
std::array<QDoubleSpinBox *, 3> fields; QMetaObject::Connection connection;
}; };
/// Transform controls and layout. /// Spinbox with 3 fields and a single label.
class SpinBoxHorizontal3D : QWidget class SpinBox3D final : QWidget
{ {
public: public:
explicit SpinBoxHorizontal3D( /// We pass a parent to ensure Qt will clean up resources.
QWidget * parent, const char * l = "SpinBoxHorizontal3D:") : /// Assigning a QWidget to a QLayout also ensures Qt will clean up.
QWidget(parent), layout(new QHBoxLayout(this)), spinBox(layout, l) explicit SpinBox3D(QWidget * parent, const char * l = "SpinBox3D:");
/// The main layout for the SpinBox3D widget.
QHBoxLayout * layout {this};
/// Label for the SpinBox3D.
QLabel * label;
/// SpinBox and a connection for each field.
SpinBox x, y, z;
/// Array for iterating over fields.
std::array<SpinBox *, 3> fields {&x, &y, &z};
};
/// Initialize the transform panel and configure QObject connections.
struct TransformPanel {
explicit TransformPanel(QWidget * parent) :
spinBox3D(parent, "Transform:")
{ {
} }
QHBoxLayout * layout;
SpinBox3D spinBox;
};
SpinBoxHorizontal3D transformPanel_, scalePanel_;
/// Reconnect QObject connections and spin box values in UI.
void setObject(const Qtk::Object * object);
SpinBox3D spinBox3D;
};
TransformPanel transformPanel_;
/// Initialize the scale panel and configure QObject connections.
struct ScalePanel {
explicit ScalePanel(QWidget * parent) : spinBox3D(parent, "Scale:") {}
/// Reconnect QObject connections and spin box values in UI.
void setObject(const Qtk::Object * object);
SpinBox3D spinBox3D;
};
ScalePanel scalePanel_;
/// Displays shader name, path, and read-only text view.
class ShaderView final : QWidget class ShaderView final : QWidget
{ {
public: public:
@ -130,17 +166,23 @@ namespace Qtk
layout(new QVBoxLayout(this)), path(parent, l), layout(new QVBoxLayout(this)), path(parent, l),
editor(new QTextEdit(parent)) editor(new QTextEdit(parent))
{ {
// Create a child horizontal layout for shader name and file path.
auto * pathLayout = new QHBoxLayout; auto * pathLayout = new QHBoxLayout;
pathLayout->addWidget(path.label); pathLayout->addWidget(path.label);
pathLayout->addWidget(path.value); pathLayout->addWidget(path.value);
layout->addLayout(pathLayout); layout->addLayout(pathLayout);
layout->addWidget(editor);
// Add the read-only text editor widget to the main layout.
editor->setReadOnly(true); editor->setReadOnly(true);
layout->addWidget(editor);
} }
/// The main layout for the ShaderView widget.
QVBoxLayout * layout; QVBoxLayout * layout;
/// Shader name and path on disk. /// Shader name and path on disk.
ObjectDetails::Item path; ObjectDetails::Item path;
/// Read-only (for now) display of the shader source code. /// Read-only (for now) display of the shader source code.
QTextEdit * editor; QTextEdit * editor;
}; };

View File

@ -244,6 +244,21 @@ namespace Qtk
mProgram.release(); mProgram.release();
} }
/*************************************************************************
* Public Static Methods
************************************************************************/
/**
* Helper to disconnect a QObject connection, only if it's valid.
* If the connection is valid and we fail to disconnect log a message.
*/
static void disconnect(const QMetaObject::Connection & con)
{
if (con && !QObject::disconnect(con)) {
qDebug() << "[Qtk] Failed to disconnect valid connection: " << con;
}
}
private: private:
/************************************************************************* /*************************************************************************
* Private Members * Private Members