From 93a73d55e1c69c97ef550e169e5ef08e75c562da Mon Sep 17 00:00:00 2001 From: Pieter Hijma Date: Thu, 28 Aug 2025 13:57:50 +0200 Subject: [PATCH] Gui: Move DlgAddPropertyVarSet -> DlgAddProperty --- src/Gui/CMakeLists.txt | 3 - src/Gui/Dialogs/DlgAddProperty.cpp | 1097 ++++++++++++++++++---- src/Gui/Dialogs/DlgAddProperty.h | 245 +++-- src/Gui/Dialogs/DlgAddProperty.ui | 98 +- src/Gui/Dialogs/DlgAddPropertyVarSet.cpp | 908 ------------------ src/Gui/Dialogs/DlgAddPropertyVarSet.h | 185 ---- src/Gui/Dialogs/DlgAddPropertyVarSet.ui | 126 --- src/Gui/Dialogs/DlgExpressionInput.cpp | 4 +- src/Gui/Dialogs/DlgExpressionInput.h | 2 +- src/Gui/ViewProviderVarSet.cpp | 2 +- src/Gui/ViewProviderVarSet.h | 4 +- 11 files changed, 1174 insertions(+), 1500 deletions(-) delete mode 100644 src/Gui/Dialogs/DlgAddPropertyVarSet.cpp delete mode 100644 src/Gui/Dialogs/DlgAddPropertyVarSet.h delete mode 100644 src/Gui/Dialogs/DlgAddPropertyVarSet.ui diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 1543a135e9..d70d360f16 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -471,7 +471,6 @@ SET(Gui_UIC_SRCS TaskElementColors.ui Dialogs/DlgObjectSelection.ui Dialogs/DlgAddProperty.ui - Dialogs/DlgAddPropertyVarSet.ui VectorListEditor.ui ) @@ -563,7 +562,6 @@ SET(Dialog_CPP_SRCS TaskElementColors.cpp Dialogs/DlgObjectSelection.cpp Dialogs/DlgAddProperty.cpp - Dialogs/DlgAddPropertyVarSet.cpp VectorListEditor.cpp ) @@ -605,7 +603,6 @@ SET(Dialog_HPP_SRCS TaskElementColors.h Dialogs/DlgObjectSelection.h Dialogs/DlgAddProperty.h - Dialogs/DlgAddPropertyVarSet.h VectorListEditor.h ) diff --git a/src/Gui/Dialogs/DlgAddProperty.cpp b/src/Gui/Dialogs/DlgAddProperty.cpp index 963a4cb295..8f0ae2b1f6 100644 --- a/src/Gui/Dialogs/DlgAddProperty.cpp +++ b/src/Gui/Dialogs/DlgAddProperty.cpp @@ -1,165 +1,932 @@ -/**************************************************************************** - * Copyright (c) 2019 Zheng Lei (realthunder) * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ****************************************************************************/ - -#include "PreCompiled.h" -#ifndef _PreComp_ -# include -#endif - -#include -#include -#include -#include -#include - -#include "Dialogs/DlgAddProperty.h" -#include "ui_DlgAddProperty.h" -#include "MainWindow.h" -#include "ViewProviderDocumentObject.h" - - -using namespace Gui; -using namespace Gui::Dialog; - -DlgAddProperty::DlgAddProperty(QWidget* parent, - std::unordered_set &&c) - : QDialog( parent ) - , containers(std::move(c)) - , ui(new Ui_DlgAddProperty) -{ - ui->setupUi(this); - - auto hGrp = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/PropertyView"); - auto defType = Base::Type::fromName( - hGrp->GetASCII("NewPropertyType","App::PropertyString").c_str()); - if(defType.isBad()) - defType = App::PropertyString::getClassTypeId(); - - std::vector proptypes; - std::vector types; - Base::Type::getAllDerivedFrom(Base::Type::fromName("App::Property"), proptypes); - std::copy_if (proptypes.begin(), proptypes.end(), std::back_inserter(types), [](const Base::Type& type) { - return type.canInstantiate(); - }); - std::sort(types.begin(), types.end(), [](Base::Type a, Base::Type b) { - return strcmp(a.getName(), b.getName()) < 0; - }); - - for(const auto& type : types) { - ui->comboType->addItem(QString::fromLatin1(type.getName())); - if(type == defType) - ui->comboType->setCurrentIndex(ui->comboType->count()-1); - } - - ui->edtGroup->setText(QString::fromLatin1( - hGrp->GetASCII("NewPropertyGroup","Base").c_str())); - ui->chkAppend->setChecked(hGrp->GetBool("NewPropertyAppend",true)); -} - -/** - * Destroys the object and frees any allocated resources - */ -DlgAddProperty::~DlgAddProperty() = default; - -static std::string containerName(const App::PropertyContainer *c) { - auto doc = freecad_cast(c); - if(doc) - return doc->getName(); - auto obj = freecad_cast(c); - if(obj) - return obj->getFullName(); - auto vpd = freecad_cast(c); - if(vpd) - return vpd->getObject()->getFullName(); - return "?"; -} - -void DlgAddProperty::accept() -{ - std::string name = ui->edtName->text().toUtf8().constData(); - std::string group = ui->edtGroup->text().toUtf8().constData(); - if(name.empty() || group.empty() - || name != Base::Tools::getIdentifier(name) - || group != Base::Tools::getIdentifier(group)) - { - QMessageBox::critical(getMainWindow(), - QObject::tr("Invalid name"), - QObject::tr("The property or group name must only contain alphanumericals,\n" - "underscore, and must not start with a digit.")); - return; - } - - if(ui->chkAppend->isChecked()) - name = group + "_" + name; - - if (App::ExpressionParser::isTokenAUnit(name) || App::ExpressionParser::isTokenAConstant(name)) { - QMessageBox::critical(getMainWindow(), - QObject::tr("Invalid name"), - QObject::tr("The property name is a reserved word.")); - return; - } - - for(auto c : containers) { - auto prop = c->getPropertyByName(name.c_str()); - if(prop && prop->getContainer() == c) { - QMessageBox::critical(getMainWindow(), - QObject::tr("Invalid name"), - QObject::tr("The property '%1' already exists in '%2'").arg( - QString::fromLatin1(name.c_str()), - QString::fromLatin1(containerName(c).c_str()))); - return; - } - } - - std::string type = ui->comboType->currentText().toLatin1().constData(); - - for(auto it=containers.begin();it!=containers.end();++it) { - try { - (*it)->addDynamicProperty(type.c_str(),name.c_str(), - group.c_str(),ui->edtDoc->toPlainText().toUtf8().constData()); - } catch(Base::Exception &e) { - e.reportException(); - for(auto it2=containers.begin();it2!=it;++it2) { - try { - (*it2)->removeDynamicProperty(name.c_str()); - } catch(Base::Exception &e) { - e.reportException(); - } - } - QMessageBox::critical(getMainWindow(), - QObject::tr("Add property"), - QObject::tr("Failed to add property to '%1': %2").arg( - QString::fromLatin1(containerName(*it).c_str()), - QString::fromUtf8(e.what()))); - return; - } - } - auto hGrp = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/PropertyView"); - hGrp->SetASCII("NewPropertyType",type.c_str()); - hGrp->SetASCII("NewPropertyGroup",group.c_str()); - hGrp->SetBool("NewPropertyAppend",ui->chkAppend->isChecked()); - QDialog::accept(); -} - -#include "moc_DlgAddProperty.cpp" +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Ondsel * + * Copyright (c) 2025 Pieter Hijma * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Dialogs/DlgAddProperty.h" +#include "ui_DlgAddProperty.h" +#include "ViewProviderVarSet.h" +#include "propertyeditor/PropertyItem.h" + +FC_LOG_LEVEL_INIT("DlgAddProperty", true, true) + +using namespace Gui; +using namespace Gui::Dialog; +using namespace Gui::PropertyEditor; + +const std::string DlgAddProperty::GroupBase = "Base"; + +/* + * This dialog has quite complex logic, so we will document it here. + * + * The design goals of this dialog are: + * - support transactions (undo/redo), + * - provide a value field as soon as possible (see #16189), + * - keep the value if the name of the property is changed, + * - support units (see #15557), + * - support enumerations (see #15553), + * - make OK available as soon as there is a valid property (see #17474), and + * - support expressions (see #19716). + * + * Especially supporting expressions in the value field makes the logic + * complex. Editors for value fields are created from PropertyItems. An + * editor has two modes: One without the possibility to add an expression and + * one with the possibility to add an expression. This depends on whether the + * PropertyItem is bound. A PropertyItem can only be bound if a valid property + * exists, which means the name of the property and the type should be known. + * + * As one of the goals of this dialog is to show an editor as soon as possible, + * so also when there is no property name known yet, this means that the editor + * won't have the expression button at first. + * + * To show the expression button as soon as possible, we create the property as + * soon as a valid type and name are known. This allows us to bind the + * PropertyItem which results in having the expression button. + * + * However, since we also want to support transactions, this means that we need + * to open a transaction as well. This means that this dialog juggles the + * following things: + * + * Given a valid property name and a property type we open a transaction and + * create the property. As soon as the property name or type is changed, we + * abort the transaction and start a new transaction with a recreated property + * with the new name or type. + * + * If the property name or type is invalid, we clear the transaction and as + * soon as the name or type become valid again, we start a new transaction. + * + * If the type is changed, we need to clear the current expression and value to + * the default value. If only the name is changed, we keep the value as much + * as possible with two exceptions: having a value based on an expression or + * being a value for a property link. + * + * Expressions and links are bound to the property and changing the name of a + * property prompts us to remove the old property and create a new one. This + * to make sure that the transaction for adding a property does not keep a + * history of old property name changes. So, because we want to have a new + * transaction and expressions and links are bound to a property, the + * expression or link becomes invalid when changing the property name. + * + * For expressions there are two choices: 1) we leave the outcome of the + * expression in the value field (which is possible) but without it being based + * on an expression or 2) we reset the value to the default value of the type. + * + * I chose the latter option because it is easy to miss that on a property name + * change, the expression is invalidated, so the user may think the value is + * the result of an expression, whereas in reality, the expression is lost. + * + * All in all, this leads to the following entities that need to be managed: + * - transaction + * - property item + * - property + * - editor + * - value of the editor + * + * We have to react on a name change and on a type change. For each of these + * changes, we need to take three cases into account: + * - the name and type are valid + * - only the type is valid + * - neither the name nor the type is valid + * + * This has been encoded in the code below as onNameFieldChanged() and + * onTypeChanged() and it works in two phases: clearing the transaction, + * property item, and related, and building it up again depending on the + * situation. + */ + +DlgAddProperty::DlgAddProperty(QWidget* parent, + App::PropertyContainer* container) + : DlgAddProperty(parent, container, nullptr) +{ +} + + +DlgAddProperty::DlgAddProperty(QWidget* parent, + ViewProviderVarSet* viewProvider) + : DlgAddProperty(parent, + viewProvider ? viewProvider->getObject() : nullptr, + viewProvider) +{ +} + +DlgAddProperty::DlgAddProperty(QWidget* parent, + App::PropertyContainer* container, + ViewProviderVarSet* viewProvider) + : QDialog(parent), + container(container), + ui(new Ui_DlgAddProperty), + comboBoxGroup(this), + completerType(this), + editor(nullptr), + transactionID(0) +{ + ui->setupUi(this); + + initializeWidgets(viewProvider); +} + +DlgAddProperty::~DlgAddProperty() = default; + +int DlgAddProperty::findLabelRow(const char* labelName, QFormLayout* layout) +{ + for (int row = 0; row < layout->rowCount(); ++row) { + QLayoutItem* labelItem = layout->itemAt(row, QFormLayout::LabelRole); + if (labelItem == nullptr) { + continue; + } + + if (auto label = qobject_cast(labelItem->widget())) { + if (label->objectName() == QString::fromLatin1(labelName)) { + return row; + } + } + } + return -1; +} + +void DlgAddProperty::removeExistingWidget(QFormLayout* formLayout, int labelRow) +{ + if (QLayoutItem* existingItem = formLayout->itemAt(labelRow, QFormLayout::FieldRole)) { + if (QWidget *existingWidget = existingItem->widget()) { + formLayout->removeWidget(existingWidget); + existingWidget->deleteLater(); + } + } +} + + +void DlgAddProperty::setWidgetForLabel(const char* labelName, QWidget* widget, + QLayout* layout) +{ + auto formLayout = qobject_cast(layout); + if (formLayout == nullptr) { + FC_ERR("Form layout not found"); + return; + } + + int labelRow = findLabelRow(labelName, formLayout); + if (labelRow < 0) { + FC_ERR("Could not find row for '" << labelName << "'"); + return; + } + + removeExistingWidget(formLayout, labelRow); + formLayout->setWidget(labelRow, QFormLayout::FieldRole, widget); +} + +void DlgAddProperty::populateGroup(EditFinishedComboBox& comboBox, + const App::PropertyContainer* container) +{ + auto paramGroup = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/PropertyView"); + std::string lastGroup = paramGroup->GetASCII("NewPropertyGroup"); + + std::vector properties; + container->getPropertyList(properties); + + std::unordered_set groupNames; + for (const auto* prop : properties) { + const char* groupName = container->getPropertyGroup(prop); + groupNames.insert(groupName ? groupName : GroupBase); + } + + std::vector groupNamesSorted(groupNames.begin(), groupNames.end()); + std::ranges::sort(groupNamesSorted, [](const std::string& a, const std::string& b) { + // prefer anything else other than Base, so move it to the back + if (a == GroupBase) { + return false; + } + if (b == GroupBase) { + return true; + } + return a < b; + }); + + for (const auto& groupName : groupNamesSorted) { + comboBox.addItem(QString::fromStdString(groupName)); + } + + if (!lastGroup.empty() && + std::ranges::find(groupNames, lastGroup) != groupNames.end()) { + comboBox.setEditText(QString::fromStdString(lastGroup)); + } + else { + comboBox.setEditText(QString::fromStdString(groupNamesSorted[0])); + } +} + +void DlgAddProperty::initializeGroup() +{ + comboBoxGroup.setObjectName(QStringLiteral("comboBoxGroup")); + comboBoxGroup.setInsertPolicy(QComboBox::InsertAtTop); + comboBoxGroup.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + setWidgetForLabel("labelGroup", &comboBoxGroup, layout()); + populateGroup(comboBoxGroup, container); + + connComboBoxGroup = connect(&comboBoxGroup, &EditFinishedComboBox::editFinished, + this, &DlgAddProperty::onGroupFinished); +} + +std::vector DlgAddProperty::getSupportedTypes() +{ + std::vector supportedTypes; + std::vector allTypes; + Base::Type::getAllDerivedFrom(Base::Type::fromName("App::Property"), allTypes); + + std::ranges::copy_if(allTypes, std::back_inserter(supportedTypes), + [](const Base::Type& type) { + return type.canInstantiate(); + }); + + std::ranges::sort(supportedTypes, [](Base::Type a, Base::Type b) { + return strcmp(a.getName(), b.getName()) < 0; + }); + + return supportedTypes; +} + +void DlgAddProperty::initializeTypes() +{ + auto paramGroup = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/PropertyView"); + auto lastType = Base::Type::fromName( + paramGroup->GetASCII("NewPropertyType", "App::PropertyLength").c_str()); + if (lastType.isBad()) { + lastType = App::PropertyLength::getClassTypeId(); + } + + std::vector types = getSupportedTypes(); + + for(const auto& type : types) { + ui->comboBoxType->addItem(QString::fromLatin1(type.getName())); + if (type == lastType) { + ui->comboBoxType->setCurrentIndex(ui->comboBoxType->count()-1); + } + } + + completerType.setModel(ui->comboBoxType->model()); + completerType.setCaseSensitivity(Qt::CaseInsensitive); + completerType.setFilterMode(Qt::MatchContains); + ui->comboBoxType->setCompleter(&completerType); + ui->comboBoxType->setInsertPolicy(QComboBox::NoInsert); + + connComboBoxType = connect(ui->comboBoxType, &QComboBox::currentTextChanged, + this, &DlgAddProperty::onTypeChanged); +} + +void DlgAddProperty::removeSelectionEditor() +{ + // If the editor has a lineedit, then Qt selects the string inside it when + // the editor is created. This interferes with the editor getting focus. + // For example, units will then be selected as well, whereas this is not + // the behavior we want. We therefore deselect the text in the lineedit. + if (auto lineEdit = editor->findChild()) { + lineEdit->deselect(); + } +} + +void DlgAddProperty::addEnumEditor(PropertyItem* propertyItem) +{ + auto* values = + static_cast(PropertyStringListItem::create()); + values->setParent(propertyItem); + values->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Enum"))); + if (propertyItem->childCount() > 0) { + auto* child = propertyItem->takeChild(0); + delete child; + } + propertyItem->appendChild(values); + editor.reset(values->createEditor(this, [this]() { + this->valueChangedEnum(); + }, FrameOption::WithFrame)); +} + +void DlgAddProperty::addNormalEditor(PropertyItem* propertyItem) +{ + editor.reset(propertyItem->createEditor(this, [this]() { + this->valueChanged(); + }, FrameOption::WithFrame)); +} + +void DlgAddProperty::addEditor(PropertyItem* propertyItem) +{ + if (isEnumPropertyItem()) { + addEnumEditor(propertyItem); + } + else { + addNormalEditor(propertyItem); + } + if (editor == nullptr) { + return; + } + + // Make sure that the editor has the same height as the + // other widgets in the dialog. + editor->setMinimumHeight(comboBoxGroup.height()); + + QSignalBlocker blockSignals(editor.get()); + + // To set the data in the editor, we need to set the data in the + // propertyItem. The propertyItem needs to have a property set to make + // sure that we get a correct value and the unit. + setEditorData(propertyItem->data(PropertyItem::ValueColumn, Qt::EditRole)); + editor->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + editor->setObjectName(QStringLiteral("editor")); + + setWidgetForLabel("labelValue", editor.get(), layout()); + + QWidget::setTabOrder(ui->comboBoxType, editor.get()); + QWidget::setTabOrder(editor.get(), ui->checkBoxAdd); + + removeSelectionEditor(); +} + +bool DlgAddProperty::isTypeWithEditor(const Base::Type& type) +{ + static const std::initializer_list subTypesWithEditor = { + // These types and their subtypes have editors. + App::PropertyBool::getClassTypeId(), + App::PropertyColor::getClassTypeId(), + App::PropertyFileIncluded::getClassTypeId(), + App::PropertyFloat::getClassTypeId(), + App::PropertyInteger::getClassTypeId() + }; + + static const std::initializer_list typesWithEditor = { + // These types have editors but not necessarily their subtypes. + App::PropertyEnumeration::getClassTypeId(), + App::PropertyFile::getClassTypeId(), + App::PropertyFloatList::getClassTypeId(), + App::PropertyFont::getClassTypeId(), + App::PropertyIntegerList::getClassTypeId(), + App::PropertyMaterialList::getClassTypeId(), + App::PropertyPath::getClassTypeId(), + App::PropertyString::getClassTypeId(), + App::PropertyStringList::getClassTypeId(), + App::PropertyVectorList::getClassTypeId() + }; + + const auto isDerivedFromType = [&type](const Base::Type& t) { + return type.isDerivedFrom(t); + }; + + return std::ranges::find(typesWithEditor, type) != typesWithEditor.end() || + std::ranges::any_of(subTypesWithEditor, isDerivedFromType); +} + +bool DlgAddProperty::isTypeWithEditor(const std::string& type) +{ + Base::Type propType = + Base::Type::getTypeIfDerivedFrom(type.c_str(), App::Property::getClassTypeId(), true); + return isTypeWithEditor(propType); +} + +static PropertyItem *createPropertyItem(App::Property *prop) +{ + const char *editor = prop->getEditorName(); + if (Base::Tools::isNullOrEmpty(editor)) { + return nullptr; + } + return PropertyItemFactory::instance().createPropertyItem(editor); +} + +void DlgAddProperty::createEditorForType(const Base::Type& type) +{ + // Temporarily create a property for two reasons: + // - to acquire the editor name from the instance, and + // - to acquire an initial value from the instance possibly with the correct unit. + void* propInstance = type.createInstance(); + if (!propInstance) { + FC_THROWM(Base::RuntimeError, "Failed to create a property of type " << type.getName()); + } + + // When prop goes out of scope, it can be deleted because we obtained the + // propertyItem (if applicable) and we initialized the editor with the data + // from the property. + std::unique_ptr prop( + static_cast(propInstance), + [](App::Property* p) { delete p; }); + prop->setContainer(container); + + propertyItem.reset(createPropertyItem(prop.get())); + + if (propertyItem && isTypeWithEditor(type)) { + propertyItem->setPropertyData({prop.get()}); + addEditor(propertyItem.get()); + propertyItem->removeProperty(prop.get()); + } +} + +void DlgAddProperty::initializeValue() +{ + std::string type = ui->comboBoxType->currentText().toStdString(); + + Base::Type propType = + Base::Type::getTypeIfDerivedFrom(type.c_str(), App::Property::getClassTypeId(), true); + if (propType.isBad()) { + return; + } + + if (isTypeWithEditor(propType)) { + createEditorForType(propType); + } + else { + removeEditor(); + } +} + +void DlgAddProperty::setTitle() +{ + setWindowTitle(tr("Add Property")); +} + +void DlgAddProperty::setOkEnabled(bool enabled) +{ + QPushButton *okButton = ui->buttonBox->button(QDialogButtonBox::Ok); + okButton->setEnabled(enabled); +} + +void DlgAddProperty::initializeWidgets(ViewProviderVarSet* viewProvider) +{ + initializeGroup(); + initializeTypes(); + initializeValue(); + + if (viewProvider) { + connect(this, &QDialog::finished, + this, [viewProvider](int result) { viewProvider->onFinished(result); }); + } + connLineEditNameTextChanged = connect(ui->lineEditName, &QLineEdit::textChanged, + this, &DlgAddProperty::onNameChanged); + + setTitle(); + setOkEnabled(false); + + ui->lineEditName->setFocus(); + + QWidget::setTabOrder(ui->lineEditName, &comboBoxGroup); + QWidget::setTabOrder(&comboBoxGroup, ui->comboBoxType); +} + +bool DlgAddProperty::propertyExists(const std::string& name) +{ + App::Property* prop = container->getPropertyByName(name.c_str()); + return prop && prop->getContainer() == container && + !(propertyItem && propertyItem->getFirstProperty() == prop); +} + +bool DlgAddProperty::isNameValid() +{ + std::string name = ui->lineEditName->text().toStdString(); + + return !name.empty() && + name == Base::Tools::getIdentifier(name) && + !App::ExpressionParser::isTokenAConstant(name) && + !App::ExpressionParser::isTokenAUnit(name) && + !propertyExists(name); +} + +bool DlgAddProperty::isGroupValid() +{ + std::string group = comboBoxGroup.currentText().toStdString(); + return !group.empty() && group == Base::Tools::getIdentifier(group); +} + +bool DlgAddProperty::isTypeValid() +{ + std::string type = ui->comboBoxType->currentText().toStdString(); + return Base::Type::fromName(type.c_str()).isDerivedFrom(App::Property::getClassTypeId()); +} + +bool DlgAddProperty::isDocument() const +{ + return container && freecad_cast(container); +} + +bool DlgAddProperty::isDocumentObject() const +{ + return container && freecad_cast(container); +} + +bool DlgAddProperty::areFieldsValid() +{ + return isNameValid() && isGroupValid() && isTypeValid(); +} + +void DlgAddProperty::showStatusMessage() +{ + QString error; + QString text = ui->lineEditName->text(); + std::string name = text.toStdString(); + + if (!isGroupValid()) { + error = tr("Invalid group name"); + } + else if (!isTypeValid()) { + error = tr("Invalid type name"); + } + else if (name.empty()) { + error.clear(); + } + else if (name != Base::Tools::getIdentifier(name)) { + error = tr("Invalid property name '%1'").arg(text); + } + else if (propertyExists(name)) { + error = tr("Property '%1' already exists").arg(text); + } + else if (App::ExpressionParser::isTokenAConstant(name)) { + error = tr("'%1' is a constant").arg(text); + } + else if (App::ExpressionParser::isTokenAUnit(name)) { + error = tr("'%1' is a unit").arg(text); + } + + ui->labelError->setText(error); +} + +void DlgAddProperty::removeEditor() +{ + if (editor == nullptr) { + return; + } + + // Create a placeholder widget to keep the layout intact. + auto* placeholder = new QWidget(this); + placeholder->setObjectName(QStringLiteral("placeholder")); + placeholder->setMinimumHeight(comboBoxGroup.height()); + setWidgetForLabel("labelValue", placeholder, layout()); + + QWidget::setTabOrder(ui->comboBoxType, ui->checkBoxAdd); + editor = nullptr; +} + +bool DlgAddProperty::isEnumPropertyItem() const +{ + return ui->comboBoxType->currentText() == + QString::fromLatin1(App::PropertyEnumeration::getClassTypeId().getName()); +} + +QVariant DlgAddProperty::getEditorData() const +{ + if (isEnumPropertyItem()) { + PropertyItem* child = propertyItem->child(0); + if (child == nullptr) { + return {}; + } + return child->editorData(editor.get()); + } + + return propertyItem->editorData(editor.get()); +} + +void DlgAddProperty::setEditorData(const QVariant& data) +{ + if (isEnumPropertyItem()) { + PropertyItem* child = propertyItem->child(0); + if (child == nullptr) { + return; + } + child->setEditorData(editor.get(), data); + } + else { + propertyItem->setEditorData(editor.get(), data); + } +} + +void DlgAddProperty::setEditor(bool valueNeedsReset) +{ + if (editor && !valueNeedsReset) { + QVariant data = getEditorData(); + addEditor(propertyItem.get()); + if (editor == nullptr) { + return; + } + setEditorData(data); + removeSelectionEditor(); + } + else if (propertyItem) { + addEditor(propertyItem.get()); + } + else { + initializeValue(); + } + + if (editor) { + QVariant data = propertyItem->editorData(editor.get()); + propertyItem->setData(data); + } +} + +void DlgAddProperty::setPropertyItem(App::Property* prop, bool supportsExpressions) +{ + if (prop == nullptr) { + return; + } + + if (propertyItem == nullptr) { + propertyItem.reset(createPropertyItem(prop)); + } + + if (propertyItem == nullptr) { + return; + } + + propertyItem->setAutoApply(true); + propertyItem->setPropertyData({prop}); + if (supportsExpressions) { + objectIdentifier = std::make_unique(*prop); + propertyItem->bind(*objectIdentifier); + } +} + +void DlgAddProperty::buildForUnbound(bool valueNeedsReset) +{ + setEditor(valueNeedsReset); +} + +void DlgAddProperty::buildForBound(bool valueNeedsReset, bool supportsExpressions) +{ + openTransaction(); + App::Property* prop = createProperty(); + setPropertyItem(prop, supportsExpressions); + setEditor(valueNeedsReset); +} + +bool DlgAddProperty::clearBoundProperty() +{ + // Both a property link and an expression are bound to a property and as a + // result need the value to be reset. + bool isPropertyLinkItem = qobject_cast(propertyItem.get()) != nullptr; + bool valueNeedsReset = isPropertyLinkItem || propertyItem->hasExpression(); + + if (App::Property* prop = propertyItem->getFirstProperty()) { + propertyItem->unbind(); + propertyItem->removeProperty(prop); + container->removeDynamicProperty(prop->getName()); + closeTransaction(TransactionOption::Abort); + } + return valueNeedsReset; +} + +bool DlgAddProperty::clear(FieldChange fieldChange) +{ + if (propertyItem == nullptr) { + return true; + } + + bool valueNeedsReset = clearBoundProperty(); + + if (fieldChange == FieldChange::Type) { + valueNeedsReset = true; + removeEditor(); + propertyItem = nullptr; + } + return valueNeedsReset; +} + +void DlgAddProperty::onNameChanged([[maybe_unused]]const QString& text) +{ + bool valueNeedsReset = clear(FieldChange::Name); + if (isNameValid() && isTypeValid()) { + buildForBound(valueNeedsReset, isDocumentObject()); + } + else if (isTypeValid()) { + buildForUnbound(valueNeedsReset); + } + else { + removeEditor(); + propertyItem = nullptr; + } + + setOkEnabled(areFieldsValid()); + showStatusMessage(); +} + +void DlgAddProperty::onGroupFinished() +{ + if (isGroupValid() && propertyItem) { + std::string group = comboBoxGroup.currentText().toStdString(); + std::string doc = ui->lineEditToolTip->text().toStdString(); + if (App::Property* prop = propertyItem->getFirstProperty(); + prop && prop->getGroup() != group) { + container->changeDynamicProperty(prop, group.c_str(), doc.c_str()); + } + } + + setOkEnabled(areFieldsValid()); + showStatusMessage(); +} + +void DlgAddProperty::onTypeChanged([[maybe_unused]] const QString& text) +{ + bool valueNeedsReset = clear(FieldChange::Type); + if (isNameValid() && isTypeValid()) { + buildForBound(valueNeedsReset, isDocumentObject()); + } + else if (isTypeValid()) { + buildForUnbound(valueNeedsReset); + } + // nothing if both name and type are invalid + + setOkEnabled(areFieldsValid()); + showStatusMessage(); +} + +void DlgAddProperty::changeEvent(QEvent* e) +{ + if (e->type() == QEvent::LanguageChange) { + ui->retranslateUi(this); + setTitle(); + } + QDialog::changeEvent(e); +} + +void DlgAddProperty::valueChangedEnum() +{ + auto* propEnum = + static_cast(propertyItem->getFirstProperty()); + if (propEnum == nullptr || propertyItem->childCount() == 0) { + return; + } + + auto* values = static_cast(propertyItem->child(0)); + QVariant data = values->editorData(editor.get()); + QStringList enumValues = data.toStringList(); + // convert to std::vector + std::vector enumValuesVec; + std::ranges::transform(enumValues, std::back_inserter(enumValuesVec), + [](const QString& value) { return value.toStdString(); }); + propEnum->setEnums(enumValuesVec); +} + +void DlgAddProperty::valueChanged() +{ + QVariant data = propertyItem->editorData(editor.get()); + propertyItem->setData(data); +} + +/* We use these functions rather than the functions provided by App::Document + * because this dialog may be opened when another transaction is in progress. + * An example is opening a sketch. If this dialog uses the functions provided + * by App::Document, a reject of the dialog would close that transaction. By + * checking whether the transaction ID is "our" transaction ID, we prevent this + * behavior. + */ +void DlgAddProperty::openTransaction() +{ + transactionID = App::GetApplication().setActiveTransaction("Add property"); +} + +void DlgAddProperty::critical(const QString& title, const QString& text) { + static bool criticalDialogShown = false; + if (!criticalDialogShown) { + criticalDialogShown = true; + QMessageBox::critical(this, title, text); + criticalDialogShown = false; + } +} + +App::Property* DlgAddProperty::createProperty() +{ + std::string name = ui->lineEditName->text().toStdString(); + std::string group = comboBoxGroup.currentText().toStdString(); + std::string type = ui->comboBoxType->currentText().toStdString(); + std::string doc = ui->lineEditToolTip->text().toStdString(); + + try { + return container->addDynamicProperty(type.c_str(), name.c_str(), + group.c_str(), doc.c_str()); + } + catch (Base::Exception& e) { + e.reportException(); + critical(QObject::tr("Add property"), + QObject::tr("Failed to add property to '%1': %2").arg( + QString::fromLatin1(container->getFullName().c_str()), + QString::fromUtf8(e.what()))); + return nullptr; + } +} + +void DlgAddProperty::closeTransaction(TransactionOption option) +{ + if (transactionID == 0) { + return; + } + + App::GetApplication().closeActiveTransaction(static_cast(option), transactionID); + transactionID = 0; +} + +void DlgAddProperty::clearFields() +{ + { + QSignalBlocker blocker(ui->lineEditName); + ui->lineEditName->clear(); + } + ui->lineEditToolTip->clear(); + initializeValue(); + setOkEnabled(false); +} + +void DlgAddProperty::addDocumentation() { + /* Since there is no check on documentation (we accept any string), there + * is no signal handler for the documentation field. This method updates + * the property that is being added with the text inserted as + * documentation/tooltip. + */ + + std::string group = comboBoxGroup.currentText().toStdString(); + std::string doc = ui->lineEditToolTip->text().toStdString(); + + if (propertyItem == nullptr) { + // If there is no property item, we cannot add documentation. + return; + } + + App::Property* prop = propertyItem->getFirstProperty(); + if (prop == nullptr) { + return; + } + + container->changeDynamicProperty(prop, group.c_str(), doc.c_str()); +} + +void DlgAddProperty::accept() +{ + addDocumentation(); + auto* object = freecad_cast(container); + if (object) { + object->ExpressionEngine.execute(); + } + closeTransaction(TransactionOption::Commit); + std::string group = comboBoxGroup.currentText().toStdString(); + std::string type = ui->comboBoxType->currentText().toStdString(); + auto paramGroup = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/PropertyView"); + paramGroup->SetASCII("NewPropertyType", type.c_str()); + paramGroup->SetASCII("NewPropertyGroup", group.c_str()); + + if (ui->checkBoxAdd->isChecked()) { + clearFields(); + ui->lineEditName->setFocus(); + } + else { + // we are done, close the dialog + QDialog::accept(); + } +} + +void DlgAddProperty::reject() +{ + if (propertyItem) { + if (App::Property* prop = propertyItem->getFirstProperty()) { + App::PropertyContainer* container = prop->getContainer(); + container->removeDynamicProperty(prop->getName()); + closeTransaction(TransactionOption::Abort); + } + } + disconnect(connComboBoxGroup); + disconnect(connComboBoxType); + disconnect(connLineEditNameTextChanged); + + QDialog::reject(); +} + +#include "moc_DlgAddProperty.cpp" diff --git a/src/Gui/Dialogs/DlgAddProperty.h b/src/Gui/Dialogs/DlgAddProperty.h index 4a26faa6c1..1ac0a598cc 100644 --- a/src/Gui/Dialogs/DlgAddProperty.h +++ b/src/Gui/Dialogs/DlgAddProperty.h @@ -1,56 +1,189 @@ -/**************************************************************************** - * Copyright (c) 2019 Zheng Lei (realthunder) * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ****************************************************************************/ - -#ifndef GUI_DIALOG_DLGADDPROPERTY_H -#define GUI_DIALOG_DLGADDPROPERTY_H - -#include -#include -#include - -namespace App { -class PropertyContainer; -} - -namespace Gui { -namespace Dialog { - -class Ui_DlgAddProperty; -class GuiExport DlgAddProperty : public QDialog -{ - Q_OBJECT - -public: - DlgAddProperty(QWidget *parent, std::unordered_set &&); - ~DlgAddProperty() override; - - void accept() override; - -private: - std::unordered_set containers; - std::unique_ptr ui; -}; - -} // namespace Dialog -} // namespace Gui - -#endif // GUI_DIALOG_DLGADDPROPERTY_H +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Ondsel * + * Copyright (c) 2025 Pieter Hijma * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef GUI_DIALOG_DLG_ADD_PROPERTY_H +#define GUI_DIALOG_DLG_ADD_PROPERTY_H + +#include + +#include +#include +#include + +#include + +#include + +#include "propertyeditor/PropertyItem.h" + +namespace Gui { + +class ViewProviderVarSet; + +namespace Dialog { + +class EditFinishedComboBox : public QComboBox { + Q_OBJECT +public: + explicit EditFinishedComboBox(QWidget *parent = nullptr) : QComboBox(parent) { + setEditable(true); + connect(this, QOverload::of(&QComboBox::currentIndexChanged), this, &EditFinishedComboBox::onIndexChanged); + connect(this->lineEdit(), &QLineEdit::editingFinished, this, &EditFinishedComboBox::onEditingFinished); + } + +Q_SIGNALS: + void editFinished(); + +private: + void onEditingFinished() { + Q_EMIT editFinished(); + } + + void onIndexChanged() { + Q_EMIT editFinished(); + } +}; + +class Ui_DlgAddProperty; + +class GuiExport DlgAddProperty : public QDialog +{ + Q_OBJECT + +public: + static const std::string GroupBase; + +public: + DlgAddProperty(QWidget* parent, ViewProviderVarSet* viewProvider); + DlgAddProperty(QWidget* parent, App::PropertyContainer* container); + + DlgAddProperty(const DlgAddProperty&) = delete; + DlgAddProperty(DlgAddProperty&&) = delete; + DlgAddProperty& operator=(const DlgAddProperty&) = delete; + DlgAddProperty& operator=(DlgAddProperty&&) = delete; + + ~DlgAddProperty() override; + + void changeEvent(QEvent* e) override; + void accept() override; + void reject() override; + static void populateGroup(EditFinishedComboBox& comboBox, + const App::PropertyContainer* container); + static void setWidgetForLabel(const char* labelName, QWidget* widget, + QLayout* layout); + +public Q_SLOTS: + void valueChanged(); + void valueChangedEnum(); + +private: + enum class TransactionOption : bool { + Commit = false, + Abort = true + }; + + enum class FieldChange : std::uint8_t { + Name, + Type + }; + + DlgAddProperty(QWidget* parent, App::PropertyContainer* container, + ViewProviderVarSet* viewProvider); + + void initializeGroup(); + + std::vector getSupportedTypes(); + void initializeTypes(); + + void removeSelectionEditor(); + QVariant getEditorData() const; + void setEditorData(const QVariant& data); + bool isEnumPropertyItem() const; + void addEnumEditor(PropertyEditor::PropertyItem* propertyItem); + void addNormalEditor(PropertyEditor::PropertyItem* propertyItem); + void addEditor(PropertyEditor::PropertyItem* propertyItem); + bool isTypeWithEditor(const Base::Type& type); + bool isTypeWithEditor(const std::string& type); + void createEditorForType(const Base::Type& type); + void initializeValue(); + + void setTitle(); + void setOkEnabled(bool enabled); + void initializeWidgets(ViewProviderVarSet* viewProvider); + + bool isDocument() const; + bool isDocumentObject() const; + bool propertyExists(const std::string& name); + bool isNameValid(); + bool isGroupValid(); + bool isTypeValid(); + bool areFieldsValid(); + + void setEditor(bool valueNeedsReset); + void buildForUnbound(bool valueNeedsReset); + void setPropertyItem(App::Property* prop, bool supportsExpressions); + void buildForBound(bool valueNeedsReset, bool supportsExpressions); + bool clearBoundProperty(); + bool clear(FieldChange fieldChange); + void onNameChanged(const QString& text); + void onGroupFinished(); + void onTypeChanged(const QString& text); + + void showStatusMessage(); + + void removeEditor(); + + void openTransaction(); + void critical(const QString& title, const QString& text); + App::Property* createProperty(); + void closeTransaction(TransactionOption option); + void clearFields(); + void addDocumentation(); + + static void removeExistingWidget(QFormLayout* layout, int labelRow); + static int findLabelRow(const char* labelName, QFormLayout* layout); + +private: + App::PropertyContainer* container; + std::unique_ptr ui; + + EditFinishedComboBox comboBoxGroup; + QCompleter completerType; + + std::unique_ptr editor; + std::unique_ptr propertyItem; + std::unique_ptr objectIdentifier; + + // a transactionID of 0 means that there is no active transaction. + int transactionID; + + QMetaObject::Connection connComboBoxGroup; + QMetaObject::Connection connComboBoxType; + QMetaObject::Connection connLineEditNameTextChanged; +}; + +} // namespace Dialog +} // namespace Gui + +#endif // GUI_DIALOG_DLG_ADD_PROPERTY_H diff --git a/src/Gui/Dialogs/DlgAddProperty.ui b/src/Gui/Dialogs/DlgAddProperty.ui index a80dd8036b..57e006e93a 100644 --- a/src/Gui/Dialogs/DlgAddProperty.ui +++ b/src/Gui/Dialogs/DlgAddProperty.ui @@ -7,7 +7,7 @@ 0 0 418 - 258 + 293 @@ -15,83 +15,79 @@ - - - Type - - - - - - - - - - Group - - - - - - - - + Name - - + + - - - - Verbose description of the new property - + + - Documentation + Group - - - - Verbose description of the new property + + + + Type + + + + + + + true + + + + + + + Value - - - Prefix the property name with the group name in the form 'Group_Name' to avoid conflicts with an existing property. -In this case the prefix will be automatically trimmed when shown in the property editor. -However, the property is still used in a script with the full name, like 'obj.Group_Name'. - -If this is not checked, the property must be uniquely named, and it is accessed like 'obj.Name'. - + - Prefix group name + Add another - + + + + Tooltip + + + + + + + + + + + + + + - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok - - comboType - edtGroup - edtName - diff --git a/src/Gui/Dialogs/DlgAddPropertyVarSet.cpp b/src/Gui/Dialogs/DlgAddPropertyVarSet.cpp deleted file mode 100644 index d62b3c18dd..0000000000 --- a/src/Gui/Dialogs/DlgAddPropertyVarSet.cpp +++ /dev/null @@ -1,908 +0,0 @@ -/**************************************************************************** - * Copyright (c) 2024 Ondsel * - * Copyright (c) 2025 Pieter Hijma * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ****************************************************************************/ - -#include "PreCompiled.h" -#ifndef _PreComp_ -# include -# include -# include -# include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Dialogs/DlgAddPropertyVarSet.h" -#include "ui_DlgAddPropertyVarSet.h" -#include "ViewProviderVarSet.h" -#include "propertyeditor/PropertyItem.h" - -FC_LOG_LEVEL_INIT("DlgAddPropertyVarSet", true, true) - -using namespace Gui; -using namespace Gui::Dialog; -using namespace Gui::PropertyEditor; - -const std::string DlgAddPropertyVarSet::GroupBase = "Base"; - -/* - * This dialog has quite complex logic, so we will document it here. - * - * The design goals of this dialog are: - * - support transactions (undo/redo), - * - provide a value field as soon as possible (see #16189), - * - keep the value if the name of the property is changed, - * - support units (see #15557), - * - support enumerations (see #15553), - * - make OK available as soon as there is a valid property (see #17474), and - * - support expressions (see #19716). - * - * Especially supporting expressions in the value field makes the logic - * complex. Editors for value fields are created from PropertyItems. An - * editor has two modes: One without the possibility to add an expression and - * one with the possibility to add an expression. This depends on whether the - * PropertyItem is bound. A PropertyItem can only be bound if a valid property - * exists, which means the name of the property and the type should be known. - * - * As one of the goals of this dialog is to show an editor as soon as possible, - * so also when there is no property name known yet, this means that the editor - * won't have the expression button at first. - * - * To show the expression button as soon as possible, we create the property as - * soon as a valid type and name are known. This allows us to bind the - * PropertyItem which results in having the expression button. - * - * However, since we also want to support transactions, this means that we need - * to open a transaction as well. This means that this dialog juggles the - * following things: - * - * Given a valid property name and a property type we open a transaction and - * create the property. As soon as the property name or type is changed, we - * abort the transaction and start a new transaction with a recreated property - * with the new name or type. - * - * If the property name or type is invalid, we clear the transaction and as - * soon as the name or type become valid again, we start a new transaction. - * - * If the type is changed, we need to clear the current expression and value to - * the default value. If only the name is changed, we keep the value as much - * as possible with two exceptions: having a value based on an expression or - * being a value for a property link. - * - * Expressions and links are bound to the property and changing the name of a - * property prompts us to remove the old property and create a new one. This - * to make sure that the transaction for adding a property does not keep a - * history of old property name changes. So, because we want to have a new - * transaction and expressions and links are bound to a property, the - * expression or link becomes invalid when changing the property name. - * - * For expressions there are two choices: 1) we leave the outcome of the - * expression in the value field (which is possible) but without it being based - * on an expression or 2) we reset the value to the default value of the type. - * - * I chose the latter option because it is easy to miss that on a property name - * change, the expression is invalidated, so the user may think the value is - * the result of an expression, whereas in reality, the expression is lost. - * - * All in all, this leads to the following entities that need to be managed: - * - transaction - * - property item - * - property - * - editor - * - value of the editor - * - * We have to react on a name change and on a type change. For each of these - * changes, we need to take three cases into account: - * - the name and type are valid - * - only the type is valid - * - neither the name nor the type is valid - * - * This has been encoded in the code below as onNameFieldChanged() and - * onTypeChanged() and it works in two phases: clearing the transaction, - * property item, and related, and building it up again depending on the - * situation. - */ - -DlgAddPropertyVarSet::DlgAddPropertyVarSet(QWidget* parent, - App::PropertyContainer* container) - : DlgAddPropertyVarSet(parent, container, nullptr) -{ -} - - -DlgAddPropertyVarSet::DlgAddPropertyVarSet(QWidget* parent, - ViewProviderVarSet* viewProvider) - : DlgAddPropertyVarSet(parent, - viewProvider ? viewProvider->getObject() : nullptr, - viewProvider) -{ -} - -DlgAddPropertyVarSet::DlgAddPropertyVarSet(QWidget* parent, - App::PropertyContainer* container, - ViewProviderVarSet* viewProvider) - : QDialog(parent), - container(container), - ui(new Ui_DlgAddPropertyVarSet), - comboBoxGroup(this), - completerType(this), - editor(nullptr), - transactionID(0) -{ - ui->setupUi(this); - - initializeWidgets(viewProvider); -} - -DlgAddPropertyVarSet::~DlgAddPropertyVarSet() = default; - -int DlgAddPropertyVarSet::findLabelRow(const char* labelName, QFormLayout* layout) -{ - for (int row = 0; row < layout->rowCount(); ++row) { - QLayoutItem* labelItem = layout->itemAt(row, QFormLayout::LabelRole); - if (labelItem == nullptr) { - continue; - } - - if (auto label = qobject_cast(labelItem->widget())) { - if (label->objectName() == QString::fromLatin1(labelName)) { - return row; - } - } - } - return -1; -} - -void DlgAddPropertyVarSet::removeExistingWidget(QFormLayout* formLayout, int labelRow) -{ - if (QLayoutItem* existingItem = formLayout->itemAt(labelRow, QFormLayout::FieldRole)) { - if (QWidget *existingWidget = existingItem->widget()) { - formLayout->removeWidget(existingWidget); - existingWidget->deleteLater(); - } - } -} - -void DlgAddPropertyVarSet::setWidgetForLabel(const char* labelName, QWidget* widget, - QLayout* layout) -{ - auto formLayout = qobject_cast(layout); - if (formLayout == nullptr) { - FC_ERR("Form layout not found"); - return; - } - - int labelRow = findLabelRow(labelName, formLayout); - if (labelRow < 0) { - FC_ERR("Could not find row for '" << labelName << "'"); - return; - } - - removeExistingWidget(formLayout, labelRow); - formLayout->setWidget(labelRow, QFormLayout::FieldRole, widget); -} - -void DlgAddPropertyVarSet::populateGroup(EditFinishedComboBox& comboBox, - const App::PropertyContainer* container) -{ - std::vector properties; - container->getPropertyList(properties); - - std::unordered_set groupNames; - for (const auto* prop : properties) { - const char* groupName = container->getPropertyGroup(prop); - groupNames.insert(groupName ? groupName : GroupBase); - } - - std::vector groupNamesSorted(groupNames.begin(), groupNames.end()); - std::ranges::sort(groupNamesSorted, [](const std::string& a, const std::string& b) { - // prefer anything else other than Base, so move it to the back - if (a == GroupBase) { - return false; - } - if (b == GroupBase) { - return true; - } - return a < b; - }); - - for (const auto& groupName : groupNamesSorted) { - comboBox.addItem(QString::fromStdString(groupName)); - } - - comboBox.setEditText(QString::fromStdString(groupNamesSorted[0])); -} - -void DlgAddPropertyVarSet::initializeGroup() -{ - comboBoxGroup.setObjectName(QStringLiteral("comboBoxGroup")); - comboBoxGroup.setInsertPolicy(QComboBox::InsertAtTop); - comboBoxGroup.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - - setWidgetForLabel("labelGroup", &comboBoxGroup, layout()); - populateGroup(comboBoxGroup, container); - - connComboBoxGroup = connect(&comboBoxGroup, &EditFinishedComboBox::editFinished, - this, &DlgAddPropertyVarSet::onGroupFinished); -} - -std::vector DlgAddPropertyVarSet::getSupportedTypes() -{ - std::vector supportedTypes; - std::vector allTypes; - Base::Type::getAllDerivedFrom(Base::Type::fromName("App::Property"), allTypes); - - std::ranges::copy_if(allTypes, std::back_inserter(supportedTypes), - [](const Base::Type& type) { - return type.canInstantiate(); - }); - - std::ranges::sort(supportedTypes, [](Base::Type a, Base::Type b) { - return strcmp(a.getName(), b.getName()) < 0; - }); - - return supportedTypes; -} - -void DlgAddPropertyVarSet::initializeTypes() -{ - auto paramGroup = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/PropertyView"); - auto lastType = Base::Type::fromName( - paramGroup->GetASCII("NewPropertyType", "App::PropertyLength").c_str()); - if (lastType.isBad()) { - lastType = App::PropertyLength::getClassTypeId(); - } - - std::vector types = getSupportedTypes(); - - for(const auto& type : types) { - ui->comboBoxType->addItem(QString::fromLatin1(type.getName())); - if (type == lastType) { - ui->comboBoxType->setCurrentIndex(ui->comboBoxType->count()-1); - } - } - - completerType.setModel(ui->comboBoxType->model()); - completerType.setCaseSensitivity(Qt::CaseInsensitive); - completerType.setFilterMode(Qt::MatchContains); - ui->comboBoxType->setCompleter(&completerType); - ui->comboBoxType->setInsertPolicy(QComboBox::NoInsert); - - connComboBoxType = connect(ui->comboBoxType, &QComboBox::currentTextChanged, - this, &DlgAddPropertyVarSet::onTypeChanged); -} - -void DlgAddPropertyVarSet::removeSelectionEditor() -{ - // If the editor has a lineedit, then Qt selects the string inside it when - // the editor is created. This interferes with the editor getting focus. - // For example, units will then be selected as well, whereas this is not - // the behavior we want. We therefore deselect the text in the lineedit. - if (auto lineEdit = editor->findChild()) { - lineEdit->deselect(); - } -} - -void DlgAddPropertyVarSet::addEnumEditor(PropertyItem* propertyItem) -{ - auto* values = - static_cast(PropertyStringListItem::create()); - values->setParent(propertyItem); - values->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Enum"))); - if (propertyItem->childCount() > 0) { - auto* child = propertyItem->takeChild(0); - delete child; - } - propertyItem->appendChild(values); - editor.reset(values->createEditor(this, [this]() { - this->valueChangedEnum(); - }, FrameOption::WithFrame)); -} - -void DlgAddPropertyVarSet::addNormalEditor(PropertyItem* propertyItem) -{ - editor.reset(propertyItem->createEditor(this, [this]() { - this->valueChanged(); - }, FrameOption::WithFrame)); -} - -void DlgAddPropertyVarSet::addEditor(PropertyItem* propertyItem) -{ - if (isEnumPropertyItem()) { - addEnumEditor(propertyItem); - } - else { - addNormalEditor(propertyItem); - } - if (editor == nullptr) { - return; - } - - // Make sure that the editor has the same height as the - // other widgets in the dialog. - editor->setMinimumHeight(comboBoxGroup.height()); - - QSignalBlocker blockSignals(editor.get()); - - // To set the data in the editor, we need to set the data in the - // propertyItem. The propertyItem needs to have a property set to make - // sure that we get a correct value and the unit. - setEditorData(propertyItem->data(PropertyItem::ValueColumn, Qt::EditRole)); - editor->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - editor->setObjectName(QStringLiteral("editor")); - - setWidgetForLabel("labelValue", editor.get(), layout()); - - QWidget::setTabOrder(ui->comboBoxType, editor.get()); - QWidget::setTabOrder(editor.get(), ui->checkBoxAdd); - - removeSelectionEditor(); -} - -bool DlgAddPropertyVarSet::isTypeWithEditor(const Base::Type& type) -{ - static const std::initializer_list subTypesWithEditor = { - // These types and their subtypes have editors. - App::PropertyBool::getClassTypeId(), - App::PropertyColor::getClassTypeId(), - App::PropertyFileIncluded::getClassTypeId(), - App::PropertyFloat::getClassTypeId(), - App::PropertyInteger::getClassTypeId() - }; - - static const std::initializer_list typesWithEditor = { - // These types have editors but not necessarily their subtypes. - App::PropertyEnumeration::getClassTypeId(), - App::PropertyFile::getClassTypeId(), - App::PropertyFloatList::getClassTypeId(), - App::PropertyFont::getClassTypeId(), - App::PropertyIntegerList::getClassTypeId(), - App::PropertyMaterialList::getClassTypeId(), - App::PropertyPath::getClassTypeId(), - App::PropertyString::getClassTypeId(), - App::PropertyStringList::getClassTypeId(), - App::PropertyVectorList::getClassTypeId() - }; - - const auto isDerivedFromType = [&type](const Base::Type& t) { - return type.isDerivedFrom(t); - }; - - return std::ranges::find(typesWithEditor, type) != typesWithEditor.end() || - std::ranges::any_of(subTypesWithEditor, isDerivedFromType); -} - -bool DlgAddPropertyVarSet::isTypeWithEditor(const std::string& type) -{ - Base::Type propType = - Base::Type::getTypeIfDerivedFrom(type.c_str(), App::Property::getClassTypeId(), true); - return isTypeWithEditor(propType); -} - -static PropertyItem *createPropertyItem(App::Property *prop) -{ - const char *editor = prop->getEditorName(); - if (Base::Tools::isNullOrEmpty(editor)) { - return nullptr; - } - return PropertyItemFactory::instance().createPropertyItem(editor); -} - -void DlgAddPropertyVarSet::createEditorForType(const Base::Type& type) -{ - // Temporarily create a property for two reasons: - // - to acquire the editor name from the instance, and - // - to acquire an initial value from the instance possibly with the correct unit. - void* propInstance = type.createInstance(); - if (!propInstance) { - FC_THROWM(Base::RuntimeError, "Failed to create a property of type " << type.getName()); - } - - // When prop goes out of scope, it can be deleted because we obtained the - // propertyItem (if applicable) and we initialized the editor with the data - // from the property. - std::unique_ptr prop( - static_cast(propInstance), - [](App::Property* p) { delete p; }); - prop->setContainer(container); - - propertyItem.reset(createPropertyItem(prop.get())); - - if (propertyItem && isTypeWithEditor(type)) { - propertyItem->setPropertyData({prop.get()}); - addEditor(propertyItem.get()); - propertyItem->removeProperty(prop.get()); - } -} - -void DlgAddPropertyVarSet::initializeValue() -{ - std::string type = ui->comboBoxType->currentText().toStdString(); - - Base::Type propType = - Base::Type::getTypeIfDerivedFrom(type.c_str(), App::Property::getClassTypeId(), true); - if (propType.isBad()) { - return; - } - - if (isTypeWithEditor(propType)) { - createEditorForType(propType); - } - else { - removeEditor(); - } -} - -void DlgAddPropertyVarSet::setTitle() -{ - setWindowTitle(tr("Add Property")); -} - -void DlgAddPropertyVarSet::setOkEnabled(bool enabled) -{ - QPushButton *okButton = ui->buttonBox->button(QDialogButtonBox::Ok); - okButton->setEnabled(enabled); -} - -void DlgAddPropertyVarSet::initializeWidgets(ViewProviderVarSet* viewProvider) -{ - initializeGroup(); - initializeTypes(); - initializeValue(); - - if (viewProvider) { - connect(this, &QDialog::finished, - this, [viewProvider](int result) { viewProvider->onFinished(result); }); - } - connLineEditNameTextChanged = connect(ui->lineEditName, &QLineEdit::textChanged, - this, &DlgAddPropertyVarSet::onNameChanged); - - setTitle(); - setOkEnabled(false); - - ui->lineEditName->setFocus(); - - QWidget::setTabOrder(ui->lineEditName, &comboBoxGroup); - QWidget::setTabOrder(&comboBoxGroup, ui->comboBoxType); -} - -bool DlgAddPropertyVarSet::propertyExists(const std::string& name) -{ - App::Property* prop = container->getPropertyByName(name.c_str()); - return prop && prop->getContainer() == container && - !(propertyItem && propertyItem->getFirstProperty() == prop); -} - -bool DlgAddPropertyVarSet::isNameValid() -{ - std::string name = ui->lineEditName->text().toStdString(); - - return !name.empty() && - name == Base::Tools::getIdentifier(name) && - !App::ExpressionParser::isTokenAConstant(name) && - !App::ExpressionParser::isTokenAUnit(name) && - !propertyExists(name); -} - -bool DlgAddPropertyVarSet::isGroupValid() -{ - std::string group = comboBoxGroup.currentText().toStdString(); - return !group.empty() && group == Base::Tools::getIdentifier(group); -} - -bool DlgAddPropertyVarSet::isTypeValid() -{ - std::string type = ui->comboBoxType->currentText().toStdString(); - return Base::Type::fromName(type.c_str()).isDerivedFrom(App::Property::getClassTypeId()); -} - -bool DlgAddPropertyVarSet::areFieldsValid() -{ - return isNameValid() && isGroupValid() && isTypeValid(); -} - -void DlgAddPropertyVarSet::showStatusMessage() -{ - QString error; - QString text = ui->lineEditName->text(); - std::string name = text.toStdString(); - - if (!isGroupValid()) { - error = tr("Invalid group name"); - } - else if (!isTypeValid()) { - error = tr("Invalid type name"); - } - else if (name.empty()) { - error.clear(); - } - else if (name != Base::Tools::getIdentifier(name)) { - error = tr("Invalid property name '%1'").arg(text); - } - else if (propertyExists(name)) { - error = tr("Property '%1' already exists").arg(text); - } - else if (App::ExpressionParser::isTokenAConstant(name)) { - error = tr("'%1' is a constant").arg(text); - } - else if (App::ExpressionParser::isTokenAUnit(name)) { - error = tr("'%1' is a unit").arg(text); - } - - ui->labelError->setText(error); -} - -void DlgAddPropertyVarSet::removeEditor() -{ - if (editor == nullptr) { - return; - } - - // Create a placeholder widget to keep the layout intact. - auto* placeholder = new QWidget(this); - placeholder->setObjectName(QStringLiteral("placeholder")); - placeholder->setMinimumHeight(comboBoxGroup.height()); - setWidgetForLabel("labelValue", placeholder, layout()); - - QWidget::setTabOrder(ui->comboBoxType, ui->checkBoxAdd); - editor = nullptr; -} - -bool DlgAddPropertyVarSet::isEnumPropertyItem() const -{ - return ui->comboBoxType->currentText() == - QString::fromLatin1(App::PropertyEnumeration::getClassTypeId().getName()); -} - -QVariant DlgAddPropertyVarSet::getEditorData() const -{ - if (isEnumPropertyItem()) { - PropertyItem* child = propertyItem->child(0); - if (child == nullptr) { - return {}; - } - return child->editorData(editor.get()); - } - - return propertyItem->editorData(editor.get()); -} - -void DlgAddPropertyVarSet::setEditorData(const QVariant& data) -{ - if (isEnumPropertyItem()) { - PropertyItem* child = propertyItem->child(0); - if (child == nullptr) { - return; - } - child->setEditorData(editor.get(), data); - } - else { - propertyItem->setEditorData(editor.get(), data); - } -} - -void DlgAddPropertyVarSet::setEditor(bool valueNeedsReset) -{ - if (editor && !valueNeedsReset) { - QVariant data = getEditorData(); - addEditor(propertyItem.get()); - if (editor == nullptr) { - return; - } - setEditorData(data); - removeSelectionEditor(); - } - else if (propertyItem) { - addEditor(propertyItem.get()); - } - else { - initializeValue(); - } - - if (editor) { - QVariant data = propertyItem->editorData(editor.get()); - propertyItem->setData(data); - } -} - -void DlgAddPropertyVarSet::setPropertyItem(App::Property* prop) -{ - if (prop == nullptr) { - return; - } - - if (propertyItem == nullptr) { - propertyItem.reset(createPropertyItem(prop)); - } - - if (propertyItem == nullptr) { - return; - } - - objectIdentifier = std::make_unique(*prop); - propertyItem->setAutoApply(true); - propertyItem->setPropertyData({prop}); - propertyItem->bind(*objectIdentifier); -} - -void DlgAddPropertyVarSet::buildForUnbound(bool valueNeedsReset) -{ - setEditor(valueNeedsReset); -} - -void DlgAddPropertyVarSet::buildForBound(bool valueNeedsReset) -{ - openTransaction(); - App::Property* prop = createProperty(); - setPropertyItem(prop); - setEditor(valueNeedsReset); -} - -bool DlgAddPropertyVarSet::clearBoundProperty() -{ - // Both a property link and an expression are bound to a property and as a - // result need the value to be reset. - bool isPropertyLinkItem = qobject_cast(propertyItem.get()) != nullptr; - bool valueNeedsReset = isPropertyLinkItem || propertyItem->hasExpression(); - - if (App::Property* prop = propertyItem->getFirstProperty()) { - propertyItem->unbind(); - propertyItem->removeProperty(prop); - container->removeDynamicProperty(prop->getName()); - closeTransaction(TransactionOption::Abort); - } - return valueNeedsReset; -} - -bool DlgAddPropertyVarSet::clear(FieldChange fieldChange) -{ - if (propertyItem == nullptr) { - return true; - } - - bool valueNeedsReset = clearBoundProperty(); - - if (fieldChange == FieldChange::Type) { - valueNeedsReset = true; - removeEditor(); - propertyItem = nullptr; - } - return valueNeedsReset; -} - -void DlgAddPropertyVarSet::onNameChanged([[maybe_unused]]const QString& text) -{ - bool valueNeedsReset = clear(FieldChange::Name); - if (isNameValid() && isTypeValid()) { - buildForBound(valueNeedsReset); - } - else if (isTypeValid()) { - buildForUnbound(valueNeedsReset); - } - else { - removeEditor(); - propertyItem = nullptr; - } - - setOkEnabled(areFieldsValid()); - showStatusMessage(); -} - -void DlgAddPropertyVarSet::onGroupFinished() -{ - if (isGroupValid() && propertyItem) { - std::string group = comboBoxGroup.currentText().toStdString(); - std::string doc = ui->lineEditToolTip->text().toStdString(); - if (App::Property* prop = propertyItem->getFirstProperty(); - prop && prop->getGroup() != group) { - container->changeDynamicProperty(prop, group.c_str(), doc.c_str()); - } - } - - setOkEnabled(areFieldsValid()); - showStatusMessage(); -} - -void DlgAddPropertyVarSet::onTypeChanged([[maybe_unused]] const QString& text) -{ - bool valueNeedsReset = clear(FieldChange::Type); - if (isNameValid() && isTypeValid()) { - buildForBound(valueNeedsReset); - } - else if (isTypeValid()) { - buildForUnbound(valueNeedsReset); - } - // nothing if both name and type are invalid - - setOkEnabled(areFieldsValid()); - showStatusMessage(); -} - -void DlgAddPropertyVarSet::changeEvent(QEvent* e) -{ - if (e->type() == QEvent::LanguageChange) { - ui->retranslateUi(this); - setTitle(); - } - QDialog::changeEvent(e); -} - -void DlgAddPropertyVarSet::valueChangedEnum() -{ - auto* propEnum = - static_cast(propertyItem->getFirstProperty()); - if (propEnum == nullptr || propertyItem->childCount() == 0) { - return; - } - - auto* values = static_cast(propertyItem->child(0)); - QVariant data = values->editorData(editor.get()); - QStringList enumValues = data.toStringList(); - // convert to std::vector - std::vector enumValuesVec; - std::ranges::transform(enumValues, std::back_inserter(enumValuesVec), - [](const QString& value) { return value.toStdString(); }); - propEnum->setEnums(enumValuesVec); -} - -void DlgAddPropertyVarSet::valueChanged() -{ - QVariant data = propertyItem->editorData(editor.get()); - propertyItem->setData(data); -} - -/* We use these functions rather than the functions provided by App::Document - * because this dialog may be opened when another transaction is in progress. - * An example is opening a sketch. If this dialog uses the functions provided - * by App::Document, a reject of the dialog would close that transaction. By - * checking whether the transaction ID is "our" transaction ID, we prevent this - * behavior. - */ -void DlgAddPropertyVarSet::openTransaction() -{ - transactionID = App::GetApplication().setActiveTransaction("Add property VarSet"); -} - -void DlgAddPropertyVarSet::critical(const QString& title, const QString& text) { - static bool criticalDialogShown = false; - if (!criticalDialogShown) { - criticalDialogShown = true; - QMessageBox::critical(this, title, text); - criticalDialogShown = false; - } -} - -App::Property* DlgAddPropertyVarSet::createProperty() -{ - std::string name = ui->lineEditName->text().toStdString(); - std::string group = comboBoxGroup.currentText().toStdString(); - std::string type = ui->comboBoxType->currentText().toStdString(); - std::string doc = ui->lineEditToolTip->text().toStdString(); - - try { - return container->addDynamicProperty(type.c_str(), name.c_str(), - group.c_str(), doc.c_str()); - } - catch (Base::Exception& e) { - e.reportException(); - critical(QObject::tr("Add property"), - QObject::tr("Failed to add property to '%1': %2").arg( - QString::fromLatin1(container->getFullName().c_str()), - QString::fromUtf8(e.what()))); - return nullptr; - } -} - -void DlgAddPropertyVarSet::closeTransaction(TransactionOption option) -{ - if (transactionID == 0) { - return; - } - - App::GetApplication().closeActiveTransaction(static_cast(option), transactionID); - transactionID = 0; -} - -void DlgAddPropertyVarSet::clearFields() -{ - { - QSignalBlocker blocker(ui->lineEditName); - ui->lineEditName->clear(); - } - ui->lineEditToolTip->clear(); - initializeValue(); - setOkEnabled(false); -} - -void DlgAddPropertyVarSet::addDocumentation() { - /* Since there is no check on documentation (we accept any string), there - * is no signal handler for the documentation field. This method updates - * the property that is being added with the text inserted as - * documentation/tooltip. - */ - - std::string group = comboBoxGroup.currentText().toStdString(); - std::string doc = ui->lineEditToolTip->text().toStdString(); - - if (propertyItem == nullptr) { - // If there is no property item, we cannot add documentation. - return; - } - - App::Property* prop = propertyItem->getFirstProperty(); - if (prop == nullptr) { - return; - } - - container->changeDynamicProperty(prop, group.c_str(), doc.c_str()); -} - -void DlgAddPropertyVarSet::accept() -{ - addDocumentation(); - auto* object = freecad_cast(container); - if (object) { - object->ExpressionEngine.execute(); - } - closeTransaction(TransactionOption::Commit); - std::string group = comboBoxGroup.currentText().toStdString(); - std::string type = ui->comboBoxType->currentText().toStdString(); - auto paramGroup = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/PropertyView"); - paramGroup->SetASCII("NewPropertyType", type.c_str()); - paramGroup->SetASCII("NewPropertyGroup", group.c_str()); - - if (ui->checkBoxAdd->isChecked()) { - clearFields(); - ui->lineEditName->setFocus(); - } - else { - // we are done, close the dialog - QDialog::accept(); - } -} - -void DlgAddPropertyVarSet::reject() -{ - if (propertyItem) { - if (App::Property* prop = propertyItem->getFirstProperty()) { - App::PropertyContainer* container = prop->getContainer(); - container->removeDynamicProperty(prop->getName()); - closeTransaction(TransactionOption::Abort); - } - } - disconnect(connComboBoxGroup); - disconnect(connComboBoxType); - disconnect(connLineEditNameTextChanged); - - QDialog::reject(); -} - -#include "moc_DlgAddPropertyVarSet.cpp" diff --git a/src/Gui/Dialogs/DlgAddPropertyVarSet.h b/src/Gui/Dialogs/DlgAddPropertyVarSet.h deleted file mode 100644 index 60b1177af8..0000000000 --- a/src/Gui/Dialogs/DlgAddPropertyVarSet.h +++ /dev/null @@ -1,185 +0,0 @@ -/**************************************************************************** - * Copyright (c) 2024 Ondsel * - * Copyright (c) 2025 Pieter Hijma * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ****************************************************************************/ - -#ifndef GUI_DIALOG_DLG_ADD_PROPERTY_VARSET_H -#define GUI_DIALOG_DLG_ADD_PROPERTY_VARSET_H - -#include - -#include -#include -#include - -#include - -#include - -#include "propertyeditor/PropertyItem.h" - -namespace Gui { - -class ViewProviderVarSet; - -namespace Dialog { - -class EditFinishedComboBox : public QComboBox { - Q_OBJECT -public: - explicit EditFinishedComboBox(QWidget *parent = nullptr) : QComboBox(parent) { - setEditable(true); - connect(this, QOverload::of(&QComboBox::currentIndexChanged), this, &EditFinishedComboBox::onIndexChanged); - connect(this->lineEdit(), &QLineEdit::editingFinished, this, &EditFinishedComboBox::onEditingFinished); - } - -Q_SIGNALS: - void editFinished(); - -private: - void onEditingFinished() { - Q_EMIT editFinished(); - } - - void onIndexChanged() { - Q_EMIT editFinished(); - } -}; - -class Ui_DlgAddPropertyVarSet; - -class GuiExport DlgAddPropertyVarSet : public QDialog -{ - Q_OBJECT - -public: - static const std::string GroupBase; - -public: - DlgAddPropertyVarSet(QWidget* parent, ViewProviderVarSet* viewProvider); - DlgAddPropertyVarSet(QWidget* parent, App::PropertyContainer* container); - - DlgAddPropertyVarSet(const DlgAddPropertyVarSet&) = delete; - DlgAddPropertyVarSet(DlgAddPropertyVarSet&&) = delete; - DlgAddPropertyVarSet& operator=(const DlgAddPropertyVarSet&) = delete; - DlgAddPropertyVarSet& operator=(DlgAddPropertyVarSet&&) = delete; - - ~DlgAddPropertyVarSet() override; - - void changeEvent(QEvent* e) override; - void accept() override; - void reject() override; - static void populateGroup(EditFinishedComboBox& comboBox, - const App::PropertyContainer* container); - static void setWidgetForLabel(const char* labelName, QWidget* widget, QLayout* layout); - -public Q_SLOTS: - void valueChanged(); - void valueChangedEnum(); - -private: - enum class TransactionOption : bool { - Commit = false, - Abort = true - }; - - enum class FieldChange : std::uint8_t { - Name, - Type - }; - - DlgAddPropertyVarSet(QWidget* parent, App::PropertyContainer* container, - ViewProviderVarSet* viewProvider); - - void initializeGroup(); - - std::vector getSupportedTypes(); - void initializeTypes(); - - void removeSelectionEditor(); - QVariant getEditorData() const; - void setEditorData(const QVariant& data); - bool isEnumPropertyItem() const; - void addEnumEditor(PropertyEditor::PropertyItem* propertyItem); - void addNormalEditor(PropertyEditor::PropertyItem* propertyItem); - void addEditor(PropertyEditor::PropertyItem* propertyItem); - bool isTypeWithEditor(const Base::Type& type); - bool isTypeWithEditor(const std::string& type); - void createEditorForType(const Base::Type& type); - void initializeValue(); - - void setTitle(); - void setOkEnabled(bool enabled); - void initializeWidgets(ViewProviderVarSet* viewProvider); - - bool propertyExists(const std::string& name); - bool isNameValid(); - bool isGroupValid(); - bool isTypeValid(); - bool areFieldsValid(); - - void setEditor(bool valueNeedsReset); - void buildForUnbound(bool valueNeedsReset); - void setPropertyItem(App::Property* prop); - void buildForBound(bool valueNeedsReset); - bool clearBoundProperty(); - bool clear(FieldChange fieldChange); - void onNameChanged(const QString& text); - void onGroupFinished(); - void onTypeChanged(const QString& text); - - void showStatusMessage(); - - void removeEditor(); - - void openTransaction(); - void critical(const QString& title, const QString& text); - App::Property* createProperty(); - void closeTransaction(TransactionOption option); - void clearFields(); - void addDocumentation(); - - static void removeExistingWidget(QFormLayout* layout, int labelRow); - static int findLabelRow(const char* labelName, QFormLayout* layout); - -private: - App::PropertyContainer* container; - std::unique_ptr ui; - - EditFinishedComboBox comboBoxGroup; - QCompleter completerType; - - std::unique_ptr editor; - std::unique_ptr propertyItem; - std::unique_ptr objectIdentifier; - - // a transactionID of 0 means that there is no active transaction. - int transactionID; - - QMetaObject::Connection connComboBoxGroup; - QMetaObject::Connection connComboBoxType; - QMetaObject::Connection connLineEditNameTextChanged; -}; - -} // namespace Dialog -} // namespace Gui - -#endif // GUI_DIALOG_DLG_ADD_PROPERTY_VARSET_H diff --git a/src/Gui/Dialogs/DlgAddPropertyVarSet.ui b/src/Gui/Dialogs/DlgAddPropertyVarSet.ui deleted file mode 100644 index b662a108e9..0000000000 --- a/src/Gui/Dialogs/DlgAddPropertyVarSet.ui +++ /dev/null @@ -1,126 +0,0 @@ - - - Gui::Dialog::DlgAddPropertyVarSet - - - - 0 - 0 - 418 - 234 - - - - Add Property - - - - - - Name - - - - - - - - - - Group - - - - - - - Type - - - - - - - true - - - - - - - Value - - - - - - - Add another - - - - - - - Tooltip - - - - - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - Gui::Dialog::DlgAddPropertyVarSet - accept() - - - 199 - 99 - - - 199 - 58 - - - - - buttonBox - rejected() - Gui::Dialog::DlgAddPropertyVarSet - reject() - - - 199 - 99 - - - 199 - 58 - - - - - diff --git a/src/Gui/Dialogs/DlgExpressionInput.cpp b/src/Gui/Dialogs/DlgExpressionInput.cpp index 78c892f39b..6307db5736 100644 --- a/src/Gui/Dialogs/DlgExpressionInput.cpp +++ b/src/Gui/Dialogs/DlgExpressionInput.cpp @@ -237,7 +237,7 @@ void DlgExpressionInput::initializeVarSets() comboBoxGroup.setObjectName(QStringLiteral("comboBoxGroup")); comboBoxGroup.setInsertPolicy(QComboBox::InsertAtTop); comboBoxGroup.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - DlgAddPropertyVarSet::setWidgetForLabel("labelGroup", &comboBoxGroup, ui->formLayout); + DlgAddProperty::setWidgetForLabel("labelGroup", &comboBoxGroup, ui->formLayout); setTabOrder(ui->comboBoxVarSet, &comboBoxGroup); setTabOrder(&comboBoxGroup, ui->lineEditPropNew); @@ -819,7 +819,7 @@ void DlgExpressionInput::onVarSetSelected(int /*index*/) return; } - DlgAddPropertyVarSet::populateGroup(comboBoxGroup, varSet); + DlgAddProperty::populateGroup(comboBoxGroup, varSet); preselectGroup(); updateVarSetInfo(); ui->lineEditPropNew->setFocus(); diff --git a/src/Gui/Dialogs/DlgExpressionInput.h b/src/Gui/Dialogs/DlgExpressionInput.h index ce62aa2d95..aeb152e2d0 100644 --- a/src/Gui/Dialogs/DlgExpressionInput.h +++ b/src/Gui/Dialogs/DlgExpressionInput.h @@ -31,7 +31,7 @@ #include #include -#include "Dialogs/DlgAddPropertyVarSet.h" +#include "Dialogs/DlgAddProperty.h" namespace Ui { class DlgExpressionInput; diff --git a/src/Gui/ViewProviderVarSet.cpp b/src/Gui/ViewProviderVarSet.cpp index d9179422d5..e43af5873a 100644 --- a/src/Gui/ViewProviderVarSet.cpp +++ b/src/Gui/ViewProviderVarSet.cpp @@ -44,7 +44,7 @@ ViewProviderVarSet::ViewProviderVarSet() bool ViewProviderVarSet::doubleClicked() { if (!dialog) { - dialog = std::make_unique(getMainWindow(), this); + dialog = std::make_unique(getMainWindow(), this); } // Do not use exec() here because it blocks and prevents command Std_VarSet diff --git a/src/Gui/ViewProviderVarSet.h b/src/Gui/ViewProviderVarSet.h index 6afa7b975e..3542abedfc 100644 --- a/src/Gui/ViewProviderVarSet.h +++ b/src/Gui/ViewProviderVarSet.h @@ -24,7 +24,7 @@ #define GUI_ViewProviderVarSet_H #include "ViewProviderDocumentObject.h" -#include "Dialogs/DlgAddPropertyVarSet.h" +#include "Dialogs/DlgAddProperty.h" namespace Gui { @@ -44,7 +44,7 @@ public: void onFinished(int); private: - std::unique_ptr dialog; + std::unique_ptr dialog; }; } // namespace Gui