1
/****************************************************************************
2
* Copyright (c) 2024 Ondsel <development@ondsel.com> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
****************************************************************************/
23
#include "PreCompiled.h"
25
# include <QMessageBox>
30
#include <App/Application.h>
31
#include <App/Document.h>
32
#include <App/DocumentObject.h>
33
#include <App/PropertyUnits.h>
34
#include <Base/Tools.h>
36
#include "DlgAddPropertyVarSet.h"
37
#include "ui_DlgAddPropertyVarSet.h"
38
#include "MainWindow.h"
39
#include "ViewProviderDocumentObject.h"
40
#include "ViewProviderVarSet.h"
42
FC_LOG_LEVEL_INIT("DlgAddPropertyVarSet", true, true)
45
using namespace Gui::Dialog;
47
const std::string DlgAddPropertyVarSet::GROUP_BASE = "Base";
49
const bool CLEAR_NAME = true;
51
DlgAddPropertyVarSet::DlgAddPropertyVarSet(QWidget* parent,
52
ViewProviderVarSet* viewProvider)
54
varSet(dynamic_cast<App::VarSet*>(viewProvider->getObject())),
55
ui(new Ui_DlgAddPropertyVarSet),
62
initializeWidgets(viewProvider);
65
DlgAddPropertyVarSet::~DlgAddPropertyVarSet() = default;
67
void DlgAddPropertyVarSet::initializeGroup()
69
connect(&comboBoxGroup, &EditFinishedComboBox::editFinished,
70
this, &DlgAddPropertyVarSet::onEditFinished);
71
comboBoxGroup.setObjectName(QString::fromUtf8("comboBoxGroup"));
72
comboBoxGroup.setInsertPolicy(QComboBox::InsertAtTop);
73
comboBoxGroup.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
74
comboBoxGroup.setEditable(true);
75
auto formLayout = qobject_cast<QFormLayout*>(layout());
76
formLayout->setWidget(1, QFormLayout::FieldRole, &comboBoxGroup);
78
std::vector<App::Property*> properties;
79
varSet->getPropertyList(properties);
80
std::unordered_set<std::string> groupNames;
81
for (auto prop : properties) {
82
const char* groupName = varSet->getPropertyGroup(prop);
83
groupNames.insert(groupName ? groupName : GROUP_BASE);
85
std::vector<std::string> groupNamesSorted(groupNames.begin(), groupNames.end());
86
std::sort(groupNamesSorted.begin(), groupNamesSorted.end(), [](std::string& a, std::string& b) {
87
// prefer anything else other than Base, so move it to the back
88
if (a == GROUP_BASE) {
91
else if (b == GROUP_BASE) {
99
for (const auto& groupName : groupNamesSorted) {
100
comboBoxGroup.addItem(QString::fromStdString(groupName));
103
comboBoxGroup.setEditText(QString::fromStdString(groupNamesSorted[0]));
106
void DlgAddPropertyVarSet::getSupportedTypes(std::vector<Base::Type>& types)
108
std::vector<Base::Type> proptypes;
109
Base::Type::getAllDerivedFrom(Base::Type::fromName("App::Property"), proptypes);
110
std::copy_if(proptypes.begin(), proptypes.end(), std::back_inserter(types), [](const Base::Type& type) {
111
return type.canInstantiate();
113
std::sort(types.begin(), types.end(), [](Base::Type a, Base::Type b) {
114
return strcmp(a.getName(), b.getName()) < 0;
118
void DlgAddPropertyVarSet::initializeTypes()
120
auto paramGroup = App::GetApplication().GetParameterGroupByPath(
121
"User parameter:BaseApp/Preferences/PropertyView");
122
auto lastType = Base::Type::fromName(
123
paramGroup->GetASCII("NewPropertyType","App::PropertyLength").c_str());
124
if(lastType.isBad()) {
125
lastType = App::PropertyLength::getClassTypeId();
128
std::vector<Base::Type> types;
129
getSupportedTypes(types);
131
for(const auto& type : types) {
132
ui->comboBoxType->addItem(QString::fromLatin1(type.getName()));
134
ui->comboBoxType->setCurrentIndex(ui->comboBoxType->count()-1);
137
completerType.setModel(ui->comboBoxType->model());
138
completerType.setCaseSensitivity(Qt::CaseInsensitive);
139
completerType.setFilterMode(Qt::MatchContains);
140
ui->comboBoxType->setCompleter(&completerType);
141
ui->comboBoxType->setInsertPolicy(QComboBox::NoInsert);
143
connect(ui->comboBoxType, &QComboBox::currentTextChanged,
144
this, &DlgAddPropertyVarSet::onEditFinished);
148
// keep some debugging code for debugging tab order
149
static void printFocusChain(QWidget *widget) {
150
FC_ERR("Focus Chain:");
151
QWidget* start = widget;
154
FC_ERR(" " << widget->objectName().toStdString();
155
widget = widget->nextInFocusChain();
157
} while (widget != nullptr && i < 30 && start != widget);
158
QWidget *currentWidget = QApplication::focusWidget();
159
FC_ERR(" Current focus widget:" << (currentWidget ? currentWidget->objectName().toStdString() : "None") << std::endl << std::endl);
163
void DlgAddPropertyVarSet::initializeWidgets(ViewProviderVarSet* viewProvider)
168
connect(this, &QDialog::finished,
169
this, [viewProvider](int result) { viewProvider->onFinished(result); });
170
connect(ui->lineEditName, &QLineEdit::editingFinished,
171
this, &DlgAddPropertyVarSet::onEditFinished);
172
connect(ui->lineEditName, &QLineEdit::textChanged,
173
this, &DlgAddPropertyVarSet::onNamePropertyChanged);
175
std::string title = "Add a property to " + varSet->getFullName();
176
setWindowTitle(QString::fromStdString(title));
180
ui->lineEditName->setFocus();
182
QWidget::setTabOrder(ui->lineEditName, &comboBoxGroup);
183
QWidget::setTabOrder(&comboBoxGroup, ui->comboBoxType);
185
// FC_ERR("Initialize widgets");
186
// printFocusChain(ui->lineEditName);
189
void DlgAddPropertyVarSet::setOkEnabled(bool enabled)
191
QPushButton *okButton = ui->buttonBox->button(QDialogButtonBox::Ok);
192
okButton->setEnabled(enabled);
195
void DlgAddPropertyVarSet::clearEditors(bool clearName)
198
bool beforeBlocked = ui->lineEditName->blockSignals(true);
199
ui->lineEditName->clear();
200
ui->lineEditName->blockSignals(beforeBlocked);
204
namePropertyToAdd.clear();
207
void DlgAddPropertyVarSet::removeEditor()
210
layout()->removeWidget(editor.get());
211
QWidget::setTabOrder(ui->comboBoxType, ui->checkBoxAdd);
214
// FC_ERR("remove editor");
215
// printFocusChain(ui->comboBoxType);
219
static PropertyEditor::PropertyItem *createPropertyItem(App::Property *prop)
221
const char *editor = prop->getEditorName();
222
if (!editor || !editor[0]) {
225
auto item = static_cast<PropertyEditor::PropertyItem*>(
226
PropertyEditor::PropertyItemFactory::instance().createPropertyItem(editor));
228
qWarning("No property item for type %s found\n", editor);
233
void DlgAddPropertyVarSet::addEditor(PropertyEditor::PropertyItem* propertyItem, std::string& type)
235
editor.reset(propertyItem->createEditor(this, [this]() {
236
this->valueChanged();
238
if (type == "App::PropertyFont") {
239
propertyItem->setEditorData(editor.get(), QVariant());
241
editor->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
242
editor->setObjectName(QString::fromUtf8("editor"));
243
auto formLayout = qobject_cast<QFormLayout*>(layout());
244
formLayout->setWidget(3, QFormLayout::FieldRole, editor.get());
246
QWidget::setTabOrder(ui->comboBoxType, editor.get());
247
QWidget::setTabOrder(editor.get(), ui->checkBoxAdd);
249
// FC_ERR("add editor");
250
// printFocusChain(editor.get());
253
bool DlgAddPropertyVarSet::isTypeWithEditor(const std::string& type)
255
return typesWithoutEditor.find(type) == typesWithoutEditor.end();
258
void DlgAddPropertyVarSet::createProperty()
260
std::string name = ui->lineEditName->text().toStdString();
261
std::string group = comboBoxGroup.currentText().toStdString();
262
std::string type = ui->comboBoxType->currentText().toStdString();
266
prop = varSet->addDynamicProperty(type.c_str(), name.c_str(),
269
catch (Base::Exception& e) {
271
QMessageBox::critical(this,
272
QObject::tr("Add property"),
273
QObject::tr("Failed to add property to '%1': %2").arg(
274
QString::fromLatin1(varSet->getFullName().c_str()),
275
QString::fromUtf8(e.what())));
280
namePropertyToAdd = name;
282
objectIdentifier = std::make_unique<App::ObjectIdentifier>(*prop);
283
// creating a propertyItem here because it has all kinds of logic for
284
// editors that we can reuse
286
propertyItem.reset(createPropertyItem(prop));
287
if (propertyItem && isTypeWithEditor(type)) {
288
propertyItem->setPropertyData({prop});
289
propertyItem->bind(*objectIdentifier);
290
addEditor(propertyItem.get(), type);
296
void DlgAddPropertyVarSet::changePropertyToAdd() {
297
// we were already adding a new property, the only option to get here
298
// is a change of type or group.
300
std::string name = ui->lineEditName->text().toStdString();
301
assert(name == namePropertyToAdd);
303
App::Property* prop = varSet->getPropertyByName(namePropertyToAdd.c_str());
305
std::string group = comboBoxGroup.currentText().toStdString();
306
if (prop->getGroup() != group) {
307
varSet->changeDynamicProperty(prop, group.c_str(), nullptr);
310
std::string type = ui->comboBoxType->currentText().toStdString();
311
if (prop->getTypeId() != Base::Type::fromName(type.c_str())) {
312
// the property should have a different type
313
varSet->removeDynamicProperty(namePropertyToAdd.c_str());
319
void DlgAddPropertyVarSet::clearCurrentProperty()
322
varSet->removeDynamicProperty(namePropertyToAdd.c_str());
323
App::Document* doc = varSet->getDocument();
324
if (doc->hasPendingTransaction()) {
325
doc->abortTransaction();
328
namePropertyToAdd.clear();
331
class CreatePropertyException : public std::exception {
333
explicit CreatePropertyException(const std::string& message) : msg(message) {}
335
const char* what() const noexcept override {
343
void DlgAddPropertyVarSet::checkName() {
344
std::string name = ui->lineEditName->text().toStdString();
345
if(name.empty() || name != Base::Tools::getIdentifier(name)) {
346
QMessageBox::critical(getMainWindow(),
347
QObject::tr("Invalid name"),
348
QObject::tr("The property name must only contain alpha numericals,\n"
349
"underscore, and must not start with a digit."));
350
clearEditors(!CLEAR_NAME);
351
throw CreatePropertyException("Invalid name");
354
if (namePropertyToAdd.empty()) {
355
// we are adding a new property, check whether it doesn't already exist
356
auto prop = varSet->getPropertyByName(name.c_str());
357
if(prop && prop->getContainer() == varSet) {
358
QMessageBox::critical(this,
359
QObject::tr("Invalid name"),
360
QObject::tr("The property '%1' already exists in '%2'").arg(
361
QString::fromLatin1(name.c_str()),
362
QString::fromLatin1(varSet->getFullName().c_str())));
363
clearEditors(!CLEAR_NAME);
364
throw CreatePropertyException("Invalid name");
369
void DlgAddPropertyVarSet::checkGroup() {
370
std::string group = comboBoxGroup.currentText().toStdString();
372
if (group.empty() || group != Base::Tools::getIdentifier(group)) {
373
QMessageBox::critical(this,
374
QObject::tr("Invalid name"),
375
QObject::tr("The group name must only contain alpha numericals,\n"
376
"underscore, and must not start with a digit."));
377
comboBoxGroup.setEditText(QString::fromUtf8("Base"));
378
throw CreatePropertyException("Invalid name");
382
void DlgAddPropertyVarSet::checkType() {
383
std::string type = ui->comboBoxType->currentText().toStdString();
385
if (Base::Type::fromName(type.c_str()) == Base::Type::badType()) {
386
throw CreatePropertyException("Invalid name");
390
void DlgAddPropertyVarSet::onEditFinished() {
391
/* The editor for the value is dynamically created if 1) the name has been
392
* determined and 2) if the type of the property has been determined. The
393
* group of the property is important too, but it is not essential, because
394
* we can change the group after the property has been created.
396
* In this function we check whether we can create a property and therefore
405
catch (const CreatePropertyException&) {
406
if (!namePropertyToAdd.empty()) {
407
clearCurrentProperty();
412
if (namePropertyToAdd.empty()) {
413
// we are adding a new property
414
App::Document* doc = varSet->getDocument();
415
doc->openTransaction("Add property VarSet");
419
// we were already adding a new property that should now be changed
420
changePropertyToAdd();
424
void DlgAddPropertyVarSet::onNamePropertyChanged(const QString& text)
426
if (!namePropertyToAdd.empty() && text.toStdString() != namePropertyToAdd) {
427
// The user decided to change the name of the property. This
428
// invalidates the editor that is strictly associated with the property.
429
clearCurrentProperty();
433
void DlgAddPropertyVarSet::valueChanged()
436
data = propertyItem->editorData(editor.get());
437
propertyItem->setData(data);
440
void DlgAddPropertyVarSet::accept()
442
App::Document* doc = varSet->getDocument();
443
doc->commitTransaction();
445
if (ui->checkBoxAdd->isChecked()) {
447
doc->openTransaction();
448
ui->lineEditName->setFocus();
452
std::string group = comboBoxGroup.currentText().toStdString();
453
std::string type = ui->comboBoxType->currentText().toStdString();
454
auto paramGroup = App::GetApplication().GetParameterGroupByPath(
455
"User parameter:BaseApp/Preferences/PropertyView");
456
paramGroup->SetASCII("NewPropertyType", type.c_str());
457
paramGroup->SetASCII("NewPropertyGroup", group.c_str());
461
void DlgAddPropertyVarSet::reject()
463
App::Document* doc = varSet->getDocument();
464
// a transaction is not pending if a name has not been determined.
465
if (doc->hasPendingTransaction()) {
466
doc->abortTransaction();
472
#include "moc_DlgAddPropertyVarSet.cpp"