Files
create/src/Gui/Dialogs/DlgExpressionInput.cpp
Pieter Hijma c97305a9fc Gui: Add more translations to expr dialog
This adds translations to the feedback on the expression, for example if
there is a unit mismatch.
2025-08-27 14:59:28 +02:00

939 lines
31 KiB
C++

/***************************************************************************
* Copyright (c) 2015 Eivind Kvedalen <eivind@kvedalen.name> *
* Copyright (c) 2025 Pieter Hijma <info@pieterhijma.net> *
* *
* 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., 51 Franklin Street, *
* Fifth Floor, Boston, MA 02110-1301, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <QApplication>
#include <QMenu>
#include <QMouseEvent>
#include <QPushButton>
#include <QTreeWidget>
#include <QStyledItemDelegate>
#endif
#include <fmt/format.h>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/ExpressionParser.h>
#include <App/VarSet.h>
#include <Base/Console.h>
#include <Base/Tools.h>
#include "Dialogs/DlgExpressionInput.h"
#include "ui_DlgExpressionInput.h"
#include "Application.h"
#include "Command.h"
#include "Tools.h"
#include "ExpressionBinding.h"
#include "BitmapFactory.h"
#include "ViewProviderDocumentObject.h"
using namespace App;
using namespace Gui::Dialog;
FC_LOG_LEVEL_INIT("DlgExpressionInput", true, true)
DlgExpressionInput::DlgExpressionInput(const App::ObjectIdentifier & _path,
std::shared_ptr<const Expression> _expression,
const Base::Unit & _impliedUnit, QWidget *parent)
: QDialog(parent)
, ui(new Ui::DlgExpressionInput)
, expression(_expression ? _expression->copy() : nullptr)
, path(_path)
, discarded(false)
, impliedUnit(_impliedUnit)
, minimumWidth(10)
, varSetsVisible(false)
, comboBoxGroup(this)
{
assert(path.getDocumentObject());
// Setup UI
ui->setupUi(this);
okBtn = ui->buttonBox->button(QDialogButtonBox::Ok);
discardBtn = ui->buttonBox->button(QDialogButtonBox::Reset);
discardBtn->setToolTip(tr("Revert to last calculated value (as constant)"));
initializeVarSets();
// Connect signal(s)
connect(ui->expression, &ExpressionLineEdit::textChanged,
this, &DlgExpressionInput::textChanged);
connect(discardBtn, &QPushButton::clicked,
this, &DlgExpressionInput::setDiscarded);
if (expression) {
ui->expression->setText(QString::fromStdString(expression->toString()));
}
else {
QVariant text = parent->property("text");
if (text.canConvert<QString>()) {
ui->expression->setText(text.toString());
}
}
// Set document object on line edit to create auto completer
DocumentObject * docObj = path.getDocumentObject();
ui->expression->setDocumentObject(docObj);
// There are some platforms where setting no system background causes a black
// rectangle to appear. To avoid this the 'NoSystemBackground' parameter can be
// set to false. Then a normal non-modal dialog will be shown instead (#0002440).
bool noBackground = App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/Expression")->GetBool("NoSystemBackground", false);
if (noBackground) {
#if defined(Q_OS_MACOS)
setWindowFlags(Qt::Widget | Qt::Popup | Qt::FramelessWindowHint);
#else
setWindowFlags(Qt::SubWindow | Qt::Widget | Qt::Popup | Qt::FramelessWindowHint);
#endif
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
}
else {
ui->expression->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
ui->horizontalSpacer_3->changeSize(0, 2);
ui->verticalLayout->setContentsMargins(9, 9, 9, 9);
this->adjustSize();
// It is strange that (at least on Linux) DlgExpressionInput will shrink
// to be narrower than ui->expression after calling adjustSize() above.
// Why?
if(this->width() < ui->expression->width() + 18)
this->resize(ui->expression->width()+18,this->height());
}
ui->expression->setFocus();
}
DlgExpressionInput::~DlgExpressionInput()
{
#if QT_VERSION >= QT_VERSION_CHECK(6,7,0)
disconnect(ui->checkBoxVarSets, &QCheckBox::checkStateChanged,
this, &DlgExpressionInput::onCheckVarSets);
#else
disconnect(ui->checkBoxVarSets, &QCheckBox::stateChanged,
this, &DlgExpressionInput::onCheckVarSets);
#endif
disconnect(ui->comboBoxVarSet, qOverload<int>(&QComboBox::currentIndexChanged),
this, &DlgExpressionInput::onVarSetSelected);
disconnect(&comboBoxGroup, &EditFinishedComboBox::currentTextChanged,
this, &DlgExpressionInput::onTextChangedGroup);
disconnect(ui->lineEditPropNew, &QLineEdit::textChanged,
this, &DlgExpressionInput::namePropChanged);
delete ui;
}
static void getVarSetsDocument(std::vector<App::VarSet*>& varSets, App::Document* doc) {
for (auto obj : doc->getObjects()) {
auto varSet = dynamic_cast<App::VarSet*>(obj);
if (varSet) {
varSets.push_back(varSet);
}
}
}
static std::vector<App::VarSet*> getAllVarSets()
{
std::vector<App::Document*> docs = App::GetApplication().getDocuments();
std::vector<App::VarSet*> varSets;
for (auto doc : docs) {
getVarSetsDocument(varSets, doc);
}
return varSets;
}
Base::Type DlgExpressionInput::getTypePath()
{
return path.getProperty()->getTypeId();
}
Base::Type DlgExpressionInput::determineTypeVarSet()
{
Base::Type typePath = getTypePath();
// The type of the path is leading. If it is one of the types below, we
// can create a property in the varset.
if (typePath == App::PropertyString::getClassTypeId() ||
typePath.isDerivedFrom(App::PropertyFloat::getClassTypeId()) ||
typePath.isDerivedFrom(App::PropertyInteger::getClassTypeId())) {
return typePath;
}
// If we cannot determine the type by means of the path, for example when
// dealing with a sketcher constraint list or with the x, y, or z of a
// Placement, the type of the unit allows us to create a property in the
// varset. Since unit properties are derived from App::PropertyFloat, it
// allows us to create a property and set the value.
std::string unitTypeString = impliedUnit.getTypeString();
if (unitTypeString.empty()) {
// no type was provided
return Base::Type::BadType;
}
std::string typeString = "App::Property" + unitTypeString;
// may return badType
return Base::Type::fromName(typeString.c_str());
}
bool DlgExpressionInput::typeOkForVarSet()
{
std::string unitType = impliedUnit.getTypeString();
return !determineTypeVarSet().isBad();
}
void DlgExpressionInput::initializeErrorFrame()
{
ui->errorFrame->setVisible(false);
const int size = style()->pixelMetric(QStyle::PM_LargeIconSize);
QIcon icon = Gui::BitmapFactory().iconFromTheme("overlay_error");
if (icon.isNull()) {
icon = style()->standardIcon(QStyle::SP_MessageBoxCritical);
}
ui->errorIconLabel->setPixmap(icon.pixmap(QSize(size, size)));
}
void DlgExpressionInput::initializeVarSets()
{
#if QT_VERSION >= QT_VERSION_CHECK(6,7,0)
connect(ui->checkBoxVarSets, &QCheckBox::checkStateChanged,
this, &DlgExpressionInput::onCheckVarSets);
#else
connect(ui->checkBoxVarSets, &QCheckBox::stateChanged,
this, &DlgExpressionInput::onCheckVarSets);
#endif
connect(ui->comboBoxVarSet, qOverload<int>(&QComboBox::currentIndexChanged),
this, &DlgExpressionInput::onVarSetSelected);
connect(&comboBoxGroup, &EditFinishedComboBox::currentTextChanged,
this, &DlgExpressionInput::onTextChangedGroup);
connect(ui->lineEditPropNew, &QLineEdit::textChanged,
this, &DlgExpressionInput::namePropChanged);
comboBoxGroup.setObjectName(QStringLiteral("comboBoxGroup"));
comboBoxGroup.setInsertPolicy(QComboBox::InsertAtTop);
comboBoxGroup.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
DlgAddPropertyVarSet::setWidgetForLabel("labelGroup", &comboBoxGroup, ui->formLayout);
setTabOrder(ui->comboBoxVarSet, &comboBoxGroup);
setTabOrder(&comboBoxGroup, ui->lineEditPropNew);
std::vector<App::VarSet*> varSets = getAllVarSets();
if (!varSets.empty() && typeOkForVarSet()) {
ui->checkBoxVarSets->setVisible(true);
ui->checkBoxVarSets->setCheckState(Qt::Unchecked);
ui->groupBoxVarSets->setVisible(false);
}
else {
// The dialog is shown without any VarSet options.
ui->checkBoxVarSets->setVisible(false);
ui->groupBoxVarSets->setVisible(false);
}
initializeErrorFrame();
}
void NumberRange::setRange(double min, double max)
{
minimum = min;
maximum = max;
defined = true;
}
void NumberRange::clearRange()
{
defined = false;
}
void NumberRange::throwIfOutOfRange(const Base::Quantity& value) const
{
if (!defined)
return;
auto toQString = [](const Base::Quantity& v) {
return QString::fromStdString(v.getUserString());
};
if (value.getValue() < minimum || value.getValue() > maximum) {
Base::Quantity minVal(minimum, value.getUnit());
Base::Quantity maxVal(maximum, value.getUnit());
const QString fmt = QCoreApplication::translate(
"Exceptions",
"Value out of range (%1 out of [%2, %3])");
const QString msg = fmt.arg(toQString(value), toQString(minVal), toQString(maxVal));
THROWM(Base::ValueError, msg.toStdString());
}
}
void DlgExpressionInput::setRange(double minimum, double maximum)
{
numberRange.setRange(minimum, maximum);
}
void DlgExpressionInput::clearRange()
{
numberRange.clearRange();
}
QPoint DlgExpressionInput::expressionPosition() const
{
return ui->expression->pos();
}
void DlgExpressionInput::checkExpression(const QString& text)
{
//now handle expression
std::shared_ptr<Expression> expr(ExpressionParser::parse(path.getDocumentObject(), text.toUtf8().constData()));
if (expr) {
std::string error = path.getDocumentObject()->ExpressionEngine.validateExpression(path, expr);
if (!error.empty())
throw Base::RuntimeError(error.c_str());
std::unique_ptr<Expression> result(expr->eval());
expression = expr;
okBtn->setEnabled(true);
ui->msg->clear();
//set default palette as we may have read text right now
ui->msg->setPalette(okBtn->palette());
auto * n = freecad_cast<NumberExpression*>(result.get());
if (n) {
Base::Quantity value = n->getQuantity();
if (!value.isValid()) {
THROWMT(Base::ValueError, QT_TRANSLATE_NOOP("Exceptions", "Not a number"));
}
QString msg = QString::fromStdString(value.getUserString());
if (impliedUnit != Base::Unit::One) {
if (!value.isDimensionless() && value.getUnit() != impliedUnit)
THROWMT(Base::UnitsMismatchError,
QT_TRANSLATE_NOOP("Exceptions", "Unit mismatch between result and required unit"));
value.setUnit(impliedUnit);
}
else if (!value.isDimensionless()) {
msg += tr(" (Warning: unit discarded)");
QPalette p(ui->msg->palette());
p.setColor(QPalette::WindowText, Qt::red);
ui->msg->setPalette(p);
}
numberRange.throwIfOutOfRange(value);
ui->msg->setText(msg);
}
else {
ui->msg->setText(QString::fromStdString(result->toString()));
}
}
}
static const bool NoCheckExpr = false;
void DlgExpressionInput::textChanged(const QString &text)
{
if (text.isEmpty()) {
okBtn->setDisabled(true);
discardBtn->setDefault(true);
return;
}
okBtn->setDefault(true);
try {
//resize the input field according to text size
QFontMetrics fm(ui->expression->font());
int width = QtTools::horizontalAdvance(fm, text) + 15;
if (width < minimumWidth)
ui->expression->setMinimumWidth(minimumWidth);
else
ui->expression->setMinimumWidth(width);
if(this->width() < ui->expression->minimumWidth())
setMinimumWidth(ui->expression->minimumWidth());
checkExpression(text);
if (varSetsVisible) {
// If varsets are visible, check whether the varset info also
// agrees that the button should be enabled.
// No need to check the expression in that function.
updateVarSetInfo(NoCheckExpr);
}
}
catch (Base::Exception & e) {
ui->msg->setText(QString::fromUtf8(e.what()));
QPalette p(ui->msg->palette());
p.setColor(QPalette::WindowText, Qt::red);
ui->msg->setPalette(p);
okBtn->setDisabled(true);
}
}
void DlgExpressionInput::setDiscarded()
{
discarded = true;
reject();
}
void DlgExpressionInput::setExpressionInputSize(int width, int height)
{
if (ui->expression->minimumHeight() < height) {
ui->expression->setMinimumHeight(height);
}
if (ui->expression->minimumWidth() < width) {
ui->expression->setMinimumWidth(width);
}
minimumWidth = width;
}
void DlgExpressionInput::mouseReleaseEvent(QMouseEvent* event)
{
Q_UNUSED(event);
}
void DlgExpressionInput::mousePressEvent(QMouseEvent* event)
{
Q_UNUSED(event);
// The 'FramelessWindowHint' is also set when the background is transparent.
if (windowFlags() & Qt::FramelessWindowHint) {
//we need to reject the dialog when clicked on the background. As the background is transparent
//this is the expected behaviour for the user
bool on = ui->expression->completerActive();
if (!on) {
this->reject();
}
}
}
void DlgExpressionInput::show()
{
QDialog::show();
this->activateWindow();
ui->expression->selectAll();
}
class Binding : public Gui::ExpressionBinding
{
// helper class to compensate for the fact that
// ExpressionBinding::setExpression is protected.
public:
Binding() = default;
void setExpression(std::shared_ptr<App::Expression> expr) override
{
ExpressionBinding::setExpression(expr);
}
};
static constexpr const char* InvalidIdentifierMessage =
QT_TR_NOOP("must contain only alphanumeric characters, underscore, and must not start with a digit");
bool DlgExpressionInput::isPropertyNameValid(const QString& nameProp,
const App::DocumentObject* obj,
QString& message) const
{
auto withPrefix = [&](const QString& detail) {
return tr("Invalid property name: %1").arg(detail);
};
if (!obj) {
message = tr("Unknown object");
return false;
}
std::string name = nameProp.toStdString();
if (name.empty()) {
message = withPrefix(tr("the name cannot be empty"));
return false;
}
if (name != Base::Tools::getIdentifier(name)) {
message = withPrefix(tr(InvalidIdentifierMessage));
return false;
}
if (ExpressionParser::isTokenAUnit(name)) {
message = withPrefix(tr("%1 is a unit").arg(nameProp));
return false;
}
if (ExpressionParser::isTokenAConstant(name)) {
message = withPrefix(tr("%1 is a constant").arg(nameProp));
return false;
}
auto prop = obj->getPropertyByName(name.c_str());
if (prop && prop->getContainer() == obj) {
message = withPrefix(tr("%1 already exists").arg(nameProp));
return false;
}
return true;
}
static const int DocRole = Qt::UserRole;
static const int VarSetNameRole = Qt::UserRole + 1;
static const int VarSetLabelRole = Qt::UserRole + 2;
static const int LevelRole = Qt::UserRole + 3;
static QString getValue(QComboBox* comboBox, int role)
{
QVariant variant = comboBox->currentData(role);
return variant.toString();
}
static void storePreferences(const std::string& nameDoc,
const std::string& nameVarSet,
const std::string& nameGroup)
{
auto paramExpressionEditor = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/ExpressionEditor");
paramExpressionEditor->SetASCII("LastDocument", nameDoc);
paramExpressionEditor->SetASCII("LastVarSet", nameVarSet);
paramExpressionEditor->SetASCII("LastGroup", nameGroup);
}
static const App::NumberExpression* toNumberExpr(const App::Expression* expr)
{
return freecad_cast<const App::NumberExpression*>(expr);
}
static const App::StringExpression* toStringExpr(const App::Expression* expr)
{
return freecad_cast<const App::StringExpression*>(expr);
}
static const App::OperatorExpression* toUnitNumberExpr(const App::Expression* expr)
{
auto* opExpr = freecad_cast<const App::OperatorExpression*>(expr);
if (opExpr && opExpr->getOperator() == App::OperatorExpression::Operator::UNIT &&
toNumberExpr(opExpr->getLeft())) {
return opExpr;
}
return nullptr;
}
void DlgExpressionInput::acceptWithVarSet()
{
// all checks have been performed in updateVarSetInfo and textChanged that
// decide to enable the button
// create a property in the VarSet
QString nameVarSet = getValue(ui->comboBoxVarSet, VarSetNameRole);
QString nameGroup = comboBoxGroup.currentText();
QString nameProp = ui->lineEditPropNew->text();
QString nameDoc = getValue(ui->comboBoxVarSet, DocRole);
App::Document* doc = App::GetApplication().getDocument(nameDoc.toUtf8());
App::DocumentObject* obj = doc->getObject(nameVarSet.toUtf8());
std::string name = nameProp.toStdString();
std::string group = nameGroup.toStdString();
std::string type = getType();
auto prop = obj->addDynamicProperty(type.c_str(), name.c_str(), group.c_str());
// Set the value of the property in the VarSet
//
// The value of the property is going to be the value that was originally
// meant to be the value for the property that this dialog is targeting.
const Expression* expr = expression.get();
if (const NumberExpression* ne = toNumberExpr(expr)) {
// the value is a number: directly assign it to the property instead of
// making it an expression in the variable set
Gui::Command::doCommand(Gui::Command::Doc, "App.getDocument('%s').getObject('%s').%s = %f",
obj->getDocument()->getName(),
obj->getNameInDocument(),
prop->getName(), ne->getValue());
}
else if (const StringExpression* se = toStringExpr(expr)) {
// the value is a string: directly assign it to the property.
Gui::Command::doCommand(Gui::Command::Doc, "App.getDocument('%s').getObject('%s').%s = \"%s\"",
obj->getDocument()->getName(),
obj->getNameInDocument(),
prop->getName(), se->getText().c_str());
}
else if (const OperatorExpression* une = toUnitNumberExpr(expr)) {
// the value is a unit number: directly assign it to the property.
Gui::Command::doCommand(Gui::Command::Doc, "App.getDocument('%s').getObject('%s').%s = \"%s\"",
obj->getDocument()->getName(),
obj->getNameInDocument(),
prop->getName(), une->toString().c_str());
}
else {
// the value is an expression: make an expression binding in the variable set.
ObjectIdentifier objId(*prop);
Binding binding;
binding.bind(objId);
binding.setExpression(expression);
binding.apply();
}
// Create a new expression that refers to the property in the variable set
// for the original property that is the target of this dialog.
expression.reset(ExpressionParser::parse(path.getDocumentObject(),
prop->getFullName().c_str()));
storePreferences(nameDoc.toStdString(), nameVarSet.toStdString(), group);
}
void DlgExpressionInput::accept() {
if (varSetsVisible) {
if (needReportOnVarSet()) {
return;
}
acceptWithVarSet();
}
QDialog::accept();
}
static App::Document* getPreselectedDocument()
{
auto paramExpressionEditor = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/ExpressionEditor");
std::string lastDoc = paramExpressionEditor->GetASCII("LastDocument", "");
if (lastDoc.empty()) {
return App::GetApplication().getActiveDocument();
}
App::Document* doc = App::GetApplication().getDocument(lastDoc.c_str());
if (doc == nullptr) {
return App::GetApplication().getActiveDocument();
}
return doc;
}
int DlgExpressionInput::getVarSetIndex(const App::Document* doc) const
{
auto paramExpressionEditor = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/ExpressionEditor");
std::string lastVarSet = paramExpressionEditor->GetASCII("LastVarSet", "VarSet");
auto* model = qobject_cast<QStandardItemModel*>(ui->comboBoxVarSet->model());
for (int i = 0; i < model->rowCount(); ++i) {
QStandardItem* item = model->item(i);
if (item->data(DocRole).toString() == QString::fromUtf8(doc->getName()) &&
item->data(VarSetNameRole).toString() == QString::fromStdString(lastVarSet)) {
return i;
}
}
// Select the first varset of the first document (the document is item 0)
return 1;
}
void DlgExpressionInput::preselectVarSet()
{
const App::Document* doc = getPreselectedDocument();
if (doc == nullptr) {
FC_ERR("No active document found");
}
ui->comboBoxVarSet->setCurrentIndex(getVarSetIndex(doc));
}
// Custom delegate to add indentation
class IndentedItemDelegate : public QStyledItemDelegate {
public:
explicit IndentedItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override {
QStyledItemDelegate::initStyleOption(option, index);
if (index.data(LevelRole) == 1) {
int indentWidth = 20;
option->rect.adjust(indentWidth, 0, 0, 0);
}
}
};
static void addVarSetsVarSetComboBox(std::vector<App::VarSet*>& varSets,
QStandardItem* docItem, QStandardItemModel* model)
{
for (auto* varSet : varSets) {
auto* vp = freecad_cast<Gui::ViewProviderDocumentObject*>(
Gui::Application::Instance->getViewProvider(varSet));
if (vp == nullptr) {
FC_ERR("No ViewProvider found for VarSet: " << varSet->getNameInDocument());
continue;
}
// The item will be owned by the model, so no need to delete it manually.
auto item = new QStandardItem();
item->setIcon(vp->getIcon());
item->setText(QString::fromUtf8(varSet->Label.getValue()));
item->setData(QString::fromUtf8(varSet->Label.getValue()), VarSetLabelRole);
item->setData(QString::fromUtf8(varSet->getNameInDocument()), VarSetNameRole);
item->setData(docItem->data(DocRole), DocRole);
item->setData(1, LevelRole);
model->appendRow(item);
}
}
static void addDocVarSetComboBox(App::Document* doc, QPixmap& docIcon,
QStandardItemModel* model)
{
if (doc->testStatus(App::Document::TempDoc)) {
// Do not add temporary documents to the VarSet combo box
return;
}
std::vector<App::VarSet*> varSets;
getVarSetsDocument(varSets, doc);
if (varSets.empty()) {
return;
}
// The item will be owned by the model, so no need to delete it manually.
auto* item = new QStandardItem();
item->setIcon(docIcon);
item->setText(QString::fromUtf8(doc->Label.getValue()));
item->setData(QByteArray(doc->getName()), DocRole);
item->setFlags(Qt::ItemIsEnabled); // Make sure this item cannot be selected
item->setData(0, LevelRole);
model->appendRow(item);
addVarSetsVarSetComboBox(varSets, item, model);
}
QStandardItemModel* DlgExpressionInput::createVarSetModel()
{
// Create the model
auto* model = new QStandardItemModel(ui->comboBoxVarSet);
model->setColumnCount(1);
// Add items to the model
QPixmap docIcon(Gui::BitmapFactory().pixmap("Document"));
std::vector<App::Document*> docs = App::GetApplication().getDocuments();
for (auto doc : docs) {
addDocVarSetComboBox(doc, docIcon, model);
}
return model;
}
void DlgExpressionInput::setupVarSets()
{
ui->comboBoxVarSet->clear();
QStandardItemModel* model = createVarSetModel();
{
QSignalBlocker blocker(ui->comboBoxVarSet);
auto* listView = new QListView(this);
listView->setSelectionMode(QAbstractItemView::SingleSelection);
listView->setModel(model);
ui->comboBoxVarSet->setView(listView);
ui->comboBoxVarSet->setModel(model);
ui->comboBoxVarSet->setItemDelegate(new IndentedItemDelegate(ui->comboBoxVarSet));
}
preselectVarSet();
okBtn->setEnabled(false);
}
std::string DlgExpressionInput::getType()
{
return determineTypeVarSet().getName();
}
void DlgExpressionInput::onCheckVarSets(int state) {
varSetsVisible = state == Qt::Checked;
ui->groupBoxVarSets->setVisible(varSetsVisible);
if (varSetsVisible) {
setupVarSets();
}
else {
okBtn->setEnabled(true); // normal expression
adjustSize();
}
}
void DlgExpressionInput::preselectGroup()
{
auto paramExpressionEditor = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/ExpressionEditor");
std::string lastGroup = paramExpressionEditor->GetASCII("LastGroup", "");
if (lastGroup.empty()) {
return;
}
if (int index = comboBoxGroup.findText(QString::fromStdString(lastGroup)); index != -1) {
comboBoxGroup.setCurrentIndex(index);
}
}
void DlgExpressionInput::onVarSetSelected(int /*index*/)
{
QString docName = getValue(ui->comboBoxVarSet, DocRole);
QString varSetName = getValue(ui->comboBoxVarSet, VarSetNameRole);
if (docName.isEmpty() || varSetName.isEmpty()) {
FC_ERR("No document or variable set selected");
return;
}
App::Document* doc = App::GetApplication().getDocument(docName.toUtf8());
if (doc == nullptr) {
FC_ERR("Document not found: " << docName.toStdString());
return;
}
App::DocumentObject* varSet = doc->getObject(varSetName.toUtf8());
if (varSet == nullptr) {
FC_ERR("Variable set not found: " << varSetName.toStdString());
return;
}
DlgAddPropertyVarSet::populateGroup(comboBoxGroup, varSet);
preselectGroup();
updateVarSetInfo();
ui->lineEditPropNew->setFocus();
}
void DlgExpressionInput::onTextChangedGroup(const QString&)
{
updateVarSetInfo();
}
void DlgExpressionInput::namePropChanged(const QString&)
{
updateVarSetInfo();
}
bool DlgExpressionInput::isGroupNameValid(const QString& nameGroup,
QString& message) const
{
auto withPrefix = [&](const QString& detail) {
return tr("Invalid group name: %1").arg(detail);
};
if(nameGroup.isEmpty()) {
message = withPrefix(tr("the name cannot be empty"));
return false;
}
std::string name = nameGroup.toStdString();
if (name != Base::Tools::getIdentifier(name)) {
message = withPrefix(tr(InvalidIdentifierMessage));
return false;
}
return true;
}
void DlgExpressionInput::reportVarSetInfo(const QString& message)
{
if (!message.isEmpty()) {
ui->errorFrame->setVisible(true);
ui->errorTextLabel->setText(message);
ui->errorTextLabel->updateGeometry();
}
}
static void setErrorState(QWidget* widget, bool on)
{
widget->setProperty("validationState", on ? QStringLiteral("error") : QVariant());
widget->style()->unpolish(widget);
widget->style()->polish(widget);
}
bool DlgExpressionInput::reportGroup(const QString& nameGroup)
{
QString message;
if (!isGroupNameValid(nameGroup, message)) {
setErrorState(&comboBoxGroup, true);
reportVarSetInfo(message);
return true;
}
return false;
}
bool DlgExpressionInput::reportName()
{
QString nameProp = ui->lineEditPropNew->text();
QString nameVarSet = getValue(ui->comboBoxVarSet, VarSetNameRole);
QString nameDoc = getValue(ui->comboBoxVarSet, DocRole);
App::Document* doc = App::GetApplication().getDocument(nameDoc.toUtf8());
App::DocumentObject* obj = doc->getObject(nameVarSet.toUtf8());
QString message;
if (!isPropertyNameValid(nameProp, obj, message)) {
setErrorState(ui->lineEditPropNew, true);
reportVarSetInfo(message);
return true;
}
return false;
}
void DlgExpressionInput::updateVarSetInfo(bool checkExpr)
{
if (ui->lineEditPropNew->text().isEmpty()) {
okBtn->setEnabled(false);
return;
}
if (comboBoxGroup.currentText().isEmpty()) {
okBtn->setEnabled(false);
return;
}
if (checkExpr) {
// We have to check the text of the expression as well
try {
checkExpression(ui->expression->text());
}
catch (Base::Exception&) {
okBtn->setEnabled(false);
}
}
okBtn->setEnabled(true);
}
bool DlgExpressionInput::needReportOnVarSet()
{
setErrorState(ui->lineEditPropNew, false);
setErrorState(&comboBoxGroup, false);
return reportGroup(comboBoxGroup.currentText()) || reportName();
}
#include "moc_DlgExpressionInput.cpp"