From 7c2acdbf093e44bfd22eb981fe7dd8af73113119 Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Wed, 14 Aug 2024 17:25:39 +0200 Subject: [PATCH] Gui: propertyeditor: Reformat, fix line endings Instead of fixing whitespaces do a full reformat according to current rules. No other changes. --- src/Gui/propertyeditor/PropertyEditor.h | 343 +- src/Gui/propertyeditor/PropertyItem.cpp | 9608 +++++++++++----------- src/Gui/propertyeditor/PropertyItem.h | 2576 +++--- src/Gui/propertyeditor/PropertyModel.cpp | 1159 +-- 4 files changed, 6907 insertions(+), 6779 deletions(-) diff --git a/src/Gui/propertyeditor/PropertyEditor.h b/src/Gui/propertyeditor/PropertyEditor.h index 73e6e72563..40fb89482e 100644 --- a/src/Gui/propertyeditor/PropertyEditor.h +++ b/src/Gui/propertyeditor/PropertyEditor.h @@ -1,166 +1,177 @@ -/*************************************************************************** - * Copyright (c) 2004 Werner Mayer * - * * - * 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 PROPERTYEDITOR_H -#define PROPERTYEDITOR_H - -#include - -#include - -#include "PropertyItem.h" -#include "PropertyModel.h" - - -namespace App { -class Property; -class Document; -} - -namespace Gui { - -class PropertyView; - -namespace PropertyEditor { - -class PropertyItemDelegate; -class PropertyModel; -/*! - Put this into the .qss file after Gui--PropertyEditor--PropertyEditor - - Gui--PropertyEditor--PropertyEditor - { - qproperty-groupBackground: gray; - qproperty-groupTextColor: white; - } - - See also: https://man42.net/blog/2011/09/qt-4-7-modify-a-custom-q_property-with-a-qt-style-sheet/ - -*/ - -class PropertyEditor : public QTreeView -{ - Q_OBJECT - - Q_PROPERTY(QBrush groupBackground READ groupBackground WRITE setGroupBackground DESIGNABLE true SCRIPTABLE true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(QColor groupTextColor READ groupTextColor WRITE setGroupTextColor DESIGNABLE true SCRIPTABLE true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(QBrush itemBackground READ itemBackground WRITE setItemBackground DESIGNABLE true SCRIPTABLE true) // clazy:exclude=qproperty-without-notify - -public: - PropertyEditor(QWidget *parent = nullptr); - ~PropertyEditor() override; - - /** Builds up the list view with the properties. */ - void buildUp(PropertyModel::PropertyList &&props = PropertyModel::PropertyList(), bool checkDocument=false); - void updateProperty(const App::Property&); - void removeProperty(const App::Property&); - void setAutomaticExpand(bool); - bool isAutomaticExpand(bool) const; - void setAutomaticDocumentUpdate(bool); - bool isAutomaticDocumentUpdate(bool) const; - /*! Reset the internal state of the view. */ - void reset() override; - - QBrush groupBackground() const; - void setGroupBackground(const QBrush& c); - QColor groupTextColor() const; - void setGroupTextColor(const QColor& c); - QBrush itemBackground() const; - void setItemBackground(const QBrush& c); - - bool isBinding() const { return binding; } - void openEditor(const QModelIndex &index); - void closeEditor(); - -protected Q_SLOTS: - void onItemActivated(const QModelIndex &index); - void onItemExpanded(const QModelIndex &index); - void onItemCollapsed(const QModelIndex &index); - void onRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &dst, int row); - void onRowsRemoved(const QModelIndex &parent, int start, int end); - -protected: - bool eventFilter(QObject* object, QEvent* event) override; - void closeEditor (QWidget * editor, QAbstractItemDelegate::EndEditHint hint) override; - void commitData (QWidget * editor) override; - void editorDestroyed (QObject * editor) override; - void currentChanged (const QModelIndex & current, const QModelIndex & previous) override; - void rowsInserted (const QModelIndex & parent, int start, int end) override; - void rowsAboutToBeRemoved (const QModelIndex & parent, int start, int end) override; - void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override; - void drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const override; -#if QT_VERSION < QT_VERSION_CHECK(6,0,0) - QStyleOptionViewItem viewOptions() const override; -#else - void initViewItemOption(QStyleOptionViewItem *option) const override; -#endif - void contextMenuEvent(QContextMenuEvent *event) override; - bool event(QEvent*) override; - -private: - void setEditorMode(const QModelIndex & parent, int start, int end); - void closeTransaction(); - void recomputeDocument(App::Document*); - - // check if mouse_pos is around right or bottom side of a cell - // and return the index of that cell if found - QModelIndex indexResizable(QPoint mouse_pos); - -private: - PropertyItemDelegate *delegate; - PropertyModel* propertyModel; - QStringList selectedProperty; - PropertyModel::PropertyList propList; - std::unordered_set propOwners; - bool autoexpand; - bool autoupdate; - bool committing; - bool delaybuild; - bool binding; - bool checkDocument; - bool closingEditor; - bool dragInProgress; - - //max distance between mouse and a cell, small enough to trigger resize - int dragSensibility = 5; // NOLINT - int dragSection = 0; - int dragPreviousPos = 0; - - int transactionID = 0; - - QColor groupColor; - QBrush background; - QBrush _itemBackground; - - QPointer activeEditor; - QPersistentModelIndex editingIndex; - int removingRows = 0; - - friend class Gui::PropertyView; - friend class PropertyItemDelegate; -}; - -} //namespace PropertyEditor -} //namespace Gui - -#endif // PROPERTYEDITOR_H +/*************************************************************************** + * Copyright (c) 2004 Werner Mayer * + * * + * 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 PROPERTYEDITOR_H +#define PROPERTYEDITOR_H + +#include + +#include + +#include "PropertyItem.h" +#include "PropertyModel.h" + + +namespace App +{ +class Property; +class Document; +} // namespace App + +namespace Gui +{ + +class PropertyView; + +namespace PropertyEditor +{ + +class PropertyItemDelegate; +class PropertyModel; +/*! + Put this into the .qss file after Gui--PropertyEditor--PropertyEditor + + Gui--PropertyEditor--PropertyEditor + { + qproperty-groupBackground: gray; + qproperty-groupTextColor: white; + } + + See also: https://man42.net/blog/2011/09/qt-4-7-modify-a-custom-q_property-with-a-qt-style-sheet/ +*/ + +class PropertyEditor: public QTreeView +{ + // clang-format off + Q_OBJECT + Q_PROPERTY(QBrush groupBackground READ groupBackground WRITE setGroupBackground DESIGNABLE true SCRIPTABLE true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(QColor groupTextColor READ groupTextColor WRITE setGroupTextColor DESIGNABLE true SCRIPTABLE true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(QBrush itemBackground READ itemBackground WRITE setItemBackground DESIGNABLE true SCRIPTABLE true) // clazy:exclude=qproperty-without-notify + // clang-format on + +public: + PropertyEditor(QWidget* parent = nullptr); + ~PropertyEditor() override; + + /** Builds up the list view with the properties. */ + void buildUp(PropertyModel::PropertyList&& props = PropertyModel::PropertyList(), + bool checkDocument = false); + void updateProperty(const App::Property&); + void removeProperty(const App::Property&); + void setAutomaticExpand(bool); + bool isAutomaticExpand(bool) const; + void setAutomaticDocumentUpdate(bool); + bool isAutomaticDocumentUpdate(bool) const; + /*! Reset the internal state of the view. */ + void reset() override; + + QBrush groupBackground() const; + void setGroupBackground(const QBrush& c); + QColor groupTextColor() const; + void setGroupTextColor(const QColor& c); + QBrush itemBackground() const; + void setItemBackground(const QBrush& c); + + bool isBinding() const + { + return binding; + } + void openEditor(const QModelIndex& index); + void closeEditor(); + +protected Q_SLOTS: + void onItemActivated(const QModelIndex& index); + void onItemExpanded(const QModelIndex& index); + void onItemCollapsed(const QModelIndex& index); + void + onRowsMoved(const QModelIndex& parent, int start, int end, const QModelIndex& dst, int row); + void onRowsRemoved(const QModelIndex& parent, int start, int end); + +protected: + bool eventFilter(QObject* object, QEvent* event) override; + void closeEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint) override; + void commitData(QWidget* editor) override; + void editorDestroyed(QObject* editor) override; + void currentChanged(const QModelIndex& current, const QModelIndex& previous) override; + void rowsInserted(const QModelIndex& parent, int start, int end) override; + void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; + void + drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override; + void drawRow(QPainter* painter, + const QStyleOptionViewItem& options, + const QModelIndex& index) const override; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QStyleOptionViewItem viewOptions() const override; +#else + void initViewItemOption(QStyleOptionViewItem* option) const override; +#endif + void contextMenuEvent(QContextMenuEvent* event) override; + bool event(QEvent*) override; + +private: + void setEditorMode(const QModelIndex& parent, int start, int end); + void closeTransaction(); + void recomputeDocument(App::Document*); + + // check if mouse_pos is around right or bottom side of a cell + // and return the index of that cell if found + QModelIndex indexResizable(QPoint mouse_pos); + +private: + PropertyItemDelegate* delegate; + PropertyModel* propertyModel; + QStringList selectedProperty; + PropertyModel::PropertyList propList; + std::unordered_set propOwners; + bool autoexpand; + bool autoupdate; + bool committing; + bool delaybuild; + bool binding; + bool checkDocument; + bool closingEditor; + bool dragInProgress; + + // max distance between mouse and a cell, small enough to trigger resize + int dragSensibility = 5; // NOLINT + int dragSection = 0; + int dragPreviousPos = 0; + + int transactionID = 0; + + QColor groupColor; + QBrush background; + QBrush _itemBackground; + + QPointer activeEditor; + QPersistentModelIndex editingIndex; + int removingRows = 0; + + friend class Gui::PropertyView; + friend class PropertyItemDelegate; +}; + +} // namespace PropertyEditor +} // namespace Gui + +#endif // PROPERTYEDITOR_H diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index a0da598b0f..83ec217cca 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -1,4782 +1,4826 @@ -/*************************************************************************** - * Copyright (c) 2004 Werner Mayer * - * * - * 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 -# include -# include -# include -# include -# include -# include -# include -# include -#endif - -#include "PropertyItem.h" -#include "PropertyView.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// NOLINTBEGIN(cppcoreguidelines-pro-*,cppcoreguidelines-prefer-member-initializer) -using namespace Gui::PropertyEditor; -using namespace Gui::Dialog; - -namespace { -constexpr const int lowPrec = 2; -constexpr const int highPrec = 16; - -int toPercent(float value) -{ - return static_cast(100 * value); // NOLINT -} - -float fromPercent(int value) -{ - return static_cast(value) / 100.0F; // NOLINT -} - -} - -PropertyItemFactory& PropertyItemFactory::instance() -{ - static PropertyItemFactory inst; - return inst; -} - -PropertyItem* PropertyItemFactory::createPropertyItem (const char* sName) const -{ - return static_cast(Produce(sName)); -} - -// ---------------------------------------------------- - -QVariant PropertyItemAttorney::toString(PropertyItem* item, const QVariant& value) -{ - return item->toString(value); -} - -// ---------------------------------------------------- - -Q_DECLARE_METATYPE(Py::Object) // NOLINT - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyItem) - -PropertyItem::PropertyItem() - : parentItem(nullptr) - , readonly(false) - , precision(Base::UnitsApi::getDecimals()) - , linked(false) - , expanded(false) -{ - setAutoApply(true); -} - -PropertyItem::~PropertyItem() -{ - qDeleteAll(childItems); -} - -void PropertyItem::initialize() -{ -} - -void PropertyItem::reset() -{ - qDeleteAll(childItems); - childItems.clear(); -} - -void PropertyItem::onChange() -{ - if (hasExpression()) { - for(auto child : std::as_const(childItems)) { - if(child && child->hasExpression()) { - child->setExpression(std::shared_ptr()); - } - } - for(auto item=parentItem;item;item=item->parentItem) { - if(item->hasExpression()) { - item->setExpression(std::shared_ptr()); - } - } - } -} - -bool PropertyItem::hasAnyExpression() const -{ - if(ExpressionBinding::hasExpression()) { - return true; - } - if(parentItem) { - return parentItem->hasExpression(); - } - return false; -} - -void PropertyItem::setPropertyData(const std::vector& items) -{ - //if we have a single property we can bind it for expression handling - if (items.size() == 1) { - const App::Property& prop = *items.front(); - - try { - // Check for 'DocumentObject' as parent because otherwise 'ObjectIdentifier' raises an exception - auto * docObj = Base::freecad_dynamic_cast(prop.getContainer()); - if (docObj && !docObj->isReadOnly(&prop)) { - App::ObjectIdentifier id(prop); - std::vector paths; - prop.getPaths(paths); - - //there may be no paths available in this property (for example an empty constraint list) - if (id.getProperty() && !paths.empty()) { - bind(id); - } - } - } - //it may happen that setting properties is not possible - catch (...) { - } - } - - propertyItems = items; - updateData(); - this->initialize(); -} - -void PropertyItem::updateData() -{ - bool ro = true; - for (auto it : propertyItems) { - App::PropertyContainer* parent = it->getContainer(); - if (parent) { - ro &= (parent->isReadOnly(it) || it->testStatus(App::Property::ReadOnly)); - } - } - this->setReadOnly(ro); -} - -const std::vector& PropertyItem::getPropertyData() const -{ - return propertyItems; -} - -bool PropertyItem::hasProperty(const App::Property* prop) const -{ - auto it = std::find(propertyItems.begin(), propertyItems.end(), prop); - return (it != propertyItems.end()); -} - -void PropertyItem::assignProperty(const App::Property* prop) -{ - Q_UNUSED(prop) -} - -bool PropertyItem::removeProperty(const App::Property* prop) -{ - auto it = std::find(propertyItems.begin(), propertyItems.end(), prop); - if (it != propertyItems.end()) { - propertyItems.erase(it); - } - - return propertyItems.empty(); -} - -App::Property* PropertyItem::getFirstProperty() -{ - if (propertyItems.empty()) { - return nullptr; - } - return propertyItems.front(); -} - -const App::Property* PropertyItem::getFirstProperty() const -{ - if (propertyItems.empty()) { - return nullptr; - } - return propertyItems.front(); -} - -void PropertyItem::setParent(PropertyItem* parent) -{ - parentItem = parent; -} - -PropertyItem *PropertyItem::parent() const -{ - return parentItem; -} - -void PropertyItem::appendChild(PropertyItem *item) -{ - childItems.append(item); -} - -void PropertyItem::insertChild(int index, PropertyItem *child) -{ - childItems.insert(index, child); -} - -/*! - * \brief PropertyItem::removeChildren - * Deletes the children in the range of [from, to] - */ -void PropertyItem::removeChildren(int from, int to) -{ - int count = to - from + 1; - for (int i=0; isetParent(nullptr); - return child; -} - -PropertyItem *PropertyItem::child(int row) -{ - return childItems.value(row); -} - -int PropertyItem::childCount() const -{ - return childItems.count(); -} - -int PropertyItem::columnCount() const -{ - return 2; -} - -void PropertyItem::setReadOnly(bool ro) -{ - readonly = ro; - for (auto it : std::as_const(childItems)) { - it->setReadOnly(ro); - } -} - -bool PropertyItem::isReadOnly() const -{ - return readonly; -} - -void PropertyItem::setLinked(bool value) -{ - linked = value; - for (auto it : std::as_const(childItems)) { - it->setLinked(value); - } -} - -bool PropertyItem::isLinked() const -{ - return linked; -} - -void PropertyItem::setExpanded(bool enable) -{ - expanded = enable; -} - -bool PropertyItem::isExpanded() const -{ - return expanded; -} - -bool PropertyItem::testStatus(App::Property::Status pos) const -{ - std::vector::const_iterator it; - for (it = propertyItems.begin(); it != propertyItems.end(); ++it) { - if ((*it)->testStatus(pos)) { - return true; - } - } - return false; -} - -void PropertyItem::setDecimals(int prec) -{ - precision = prec; -} - -int PropertyItem::decimals() const -{ - return precision; -} - -QVariant PropertyItem::displayName() const -{ - return {displayText}; -} - -QVariant PropertyItem::toolTip(const App::Property* prop) const -{ - QString str = QApplication::translate("App::Property", - prop->getDocumentation()); - return {str}; -} - -QVariant PropertyItem::decoration(const QVariant& value) const -{ - Q_UNUSED(value) - return {}; -} - -QString PropertyItem::asNone(const Py::Object& pyobj) const -{ - Q_UNUSED(pyobj) - return QString::fromUtf8(""); -} - -QString PropertyItem::asString(const Py::Object& pyobj) const -{ - return QString::fromStdString(pyobj.as_string()); -} - -QString PropertyItem::asSequence(const Py::Object& pyobj) const -{ - std::ostringstream ss; - ss << '['; - Py::Sequence seq(pyobj); - bool first = true; - Py_ssize_t i = 0; - for (i=0; i < 2 && i < seq.size(); ++i) { - if (first) { - first = false; - } - else { - ss << ", "; - } - ss << Py::Object(seq[i]).as_string(); - } - - if (i < seq.size()) { - ss << "..."; - } - ss << ']'; - return QString::fromUtf8(ss.str().c_str()); -} - -QString PropertyItem::asMapping(const Py::Object& pyobj) const -{ - std::ostringstream ss; - ss << '{'; - Py::Mapping map(pyobj); - bool first = true; - auto it = map.begin(); - for (int i = 0; i < 2 && it != map.end(); ++it, ++i) { - if (first) { - first = false; - } - else { - ss << ", "; - } - const auto &v = *it; - ss << Py::Object(v.first).as_string() << ':' << Py::Object(v.second).as_string(); - } - - if (it != map.end()) { - ss << "..."; - } - ss << '}'; - return QString::fromUtf8(ss.str().c_str()); -} - -QString PropertyItem::toString(const Py::Object& pyobj) const -{ - if (pyobj.isNone()) { - return asNone(pyobj); - } - if (pyobj.isSequence()) { - return asSequence(pyobj); - } - if (pyobj.isMapping()) { - return asMapping(pyobj); - } - - return asString(pyobj); -} - -QVariant PropertyItem::toString(const QVariant& prop) const -{ - if (prop != QVariant() || propertyItems.size()!=1) { - return prop; - } - - std::ostringstream ss; - Base::PyGILStateLocker lock; - try { - Py::Object pyobj(propertyItems[0]->getPyObject(), true); - return toString(pyobj); - } - catch (Py::Exception &) { - Base::PyException e; - ss.str(""); - ss << "ERR: " << e.what(); - } - catch (Base::Exception &e) { - ss.str(""); - ss << "ERR: " << e.what(); - } - catch (std::exception &e) { - ss.str(""); - ss << "ERR: " << e.what(); - } - catch (...) { - ss.str(""); - ss << "ERR!"; - } - - return {QString::fromUtf8(ss.str().c_str())}; -} - -QVariant PropertyItem::value(const App::Property* /*prop*/) const -{ - return {}; -} - -void PropertyItem::setValue(const QVariant& /*value*/) -{ -} - -QWidget* PropertyItem::createEditor(QWidget* /*parent*/, const std::function& /*method*/) const -{ - return nullptr; -} - -void PropertyItem::setEditorData(QWidget * /*editor*/, const QVariant& /*data*/) const -{ -} - -QVariant PropertyItem::editorData(QWidget * /*editor*/) const -{ - return {}; -} - -QWidget* PropertyItem::createExpressionEditor(QWidget* parent, const std::function& method) const -{ - if(!isBound()) { - return nullptr; - } - auto le = new ExpLineEdit(parent,true); - le->setFrame(false); - le->setReadOnly(true); - QObject::connect(le, &ExpLineEdit::textChanged, method); - le->bind(getPath()); - le->setAutoApply(autoApply()); - return le; -} - -void PropertyItem::setExpressionEditorData(QWidget *editor, const QVariant& data) const -{ - auto le = qobject_cast(editor); - if (le) { - le->setText(data.toString()); - } -} - -QVariant PropertyItem::expressionEditorData(QWidget *editor) const -{ - auto le = qobject_cast(editor); - if(le) { - return {le->text()}; - } - return {}; -} - -PropertyEditorWidget* PropertyItem::createPropertyEditorWidget(QWidget* parent) const -{ - auto editor = new PropertyEditorWidget(parent); - connect(editor, &PropertyEditorWidget::buttonClick, this, [this]() { - const auto &props = this->getPropertyData(); - if (!props.empty() - && props[0]->getName() - && props[0]->testStatus(App::Property::UserEdit) - && props[0]->getContainer()) { - props[0]->getContainer()->editProperty(props[0]->getName()); - } - }); - return editor; -} - -QString PropertyItem::propertyName() const -{ - if (propName.isEmpty()) { - return QLatin1String(QT_TRANSLATE_NOOP("App::Property", "")); - } - return propName; -} - -void PropertyItem::setPropertyName(const QString& name, const QString& realName) -{ - if (realName.size()) { - propName = realName; - } - else { - propName = name; - } - - setObjectName(propName); - - QString display; - bool upper = false; - for (auto && i : name) { - if (i.isUpper() && !display.isEmpty()) { - // if there is a sequence of capital letters do not insert spaces - if (!upper) { - QChar last = display.at(display.length()-1); - if (!last.isSpace()) { - display += QLatin1String(" "); - } - } - } - upper = i.isUpper(); - display += i; - } - - propName = display; - - QString str = QApplication::translate("App::Property", propName.toUtf8()); - displayText = str; -} - -void PropertyItem::setPropertyValue(const QString& value) -{ - // Construct command for property assignment in one go, in case of any - // intermediate changes caused by property change that may potentially - // invalidate the current property array. - std::ostringstream ss; - for (auto prop : propertyItems) { - App::PropertyContainer* parent = prop->getContainer(); - if (!parent || parent->isReadOnly(prop) || prop->testStatus(App::Property::ReadOnly)) { - continue; - } - - if (parent->isDerivedFrom(App::Document::getClassTypeId())) { - auto doc = static_cast(parent); - ss << "FreeCAD.getDocument('" << doc->getName() << "')."; - } - else if (parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) { - auto obj = static_cast(parent); - App::Document* doc = obj->getDocument(); - ss << "FreeCAD.getDocument('" << doc->getName() << "').getObject('" - << obj->getNameInDocument() << "')."; - } - else if (parent->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { - App::DocumentObject* obj = static_cast(parent)->getObject(); - App::Document* doc = obj->getDocument(); - ss << "FreeCADGui.getDocument('" << doc->getName() << "').getObject('" - << obj->getNameInDocument() << "')."; - } - else { - continue; - } - - ss << parent->getPropertyPrefix() << prop->getName() - << " = " << value.toUtf8().constData() << '\n'; - } - - std::string cmd = ss.str(); - if (cmd.empty()) { - return; - } - - try { - Gui::Command::runCommand(Gui::Command::App, cmd.c_str()); - } - catch (Base::PyException &e) { - e.ReportException(); - Base::Console().Error("Stack Trace: %s\n",e.getStackTrace().c_str()); - } - catch (Base::Exception &e) { - e.ReportException(); - } - catch (...) { - Base::Console().Error("Unknown C++ exception in PropertyItem::setPropertyValue thrown\n"); - } -} - -QVariant PropertyItem::dataProperty(int role) const -{ - if (role == Qt::ForegroundRole && linked) { - return QVariant::fromValue(QColor(0x20, 0xaa, 0x20)); // NOLINT - } - - if (role == Qt::BackgroundRole || role == Qt::ForegroundRole) { - if (PropertyView::showAll() - && propertyItems.size() == 1 - && propertyItems.front()->testStatus(App::Property::PropDynamic) - && !propertyItems.front()->testStatus(App::Property::LockDynamic)) { - return role == Qt::BackgroundRole - ? QVariant::fromValue(QColor(0xFF, 0xFF, 0x99)) // NOLINT - : QVariant::fromValue(QColor(0, 0, 0)); - } - return {}; - } - if (role == Qt::DisplayRole) { - return displayName(); - } - // no properties set - if (propertyItems.empty()) { - return {}; - } - if (role == Qt::ToolTipRole) { - QString type = QString::fromLatin1("Type: %1\nName: %2").arg( - QString::fromLatin1(propertyItems[0]->getTypeId().getName()), objectName()); - - QString doc = PropertyItem::toolTip(propertyItems[0]).toString(); - if (doc.isEmpty()) { - doc = toolTip(propertyItems[0]).toString(); - } - if (doc.size()) { - return type + QLatin1String("\n\n") + doc; - } - return type; - } - - return {}; -} - -QVariant PropertyItem::dataValue(int role) const -{ - // no properties set - if (propertyItems.empty()) { - PropertyItem* parent = this->parent(); - if (!parent || !parent->parent()) { - return {}; - } - if (role == Qt::EditRole) { - return parent->property(qPrintable(objectName())); - } - if (role == Qt::DecorationRole) { - QVariant val = parent->property(qPrintable(objectName())); - return decoration(val); - } - if (role == Qt::DisplayRole) { - QVariant val = parent->property(qPrintable(objectName())); - return toString(val); - } - if (role == Qt::ForegroundRole) { - if (hasExpression()) { - return QVariant::fromValue(QApplication::palette().color(QPalette::Link)); - } - return {}; - } - - return {}; - } - if (role == Qt::EditRole) { - return value(propertyItems[0]); - } - if (role == Qt::DecorationRole) { - return decoration(value(propertyItems[0])); - } - if (role == Qt::DisplayRole) { - return toString(value(propertyItems[0])); - } - if (role == Qt::ToolTipRole) { - return toolTip(propertyItems[0]); - } - if (role == Qt::ForegroundRole) { - if (hasExpression()) { - return QVariant::fromValue(QApplication::palette().color(QPalette::Link)); - } - return {}; - } - - return {}; -} - -QVariant PropertyItem::data(int column, int role) const -{ - // property name - if (column == 0) { - return dataProperty(role); - } - - return dataValue(role); -} - -bool PropertyItem::setData (const QVariant& value) -{ - // This is the basic mechanism to set the value to - // a property and if no property is set for this item - // it delegates it to its parent which sets then the - // property or delegates again to its parent... - if (propertyItems.empty()) { - PropertyItem* parent = this->parent(); - if (!parent || !parent->parent() || hasAnyExpression()) { - return false; - } - - parent->setProperty(qPrintable(objectName()),value); - return true; - } - - setValue(value); - return true; -} - -Qt::ItemFlags PropertyItem::flags(int column) const -{ - Qt::ItemFlags basicFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; - if (column == 1 && !isReadOnly()) { - return basicFlags | Qt::ItemIsEditable; - } - - return basicFlags; -} - -int PropertyItem::row() const -{ - if (parentItem) { - return parentItem->childItems.indexOf(const_cast(this)); // NOLINT - } - - return 0; -} - -void PropertyItem::bind(const App::ObjectIdentifier& _path) -{ - Gui::ExpressionBinding::bind(_path); - propertyBound(); -} - -void PropertyItem::bind(const App::Property& prop) -{ - Gui::ExpressionBinding::bind(prop); - propertyBound(); -} - -QString PropertyItem::expressionAsString() const -{ - if (hasExpression()) { - try { - std::unique_ptr result(getExpression()->eval()); - return QString::fromStdString(result->toString()); - } - catch (const Base::Exception& e) { - e.ReportException(); - } - } - - return {}; -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyStringItem) - -PropertyStringItem::PropertyStringItem() = default; - -QVariant PropertyStringItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - std::string value = static_cast(prop)->getValue(); - return {QString::fromUtf8(value.c_str())}; -} - -void PropertyStringItem::setValue(const QVariant& value) -{ - if(!hasExpression()) { - if (!value.canConvert()) { - return; - } - QString val = value.toString(); - val = QString::fromUtf8(Base::InterpreterSingleton::strToPython(val.toUtf8()).c_str()); - QString data = QString::fromLatin1("\"%1\"").arg(val); - setPropertyValue(data); - } -} - -QWidget* PropertyStringItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto le = new ExpLineEdit(parent); - le->setFrame(false); - le->setReadOnly(isReadOnly()); - QObject::connect(le, &ExpLineEdit::textChanged, method); - if(isBound()) { - le->bind(getPath()); - le->setAutoApply(autoApply()); - } - - return le; -} - -void PropertyStringItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto le = qobject_cast(editor); - le->setText(data.toString()); -} - -QVariant PropertyStringItem::editorData(QWidget *editor) const -{ - auto le = qobject_cast(editor); - return {le->text()}; -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFontItem) - -PropertyFontItem::PropertyFontItem() = default; - -QVariant PropertyFontItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - std::string value = static_cast(prop)->getValue(); - return {QString::fromUtf8(value.c_str())}; -} - -void PropertyFontItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - - QString val = value.toString(); - QString data = QString::fromLatin1("\"%1\"").arg(val); - setPropertyValue(data); -} - -QWidget* PropertyFontItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto cb = new QComboBox(parent); - cb->setFrame(false); - cb->setDisabled(isReadOnly()); -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - QObject::connect(cb, qOverload(&QComboBox::activated), method); -#else - QObject::connect(cb, &QComboBox::textActivated, method); -#endif - return cb; -} - -void PropertyFontItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto cb = qobject_cast(editor); -#if QT_VERSION < QT_VERSION_CHECK(6,0,0) - QStringList familyNames = QFontDatabase().families(QFontDatabase::Any); -#else - QStringList familyNames = QFontDatabase::families(QFontDatabase::Any); -#endif - cb->addItems(familyNames); - int index = familyNames.indexOf(data.toString()); - cb->setCurrentIndex(index); -} - -QVariant PropertyFontItem::editorData(QWidget *editor) const -{ - auto cb = qobject_cast(editor); - return {cb->currentText()}; -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertySeparatorItem) - -QWidget* PropertySeparatorItem::createEditor(QWidget* parent, const std::function& method) const -{ - Q_UNUSED(parent); - Q_UNUSED(method); - return nullptr; -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyIntegerItem) - -PropertyIntegerItem::PropertyIntegerItem() = default; - -QVariant PropertyIntegerItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - int value = (int)static_cast(prop)->getValue(); - return {value}; -} - -void PropertyIntegerItem::setValue(const QVariant& value) -{ - //if the item has an expression it issues the python code - if (!hasExpression()) { - if (!value.canConvert()) { - return; - } - int val = value.toInt(); - QString data = QString::fromLatin1("%1").arg(val); - setPropertyValue(data); - } -} - -QWidget* PropertyIntegerItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto sb = new Gui::IntSpinBox(parent); - sb->setFrame(false); - sb->setReadOnly(isReadOnly()); - QObject::connect(sb, qOverload(&Gui::IntSpinBox::valueChanged), method); - - if (isBound()) { - sb->bind(getPath()); - sb->setAutoApply(autoApply()); - } - - return sb; -} - -void PropertyIntegerItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto sb = qobject_cast(editor); - sb->setRange(INT_MIN, INT_MAX); - sb->setValue(data.toInt()); -} - -QVariant PropertyIntegerItem::editorData(QWidget *editor) const -{ - auto sb = qobject_cast(editor); - return {sb->value()}; -} - -QVariant PropertyIntegerItem::toString(const QVariant& v) const -{ - QString string(PropertyItem::toString(v).toString()); - - if (hasExpression()) { - string += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); - } - - return {string}; -} - - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyIntegerConstraintItem) - -PropertyIntegerConstraintItem::PropertyIntegerConstraintItem() = default; - -QVariant PropertyIntegerConstraintItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - int value = (int)static_cast(prop)->getValue(); - return {value}; -} - -void PropertyIntegerConstraintItem::setValue(const QVariant& value) -{ - //if the item has an expression it issues the python code - if (!hasExpression()) { - if (!value.canConvert()) { - return; - } - int val = value.toInt(); - QString data = QString::fromLatin1("%1").arg(val); - setPropertyValue(data); - } -} - -QWidget* PropertyIntegerConstraintItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto sb = new Gui::IntSpinBox(parent); - sb->setFrame(false); - sb->setReadOnly(isReadOnly()); - QObject::connect(sb, qOverload(&Gui::IntSpinBox::valueChanged), method); - - if (isBound()) { - sb->bind(getPath()); - sb->setAutoApply(autoApply()); - } - - return sb; -} - -void PropertyIntegerConstraintItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - const auto prop = static_cast - (getFirstProperty()); - - const App::PropertyIntegerConstraint::Constraints* c = nullptr; - if (prop) { - c = prop->getConstraints(); - } - - auto sb = qobject_cast(editor); - if (c) { - sb->setMinimum(int(c->LowerBound)); - sb->setMaximum(int(c->UpperBound)); - sb->setSingleStep(int(c->StepSize)); - } - else { - sb->setMinimum(min); - sb->setMaximum(max); - sb->setSingleStep(steps); - } - - sb->setValue(data.toInt()); -} - -QVariant PropertyIntegerConstraintItem::editorData(QWidget *editor) const -{ - auto sb = qobject_cast(editor); - return {sb->value()}; -} - -QVariant PropertyIntegerConstraintItem::toString(const QVariant& v) const -{ - QString string(PropertyItem::toString(v).toString()); - - if (hasExpression()) { - string += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); - } - - return {string}; -} - - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFloatItem) - -PropertyFloatItem::PropertyFloatItem() = default; - -QVariant PropertyFloatItem::toString(const QVariant& prop) const -{ - double value = prop.toDouble(); - QString data = QLocale().toString(value, 'f', decimals()); - - if (hasExpression()) { - data += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); - } - - return {data}; -} - -QVariant PropertyFloatItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - double value = static_cast(prop)->getValue(); - return {value}; -} - -void PropertyFloatItem::setValue(const QVariant& value) -{ - //if the item has an expression it issues the python code - if (!hasExpression()) { - if (!value.canConvert()) { - return; - } - double val = value.toDouble(); - QString data = QString::fromLatin1("%1").arg(val, 0, 'g', highPrec); - setPropertyValue(data); - } -} - -QWidget* PropertyFloatItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto sb = new Gui::DoubleSpinBox(parent); - sb->setFrame(false); - sb->setDecimals(decimals()); - sb->setReadOnly(isReadOnly()); - QObject::connect(sb, qOverload(&Gui::DoubleSpinBox::valueChanged), method); - - if (isBound()) { - sb->bind(getPath()); - sb->setAutoApply(autoApply()); - } - - return sb; -} - -void PropertyFloatItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto sb = qobject_cast(editor); - sb->setRange((double)INT_MIN, (double)INT_MAX); - sb->setValue(data.toDouble()); -} - -QVariant PropertyFloatItem::editorData(QWidget *editor) const -{ - auto sb = qobject_cast(editor); - return {sb->value()}; -} - -// -------------------------------------------------------------------- - - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyUnitItem) - -PropertyUnitItem::PropertyUnitItem() = default; - -QVariant PropertyUnitItem::toString(const QVariant& prop) const -{ - const Base::Quantity& unit = prop.value(); - QString string = unit.getUserString(); - if (hasExpression()) { - string += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); - } - - return {string}; -} - -QVariant PropertyUnitItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - Base::Quantity value = static_cast(prop)->getQuantityValue(); - return QVariant::fromValue(value); -} - -void PropertyUnitItem::setValue(const QVariant& value) -{ - //if the item has an expression it handles the python code - if (!hasExpression()) { - if (!value.canConvert()) { - return; - } - const Base::Quantity& val = value.value(); - - Base::QuantityFormat format(Base::QuantityFormat::Default, highPrec); - QString unit = Base::UnitsApi::toString(val, format); - setPropertyValue(unit); - } -} - -QWidget* PropertyUnitItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto infield = new Gui::QuantitySpinBox(parent); - infield->setFrame(false); - infield->setMinimumHeight(0); - infield->setReadOnly(isReadOnly()); - - //if we are bound to an expression we need to bind it to the input field - if (isBound()) { - infield->bind(getPath()); - infield->setAutoApply(autoApply()); - } - - - QObject::connect(infield, qOverload(&Gui::QuantitySpinBox::valueChanged), method); - return infield; -} - -void PropertyUnitItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - const Base::Quantity& value = data.value(); - - auto infield = qobject_cast(editor); - infield->setValue(value); - infield->selectAll(); -} - -QVariant PropertyUnitItem::editorData(QWidget *editor) const -{ - auto infield = qobject_cast(editor); - Base::Quantity value = infield->value(); - return QVariant::fromValue(value); -} - -// -------------------------------------------------------------------- - - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyUnitConstraintItem) - -PropertyUnitConstraintItem::PropertyUnitConstraintItem() = default; - -void PropertyUnitConstraintItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - const Base::Quantity& value = data.value(); - - auto infield = qobject_cast(editor); - infield->setValue(value); - infield->selectAll(); - - const auto prop = static_cast - (getFirstProperty()); - - const App::PropertyQuantityConstraint::Constraints* c = nullptr; - if (prop) { - c = prop->getConstraints(); - } - - if (c) { - infield->setMinimum(c->LowerBound); - infield->setMaximum(c->UpperBound); - infield->setSingleStep(c->StepSize); - } - else { - infield->setMinimum(min); - infield->setMaximum(max); - infield->setSingleStep(steps); - } -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFloatConstraintItem) - -PropertyFloatConstraintItem::PropertyFloatConstraintItem() = default; - -QVariant PropertyFloatConstraintItem::toString(const QVariant& prop) const -{ - double value = prop.toDouble(); - QString data = QLocale().toString(value, 'f', decimals()); - return {data}; -} - -QVariant PropertyFloatConstraintItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - double value = static_cast(prop)->getValue(); - return {value}; -} - -void PropertyFloatConstraintItem::setValue(const QVariant& value) -{ - //if the item has an expression it issues the python code - if (!hasExpression()) { - if (!value.canConvert()) { - return; - } - double val = value.toDouble(); - QString data = QString::fromLatin1("%1").arg(val, 0, 'g', highPrec); - setPropertyValue(data); - } -} - -QWidget* PropertyFloatConstraintItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto sb = new Gui::DoubleSpinBox(parent); - sb->setDecimals(decimals()); - sb->setFrame(false); - sb->setReadOnly(isReadOnly()); - QObject::connect(sb, qOverload(&Gui::DoubleSpinBox::valueChanged), method); - - if (isBound()) { - sb->bind(getPath()); - sb->setAutoApply(autoApply()); - } - - return sb; -} - -void PropertyFloatConstraintItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - const auto prop = static_cast - (getFirstProperty()); - - const App::PropertyFloatConstraint::Constraints* c = nullptr; - if (prop) { - c = prop->getConstraints(); - } - - auto sb = qobject_cast(editor); - if (c) { - sb->setMinimum(c->LowerBound); - sb->setMaximum(c->UpperBound); - sb->setSingleStep(c->StepSize); - } - else { - sb->setMinimum(min); - sb->setMaximum(max); - sb->setSingleStep(steps); - } - - sb->setValue(data.toDouble()); -} - -QVariant PropertyFloatConstraintItem::editorData(QWidget *editor) const -{ - auto sb = qobject_cast(editor); - return {sb->value()}; -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyPrecisionItem) - -PropertyPrecisionItem::PropertyPrecisionItem() -{ - setDecimals(highPrec); -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyAngleItem) - -PropertyAngleItem::PropertyAngleItem() = default; - -void PropertyAngleItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - PropertyUnitConstraintItem::setEditorData(editor, data); -} - -QVariant PropertyAngleItem::toString(const QVariant& prop) const -{ - return PropertyUnitConstraintItem::toString(prop); -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyBoolItem) - -PropertyBoolItem::PropertyBoolItem() = default; - -QVariant PropertyBoolItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - bool value = static_cast(prop)->getValue(); - return {value}; -} - -void PropertyBoolItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - bool val = value.toBool(); - QString data = (val ? QLatin1String("True") : QLatin1String("False")); - setPropertyValue(data); -} - -QWidget* PropertyBoolItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto cb = new QComboBox(parent); - cb->setFrame(false); - cb->addItem(QLatin1String("false")); - cb->addItem(QLatin1String("true")); - cb->setDisabled(isReadOnly()); - QObject::connect(cb, qOverload(&QComboBox::activated), method); - return cb; -} - -void PropertyBoolItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto cb = qobject_cast(editor); - cb->setCurrentIndex(cb->findText(data.toString())); -} - -QVariant PropertyBoolItem::editorData(QWidget *editor) const -{ - auto cb = qobject_cast(editor); - return {cb->currentText()}; -} - -// --------------------------------------------------------------- - -namespace Gui::PropertyEditor { -class VectorLineEdit : public Gui::ExpLineEdit -{ - int decimals; -public: - explicit VectorLineEdit (int decimals, QWidget * parent=nullptr, bool expressionOnly=false) - : Gui::ExpLineEdit(parent, expressionOnly) - , decimals(decimals) - { - } - - bool apply(const std::string &propName) override { - // NOLINTNEXTLINE - if (!ExpressionBinding::apply(propName)) { // clazy:exclude=skipped-base-method - QVariant data = property("coords"); - if (data.canConvert()) { - const Base::Vector3d& value = data.value(); - - QString str = QString::fromLatin1("(%1, %2, %3)") - .arg(value.x, 0, 'f', decimals) - .arg(value.y, 0, 'f', decimals) - .arg(value.z, 0, 'f', decimals); - - Gui::Command::doCommand(Gui::Command::Doc, "%s = %s", propName.c_str(), str.toLatin1().constData()); - return true; - } - } - - return false; - } -}; -} - -// --------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyVectorItem) - -PropertyVectorItem::PropertyVectorItem() -{ - m_x = static_cast(PropertyFloatItem::create()); - m_x->setParent(this); - m_x->setPropertyName(QLatin1String("x")); - this->appendChild(m_x); - m_y = static_cast(PropertyFloatItem::create()); - m_y->setParent(this); - m_y->setPropertyName(QLatin1String("y")); - this->appendChild(m_y); - m_z = static_cast(PropertyFloatItem::create()); - m_z->setParent(this); - m_z->setPropertyName(QLatin1String("z")); - this->appendChild(m_z); -} - -QVariant PropertyVectorItem::toString(const QVariant& prop) const -{ - QLocale loc; - const Base::Vector3d& value = prop.value(); - QString data = QString::fromLatin1("[%1 %2 %3]") - .arg(loc.toString(value.x, 'f', lowPrec), - loc.toString(value.y, 'f', lowPrec), - loc.toString(value.z, 'f', lowPrec)); - if (hasExpression()) { - data += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); - } - return {data}; -} - -QVariant PropertyVectorItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const Base::Vector3d& value = static_cast(prop)->getValue(); - return QVariant::fromValue(value); -} - -void PropertyVectorItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - const Base::Vector3d& val = value.value(); - QString data = QString::fromLatin1("(%1, %2, %3)") - .arg(val.x, 0, 'g', highPrec) - .arg(val.y, 0, 'g', highPrec) - .arg(val.z, 0, 'g', highPrec); - setPropertyValue(data); -} - -QWidget* PropertyVectorItem::createEditor(QWidget* parent, const std::function& /*method*/) const -{ - auto le = new VectorLineEdit(decimals(), parent); - le->setFrame(false); - le->setReadOnly(true); - - if (isBound()) { - le->bind(getPath()); - le->setAutoApply(autoApply()); - } - - return le; -} - -void PropertyVectorItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - QLocale loc; - auto le = qobject_cast(editor); - const Base::Vector3d& value = data.value(); - QString text = QString::fromLatin1("[%1 %2 %3]") - .arg(loc.toString(value.x, 'f', lowPrec), - loc.toString(value.y, 'f', lowPrec), - loc.toString(value.z, 'f', lowPrec)); - le->setProperty("coords", data); - le->setText(text); -} - -QVariant PropertyVectorItem::editorData(QWidget *editor) const -{ - auto le = qobject_cast(editor); - return {le->text()}; -} - -double PropertyVectorItem::x() const -{ - return data(1,Qt::EditRole).value().x; -} - -void PropertyVectorItem::setX(double x) -{ - setData(QVariant::fromValue(Base::Vector3d(x, y(), z()))); -} - -double PropertyVectorItem::y() const -{ - return data(1,Qt::EditRole).value().y; -} - -void PropertyVectorItem::setY(double y) -{ - setData(QVariant::fromValue(Base::Vector3d(x(), y, z()))); -} - -double PropertyVectorItem::z() const -{ - return data(1,Qt::EditRole).value().z; -} - -void PropertyVectorItem::setZ(double z) -{ - setData(QVariant::fromValue(Base::Vector3d(x(), y(), z))); -} - -void PropertyVectorItem::propertyBound() -{ - m_x->bind(App::ObjectIdentifier(getPath())<bind(App::ObjectIdentifier(getPath())<bind(App::ObjectIdentifier(getPath())<setContentsMargins(0, 0, 0, 0); - layout->setSpacing(2); - - lineEdit = new QLineEdit(this); - lineEdit->setReadOnly(true); - layout->addWidget(lineEdit); - - button = new QPushButton(QLatin1String("..."), this); -#if defined (Q_OS_MACOS) - button->setAttribute(Qt::WA_LayoutUsesWidgetRect); // layout size from QMacStyle was not correct -#endif - layout->addWidget(button); - - connect(button, &QPushButton::clicked, this, &PropertyEditorWidget::buttonClick); - - // QAbstractItemView will call selectAll() if a QLineEdit is the focus - // proxy. Since the QLineEdit here is read-only and not meant for editing, - // do not set it as focus proxy. Otherwise, the text won't even shown for - // most stylesheets (which contain a trick to hide the content of a selected - // read-only/disabled editor widgets). - // - // setFocusProxy(lineEdit); -} - -PropertyEditorWidget::~PropertyEditorWidget() = default; - -void PropertyEditorWidget::resizeEvent(QResizeEvent* e) -{ - button->setFixedWidth(e->size().height()); - button->setFixedHeight(e->size().height()); -} - -void PropertyEditorWidget::showValue(const QVariant &d) -{ - lineEdit->setText(d.toString()); -} - -QVariant PropertyEditorWidget::value() const -{ - return variant; -} - -void PropertyEditorWidget::setValue(const QVariant& val) -{ - variant = val; - showValue(variant); - Q_EMIT valueChanged(variant); -} - -// --------------------------------------------------------------- - -VectorListWidget::VectorListWidget(int decimals, QWidget *parent) - : PropertyEditorWidget(parent) - , decimals(decimals) -{ - connect(button, &QPushButton::clicked, this, &VectorListWidget::buttonClicked); -} - -void VectorListWidget::buttonClicked() -{ - auto dlg = new VectorListEditor(decimals, this); - dlg->setAttribute(Qt::WA_DeleteOnClose); - dlg->setValues(value().value>()); - QPoint p(0, 0); - p = this->mapToGlobal(p); - dlg->move(p); - connect(dlg, &VectorListEditor::accepted, this, [this, dlg] { - QVariant data = QVariant::fromValue>(dlg->getValues()); - setValue(data); - }); - - dlg->exec(); -} - -void VectorListWidget::showValue(const QVariant& d) -{ - QLocale loc; - QString data; - const QList& value = d.value>(); - if (value.isEmpty()) { - data = QString::fromLatin1("[]"); - } - else { - data = QString::fromLatin1("[%1 %2 %3], ...") - .arg(loc.toString(value[0].x, 'f', lowPrec), - loc.toString(value[0].y, 'f', lowPrec), - loc.toString(value[0].z, 'f', lowPrec)); - } - lineEdit->setText(data); -} -// --------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyVectorListItem) - -PropertyVectorListItem::PropertyVectorListItem() = default; - -QVariant PropertyVectorListItem::toString(const QVariant& prop) const -{ - QLocale loc; - QString data; - const QList& value = prop.value>(); - if (value.isEmpty()) { - data = QString::fromLatin1("[]"); - } - else { - data = QString::fromLatin1("[%1 %2 %3], ...") - .arg(loc.toString(value[0].x, 'f', lowPrec), - loc.toString(value[0].y, 'f', lowPrec), - loc.toString(value[0].z, 'f', lowPrec)); - } - - if (hasExpression()) { - data += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); - } - return {data}; -} - -QVariant PropertyVectorListItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const std::vector& value = static_cast(prop)->getValue(); - QList list; - std::copy(value.begin(), value.end(), std::back_inserter(list)); - return QVariant::fromValue>(list); -} - -void PropertyVectorListItem::setValue(const QVariant& value) -{ - if (!value.canConvert>()) { - return; - } - const QList& val = value.value>(); - QString data; - QTextStream str(&data); - str << "["; - for (const auto& it : val) { - str << QString::fromLatin1("(%1, %2, %3), ") - .arg(it.x, 0, 'g', highPrec) - .arg(it.y, 0, 'g', highPrec) - .arg(it.z, 0, 'g', highPrec); - } - str << "]"; - setPropertyValue(data); -} - -QWidget* PropertyVectorListItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto pe = new VectorListWidget(decimals(), parent); - QObject::connect(pe, &VectorListWidget::valueChanged, method); - pe->setDisabled(isReadOnly()); - return pe; -} - -void PropertyVectorListItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto pe = qobject_cast(editor); - pe->setValue(data); -} - -QVariant PropertyVectorListItem::editorData(QWidget *editor) const -{ - auto pe = qobject_cast(editor); - return pe->value(); -} - -// --------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyVectorDistanceItem) - -PropertyVectorDistanceItem::PropertyVectorDistanceItem() -{ - m_x = static_cast(PropertyUnitItem::create()); - m_x->setParent(this); - m_x->setPropertyName(QLatin1String("x")); - this->appendChild(m_x); - m_y = static_cast(PropertyUnitItem::create()); - m_y->setParent(this); - m_y->setPropertyName(QLatin1String("y")); - this->appendChild(m_y); - m_z = static_cast(PropertyUnitItem::create()); - m_z->setParent(this); - m_z->setPropertyName(QLatin1String("z")); - this->appendChild(m_z); -} - -QVariant PropertyVectorDistanceItem::toString(const QVariant& prop) const -{ - const Base::Vector3d& value = prop.value(); - QString data = QString::fromLatin1("[") + - Base::Quantity(value.x, Base::Unit::Length).getUserString() + QString::fromLatin1(" ") + - Base::Quantity(value.y, Base::Unit::Length).getUserString() + QString::fromLatin1(" ") + - Base::Quantity(value.z, Base::Unit::Length).getUserString() + QString::fromLatin1("]"); - if (hasExpression()) { - data += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); - } - return {data}; -} - - -QVariant PropertyVectorDistanceItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const Base::Vector3d& value = static_cast(prop)->getValue(); - return QVariant::fromValue(value); -} - -void PropertyVectorDistanceItem::setValue(const QVariant& variant) -{ - if (hasExpression() || !variant.canConvert()) { - return; - } - const Base::Vector3d& value = variant.value(); - - Base::Quantity x = Base::Quantity(value.x, Base::Unit::Length); - Base::Quantity y = Base::Quantity(value.y, Base::Unit::Length); - Base::Quantity z = Base::Quantity(value.z, Base::Unit::Length); - - Base::QuantityFormat format(Base::QuantityFormat::Default, highPrec); - QString data = QString::fromLatin1("(%1, %2, %3)") - .arg(Base::UnitsApi::toNumber(x, format), - Base::UnitsApi::toNumber(y, format), - Base::UnitsApi::toNumber(z, format)); - setPropertyValue(data); -} - -void PropertyVectorDistanceItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto le = qobject_cast(editor); - le->setProperty("coords", data); - le->setText(toString(data).toString()); -} - -QWidget* PropertyVectorDistanceItem::createEditor(QWidget* parent, const std::function& /*method*/) const -{ - auto le = new VectorLineEdit(decimals(), parent); - le->setFrame(false); - le->setReadOnly(true); - - if (isBound()) { - le->bind(getPath()); - le->setAutoApply(autoApply()); - } - - return le; -} - -QVariant PropertyVectorDistanceItem::editorData(QWidget *editor) const -{ - auto le = qobject_cast(editor); - return {le->text()}; -} - -Base::Quantity PropertyVectorDistanceItem::x() const -{ - return Base::Quantity(data(1,Qt::EditRole).value().x, Base::Unit::Length); -} - -void PropertyVectorDistanceItem::setX(Base::Quantity x) -{ - setData(QVariant::fromValue(Base::Vector3d(x.getValue(), y().getValue(), z().getValue()))); -} - -Base::Quantity PropertyVectorDistanceItem::y() const -{ - return Base::Quantity(data(1,Qt::EditRole).value().y, Base::Unit::Length); -} - -void PropertyVectorDistanceItem::setY(Base::Quantity y) -{ - setData(QVariant::fromValue(Base::Vector3d(x().getValue(), y.getValue(), z().getValue()))); -} - -Base::Quantity PropertyVectorDistanceItem::z() const -{ - return Base::Quantity(data(1,Qt::EditRole).value().z, Base::Unit::Length); -} - -void PropertyVectorDistanceItem::setZ(Base::Quantity z) -{ - setData(QVariant::fromValue(Base::Vector3d(x().getValue(), y().getValue(), z.getValue()))); -} - -void PropertyVectorDistanceItem::propertyBound() -{ - if (isBound()) { - m_x->bind(App::ObjectIdentifier(getPath())<bind(App::ObjectIdentifier(getPath())<bind(App::ObjectIdentifier(getPath())<(PropertyFloatItem::create()); - m_a11->setParent(this); - m_a11->setPropertyName(QLatin1String("A11")); - m_a11->setDecimals(decimals); - this->appendChild(m_a11); - m_a12 = static_cast(PropertyFloatItem::create()); - m_a12->setParent(this); - m_a12->setPropertyName(QLatin1String("A12")); - m_a12->setDecimals(decimals); - this->appendChild(m_a12); - m_a13 = static_cast(PropertyFloatItem::create()); - m_a13->setParent(this); - m_a13->setPropertyName(QLatin1String("A13")); - m_a13->setDecimals(decimals); - this->appendChild(m_a13); - m_a14 = static_cast(PropertyFloatItem::create()); - m_a14->setParent(this); - m_a14->setPropertyName(QLatin1String("A14")); - m_a14->setDecimals(decimals); - this->appendChild(m_a14); - m_a21 = static_cast(PropertyFloatItem::create()); - m_a21->setParent(this); - m_a21->setPropertyName(QLatin1String("A21")); - m_a21->setDecimals(decimals); - this->appendChild(m_a21); - m_a22 = static_cast(PropertyFloatItem::create()); - m_a22->setParent(this); - m_a22->setPropertyName(QLatin1String("A22")); - m_a22->setDecimals(decimals); - this->appendChild(m_a22); - m_a23 = static_cast(PropertyFloatItem::create()); - m_a23->setParent(this); - m_a23->setPropertyName(QLatin1String("A23")); - m_a23->setDecimals(decimals); - this->appendChild(m_a23); - m_a24 = static_cast(PropertyFloatItem::create()); - m_a24->setParent(this); - m_a24->setPropertyName(QLatin1String("A24")); - m_a24->setDecimals(decimals); - this->appendChild(m_a24); - m_a31 = static_cast(PropertyFloatItem::create()); - m_a31->setParent(this); - m_a31->setPropertyName(QLatin1String("A31")); - m_a31->setDecimals(decimals); - this->appendChild(m_a31); - m_a32 = static_cast(PropertyFloatItem::create()); - m_a32->setParent(this); - m_a32->setPropertyName(QLatin1String("A32")); - m_a32->setDecimals(decimals); - this->appendChild(m_a32); - m_a33 = static_cast(PropertyFloatItem::create()); - m_a33->setParent(this); - m_a33->setPropertyName(QLatin1String("A33")); - m_a33->setDecimals(decimals); - this->appendChild(m_a33); - m_a34 = static_cast(PropertyFloatItem::create()); - m_a34->setParent(this); - m_a34->setPropertyName(QLatin1String("A34")); - m_a34->setDecimals(decimals); - this->appendChild(m_a34); - m_a41 = static_cast(PropertyFloatItem::create()); - m_a41->setParent(this); - m_a41->setPropertyName(QLatin1String("A41")); - m_a41->setDecimals(decimals); - this->appendChild(m_a41); - m_a42 = static_cast(PropertyFloatItem::create()); - m_a42->setParent(this); - m_a42->setPropertyName(QLatin1String("A42")); - m_a42->setDecimals(decimals); - this->appendChild(m_a42); - m_a43 = static_cast(PropertyFloatItem::create()); - m_a43->setParent(this); - m_a43->setPropertyName(QLatin1String("A43")); - m_a43->setDecimals(decimals); - this->appendChild(m_a43); - m_a44 = static_cast(PropertyFloatItem::create()); - m_a44->setParent(this); - m_a44->setPropertyName(QLatin1String("A44")); - m_a44->setDecimals(decimals); - this->appendChild(m_a44); -} - -QVariant PropertyMatrixItem::toString(const QVariant& prop) const -{ - QLocale loc; - const Base::Matrix4D& value = prop.value(); - // NOLINTBEGIN - QString text = QString::fromLatin1("[%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16]") - .arg(loc.toString(value[0][0], 'f', lowPrec), //(unsigned short usNdx) - loc.toString(value[0][1], 'f', lowPrec), - loc.toString(value[0][2], 'f', lowPrec), - loc.toString(value[0][3], 'f', lowPrec), - loc.toString(value[1][0], 'f', lowPrec), - loc.toString(value[1][1], 'f', lowPrec), - loc.toString(value[1][2], 'f', lowPrec), - loc.toString(value[1][3], 'f', lowPrec), - loc.toString(value[2][0], 'f', lowPrec)) - .arg(loc.toString(value[2][1], 'f', lowPrec), - loc.toString(value[2][2], 'f', lowPrec), - loc.toString(value[2][3], 'f', lowPrec), - loc.toString(value[3][0], 'f', lowPrec), - loc.toString(value[3][1], 'f', lowPrec), - loc.toString(value[3][2], 'f', lowPrec), - loc.toString(value[3][3], 'f', lowPrec)); - // NOLINTEND - return {text}; -} - -QVariant PropertyMatrixItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const Base::Matrix4D& value = static_cast(prop)->getValue(); - return QVariant::fromValue(value); -} - -QVariant PropertyMatrixItem::toolTip(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const Base::Matrix4D& value = static_cast(prop)->getValue(); - return {QString::fromStdString(value.analyse())}; -} - -void PropertyMatrixItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - const Base::Matrix4D& val = value.value(); - // NOLINTBEGIN - QString data = QString::fromLatin1("FreeCAD.Matrix(%1, %2, %3, %4, %5, %6, %7, %8, %9, %10, %11, %12, %13, %14, %15, %16)") - .arg(val[0][0],0, 'g', highPrec) - .arg(val[0][1],0, 'g', highPrec) - .arg(val[0][2],0, 'g', highPrec) - .arg(val[0][3],0, 'g', highPrec) - .arg(val[1][0],0, 'g', highPrec) - .arg(val[1][1],0, 'g', highPrec) - .arg(val[1][2],0, 'g', highPrec) - .arg(val[1][3],0, 'g', highPrec) - .arg(val[2][0],0, 'g', highPrec) - .arg(val[2][1],0, 'g', highPrec) - .arg(val[2][2],0, 'g', highPrec) - .arg(val[2][3],0, 'g', highPrec) - .arg(val[3][0],0, 'g', highPrec) - .arg(val[3][1],0, 'g', highPrec) - .arg(val[3][2],0, 'g', highPrec) - .arg(val[3][3],0, 'g', highPrec); - // NOLINTEND - setPropertyValue(data); -} - -QWidget* PropertyMatrixItem::createEditor(QWidget* parent, const std::function& /*method*/) const -{ - auto le = new QLineEdit(parent); - le->setFrame(false); - le->setReadOnly(true); - return le; -} - -void PropertyMatrixItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - QLocale loc; - auto le = qobject_cast(editor); - const Base::Matrix4D& value = data.value(); - // NOLINTBEGIN - QString text = QString::fromLatin1("[%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16]") - .arg(loc.toString(value[0][0], 'f', lowPrec), //(unsigned short usNdx) - loc.toString(value[0][1], 'f', lowPrec), - loc.toString(value[0][2], 'f', lowPrec), - loc.toString(value[0][3], 'f', lowPrec), - loc.toString(value[1][0], 'f', lowPrec), - loc.toString(value[1][1], 'f', lowPrec), - loc.toString(value[1][2], 'f', lowPrec), - loc.toString(value[1][3], 'f', lowPrec), - loc.toString(value[2][0], 'f', lowPrec)) - .arg(loc.toString(value[2][1], 'f', lowPrec), - loc.toString(value[2][2], 'f', lowPrec), - loc.toString(value[2][3], 'f', lowPrec), - loc.toString(value[3][0], 'f', lowPrec), - loc.toString(value[3][1], 'f', lowPrec), - loc.toString(value[3][2], 'f', lowPrec), - loc.toString(value[3][3], 'f', lowPrec)); - // NOLINTEND - le->setText(text); -} - -QVariant PropertyMatrixItem::editorData(QWidget *editor) const -{ - auto le = qobject_cast(editor); - return {le->text()}; -} - -// clang-format off -double PropertyMatrixItem::getA11() const -{ - return data(1, Qt::EditRole).value()[0][0]; -} - -void PropertyMatrixItem::setA11(double A11) -{ - setData(QVariant::fromValue(Base::Matrix4D(A11, getA12(), getA13(),getA14(), - getA21(), getA22(), getA23(), getA24(), - getA31(), getA32(), getA33(), getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA12() const -{ - return data(1, Qt::EditRole).value()[0][1]; -} - -void PropertyMatrixItem::setA12(double A12) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), A12, getA13(), getA14(), - getA21(), getA22(), getA23(), getA24(), - getA31(), getA32(), getA33(), getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA13() const -{ - return data(1, Qt::EditRole).value()[0][2]; -} - -void PropertyMatrixItem::setA13(double A13) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), A13, getA14(), - getA21(), getA22(), getA23(), getA24(), - getA31(), getA32(), getA33(), getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA14() const -{ - return data(1, Qt::EditRole).value()[0][3]; -} - -void PropertyMatrixItem::setA14(double A14) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), A14, - getA21(), getA22(), getA23(), getA24(), - getA31(), getA32(), getA33(), getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA21() const -{ - return data(1, Qt::EditRole).value()[1][0]; -} - -void PropertyMatrixItem::setA21(double A21) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - A21, getA22(), getA23(), getA24(), - getA31(), getA32(), getA33(), getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA22() const -{ - return data(1, Qt::EditRole).value()[1][1]; -} - -void PropertyMatrixItem::setA22(double A22) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), A22, getA23(), getA24(), - getA31(), getA32(), getA33(), getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA23() const -{ - return data(1, Qt::EditRole).value()[1][2]; -} - -void PropertyMatrixItem::setA23(double A23) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), getA22(), A23, getA24(), - getA31(), getA32(), getA33(), getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA24() const -{ - return data(1, Qt::EditRole).value()[1][3]; -} - -void PropertyMatrixItem::setA24(double A24) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), getA22(), getA23(), A24, - getA31(), getA32(), getA33(), getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA31() const -{ - return data(1, Qt::EditRole).value()[2][0]; -} - -void PropertyMatrixItem::setA31(double A31) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), getA22(), getA23(), getA24(), - A31, getA32(), getA33(), getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA32() const -{ - return data(1, Qt::EditRole).value()[2][1]; -} - -void PropertyMatrixItem::setA32(double A32) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), getA22(), getA23(), getA24(), - getA31(), A32, getA33(), getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA33() const -{ - return data(1, Qt::EditRole).value()[2][2]; -} - -void PropertyMatrixItem::setA33(double A33) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), getA22(), getA23(), getA24(), - getA31(), getA32(), A33, getA34(), - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA34() const -{ - return data(1, Qt::EditRole).value()[2][3]; -} - -void PropertyMatrixItem::setA34(double A34) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), getA22(), getA23(), getA24(), - getA31(), getA32(), getA33(), A34, - getA41(), getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA41() const -{ - return data(1, Qt::EditRole).value()[3][0]; -} - -void PropertyMatrixItem::setA41(double A41) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), getA22(), getA23(), getA24(), - getA31(), getA32(), getA33(), getA34(), - A41, getA42(), getA43(), getA44()))); -} - -double PropertyMatrixItem::getA42() const -{ - return data(1, Qt::EditRole).value()[3][1]; -} - -void PropertyMatrixItem::setA42(double A42) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), getA22(), getA23(), getA24(), - getA31(), getA32(), getA33(), getA34(), - getA41(), A42, getA43(), getA44()))); -} - -double PropertyMatrixItem::getA43() const -{ - return data(1, Qt::EditRole).value()[3][2]; -} - -void PropertyMatrixItem::setA43(double A43) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), getA22(), getA23(), getA24(), - getA31(), getA32(), getA33(), getA34(), - getA41(), getA42(), A43, getA44()))); -} - -double PropertyMatrixItem::getA44() const -{ - return data(1, Qt::EditRole).value()[3][3]; -} - -void PropertyMatrixItem::setA44(double A44) -{ - setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), - getA21(), getA22(), getA23(), getA24(), - getA31(), getA32(), getA33(), getA34(), - getA41(), getA42(), getA43(), A44))); -} -// clang-format on - -// --------------------------------------------------------------- - -RotationHelper::RotationHelper() - : init_axis(false) - , changed_value(false) - , rot_angle(0) - , rot_axis(0,0,1) -{ -} - -void RotationHelper::setChanged(bool value) -{ - changed_value = value; -} - -bool RotationHelper::hasChangedAndReset() -{ - if (!changed_value) { - return false; - } - - changed_value = false; - return true; -} - -bool RotationHelper::isAxisInitialized() const -{ - return init_axis; -} - -void RotationHelper::setValue(const Base::Vector3d& axis, double angle) -{ - rot_axis = axis; - rot_angle = angle; - init_axis = true; -} - -void RotationHelper::getValue(Base::Vector3d& axis, double& angle) const -{ - axis = rot_axis; - angle = rot_angle; -} - -double RotationHelper::getAngle(const Base::Rotation& val) const -{ - double angle {}; - Base::Vector3d dir; - val.getRawValue(dir, angle); - if (dir * this->rot_axis < 0.0) { - angle = -angle; - } - return angle; -} - -Base::Rotation RotationHelper::setAngle(double angle) -{ - Base::Rotation rot; - rot.setValue(this->rot_axis, Base::toRadians(angle)); - changed_value = true; - rot_angle = angle; - return rot; -} - -Base::Vector3d RotationHelper::getAxis() const -{ - // We must store the rotation axis in a member because - // if we read the value from the property we would always - // get a normalized vector which makes it quite unhandy - // to work with - return this->rot_axis; -} - -Base::Rotation RotationHelper::setAxis(const Base::Rotation& value, const Base::Vector3d& axis) -{ - this->rot_axis = axis; - Base::Rotation rot = value; - Base::Vector3d dummy; - double angle {}; - rot.getValue(dummy, angle); - if (dummy * axis < 0.0) { - angle = -angle; - } - rot.setValue(axis, angle); - changed_value = true; - return rot; -} - -void RotationHelper::assignProperty(const Base::Rotation& value, double eps) -{ - double angle {}; - Base::Vector3d dir; - value.getRawValue(dir, angle); - Base::Vector3d cross = this->rot_axis.Cross(dir); - double len2 = cross.Sqr(); - if (angle != 0) { - // vectors are not parallel - if (len2 > eps) { - this->rot_axis = dir; - } - // vectors point into opposite directions - else if (this->rot_axis.Dot(dir) < 0) { - this->rot_axis = -this->rot_axis; - } - } - this->rot_angle = Base::toDegrees(angle); -} - -// --------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyRotationItem) - -PropertyRotationItem::PropertyRotationItem() -{ - m_a = static_cast(PropertyUnitItem::create()); - m_a->setParent(this); - m_a->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Angle"))); - this->appendChild(m_a); - m_d = static_cast(PropertyVectorItem::create()); - m_d->setParent(this); - m_d->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Axis"))); - m_d->setReadOnly(true); - this->appendChild(m_d); -} - -PropertyRotationItem::~PropertyRotationItem() = default; - -Base::Quantity PropertyRotationItem::getAngle() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return Base::Quantity(0.0); - } - - const Base::Rotation& val = value.value(); - double angle = h.getAngle(val); - return Base::Quantity(Base::toDegrees(angle), Base::Unit::Angle); -} - -void PropertyRotationItem::setAngle(Base::Quantity angle) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - Base::Rotation rot = h.setAngle(angle.getValue()); - setValue(QVariant::fromValue(rot)); -} - -Base::Vector3d PropertyRotationItem::getAxis() const -{ - return h.getAxis(); -} - -void PropertyRotationItem::setAxis(const Base::Vector3d& axis) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - auto rot = value.value(); - rot = h.setAxis(rot, axis); - setValue(QVariant::fromValue(rot)); -} - -void PropertyRotationItem::assignProperty(const App::Property* prop) -{ - // Choose an adaptive epsilon to avoid changing the axis when they are considered to - // be equal. See https://forum.freecad.org/viewtopic.php?f=10&t=24662&start=10 - double eps = std::pow(10.0, -2 * (decimals() + 1)); // NOLINT - if (prop->isDerivedFrom()) { - const Base::Rotation& value = static_cast(prop)->getValue(); - h.assignProperty(value, eps); - } -} - -QVariant PropertyRotationItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const Base::Rotation& value = static_cast(prop)->getValue(); - double angle {}; - Base::Vector3d dir; - value.getRawValue(dir, angle); - if (!h.isAxisInitialized()) { - if (m_a->hasExpression()) { - QString str = m_a->expressionAsString(); - angle = str.toDouble(); - } - else { - angle = Base::toDegrees(angle); - } - - PropertyItem* x = m_d->child(0); - PropertyItem* y = m_d->child(1); - PropertyItem* z = m_d->child(2); - if (x->hasExpression()) { - QString str = x->expressionAsString(); - dir.x = str.toDouble(); - } - if (y->hasExpression()) { - QString str = y->expressionAsString(); - dir.y = str.toDouble(); - } - if (z->hasExpression()) { - QString str = z->expressionAsString(); - dir.z = str.toDouble(); - } - h.setValue(dir, angle); - } - return QVariant::fromValue(value); -} - -QVariant PropertyRotationItem::toolTip(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const Base::Rotation& p = static_cast(prop)->getValue(); - double angle {}; - Base::Vector3d dir; - p.getRawValue(dir, angle); - angle = Base::toDegrees(angle); - - QLocale loc; - QString data = QString::fromUtf8("Axis: (%1 %2 %3)\n" - "Angle: %4") - .arg(loc.toString(dir.x, 'f', decimals()), - loc.toString(dir.y, 'f', decimals()), - loc.toString(dir.z, 'f', decimals()), - Base::Quantity(angle, Base::Unit::Angle).getUserString()); - return {data}; -} - -QVariant PropertyRotationItem::toString(const QVariant& prop) const -{ - const Base::Rotation& p = prop.value(); - double angle {}; - Base::Vector3d dir; - p.getRawValue(dir, angle); - angle = Base::toDegrees(angle); - - QLocale loc; - QString data = QString::fromUtf8("[(%1 %2 %3); %4]") - .arg(loc.toString(dir.x, 'f', lowPrec), - loc.toString(dir.y, 'f', lowPrec), - loc.toString(dir.z, 'f', lowPrec), - Base::Quantity(angle, Base::Unit::Angle).getUserString()); - return {data}; -} - -void PropertyRotationItem::setValue(const QVariant& value) -{ - if (!value.canConvert()) { - return; - } - // Accept this only if the user changed the axis, angle or position but - // not if >this< item loses focus - if (!h.hasChangedAndReset()) { - return; - } - - Base::Vector3d axis; - double angle {}; - h.getValue(axis, angle); - Base::QuantityFormat format(Base::QuantityFormat::Default, highPrec); - QString data = QString::fromLatin1("App.Rotation(App.Vector(%1,%2,%3),%4)") - .arg(Base::UnitsApi::toNumber(axis.x, format), - Base::UnitsApi::toNumber(axis.y, format), - Base::UnitsApi::toNumber(axis.z, format), - Base::UnitsApi::toNumber(angle, format)); - setPropertyValue(data); -} - -QWidget* PropertyRotationItem::createEditor(QWidget* parent, const std::function& method) const -{ - Q_UNUSED(parent) - Q_UNUSED(method) - return nullptr; -} - -void PropertyRotationItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - Q_UNUSED(editor) - Q_UNUSED(data) -} - -QVariant PropertyRotationItem::editorData(QWidget *editor) const -{ - Q_UNUSED(editor) - return {}; -} - -void PropertyRotationItem::propertyBound() -{ - if (isBound()) { - m_a->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("Angle")); - - m_d->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("Axis")); - } -} - -// -------------------------------------------------------------------- - -PlacementEditor::PlacementEditor(QString name, QWidget * parent) - : LabelButton(parent) - , _task(nullptr) - , propertyname{std::move(name)} -{ - propertyname.replace(QLatin1String(" "), QLatin1String("")); -} - -PlacementEditor::~PlacementEditor() = default; - -void PlacementEditor::browse() -{ - Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); - Gui::Dialog::TaskPlacement* task {}; - task = qobject_cast(dlg); - if (dlg && !task) { - // there is already another task dialog which must be closed first - Gui::Control().showDialog(dlg); - return; - } - if (!task) { - task = new Gui::Dialog::TaskPlacement(); - } - if (!_task) { - _task = task; - connect(task, &TaskPlacement::placementChanged, - this, &PlacementEditor::updateValue); - } - task->setPlacement(value().value()); - task->setPropertyName(propertyname); - task->setSelection(Gui::Selection().getSelectionEx()); - task->bindObject(); - Gui::Control().showDialog(task); -} - -void PlacementEditor::showValue(const QVariant& d) -{ - const Base::Placement& p = d.value(); - double angle {}; - Base::Vector3d dir; - Base::Vector3d pos; - p.getRotation().getRawValue(dir, angle); - angle = Base::toDegrees(angle); - pos = p.getPosition(); - - QLocale loc; - QString data = QString::fromUtf8("[(%1 %2 %3);%4 \xc2\xb0;(%5 %6 %7)]") - .arg(loc.toString(dir.x, 'f', lowPrec), - loc.toString(dir.y, 'f', lowPrec), - loc.toString(dir.z, 'f', lowPrec), - loc.toString(angle, 'f', lowPrec), - loc.toString(pos.x, 'f', lowPrec), - loc.toString(pos.y, 'f', lowPrec), - loc.toString(pos.z, 'f', lowPrec)); - getLabel()->setText(data); -} - -void PlacementEditor::updateValue(const QVariant& v, bool incr, bool data) -{ - if (data) { - if (incr) { - QVariant u = value(); - const Base::Placement& plm = u.value(); - const Base::Placement& rel = v.value(); - Base::Placement newp = rel * plm; - setValue(QVariant::fromValue(newp)); - } - else { - setValue(v); - } - } -} - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyPlacementItem) - -PropertyPlacementItem::PropertyPlacementItem() -{ - m_a = static_cast(PropertyUnitItem::create()); - m_a->setParent(this); - m_a->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Angle"))); - this->appendChild(m_a); - m_d = static_cast(PropertyVectorItem::create()); - m_d->setParent(this); - m_d->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Axis"))); - m_d->setReadOnly(true); - this->appendChild(m_d); - m_p = static_cast(PropertyVectorDistanceItem::create()); - m_p->setParent(this); - m_p->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Position"))); - m_p->setReadOnly(true); - this->appendChild(m_p); -} - -PropertyPlacementItem::~PropertyPlacementItem() = default; - -Base::Quantity PropertyPlacementItem::getAngle() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return Base::Quantity(0.0); - } - - const Base::Placement& val = value.value(); - double angle = h.getAngle(val.getRotation()); - return Base::Quantity(Base::toDegrees(angle), Base::Unit::Angle); -} - -void PropertyPlacementItem::setAngle(Base::Quantity angle) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - auto val = value.value(); - Base::Rotation rot = h.setAngle(angle.getValue()); - val.setRotation(rot); - setValue(QVariant::fromValue(val)); -} - -Base::Vector3d PropertyPlacementItem::getAxis() const -{ - return h.getAxis(); -} - -void PropertyPlacementItem::setAxis(const Base::Vector3d& axis) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - auto val = value.value(); - Base::Rotation rot = val.getRotation(); - rot = h.setAxis(rot, axis); - val.setRotation(rot); - setValue(QVariant::fromValue(val)); -} - -Base::Vector3d PropertyPlacementItem::getPosition() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return Base::Vector3d(0,0,0); - } - const Base::Placement& val = value.value(); - return val.getPosition(); -} - -void PropertyPlacementItem::setPosition(const Base::Vector3d& pos) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - auto val = value.value(); - val.setPosition(pos); - h.setChanged(true); - setValue(QVariant::fromValue(val)); -} - -void PropertyPlacementItem::assignProperty(const App::Property* prop) -{ - // Choose an adaptive epsilon to avoid changing the axis when they are considered to - // be equal. See https://forum.freecad.org/viewtopic.php?f=10&t=24662&start=10 - double eps = std::pow(10.0, -2 * (decimals() + 1)); // NOLINT - if (prop->isDerivedFrom()) { - const Base::Placement& value = static_cast(prop)->getValue(); - h.assignProperty(value.getRotation(), eps); - } -} - -QVariant PropertyPlacementItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const Base::Placement& value = static_cast(prop)->getValue(); - double angle {}; - Base::Vector3d dir; - value.getRotation().getRawValue(dir, angle); - if (!h.isAxisInitialized()) { - if (m_a->hasExpression()) { - QString str = m_a->expressionAsString(); - angle = str.toDouble(); - } - else { - angle = Base::toDegrees(angle); - } - - PropertyItem* x = m_d->child(0); - PropertyItem* y = m_d->child(1); - PropertyItem* z = m_d->child(2); - if (x->hasExpression()) { - QString str = x->expressionAsString(); - dir.x = str.toDouble(); - } - if (y->hasExpression()) { - QString str = y->expressionAsString(); - dir.y = str.toDouble(); - } - if (z->hasExpression()) { - QString str = z->expressionAsString(); - dir.z = str.toDouble(); - } - h.setValue(dir, angle); - } - return QVariant::fromValue(value); -} - -QVariant PropertyPlacementItem::toolTip(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const Base::Placement& p = static_cast(prop)->getValue(); - double angle {}; - Base::Vector3d dir; - Base::Vector3d pos; - p.getRotation().getRawValue(dir, angle); - angle = Base::toDegrees(angle); - pos = p.getPosition(); - - QLocale loc; - QString data = QString::fromUtf8("Axis: (%1 %2 %3)\n" - "Angle: %4\n" - "Position: (%5 %6 %7)") - .arg(loc.toString(dir.x, 'f', decimals()), - loc.toString(dir.y, 'f', decimals()), - loc.toString(dir.z, 'f', decimals()), - Base::Quantity(angle, Base::Unit::Angle).getUserString(), - Base::Quantity(pos.x, Base::Unit::Length).getUserString(), - Base::Quantity(pos.y, Base::Unit::Length).getUserString(), - Base::Quantity(pos.z, Base::Unit::Length).getUserString()); - return {data}; -} - -QVariant PropertyPlacementItem::toString(const QVariant& prop) const -{ - const Base::Placement& p = prop.value(); - double angle {}; - Base::Vector3d dir; - Base::Vector3d pos; - p.getRotation().getRawValue(dir, angle); - angle = Base::toDegrees(angle); - pos = p.getPosition(); - - QLocale loc; - QString data = QString::fromUtf8("[(%1 %2 %3); %4; (%5 %6 %7)]") - .arg(loc.toString(dir.x, 'f', lowPrec), - loc.toString(dir.y, 'f', lowPrec), - loc.toString(dir.z, 'f', lowPrec), - Base::Quantity(angle, Base::Unit::Angle).getUserString(), - Base::Quantity(pos.x, Base::Unit::Length).getUserString(), - Base::Quantity(pos.y, Base::Unit::Length).getUserString(), - Base::Quantity(pos.z, Base::Unit::Length).getUserString()); - return {data}; -} - -void PropertyPlacementItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - // Accept this only if the user changed the axis, angle or position but - // not if >this< item loses focus - if (!h.hasChangedAndReset()) { - return; - } - - const Base::Placement& val = value.value(); - Base::Vector3d pos = val.getPosition(); - - Base::Vector3d axis; - double angle {}; - h.getValue(axis, angle); - - Base::QuantityFormat format(Base::QuantityFormat::Default, highPrec); - QString data = QString::fromLatin1("App.Placement(" - "App.Vector(%1,%2,%3)," - "App.Rotation(App.Vector(%4,%5,%6),%7))") - .arg(Base::UnitsApi::toNumber(pos.x, format), - Base::UnitsApi::toNumber(pos.y, format), - Base::UnitsApi::toNumber(pos.z, format), - Base::UnitsApi::toNumber(axis.x, format), - Base::UnitsApi::toNumber(axis.y, format), - Base::UnitsApi::toNumber(axis.z, format), - Base::UnitsApi::toNumber(angle, format)); - setPropertyValue(data); -} - -QWidget* PropertyPlacementItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto pe = new PlacementEditor(this->propertyName(), parent); - QObject::connect(pe, &PlacementEditor::valueChanged, method); - - // The Placement dialog only works if property is part of a DocumentObject - bool readonly = isReadOnly(); - if (auto prop = getFirstProperty()) { - readonly |= (!prop->getContainer()->isDerivedFrom()); - } - pe->setDisabled(readonly); - return pe; -} - -void PropertyPlacementItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto pe = qobject_cast(editor); - pe->setValue(data); -} - -QVariant PropertyPlacementItem::editorData(QWidget *editor) const -{ - auto pe = qobject_cast(editor); - return pe->value(); -} - -void PropertyPlacementItem::propertyBound() -{ - if (isBound()) { - m_a->bind(App::ObjectIdentifier(getPath())<bind(App::ObjectIdentifier(getPath())<bind(App::ObjectIdentifier(getPath())<(PropertyStringListItem::create()); - m_enum->setParent(this); - m_enum->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Enum"))); - this->appendChild(m_enum); - } -} - -void PropertyEnumItem::propertyBound() -{ - if (m_enum && isBound()) { - m_enum->bind(App::ObjectIdentifier(getPath())<isDerivedFrom()) { - const auto prop_enum = static_cast(prop); - std::vector enums = prop_enum->getEnumVector(); - for (const auto& it : enums) { - res.push_back(QString::fromStdString(it)); - } - } - return res; -} - -QVariant PropertyEnumItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const auto prop_enum = static_cast(prop); - if(!prop_enum->isValid()) { - return {QString()}; - } - return {QString::fromUtf8(prop_enum->getValueAsString())}; -} - -void PropertyEnumItem::setValue(const QVariant& value) -{ - if (hasExpression()) { - return; - } - - QString data; - - if (value.userType() == QMetaType::QStringList) { - QStringList values = value.toStringList(); - QTextStream str(&data); - str << "["; - for (const auto & it : values) { - QString text(it); - text.replace(QString::fromUtf8("'"),QString::fromUtf8("\\'")); - - std::string pystr = Base::Tools::escapedUnicodeFromUtf8(text.toUtf8()); - pystr = Base::InterpreterSingleton::strToPython(pystr.c_str()); - str << "u\"" << pystr.c_str() << "\", "; - } - str << "]"; - setPropertyValue(data); - } - else if (value.canConvert()) { - QByteArray val = value.toString().toUtf8(); - std::string str = Base::Tools::escapedUnicodeFromUtf8(val); - data = QString::fromLatin1("u\"%1\"").arg(QString::fromStdString(str)); - setPropertyValue(data); - } -} - -namespace { - -class EnumItems; - -struct EnumItem { - QString text; - QString fullText; - std::shared_ptr children; - explicit EnumItem(QString t = QString(), QString f = QString()) - : text(std::move(t)) - , fullText(std::move(f)) - {} - void populate(QMenu *menu); -}; - -class EnumItems : public std::vector -{ -}; - -void EnumItem::populate(QMenu *menu) -{ - if (!children || children->empty()) { - auto action = menu->addAction(text); - action->setData(fullText); - return; - } - auto subMenu = menu->addMenu(text); - for (auto &item : *children) { - item.populate(subMenu); - } -} - -std::shared_ptr getEnumItems(const QStringList& commonModes) // NOLINT -{ - int index = -1; - std::shared_ptr enumItems; - for (auto &mode : commonModes) { - ++index; - auto fields = mode.split(QStringLiteral("|")); - if (!enumItems && fields.size() <= 1) { - continue; - } - if (!enumItems) { - enumItems = std::make_shared(); - for (int i=0; iemplace_back(commonModes[i], mode); - } - } - auto children = enumItems; - int j = -1; - for (auto &field : fields) { - ++j; - field = field.trimmed(); - auto it = children->end(); - if (field.isEmpty()) { - if (!children->empty()) { - --it; - } - else { - continue; - } - } - else { - it = std::find_if(children->begin(), children->end(), - [&field](const EnumItem &item) { - return item.text == field; - }); - if (it == children->end()) { - it = children->emplace(children->end(), field, mode); - } - } - if (j + 1 == (int)fields.size()) { - break; - } - if (!it->children) { - it->children = std::make_shared(); - } - children = it->children; - } - } - - return enumItems; -} - -} // anonymous namespace - -QStringList PropertyEnumItem::getCommonModes() const -{ - const std::vector& items = getPropertyData(); - - QStringList commonModes; - QStringList modes; - for (auto it = items.begin(); it != items.end(); ++it) { - if ((*it)->is()) { - auto prop = static_cast(*it); - if (!prop->hasEnums()) { - commonModes.clear(); - return {}; - } - const std::vector& value = prop->getEnumVector(); - if (it == items.begin()) { - for (const auto & jt : value) { - commonModes << QString::fromUtf8(jt.c_str()); - } - } - else { - for (const auto & jt : value) { - if (commonModes.contains(QString::fromUtf8(jt.c_str()))) { - modes << QString::fromUtf8(jt.c_str()); - } - } - - commonModes = modes; - modes.clear(); - } - } - } - - return commonModes; -} - -QWidget* PropertyEnumItem::createEditor(QWidget* parent, const std::function& method) const -{ - QStringList commonModes = getCommonModes(); - if (commonModes.isEmpty()) { - return nullptr; - } - - std::shared_ptr enumItems = getEnumItems(commonModes); - - if (!enumItems) { - auto cb = new QComboBox(parent); - cb->setFrame(false); - cb->setDisabled(isReadOnly()); - cb->addItems(commonModes); - QObject::connect(cb, qOverload(&QComboBox::activated), method); - return cb; - } - - auto button = new PropertyEnumButton(parent); - button->setDisabled(isReadOnly()); - auto menu = new QMenu(button); - for (auto &item : *enumItems) { - item.populate(menu); - } - button->setMenu(menu); - QObject::connect(menu, &QMenu::aboutToShow, this, [=]() { - menu->setMinimumWidth(button->width()); - }); - QObject::connect(menu, &QMenu::triggered, this, [=](QAction *action) { - button->setText(action->data().toString()); - Q_EMIT button->picked(); - }); - QObject::connect(button, &PropertyEnumButton::picked, method); - return button; -} - -void PropertyEnumItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - if (auto cb = qobject_cast(editor)) { - cb->setEditable(false); - cb->setCurrentIndex(cb->findText(data.toString())); - } - else if (auto btn = qobject_cast(editor)) { - btn->setText(data.toString()); - } -} - -QVariant PropertyEnumItem::editorData(QWidget *editor) const -{ - if (auto cb = qobject_cast(editor)) { - return {cb->currentText()}; - } - if (auto btn = qobject_cast(editor)) { - return btn->text(); - } - return {}; -} - -// --------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyStringListItem) - -PropertyStringListItem::PropertyStringListItem() = default; - -QWidget* PropertyStringListItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto le = new Gui::LabelEditor(parent); - le->setAutoFillBackground(true); - le->setDisabled(isReadOnly()); - QObject::connect(le, &Gui::LabelEditor::textChanged, method); - return le; -} - -void PropertyStringListItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto le = qobject_cast(editor); - QStringList list = data.toStringList(); - le->setText(list.join(QChar::fromLatin1('\n'))); -} - -QVariant PropertyStringListItem::editorData(QWidget *editor) const -{ - auto le = qobject_cast(editor); - QString complete = le->text(); - QStringList list = complete.split(QChar::fromLatin1('\n')); - return {list}; -} - -QVariant PropertyStringListItem::toString(const QVariant& prop) const -{ - QStringList list = prop.toStringList(); - const int size = 10; - if (list.size() > size) { - list = list.mid(0, size); - list.append(QLatin1String("...")); - } - - QString text = QString::fromUtf8("[%1]").arg(list.join(QLatin1String(","))); - - return {text}; -} - -QVariant PropertyStringListItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - QStringList list; - const std::vector& value = (static_cast(prop))->getValues(); - for (const auto & jt : value) { - list << QString::fromUtf8(jt.c_str()); - } - - return {list}; -} - -void PropertyStringListItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - QStringList values = value.toStringList(); - QString data; - QTextStream str(&data); -#if QT_VERSION < QT_VERSION_CHECK(6,0,0) - str.setCodec("UTF-8"); -#endif - - str << "["; - for (const auto & it : values) { - QString text(it); - std::string pystr = Base::InterpreterSingleton::strToPython(text.toUtf8().constData()); - str << "\"" << QString::fromUtf8(pystr.c_str()) << "\", "; - } - str << "]"; - - setPropertyValue(data); -} - -// --------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFloatListItem) - -PropertyFloatListItem::PropertyFloatListItem() = default; - -QWidget* PropertyFloatListItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto le = new Gui::LabelEditor(parent); - le->setAutoFillBackground(true); - le->setInputType(Gui::LabelEditor::Float); - le->setDisabled(isReadOnly()); - QObject::connect(le, &Gui::LabelEditor::textChanged, method); - return le; -} - -void PropertyFloatListItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto le = qobject_cast(editor); - QStringList list = data.toStringList(); - le->setText(list.join(QChar::fromLatin1('\n'))); -} - -QVariant PropertyFloatListItem::editorData(QWidget *editor) const -{ - auto le = qobject_cast(editor); - QString complete = le->text(); - QStringList list = complete.split(QChar::fromLatin1('\n')); - return {list}; -} - -QVariant PropertyFloatListItem::toString(const QVariant& prop) const -{ - QStringList list = prop.toStringList(); - const int size = 10; - if (list.size() > size) { - list = list.mid(0, size); - list.append(QLatin1String("...")); - } - QString text = QString::fromUtf8("[%1]").arg(list.join(QLatin1String(","))); - return {text}; -} - -QVariant PropertyFloatListItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - QStringList list; - const std::vector& value = static_cast(prop)->getValues(); - for (double jt : value) { - list << QString::number(jt, 'f', decimals()); - } - - return {list}; -} - -void PropertyFloatListItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - QStringList values = value.toStringList(); - QString data; - QTextStream str(&data); - str << "["; - for (const auto & it : values) { - str << it << ","; - } - str << "]"; - if (data == QString::fromUtf8("[,]")) { - data = QString::fromUtf8("[]"); - } - setPropertyValue(data); -} - -// --------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyIntegerListItem) - -PropertyIntegerListItem::PropertyIntegerListItem() = default; - -QWidget* PropertyIntegerListItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto le = new Gui::LabelEditor(parent); - le->setAutoFillBackground(true); - le->setInputType(Gui::LabelEditor::Integer); - le->setDisabled(isReadOnly()); - QObject::connect(le, &Gui::LabelEditor::textChanged, method); - return le; -} - -void PropertyIntegerListItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto le = qobject_cast(editor); - QStringList list = data.toStringList(); - le->setText(list.join(QChar::fromLatin1('\n'))); -} - -QVariant PropertyIntegerListItem::editorData(QWidget *editor) const -{ - auto le = qobject_cast(editor); - QString complete = le->text(); - QStringList list = complete.split(QChar::fromLatin1('\n')); - return {list}; -} - -QVariant PropertyIntegerListItem::toString(const QVariant& prop) const -{ - QStringList list = prop.toStringList(); - const int size = 10; - if (list.size() > size) { - list = list.mid(0, size); - list.append(QLatin1String("...")); - } - QString text = QString::fromUtf8("[%1]").arg(list.join(QLatin1String(","))); - - return {text}; -} - -QVariant PropertyIntegerListItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - QStringList list; - const std::vector& value = static_cast(prop)->getValues(); - for (long jt : value) { - list << QString::number(jt); - } - - return {list}; -} - -void PropertyIntegerListItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - QStringList values = value.toStringList(); - QString data; - QTextStream str(&data); - str << "["; - for (const auto & value : values) { - str << value << ","; - } - str << "]"; - if (data == QString::fromUtf8("[,]")) { - data = QString::fromUtf8("[]"); - } - setPropertyValue(data); -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyColorItem) - -PropertyColorItem::PropertyColorItem() = default; - -QVariant PropertyColorItem::decoration(const QVariant& value) const -{ - auto color = value.value(); - - int size = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize); - QPixmap p(size, size); - p.fill(color); - - return QVariant(p); -} - -QVariant PropertyColorItem::toString(const QVariant& prop) const -{ - auto value = prop.value(); - QString color = QString::fromLatin1("[%1, %2, %3]") - .arg(value.red()).arg(value.green()).arg(value.blue()); - return {color}; -} - -QVariant PropertyColorItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - App::Color value = static_cast(prop)->getValue(); - return QVariant(value.asValue()); -} - -void PropertyColorItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - auto col = value.value(); - QString data = QString::fromLatin1("(%1,%2,%3)") - .arg(col.red()) - .arg(col.green()) - .arg(col.blue()); - setPropertyValue(data); -} - -QWidget* PropertyColorItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto cb = new Gui::ColorButton( parent ); - cb->setDisabled(isReadOnly()); - QObject::connect(cb, &Gui::ColorButton::changed, method); - return cb; -} - -void PropertyColorItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto cb = qobject_cast(editor); - auto color = data.value(); - cb->setColor(color); -} - -QVariant PropertyColorItem::editorData(QWidget *editor) const -{ - auto cb = qobject_cast(editor); - QVariant var; - var.setValue(cb->color()); - return var; -} - -// -------------------------------------------------------------------- - -namespace Gui::PropertyEditor { - class Material - { - public: - QColor diffuseColor; - QColor ambientColor; - QColor specularColor; - QColor emissiveColor; - float shininess {}; - float transparency {}; - }; -} - -Q_DECLARE_METATYPE(Gui::PropertyEditor::Material) // NOLINT - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyMaterialItem) - -PropertyMaterialItem::PropertyMaterialItem() -{ - const int min = 0; - const int max = 100; - const int steps = 5; - diffuse = static_cast(PropertyColorItem::create()); - diffuse->setParent(this); - diffuse->setPropertyName(QLatin1String("DiffuseColor")); - this->appendChild(diffuse); - - ambient = static_cast(PropertyColorItem::create()); - ambient->setParent(this); - ambient->setPropertyName(QLatin1String("AmbientColor")); - this->appendChild(ambient); - - specular = static_cast(PropertyColorItem::create()); - specular->setParent(this); - specular->setPropertyName(QLatin1String("SpecularColor")); - this->appendChild(specular); - - emissive = static_cast(PropertyColorItem::create()); - emissive->setParent(this); - emissive->setPropertyName(QLatin1String("EmissiveColor")); - this->appendChild(emissive); - - shininess = static_cast(PropertyIntegerConstraintItem::create()); - shininess->setRange(min, max); - shininess->setStepSize(steps); - shininess->setParent(this); - shininess->setPropertyName(QLatin1String("Shininess")); - this->appendChild(shininess); - - transparency = static_cast(PropertyIntegerConstraintItem::create()); - transparency->setRange(min, max); - transparency->setStepSize(steps); - transparency->setParent(this); - transparency->setPropertyName(QLatin1String("Transparency")); - this->appendChild(transparency); -} - -PropertyMaterialItem::~PropertyMaterialItem() = default; - -void PropertyMaterialItem::propertyBound() -{ -} - -QColor PropertyMaterialItem::getDiffuseColor() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return {}; - } - - auto val = value.value(); - return val.diffuseColor; -} - -void PropertyMaterialItem::setDiffuseColor(const QColor& color) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - auto mat = value.value(); - mat.diffuseColor = color; - setValue(QVariant::fromValue(mat)); -} - -QColor PropertyMaterialItem::getAmbientColor() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return {}; - } - - auto val = value.value(); - return val.ambientColor; -} - -void PropertyMaterialItem::setAmbientColor(const QColor& color) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - auto mat = value.value(); - mat.ambientColor = color; - setValue(QVariant::fromValue(mat)); -} - -QColor PropertyMaterialItem::getSpecularColor() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return {}; - } - - auto val = value.value(); - return val.specularColor; -} - -void PropertyMaterialItem::setSpecularColor(const QColor& color) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - auto mat = value.value(); - mat.specularColor = color; - setValue(QVariant::fromValue(mat)); -} - -QColor PropertyMaterialItem::getEmissiveColor() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return {}; - } - - auto val = value.value(); - return val.emissiveColor; -} - -void PropertyMaterialItem::setEmissiveColor(const QColor& color) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - auto mat = value.value(); - mat.emissiveColor = color; - setValue(QVariant::fromValue(mat)); -} - -int PropertyMaterialItem::getShininess() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return 0; - } - - auto val = value.value(); - return toPercent(val.shininess); -} - -void PropertyMaterialItem::setShininess(int s) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - auto mat = value.value(); - mat.shininess = fromPercent(s); - setValue(QVariant::fromValue(mat)); -} - -int PropertyMaterialItem::getTransparency() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return 0; - } - - auto val = value.value(); - return toPercent(val.transparency); -} - -void PropertyMaterialItem::setTransparency(int t) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - auto mat = value.value(); - mat.transparency = fromPercent(t); - setValue(QVariant::fromValue(mat)); -} - -QVariant PropertyMaterialItem::decoration(const QVariant& value) const -{ - // use the diffuse color - auto val = value.value(); - QColor color = val.diffuseColor; - - int size = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize); - QPixmap p(size, size); - p.fill(color); - - return QVariant(p); -} - -QVariant PropertyMaterialItem::toString(const QVariant& prop) const -{ - // use the diffuse color - auto val = prop.value(); - QColor value = val.diffuseColor; - QString color = QString::fromLatin1("[%1, %2, %3]") - .arg(value.red()).arg(value.green()).arg(value.blue()); - return {color}; -} - -QVariant PropertyMaterialItem::toolTip(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const App::Material& value = static_cast(prop)->getValue(); - auto dc = value.diffuseColor.asValue(); - auto ac = value.ambientColor.asValue(); - auto sc = value.specularColor.asValue(); - auto ec = value.emissiveColor.asValue(); - - QString data = QString::fromUtf8( - "Diffuse color: [%1, %2, %3]\n" - "Ambient color: [%4, %5, %6]\n" - "Specular color: [%7, %8, %9]\n" - "Emissive color: [%10, %11, %12]\n" - "Shininess: %13\n" - "Transparency: %14" - ) - .arg(dc.red()).arg(dc.green()).arg(dc.blue()) - .arg(ac.red()).arg(ac.green()).arg(ac.blue()) - .arg(sc.red()).arg(sc.green()).arg(sc.blue()) - .arg(ec.red()).arg(ec.green()).arg(ec.blue()) - .arg(toPercent(value.shininess)) - .arg(toPercent(value.transparency)) - ; - - return {data}; -} - -QVariant PropertyMaterialItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const App::Material& value = static_cast(prop)->getValue(); - Material mat; - - mat.diffuseColor = value.diffuseColor.asValue(); - mat.ambientColor = value.ambientColor.asValue(); - mat.specularColor = value.specularColor.asValue(); - mat.emissiveColor = value.emissiveColor.asValue(); - mat.shininess = value.shininess; - mat.transparency = value.transparency; - - return QVariant::fromValue(mat); -} - -void PropertyMaterialItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - - auto mat = value.value(); - App::Color dc; dc.setValue(mat.diffuseColor); - App::Color ac; ac.setValue(mat.ambientColor); - App::Color sc; sc.setValue(mat.specularColor); - App::Color ec; ec.setValue(mat.emissiveColor); - float s = mat.shininess; - float t = mat.transparency; - - QString data = QString::fromLatin1( - "App.Material(" - "DiffuseColor=(%1,%2,%3)," - "AmbientColor=(%4,%5,%6)," - "SpecularColor=(%7,%8,%9)," - "EmissiveColor=(%10,%11,%12)," - "Shininess=(%13)," - "Transparency=(%14)," - ")" - ) - .arg(dc.r, 0, 'f', decimals()) - .arg(dc.g, 0, 'f', decimals()) - .arg(dc.b, 0, 'f', decimals()) - .arg(ac.r, 0, 'f', decimals()) - .arg(ac.g, 0, 'f', decimals()) - .arg(ac.b, 0, 'f', decimals()) - .arg(sc.r, 0, 'f', decimals()) - .arg(sc.g, 0, 'f', decimals()) - .arg(sc.b, 0, 'f', decimals()) - .arg(ec.r, 0, 'f', decimals()) - .arg(ec.g, 0, 'f', decimals()) - .arg(ec.b, 0, 'f', decimals()) - .arg(s, 0, 'f', decimals()) - .arg(t, 0, 'f', decimals()) - ; - - setPropertyValue(data); -} - -QWidget* PropertyMaterialItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto cb = new Gui::ColorButton(parent); - cb->setDisabled(isReadOnly()); - QObject::connect(cb, &Gui::ColorButton::changed, method); - return cb; -} - -void PropertyMaterialItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - if (!data.canConvert()) { - return; - } - - auto val = data.value(); - auto cb = qobject_cast(editor); - cb->setColor(val.diffuseColor); -} - -QVariant PropertyMaterialItem::editorData(QWidget *editor) const -{ - auto cb = qobject_cast(editor); - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return {}; - } - - auto val = value.value(); - val.diffuseColor = cb->color(); - return QVariant::fromValue(val); -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyMaterialListItem) - -PropertyMaterialListItem::PropertyMaterialListItem() -{ - const int min = 0; - const int max = 100; - const int steps = 5; - - // This editor gets a list of materials but it only edits the first item. - diffuse = static_cast(PropertyColorItem::create()); - diffuse->setParent(this); - diffuse->setPropertyName(QLatin1String("DiffuseColor")); - this->appendChild(diffuse); - - ambient = static_cast(PropertyColorItem::create()); - ambient->setParent(this); - ambient->setPropertyName(QLatin1String("AmbientColor")); - this->appendChild(ambient); - - specular = static_cast(PropertyColorItem::create()); - specular->setParent(this); - specular->setPropertyName(QLatin1String("SpecularColor")); - this->appendChild(specular); - - emissive = static_cast(PropertyColorItem::create()); - emissive->setParent(this); - emissive->setPropertyName(QLatin1String("EmissiveColor")); - this->appendChild(emissive); - - shininess = static_cast(PropertyIntegerConstraintItem::create()); - shininess->setRange(min, max); - shininess->setStepSize(steps); - shininess->setParent(this); - shininess->setPropertyName(QLatin1String("Shininess")); - this->appendChild(shininess); - - transparency = static_cast(PropertyIntegerConstraintItem::create()); - transparency->setRange(min, max); - transparency->setStepSize(steps); - transparency->setParent(this); - transparency->setPropertyName(QLatin1String("Transparency")); - this->appendChild(transparency); -} - -PropertyMaterialListItem::~PropertyMaterialListItem() = default; - -void PropertyMaterialListItem::propertyBound() -{ -} - -QColor PropertyMaterialListItem::getDiffuseColor() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return {}; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return {}; - } - - if (!list[0].canConvert()) { - return {}; - } - - auto mat = list[0].value(); - return mat.diffuseColor; -} - -void PropertyMaterialListItem::setDiffuseColor(const QColor& color) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return; - } - - if (!list[0].canConvert()) { - return; - } - - auto mat = list[0].value(); - mat.diffuseColor = color; - list[0] = QVariant::fromValue(mat); - setValue(list); -} - -QColor PropertyMaterialListItem::getAmbientColor() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return {}; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return {}; - } - - if (!list[0].canConvert()) { - return {}; - } - - auto mat = list[0].value(); - return mat.ambientColor; -} - -void PropertyMaterialListItem::setAmbientColor(const QColor& color) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return; - } - - if (!list[0].canConvert()) { - return; - } - - auto mat = list[0].value(); - mat.ambientColor = color; - list[0] = QVariant::fromValue(mat); - setValue(list); -} - -QColor PropertyMaterialListItem::getSpecularColor() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return {}; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return {}; - } - - if (!list[0].canConvert()) { - return {}; - } - - auto mat = list[0].value(); - return mat.specularColor; -} - -void PropertyMaterialListItem::setSpecularColor(const QColor& color) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return; - } - - if (!list[0].canConvert()) { - return; - } - - auto mat = list[0].value(); - mat.specularColor = color; - list[0] = QVariant::fromValue(mat); - setValue(list); -} - -QColor PropertyMaterialListItem::getEmissiveColor() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return {}; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return {}; - } - - if (!list[0].canConvert()) { - return {}; - } - - auto mat = list[0].value(); - return mat.emissiveColor; -} - -void PropertyMaterialListItem::setEmissiveColor(const QColor& color) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return; - } - - if (!list[0].canConvert()) { - return; - } - - auto mat = list[0].value(); - mat.emissiveColor = color; - list[0] = QVariant::fromValue(mat); - setValue(list); -} - -int PropertyMaterialListItem::getShininess() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return 0; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return 0; - } - - if (!list[0].canConvert()) { - return 0; - } - - auto mat = list[0].value(); - return toPercent(mat.shininess); -} - -void PropertyMaterialListItem::setShininess(int s) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return; - } - - if (!list[0].canConvert()) { - return; - } - - auto mat = list[0].value(); - mat.shininess = fromPercent(s); - list[0] = QVariant::fromValue(mat); - setValue(list); -} - -int PropertyMaterialListItem::getTransparency() const -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return 0; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return 0; - } - - if (!list[0].canConvert()) { - return 0; - } - - auto mat = list[0].value(); - return toPercent(mat.transparency); -} - -void PropertyMaterialListItem::setTransparency(int t) -{ - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return; - } - - if (!list[0].canConvert()) { - return; - } - - auto mat = list[0].value(); - mat.transparency = fromPercent(t); - list[0] = QVariant::fromValue(mat); - setValue(list); -} - -QVariant PropertyMaterialListItem::decoration(const QVariant& value) const -{ - if (!value.canConvert()) { - return {}; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return {}; - } - - if (!list[0].canConvert()) { - return {}; - } - - // use the diffuse color - auto mat = list[0].value(); - QColor color = mat.diffuseColor; - - int size = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize); - QPixmap p(size, size); - p.fill(color); - - return QVariant(p); -} - -QVariant PropertyMaterialListItem::toString(const QVariant& prop) const -{ - if (!prop.canConvert()) { - return {}; - } - - QVariantList list = prop.toList(); - if (list.isEmpty()) { - return {}; - } - - if (!list[0].canConvert()) { - return {}; - } - - // use the diffuse color - auto mat = list[0].value(); - QColor value = mat.diffuseColor; - QString color = QString::fromLatin1("[%1, %2, %3]") - .arg(value.red()).arg(value.green()).arg(value.blue()); - return {color}; -} - -QVariant PropertyMaterialListItem::toolTip(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const std::vector& values = static_cast(prop)->getValues(); - if (values.empty()) { - return {}; - } - - App::Material value = values.front(); - auto dc = value.diffuseColor.asValue(); - auto ac = value.ambientColor.asValue(); - auto sc = value.specularColor.asValue(); - auto ec = value.emissiveColor.asValue(); - - QString data = QString::fromUtf8( - "Diffuse color: [%1, %2, %3]\n" - "Ambient color: [%4, %5, %6]\n" - "Specular color: [%7, %8, %9]\n" - "Emissive color: [%10, %11, %12]\n" - "Shininess: %13\n" - "Transparency: %14" - ) - .arg(dc.red()).arg(dc.green()).arg(dc.blue()) - .arg(ac.red()).arg(ac.green()).arg(ac.blue()) - .arg(sc.red()).arg(sc.green()).arg(sc.blue()) - .arg(ec.red()).arg(ec.green()).arg(ec.blue()) - .arg(toPercent(value.shininess)) - .arg(toPercent(value.transparency)) - ; - - return {data}; -} - -QVariant PropertyMaterialListItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - const std::vector& value = static_cast(prop)->getValues(); - QVariantList variantList; - - for (const auto & it : value) { - Material mat; - mat.diffuseColor = it.diffuseColor.asValue(); - mat.ambientColor = it.ambientColor.asValue(); - mat.specularColor = it.specularColor.asValue(); - mat.emissiveColor = it.emissiveColor.asValue(); - mat.shininess = it.shininess; - mat.transparency = it.transparency; - - variantList << QVariant::fromValue(mat); - } - - return variantList; -} - -void PropertyMaterialListItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return; - } - - // Setting an appearance using the property editor resets the - // per-face appearance - list = list.mid(0, 1); - - QString data; - QTextStream str(&data); - str << "("; - - auto mat = list[0].value(); - App::Color dc; dc.setValue(mat.diffuseColor); - App::Color ac; ac.setValue(mat.ambientColor); - App::Color sc; sc.setValue(mat.specularColor); - App::Color ec; ec.setValue(mat.emissiveColor); - float s = mat.shininess; - float t = mat.transparency; - - QString item = QString::fromLatin1( - "App.Material(" - "DiffuseColor=(%1,%2,%3)," - "AmbientColor=(%4,%5,%6)," - "SpecularColor=(%7,%8,%9)," - "EmissiveColor=(%10,%11,%12)," - "Shininess=(%13)," - "Transparency=(%14)," - ")" - ) - .arg(dc.r, 0, 'f', decimals()) - .arg(dc.g, 0, 'f', decimals()) - .arg(dc.b, 0, 'f', decimals()) - .arg(ac.r, 0, 'f', decimals()) - .arg(ac.g, 0, 'f', decimals()) - .arg(ac.b, 0, 'f', decimals()) - .arg(sc.r, 0, 'f', decimals()) - .arg(sc.g, 0, 'f', decimals()) - .arg(sc.b, 0, 'f', decimals()) - .arg(ec.r, 0, 'f', decimals()) - .arg(ec.g, 0, 'f', decimals()) - .arg(ec.b, 0, 'f', decimals()) - .arg(s, 0, 'f', decimals()) - .arg(t, 0, 'f', decimals()) - ; - str << item << ")"; - - setPropertyValue(data); -} - -QWidget* PropertyMaterialListItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto cb = new Gui::ColorButton(parent); - cb->setDisabled(isReadOnly()); - QObject::connect(cb, &Gui::ColorButton::changed, method); - return cb; -} - -void PropertyMaterialListItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - if (!data.canConvert()) { - return; - } - - QVariantList list = data.toList(); - if (list.isEmpty()) { - return; - } - - if (!list[0].canConvert()) { - return; - } - - // use the diffuse color - auto mat = list[0].value(); - QColor color = mat.diffuseColor; - - auto cb = qobject_cast(editor); - cb->setColor(color); -} - -QVariant PropertyMaterialListItem::editorData(QWidget *editor) const -{ - auto cb = qobject_cast(editor); - QVariant value = data(1, Qt::EditRole); - if (!value.canConvert()) { - return {}; - } - - QVariantList list = value.toList(); - if (list.isEmpty()) { - return {}; - } - - if (!list[0].canConvert()) { - return {}; - } - - // use the diffuse color - auto mat = list[0].value(); - mat.diffuseColor = cb->color(); - list[0] = QVariant::fromValue(mat); - - return list; -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFileItem) - -PropertyFileItem::PropertyFileItem() = default; - -QVariant PropertyFileItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - std::string value = static_cast(prop)->getValue(); - return {QString::fromUtf8(value.c_str())}; -} - -void PropertyFileItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - QString val = value.toString(); - QString data = QString::fromLatin1("\"%1\"").arg(val); - setPropertyValue(data); -} - -QVariant PropertyFileItem::toolTip(const App::Property* prop) const -{ - return value(prop); -} - -QWidget* PropertyFileItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto fc = new Gui::FileChooser(parent); - fc->setAutoFillBackground(true); - fc->setDisabled(isReadOnly()); - QObject::connect(fc, &Gui::FileChooser::fileNameSelected, method); - return fc; -} - -void PropertyFileItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - const App::Property* prop = getFirstProperty(); - if (const auto propFile = dynamic_cast(prop)) { - std::string filter = propFile->getFilter(); - auto fc = qobject_cast(editor); - if (!filter.empty()) { - fc->setFilter(Base::Tools::fromStdString(filter)); - } - fc->setFileName(data.toString()); - } -} - -QVariant PropertyFileItem::editorData(QWidget *editor) const -{ - auto fc = qobject_cast(editor); - return {fc->fileName()}; -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyPathItem) - -PropertyPathItem::PropertyPathItem() = default; - -QVariant PropertyPathItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - std::string value = static_cast(prop)->getValue().string(); - return {QString::fromUtf8(value.c_str())}; -} - -void PropertyPathItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - - QString val = value.toString(); - QString data = QString::fromLatin1("\"%1\"").arg(val); - setPropertyValue(data); -} - -QVariant PropertyPathItem::toolTip(const App::Property* prop) const -{ - return value(prop); -} - -QWidget* PropertyPathItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto fc = new Gui::FileChooser(parent); - fc->setMode(FileChooser::Directory); - fc->setAutoFillBackground(true); - fc->setDisabled(isReadOnly()); - QObject::connect(fc, &Gui::FileChooser::fileNameSelected, method); - return fc; -} - -void PropertyPathItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto fc = qobject_cast(editor); - fc->setFileName(data.toString()); -} - -QVariant PropertyPathItem::editorData(QWidget *editor) const -{ - auto fc = qobject_cast(editor); - return {fc->fileName()}; -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyTransientFileItem) - -PropertyTransientFileItem::PropertyTransientFileItem() = default; - -QVariant PropertyTransientFileItem::value(const App::Property* prop) const -{ - assert(prop && prop->isDerivedFrom()); - - std::string value = static_cast(prop)->getValue(); - return {QString::fromUtf8(value.c_str())}; -} - -void PropertyTransientFileItem::setValue(const QVariant& value) -{ - if (hasExpression() || !value.canConvert()) { - return; - } - - QString val = value.toString(); - QString data = QString::fromLatin1("\"%1\"").arg(val); - setPropertyValue(data); -} - -QVariant PropertyTransientFileItem::toolTip(const App::Property* prop) const -{ - return value(prop); -} - -QWidget* PropertyTransientFileItem::createEditor(QWidget* parent, const std::function& method) const -{ - auto fc = new Gui::FileChooser(parent); - fc->setAutoFillBackground(true); - fc->setDisabled(isReadOnly()); - QObject::connect(fc, &Gui::FileChooser::fileNameSelected, method); - return fc; -} - -void PropertyTransientFileItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - auto fc = qobject_cast(editor); - fc->setFileName(data.toString()); - - const auto prop = dynamic_cast - (getFirstProperty()); - - if (prop) { - std::string filter = prop->getFilter(); - if (!filter.empty()) { - fc->setFilter(QString::fromStdString(filter)); - } - } -} - -QVariant PropertyTransientFileItem::editorData(QWidget *editor) const -{ - auto fc = qobject_cast(editor); - return {fc->fileName()}; -} - -// --------------------------------------------------------------- - -LinkSelection::LinkSelection(App::SubObjectT link) - : link(std::move(link)) -{ -} - -LinkSelection::~LinkSelection() = default; - -void LinkSelection::select() -{ - auto sobj = link.getSubObject(); - if(!sobj) { - QMessageBox::critical(getMainWindow(), tr("Error"), tr("Object not found")); - return; - } - Gui::Selection().selStackPush(); - Gui::Selection().clearSelection(); - Gui::Selection().addSelection(link.getDocumentName().c_str(), - link.getObjectName().c_str(), - link.getSubName().c_str()); - this->deleteLater(); -} - -// --------------------------------------------------------------- - -LinkLabel::LinkLabel (QWidget * parent, const App::Property *prop) - : QWidget(parent), objProp(prop), dlg(nullptr) -{ - auto layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(1); - - label = new QLabel(this); - label->setAutoFillBackground(true); - label->setTextFormat(Qt::RichText); - // Below is necessary for the hytperlink to be clickable without losing focus - label->setTextInteractionFlags(Qt::TextBrowserInteraction); - layout->addWidget(label); - - editButton = new QPushButton(QLatin1String("..."), this); -#if defined (Q_OS_MACOS) - editButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // layout size from QMacStyle was not correct -#endif - editButton->setToolTip(tr("Change the linked object")); - layout->addWidget(editButton); - - this->setFocusPolicy(Qt::StrongFocus); - this->setFocusProxy(label); - - // setLayout(layout); - - connect(label, &QLabel::linkActivated, - this, &LinkLabel::onLinkActivated); - connect(editButton, &QPushButton::clicked, - this, &LinkLabel::onEditClicked); -} - -LinkLabel::~LinkLabel() = default; - -void LinkLabel::updatePropertyLink() -{ - QString text; - auto owner = objProp.getObject(); - auto prop = Base::freecad_dynamic_cast(objProp.getProperty()); - - link = QVariant(); - - if(owner && prop) { - auto links = DlgPropertyLink::getLinksFromProperty(prop); - if(links.size() == 1) { - auto &sobj = links.front(); - link = QVariant::fromValue(sobj); - QString linkcolor = QApplication::palette().color(QPalette::Link).name(); - text = QString::fromLatin1( - "" - "

" - "%5" - "

" - ) - .arg(QLatin1String(sobj.getDocumentName().c_str()), - QLatin1String(sobj.getObjectName().c_str()), - QString::fromUtf8(sobj.getSubName().c_str()), - linkcolor, - DlgPropertyLink::formatObject( - owner->getDocument(), sobj.getObject(), sobj.getSubName().c_str())); - } else if (!links.empty()) { - text = DlgPropertyLink::formatLinks(owner->getDocument(), links); - } - } - label->setText(text); -} - -QVariant LinkLabel::propertyLink() const -{ - return link; -} - -void LinkLabel::onLinkActivated (const QString& s) -{ - Q_UNUSED(s); - auto select = new LinkSelection(qvariant_cast(link)); - QTimer::singleShot(50, select, &LinkSelection::select); // NOLINT -} - -void LinkLabel::onEditClicked () -{ - if (!dlg) { - dlg = new DlgPropertyLink(this); - dlg->init(objProp,true); - connect(dlg, &DlgPropertyLink::accepted, this, &LinkLabel::onLinkChanged); - } - else { - dlg->init(objProp,false); - } - - dlg->show(); -} - -void LinkLabel::onLinkChanged() { - if(dlg) { - auto links = dlg->currentLinks(); - if(links != dlg->originalLinks()) { - link = QVariant::fromValue(links); - Q_EMIT linkChanged(link); - updatePropertyLink(); - } - } -} - -void LinkLabel::resizeEvent(QResizeEvent* e) -{ - editButton->setFixedWidth(e->size().height()); -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyLinkItem) - -PropertyLinkItem::PropertyLinkItem() = default; - -QVariant PropertyLinkItem::toString(const QVariant& prop) const -{ - QString res; - if(!propertyItems.empty()) { - App::DocumentObjectT owner(propertyItems[0]); - res = DlgPropertyLink::formatLinks(owner.getDocument(), - qvariant_cast >(prop)); - } - return res; -} - -QVariant PropertyLinkItem::data(int column, int role) const { - if(!propertyItems.empty() && column == 1 - && (role == Qt::ForegroundRole || role == Qt::ToolTipRole)) - { - if (auto propLink = dynamic_cast(propertyItems[0])) { - if (role==Qt::ForegroundRole && propLink->checkRestore() > 1) { - return QVariant::fromValue(QColor(0xff, 0, 0)); // NOLINT - } - if (role == Qt::ToolTipRole) { - if (auto xlink = dynamic_cast(propertyItems[0])) { - const char *filePath = xlink->getFilePath(); - if (filePath && filePath[0]) { - return QVariant::fromValue(QString::fromUtf8(filePath)); - } - } - } - } - } - - return PropertyItem::data(column,role); -} - -QVariant PropertyLinkItem::value(const App::Property* prop) const -{ - auto propLink = Base::freecad_dynamic_cast(prop); - if (!propLink) { - return {}; - } - - auto links = DlgPropertyLink::getLinksFromProperty(propLink); - if (links.empty()) { - return {}; - } - - return QVariant::fromValue(links); -} - -void PropertyLinkItem::setValue(const QVariant& value) -{ - auto links = qvariant_cast >(value); - setPropertyValue(DlgPropertyLink::linksToPython(links)); -} - -QWidget* PropertyLinkItem::createEditor(QWidget* parent, const std::function& method) const -{ - if (propertyItems.empty()) { - return nullptr; - } - auto ll = new LinkLabel(parent, propertyItems.front()); - ll->setAutoFillBackground(true); - ll->setDisabled(isReadOnly()); - QObject::connect(ll, &LinkLabel::linkChanged, method); - return ll; -} - -void PropertyLinkItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - (void)data; - auto ll = dynamic_cast(editor); - return ll->updatePropertyLink(); -} - -QVariant PropertyLinkItem::editorData(QWidget *editor) const -{ - auto ll = dynamic_cast(editor); - return ll->propertyLink(); -} - -// -------------------------------------------------------------------- - -PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyLinkListItem) - -PropertyLinkListItem::PropertyLinkListItem() = default; - -PropertyItemEditorFactory::PropertyItemEditorFactory() = default; - -PropertyItemEditorFactory::~PropertyItemEditorFactory() = default; - -QWidget * PropertyItemEditorFactory::createEditor (int /*type*/, QWidget * /*parent*/) const -{ - // do not allow to create any editor widgets because we do that in subclasses of PropertyItem - return nullptr; -} - -QByteArray PropertyItemEditorFactory::valuePropertyName (int /*type*/) const -{ - // do not allow to set properties because we do that in subclasses of PropertyItem - return ""; -} -// NOLINTEND(cppcoreguidelines-pro-*,cppcoreguidelines-prefer-member-initializer) - -#include "moc_PropertyItem.cpp" - +/*************************************************************************** + * Copyright (c) 2004 Werner Mayer * + * * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "PropertyItem.h" +#include "PropertyView.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NOLINTBEGIN(cppcoreguidelines-pro-*,cppcoreguidelines-prefer-member-initializer) +using namespace Gui::PropertyEditor; +using namespace Gui::Dialog; + +namespace +{ +constexpr const int lowPrec = 2; +constexpr const int highPrec = 16; + +int toPercent(float value) +{ + return static_cast(100 * value); // NOLINT +} + +float fromPercent(int value) +{ + return static_cast(value) / 100.0F; // NOLINT +} + +} // namespace + +PropertyItemFactory& PropertyItemFactory::instance() +{ + static PropertyItemFactory inst; + return inst; +} + +PropertyItem* PropertyItemFactory::createPropertyItem(const char* sName) const +{ + return static_cast(Produce(sName)); +} + +// ---------------------------------------------------- + +QVariant PropertyItemAttorney::toString(PropertyItem* item, const QVariant& value) +{ + return item->toString(value); +} + +// ---------------------------------------------------- + +Q_DECLARE_METATYPE(Py::Object) // NOLINT + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyItem) + +PropertyItem::PropertyItem() + : parentItem(nullptr) + , readonly(false) + , precision(Base::UnitsApi::getDecimals()) + , linked(false) + , expanded(false) +{ + setAutoApply(true); +} + +PropertyItem::~PropertyItem() +{ + qDeleteAll(childItems); +} + +void PropertyItem::initialize() +{} + +void PropertyItem::reset() +{ + qDeleteAll(childItems); + childItems.clear(); +} + +void PropertyItem::onChange() +{ + if (hasExpression()) { + for (auto child : std::as_const(childItems)) { + if (child && child->hasExpression()) { + child->setExpression(std::shared_ptr()); + } + } + for (auto item = parentItem; item; item = item->parentItem) { + if (item->hasExpression()) { + item->setExpression(std::shared_ptr()); + } + } + } +} + +bool PropertyItem::hasAnyExpression() const +{ + if (ExpressionBinding::hasExpression()) { + return true; + } + if (parentItem) { + return parentItem->hasExpression(); + } + return false; +} + +void PropertyItem::setPropertyData(const std::vector& items) +{ + // if we have a single property we can bind it for expression handling + if (items.size() == 1) { + const App::Property& prop = *items.front(); + + try { + // Check for 'DocumentObject' as parent because otherwise 'ObjectIdentifier' raises an + // exception + auto* docObj = Base::freecad_dynamic_cast(prop.getContainer()); + if (docObj && !docObj->isReadOnly(&prop)) { + App::ObjectIdentifier id(prop); + std::vector paths; + prop.getPaths(paths); + + // there may be no paths available in this property (for example an empty constraint + // list) + if (id.getProperty() && !paths.empty()) { + bind(id); + } + } + } + // it may happen that setting properties is not possible + catch (...) { + } + } + + propertyItems = items; + updateData(); + this->initialize(); +} + +void PropertyItem::updateData() +{ + bool ro = true; + for (auto it : propertyItems) { + App::PropertyContainer* parent = it->getContainer(); + if (parent) { + ro &= (parent->isReadOnly(it) || it->testStatus(App::Property::ReadOnly)); + } + } + this->setReadOnly(ro); +} + +const std::vector& PropertyItem::getPropertyData() const +{ + return propertyItems; +} + +bool PropertyItem::hasProperty(const App::Property* prop) const +{ + auto it = std::find(propertyItems.begin(), propertyItems.end(), prop); + return (it != propertyItems.end()); +} + +void PropertyItem::assignProperty(const App::Property* prop) +{ + Q_UNUSED(prop) +} + +bool PropertyItem::removeProperty(const App::Property* prop) +{ + auto it = std::find(propertyItems.begin(), propertyItems.end(), prop); + if (it != propertyItems.end()) { + propertyItems.erase(it); + } + + return propertyItems.empty(); +} + +App::Property* PropertyItem::getFirstProperty() +{ + if (propertyItems.empty()) { + return nullptr; + } + return propertyItems.front(); +} + +const App::Property* PropertyItem::getFirstProperty() const +{ + if (propertyItems.empty()) { + return nullptr; + } + return propertyItems.front(); +} + +void PropertyItem::setParent(PropertyItem* parent) +{ + parentItem = parent; +} + +PropertyItem* PropertyItem::parent() const +{ + return parentItem; +} + +void PropertyItem::appendChild(PropertyItem* item) +{ + childItems.append(item); +} + +void PropertyItem::insertChild(int index, PropertyItem* child) +{ + childItems.insert(index, child); +} + +/*! + * \brief PropertyItem::removeChildren + * Deletes the children in the range of [from, to] + */ +void PropertyItem::removeChildren(int from, int to) +{ + int count = to - from + 1; + for (int i = 0; i < count; i++) { + PropertyItem* child = childItems.takeAt(from); + delete child; + } +} + +void PropertyItem::moveChild(int from, int to) +{ + childItems.move(from, to); +} + +/*! + * \brief PropertyItem::takeChild + * Removes the child at index row but doesn't delete it + */ +PropertyItem* PropertyItem::takeChild(int row) +{ + PropertyItem* child = childItems.takeAt(row); + child->setParent(nullptr); + return child; +} + +PropertyItem* PropertyItem::child(int row) +{ + return childItems.value(row); +} + +int PropertyItem::childCount() const +{ + return childItems.count(); +} + +int PropertyItem::columnCount() const +{ + return 2; +} + +void PropertyItem::setReadOnly(bool ro) +{ + readonly = ro; + for (auto it : std::as_const(childItems)) { + it->setReadOnly(ro); + } +} + +bool PropertyItem::isReadOnly() const +{ + return readonly; +} + +void PropertyItem::setLinked(bool value) +{ + linked = value; + for (auto it : std::as_const(childItems)) { + it->setLinked(value); + } +} + +bool PropertyItem::isLinked() const +{ + return linked; +} + +void PropertyItem::setExpanded(bool enable) +{ + expanded = enable; +} + +bool PropertyItem::isExpanded() const +{ + return expanded; +} + +bool PropertyItem::testStatus(App::Property::Status pos) const +{ + std::vector::const_iterator it; + for (it = propertyItems.begin(); it != propertyItems.end(); ++it) { + if ((*it)->testStatus(pos)) { + return true; + } + } + return false; +} + +void PropertyItem::setDecimals(int prec) +{ + precision = prec; +} + +int PropertyItem::decimals() const +{ + return precision; +} + +QVariant PropertyItem::displayName() const +{ + return {displayText}; +} + +QVariant PropertyItem::toolTip(const App::Property* prop) const +{ + QString str = QApplication::translate("App::Property", prop->getDocumentation()); + return {str}; +} + +QVariant PropertyItem::decoration(const QVariant& value) const +{ + Q_UNUSED(value) + return {}; +} + +QString PropertyItem::asNone(const Py::Object& pyobj) const +{ + Q_UNUSED(pyobj) + return QString::fromUtf8(""); +} + +QString PropertyItem::asString(const Py::Object& pyobj) const +{ + return QString::fromStdString(pyobj.as_string()); +} + +QString PropertyItem::asSequence(const Py::Object& pyobj) const +{ + std::ostringstream ss; + ss << '['; + Py::Sequence seq(pyobj); + bool first = true; + Py_ssize_t i = 0; + for (i = 0; i < 2 && i < seq.size(); ++i) { + if (first) { + first = false; + } + else { + ss << ", "; + } + ss << Py::Object(seq[i]).as_string(); + } + + if (i < seq.size()) { + ss << "..."; + } + ss << ']'; + return QString::fromUtf8(ss.str().c_str()); +} + +QString PropertyItem::asMapping(const Py::Object& pyobj) const +{ + std::ostringstream ss; + ss << '{'; + Py::Mapping map(pyobj); + bool first = true; + auto it = map.begin(); + for (int i = 0; i < 2 && it != map.end(); ++it, ++i) { + if (first) { + first = false; + } + else { + ss << ", "; + } + const auto& v = *it; + ss << Py::Object(v.first).as_string() << ':' << Py::Object(v.second).as_string(); + } + + if (it != map.end()) { + ss << "..."; + } + ss << '}'; + return QString::fromUtf8(ss.str().c_str()); +} + +QString PropertyItem::toString(const Py::Object& pyobj) const +{ + if (pyobj.isNone()) { + return asNone(pyobj); + } + if (pyobj.isSequence()) { + return asSequence(pyobj); + } + if (pyobj.isMapping()) { + return asMapping(pyobj); + } + + return asString(pyobj); +} + +QVariant PropertyItem::toString(const QVariant& prop) const +{ + if (prop != QVariant() || propertyItems.size() != 1) { + return prop; + } + + std::ostringstream ss; + Base::PyGILStateLocker lock; + try { + Py::Object pyobj(propertyItems[0]->getPyObject(), true); + return toString(pyobj); + } + catch (Py::Exception&) { + Base::PyException e; + ss.str(""); + ss << "ERR: " << e.what(); + } + catch (Base::Exception& e) { + ss.str(""); + ss << "ERR: " << e.what(); + } + catch (std::exception& e) { + ss.str(""); + ss << "ERR: " << e.what(); + } + catch (...) { + ss.str(""); + ss << "ERR!"; + } + + return {QString::fromUtf8(ss.str().c_str())}; +} + +QVariant PropertyItem::value(const App::Property* /*prop*/) const +{ + return {}; +} + +void PropertyItem::setValue(const QVariant& /*value*/) +{} + +QWidget* PropertyItem::createEditor(QWidget* /*parent*/, + const std::function& /*method*/) const +{ + return nullptr; +} + +void PropertyItem::setEditorData(QWidget* /*editor*/, const QVariant& /*data*/) const +{} + +QVariant PropertyItem::editorData(QWidget* /*editor*/) const +{ + return {}; +} + +QWidget* PropertyItem::createExpressionEditor(QWidget* parent, + const std::function& method) const +{ + if (!isBound()) { + return nullptr; + } + auto le = new ExpLineEdit(parent, true); + le->setFrame(false); + le->setReadOnly(true); + QObject::connect(le, &ExpLineEdit::textChanged, method); + le->bind(getPath()); + le->setAutoApply(autoApply()); + return le; +} + +void PropertyItem::setExpressionEditorData(QWidget* editor, const QVariant& data) const +{ + auto le = qobject_cast(editor); + if (le) { + le->setText(data.toString()); + } +} + +QVariant PropertyItem::expressionEditorData(QWidget* editor) const +{ + auto le = qobject_cast(editor); + if (le) { + return {le->text()}; + } + return {}; +} + +PropertyEditorWidget* PropertyItem::createPropertyEditorWidget(QWidget* parent) const +{ + auto editor = new PropertyEditorWidget(parent); + connect(editor, &PropertyEditorWidget::buttonClick, this, [this]() { + const auto& props = this->getPropertyData(); + if (!props.empty() && props[0]->getName() && props[0]->testStatus(App::Property::UserEdit) + && props[0]->getContainer()) { + props[0]->getContainer()->editProperty(props[0]->getName()); + } + }); + return editor; +} + +QString PropertyItem::propertyName() const +{ + if (propName.isEmpty()) { + return QLatin1String(QT_TRANSLATE_NOOP("App::Property", "")); + } + return propName; +} + +void PropertyItem::setPropertyName(const QString& name, const QString& realName) +{ + if (realName.size()) { + propName = realName; + } + else { + propName = name; + } + + setObjectName(propName); + + QString display; + bool upper = false; + for (auto&& i : name) { + if (i.isUpper() && !display.isEmpty()) { + // if there is a sequence of capital letters do not insert spaces + if (!upper) { + QChar last = display.at(display.length() - 1); + if (!last.isSpace()) { + display += QLatin1String(" "); + } + } + } + upper = i.isUpper(); + display += i; + } + + propName = display; + + QString str = QApplication::translate("App::Property", propName.toUtf8()); + displayText = str; +} + +void PropertyItem::setPropertyValue(const QString& value) +{ + // Construct command for property assignment in one go, in case of any + // intermediate changes caused by property change that may potentially + // invalidate the current property array. + std::ostringstream ss; + for (auto prop : propertyItems) { + App::PropertyContainer* parent = prop->getContainer(); + if (!parent || parent->isReadOnly(prop) || prop->testStatus(App::Property::ReadOnly)) { + continue; + } + + if (parent->isDerivedFrom(App::Document::getClassTypeId())) { + auto doc = static_cast(parent); + ss << "FreeCAD.getDocument('" << doc->getName() << "')."; + } + else if (parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) { + auto obj = static_cast(parent); + App::Document* doc = obj->getDocument(); + ss << "FreeCAD.getDocument('" << doc->getName() << "').getObject('" + << obj->getNameInDocument() << "')."; + } + else if (parent->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { + App::DocumentObject* obj = + static_cast(parent)->getObject(); + App::Document* doc = obj->getDocument(); + ss << "FreeCADGui.getDocument('" << doc->getName() << "').getObject('" + << obj->getNameInDocument() << "')."; + } + else { + continue; + } + + ss << parent->getPropertyPrefix() << prop->getName() << " = " << value.toUtf8().constData() + << '\n'; + } + + std::string cmd = ss.str(); + if (cmd.empty()) { + return; + } + + try { + Gui::Command::runCommand(Gui::Command::App, cmd.c_str()); + } + catch (Base::PyException& e) { + e.ReportException(); + Base::Console().Error("Stack Trace: %s\n", e.getStackTrace().c_str()); + } + catch (Base::Exception& e) { + e.ReportException(); + } + catch (...) { + Base::Console().Error("Unknown C++ exception in PropertyItem::setPropertyValue thrown\n"); + } +} + +QVariant PropertyItem::dataProperty(int role) const +{ + if (role == Qt::ForegroundRole && linked) { + return QVariant::fromValue(QColor(0x20, 0xaa, 0x20)); // NOLINT + } + + if (role == Qt::BackgroundRole || role == Qt::ForegroundRole) { + if (PropertyView::showAll() && propertyItems.size() == 1 + && propertyItems.front()->testStatus(App::Property::PropDynamic) + && !propertyItems.front()->testStatus(App::Property::LockDynamic)) { + return role == Qt::BackgroundRole + ? QVariant::fromValue(QColor(0xFF, 0xFF, 0x99)) // NOLINT + : QVariant::fromValue(QColor(0, 0, 0)); + } + return {}; + } + if (role == Qt::DisplayRole) { + return displayName(); + } + // no properties set + if (propertyItems.empty()) { + return {}; + } + if (role == Qt::ToolTipRole) { + QString type = + QString::fromLatin1("Type: %1\nName: %2") + .arg(QString::fromLatin1(propertyItems[0]->getTypeId().getName()), objectName()); + + QString doc = PropertyItem::toolTip(propertyItems[0]).toString(); + if (doc.isEmpty()) { + doc = toolTip(propertyItems[0]).toString(); + } + if (doc.size()) { + return type + QLatin1String("\n\n") + doc; + } + return type; + } + + return {}; +} + +QVariant PropertyItem::dataValue(int role) const +{ + // no properties set + if (propertyItems.empty()) { + PropertyItem* parent = this->parent(); + if (!parent || !parent->parent()) { + return {}; + } + if (role == Qt::EditRole) { + return parent->property(qPrintable(objectName())); + } + if (role == Qt::DecorationRole) { + QVariant val = parent->property(qPrintable(objectName())); + return decoration(val); + } + if (role == Qt::DisplayRole) { + QVariant val = parent->property(qPrintable(objectName())); + return toString(val); + } + if (role == Qt::ForegroundRole) { + if (hasExpression()) { + return QVariant::fromValue(QApplication::palette().color(QPalette::Link)); + } + return {}; + } + + return {}; + } + if (role == Qt::EditRole) { + return value(propertyItems[0]); + } + if (role == Qt::DecorationRole) { + return decoration(value(propertyItems[0])); + } + if (role == Qt::DisplayRole) { + return toString(value(propertyItems[0])); + } + if (role == Qt::ToolTipRole) { + return toolTip(propertyItems[0]); + } + if (role == Qt::ForegroundRole) { + if (hasExpression()) { + return QVariant::fromValue(QApplication::palette().color(QPalette::Link)); + } + return {}; + } + + return {}; +} + +QVariant PropertyItem::data(int column, int role) const +{ + // property name + if (column == 0) { + return dataProperty(role); + } + + return dataValue(role); +} + +bool PropertyItem::setData(const QVariant& value) +{ + // This is the basic mechanism to set the value to + // a property and if no property is set for this item + // it delegates it to its parent which sets then the + // property or delegates again to its parent... + if (propertyItems.empty()) { + PropertyItem* parent = this->parent(); + if (!parent || !parent->parent() || hasAnyExpression()) { + return false; + } + + parent->setProperty(qPrintable(objectName()), value); + return true; + } + + setValue(value); + return true; +} + +Qt::ItemFlags PropertyItem::flags(int column) const +{ + Qt::ItemFlags basicFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + if (column == 1 && !isReadOnly()) { + return basicFlags | Qt::ItemIsEditable; + } + + return basicFlags; +} + +int PropertyItem::row() const +{ + if (parentItem) { + return parentItem->childItems.indexOf(const_cast(this)); // NOLINT + } + + return 0; +} + +void PropertyItem::bind(const App::ObjectIdentifier& _path) +{ + Gui::ExpressionBinding::bind(_path); + propertyBound(); +} + +void PropertyItem::bind(const App::Property& prop) +{ + Gui::ExpressionBinding::bind(prop); + propertyBound(); +} + +QString PropertyItem::expressionAsString() const +{ + if (hasExpression()) { + try { + std::unique_ptr result(getExpression()->eval()); + return QString::fromStdString(result->toString()); + } + catch (const Base::Exception& e) { + e.ReportException(); + } + } + + return {}; +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyStringItem) + +PropertyStringItem::PropertyStringItem() = default; + +QVariant PropertyStringItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + std::string value = static_cast(prop)->getValue(); + return {QString::fromUtf8(value.c_str())}; +} + +void PropertyStringItem::setValue(const QVariant& value) +{ + if (!hasExpression()) { + if (!value.canConvert()) { + return; + } + QString val = value.toString(); + val = QString::fromUtf8(Base::InterpreterSingleton::strToPython(val.toUtf8()).c_str()); + QString data = QString::fromLatin1("\"%1\"").arg(val); + setPropertyValue(data); + } +} + +QWidget* PropertyStringItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto le = new ExpLineEdit(parent); + le->setFrame(false); + le->setReadOnly(isReadOnly()); + QObject::connect(le, &ExpLineEdit::textChanged, method); + if (isBound()) { + le->bind(getPath()); + le->setAutoApply(autoApply()); + } + + return le; +} + +void PropertyStringItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto le = qobject_cast(editor); + le->setText(data.toString()); +} + +QVariant PropertyStringItem::editorData(QWidget* editor) const +{ + auto le = qobject_cast(editor); + return {le->text()}; +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFontItem) + +PropertyFontItem::PropertyFontItem() = default; + +QVariant PropertyFontItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + std::string value = static_cast(prop)->getValue(); + return {QString::fromUtf8(value.c_str())}; +} + +void PropertyFontItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + + QString val = value.toString(); + QString data = QString::fromLatin1("\"%1\"").arg(val); + setPropertyValue(data); +} + +QWidget* PropertyFontItem::createEditor(QWidget* parent, const std::function& method) const +{ + auto cb = new QComboBox(parent); + cb->setFrame(false); + cb->setDisabled(isReadOnly()); +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + QObject::connect(cb, qOverload(&QComboBox::activated), method); +#else + QObject::connect(cb, &QComboBox::textActivated, method); +#endif + return cb; +} + +void PropertyFontItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto cb = qobject_cast(editor); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QStringList familyNames = QFontDatabase().families(QFontDatabase::Any); +#else + QStringList familyNames = QFontDatabase::families(QFontDatabase::Any); +#endif + cb->addItems(familyNames); + int index = familyNames.indexOf(data.toString()); + cb->setCurrentIndex(index); +} + +QVariant PropertyFontItem::editorData(QWidget* editor) const +{ + auto cb = qobject_cast(editor); + return {cb->currentText()}; +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertySeparatorItem) + +QWidget* PropertySeparatorItem::createEditor(QWidget* parent, + const std::function& method) const +{ + Q_UNUSED(parent); + Q_UNUSED(method); + return nullptr; +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyIntegerItem) + +PropertyIntegerItem::PropertyIntegerItem() = default; + +QVariant PropertyIntegerItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + int value = (int)static_cast(prop)->getValue(); + return {value}; +} + +void PropertyIntegerItem::setValue(const QVariant& value) +{ + // if the item has an expression it issues the python code + if (!hasExpression()) { + if (!value.canConvert()) { + return; + } + int val = value.toInt(); + QString data = QString::fromLatin1("%1").arg(val); + setPropertyValue(data); + } +} + +QWidget* PropertyIntegerItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto sb = new Gui::IntSpinBox(parent); + sb->setFrame(false); + sb->setReadOnly(isReadOnly()); + QObject::connect(sb, qOverload(&Gui::IntSpinBox::valueChanged), method); + + if (isBound()) { + sb->bind(getPath()); + sb->setAutoApply(autoApply()); + } + + return sb; +} + +void PropertyIntegerItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto sb = qobject_cast(editor); + sb->setRange(INT_MIN, INT_MAX); + sb->setValue(data.toInt()); +} + +QVariant PropertyIntegerItem::editorData(QWidget* editor) const +{ + auto sb = qobject_cast(editor); + return {sb->value()}; +} + +QVariant PropertyIntegerItem::toString(const QVariant& v) const +{ + QString string(PropertyItem::toString(v).toString()); + + if (hasExpression()) { + string += + QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); + } + + return {string}; +} + + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyIntegerConstraintItem) + +PropertyIntegerConstraintItem::PropertyIntegerConstraintItem() = default; + +QVariant PropertyIntegerConstraintItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + int value = (int)static_cast(prop)->getValue(); + return {value}; +} + +void PropertyIntegerConstraintItem::setValue(const QVariant& value) +{ + // if the item has an expression it issues the python code + if (!hasExpression()) { + if (!value.canConvert()) { + return; + } + int val = value.toInt(); + QString data = QString::fromLatin1("%1").arg(val); + setPropertyValue(data); + } +} + +QWidget* PropertyIntegerConstraintItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto sb = new Gui::IntSpinBox(parent); + sb->setFrame(false); + sb->setReadOnly(isReadOnly()); + QObject::connect(sb, qOverload(&Gui::IntSpinBox::valueChanged), method); + + if (isBound()) { + sb->bind(getPath()); + sb->setAutoApply(autoApply()); + } + + return sb; +} + +void PropertyIntegerConstraintItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + const auto prop = static_cast(getFirstProperty()); + + const App::PropertyIntegerConstraint::Constraints* c = nullptr; + if (prop) { + c = prop->getConstraints(); + } + + auto sb = qobject_cast(editor); + if (c) { + sb->setMinimum(int(c->LowerBound)); + sb->setMaximum(int(c->UpperBound)); + sb->setSingleStep(int(c->StepSize)); + } + else { + sb->setMinimum(min); + sb->setMaximum(max); + sb->setSingleStep(steps); + } + + sb->setValue(data.toInt()); +} + +QVariant PropertyIntegerConstraintItem::editorData(QWidget* editor) const +{ + auto sb = qobject_cast(editor); + return {sb->value()}; +} + +QVariant PropertyIntegerConstraintItem::toString(const QVariant& v) const +{ + QString string(PropertyItem::toString(v).toString()); + + if (hasExpression()) { + string += + QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); + } + + return {string}; +} + + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFloatItem) + +PropertyFloatItem::PropertyFloatItem() = default; + +QVariant PropertyFloatItem::toString(const QVariant& prop) const +{ + double value = prop.toDouble(); + QString data = QLocale().toString(value, 'f', decimals()); + + if (hasExpression()) { + data += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); + } + + return {data}; +} + +QVariant PropertyFloatItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + double value = static_cast(prop)->getValue(); + return {value}; +} + +void PropertyFloatItem::setValue(const QVariant& value) +{ + // if the item has an expression it issues the python code + if (!hasExpression()) { + if (!value.canConvert()) { + return; + } + double val = value.toDouble(); + QString data = QString::fromLatin1("%1").arg(val, 0, 'g', highPrec); + setPropertyValue(data); + } +} + +QWidget* PropertyFloatItem::createEditor(QWidget* parent, const std::function& method) const +{ + auto sb = new Gui::DoubleSpinBox(parent); + sb->setFrame(false); + sb->setDecimals(decimals()); + sb->setReadOnly(isReadOnly()); + QObject::connect(sb, qOverload(&Gui::DoubleSpinBox::valueChanged), method); + + if (isBound()) { + sb->bind(getPath()); + sb->setAutoApply(autoApply()); + } + + return sb; +} + +void PropertyFloatItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto sb = qobject_cast(editor); + sb->setRange((double)INT_MIN, (double)INT_MAX); + sb->setValue(data.toDouble()); +} + +QVariant PropertyFloatItem::editorData(QWidget* editor) const +{ + auto sb = qobject_cast(editor); + return {sb->value()}; +} + +// -------------------------------------------------------------------- + + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyUnitItem) + +PropertyUnitItem::PropertyUnitItem() = default; + +QVariant PropertyUnitItem::toString(const QVariant& prop) const +{ + const Base::Quantity& unit = prop.value(); + QString string = unit.getUserString(); + if (hasExpression()) { + string += + QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); + } + + return {string}; +} + +QVariant PropertyUnitItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + Base::Quantity value = static_cast(prop)->getQuantityValue(); + return QVariant::fromValue(value); +} + +void PropertyUnitItem::setValue(const QVariant& value) +{ + // if the item has an expression it handles the python code + if (!hasExpression()) { + if (!value.canConvert()) { + return; + } + const Base::Quantity& val = value.value(); + + Base::QuantityFormat format(Base::QuantityFormat::Default, highPrec); + QString unit = Base::UnitsApi::toString(val, format); + setPropertyValue(unit); + } +} + +QWidget* PropertyUnitItem::createEditor(QWidget* parent, const std::function& method) const +{ + auto infield = new Gui::QuantitySpinBox(parent); + infield->setFrame(false); + infield->setMinimumHeight(0); + infield->setReadOnly(isReadOnly()); + + // if we are bound to an expression we need to bind it to the input field + if (isBound()) { + infield->bind(getPath()); + infield->setAutoApply(autoApply()); + } + + QObject::connect(infield, qOverload(&Gui::QuantitySpinBox::valueChanged), method); + return infield; +} + +void PropertyUnitItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + const Base::Quantity& value = data.value(); + + auto infield = qobject_cast(editor); + infield->setValue(value); + infield->selectAll(); +} + +QVariant PropertyUnitItem::editorData(QWidget* editor) const +{ + auto infield = qobject_cast(editor); + Base::Quantity value = infield->value(); + return QVariant::fromValue(value); +} + +// -------------------------------------------------------------------- + + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyUnitConstraintItem) + +PropertyUnitConstraintItem::PropertyUnitConstraintItem() = default; + +void PropertyUnitConstraintItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + const Base::Quantity& value = data.value(); + + auto infield = qobject_cast(editor); + infield->setValue(value); + infield->selectAll(); + + const auto prop = static_cast(getFirstProperty()); + + const App::PropertyQuantityConstraint::Constraints* c = nullptr; + if (prop) { + c = prop->getConstraints(); + } + + if (c) { + infield->setMinimum(c->LowerBound); + infield->setMaximum(c->UpperBound); + infield->setSingleStep(c->StepSize); + } + else { + infield->setMinimum(min); + infield->setMaximum(max); + infield->setSingleStep(steps); + } +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFloatConstraintItem) + +PropertyFloatConstraintItem::PropertyFloatConstraintItem() = default; + +QVariant PropertyFloatConstraintItem::toString(const QVariant& prop) const +{ + double value = prop.toDouble(); + QString data = QLocale().toString(value, 'f', decimals()); + return {data}; +} + +QVariant PropertyFloatConstraintItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + double value = static_cast(prop)->getValue(); + return {value}; +} + +void PropertyFloatConstraintItem::setValue(const QVariant& value) +{ + // if the item has an expression it issues the python code + if (!hasExpression()) { + if (!value.canConvert()) { + return; + } + double val = value.toDouble(); + QString data = QString::fromLatin1("%1").arg(val, 0, 'g', highPrec); + setPropertyValue(data); + } +} + +QWidget* PropertyFloatConstraintItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto sb = new Gui::DoubleSpinBox(parent); + sb->setDecimals(decimals()); + sb->setFrame(false); + sb->setReadOnly(isReadOnly()); + QObject::connect(sb, qOverload(&Gui::DoubleSpinBox::valueChanged), method); + + if (isBound()) { + sb->bind(getPath()); + sb->setAutoApply(autoApply()); + } + + return sb; +} + +void PropertyFloatConstraintItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + const auto prop = static_cast(getFirstProperty()); + + const App::PropertyFloatConstraint::Constraints* c = nullptr; + if (prop) { + c = prop->getConstraints(); + } + + auto sb = qobject_cast(editor); + if (c) { + sb->setMinimum(c->LowerBound); + sb->setMaximum(c->UpperBound); + sb->setSingleStep(c->StepSize); + } + else { + sb->setMinimum(min); + sb->setMaximum(max); + sb->setSingleStep(steps); + } + + sb->setValue(data.toDouble()); +} + +QVariant PropertyFloatConstraintItem::editorData(QWidget* editor) const +{ + auto sb = qobject_cast(editor); + return {sb->value()}; +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyPrecisionItem) + +PropertyPrecisionItem::PropertyPrecisionItem() +{ + setDecimals(highPrec); +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyAngleItem) + +PropertyAngleItem::PropertyAngleItem() = default; + +void PropertyAngleItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + PropertyUnitConstraintItem::setEditorData(editor, data); +} + +QVariant PropertyAngleItem::toString(const QVariant& prop) const +{ + return PropertyUnitConstraintItem::toString(prop); +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyBoolItem) + +PropertyBoolItem::PropertyBoolItem() = default; + +QVariant PropertyBoolItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + bool value = static_cast(prop)->getValue(); + return {value}; +} + +void PropertyBoolItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + bool val = value.toBool(); + QString data = (val ? QLatin1String("True") : QLatin1String("False")); + setPropertyValue(data); +} + +QWidget* PropertyBoolItem::createEditor(QWidget* parent, const std::function& method) const +{ + auto cb = new QComboBox(parent); + cb->setFrame(false); + cb->addItem(QLatin1String("false")); + cb->addItem(QLatin1String("true")); + cb->setDisabled(isReadOnly()); + QObject::connect(cb, qOverload(&QComboBox::activated), method); + return cb; +} + +void PropertyBoolItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto cb = qobject_cast(editor); + cb->setCurrentIndex(cb->findText(data.toString())); +} + +QVariant PropertyBoolItem::editorData(QWidget* editor) const +{ + auto cb = qobject_cast(editor); + return {cb->currentText()}; +} + +// --------------------------------------------------------------- + +namespace Gui::PropertyEditor +{ +class VectorLineEdit: public Gui::ExpLineEdit +{ + int decimals; + +public: + explicit VectorLineEdit(int decimals, QWidget* parent = nullptr, bool expressionOnly = false) + : Gui::ExpLineEdit(parent, expressionOnly) + , decimals(decimals) + {} + + bool apply(const std::string& propName) override + { + // NOLINTNEXTLINE + if (!ExpressionBinding::apply(propName)) { // clazy:exclude=skipped-base-method + QVariant data = property("coords"); + if (data.canConvert()) { + const Base::Vector3d& value = data.value(); + + QString str = QString::fromLatin1("(%1, %2, %3)") + .arg(value.x, 0, 'f', decimals) + .arg(value.y, 0, 'f', decimals) + .arg(value.z, 0, 'f', decimals); + + Gui::Command::doCommand(Gui::Command::Doc, + "%s = %s", + propName.c_str(), + str.toLatin1().constData()); + return true; + } + } + + return false; + } +}; +} // namespace Gui::PropertyEditor + +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyVectorItem) + +PropertyVectorItem::PropertyVectorItem() +{ + m_x = static_cast(PropertyFloatItem::create()); + m_x->setParent(this); + m_x->setPropertyName(QLatin1String("x")); + this->appendChild(m_x); + m_y = static_cast(PropertyFloatItem::create()); + m_y->setParent(this); + m_y->setPropertyName(QLatin1String("y")); + this->appendChild(m_y); + m_z = static_cast(PropertyFloatItem::create()); + m_z->setParent(this); + m_z->setPropertyName(QLatin1String("z")); + this->appendChild(m_z); +} + +QVariant PropertyVectorItem::toString(const QVariant& prop) const +{ + QLocale loc; + const Base::Vector3d& value = prop.value(); + QString data = QString::fromLatin1("[%1 %2 %3]") + .arg(loc.toString(value.x, 'f', lowPrec), + loc.toString(value.y, 'f', lowPrec), + loc.toString(value.z, 'f', lowPrec)); + if (hasExpression()) { + data += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); + } + return {data}; +} + +QVariant PropertyVectorItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const Base::Vector3d& value = static_cast(prop)->getValue(); + return QVariant::fromValue(value); +} + +void PropertyVectorItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + const Base::Vector3d& val = value.value(); + QString data = QString::fromLatin1("(%1, %2, %3)") + .arg(val.x, 0, 'g', highPrec) + .arg(val.y, 0, 'g', highPrec) + .arg(val.z, 0, 'g', highPrec); + setPropertyValue(data); +} + +QWidget* PropertyVectorItem::createEditor(QWidget* parent, + const std::function& /*method*/) const +{ + auto le = new VectorLineEdit(decimals(), parent); + le->setFrame(false); + le->setReadOnly(true); + + if (isBound()) { + le->bind(getPath()); + le->setAutoApply(autoApply()); + } + + return le; +} + +void PropertyVectorItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + QLocale loc; + auto le = qobject_cast(editor); + const Base::Vector3d& value = data.value(); + QString text = QString::fromLatin1("[%1 %2 %3]") + .arg(loc.toString(value.x, 'f', lowPrec), + loc.toString(value.y, 'f', lowPrec), + loc.toString(value.z, 'f', lowPrec)); + le->setProperty("coords", data); + le->setText(text); +} + +QVariant PropertyVectorItem::editorData(QWidget* editor) const +{ + auto le = qobject_cast(editor); + return {le->text()}; +} + +double PropertyVectorItem::x() const +{ + return data(1, Qt::EditRole).value().x; +} + +void PropertyVectorItem::setX(double x) +{ + setData(QVariant::fromValue(Base::Vector3d(x, y(), z()))); +} + +double PropertyVectorItem::y() const +{ + return data(1, Qt::EditRole).value().y; +} + +void PropertyVectorItem::setY(double y) +{ + setData(QVariant::fromValue(Base::Vector3d(x(), y, z()))); +} + +double PropertyVectorItem::z() const +{ + return data(1, Qt::EditRole).value().z; +} + +void PropertyVectorItem::setZ(double z) +{ + setData(QVariant::fromValue(Base::Vector3d(x(), y(), z))); +} + +void PropertyVectorItem::propertyBound() +{ + m_x->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("x")); + m_y->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("y")); + m_z->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("z")); +} + +// --------------------------------------------------------------- + +PropertyEditorWidget::PropertyEditorWidget(QWidget* parent) + : QWidget(parent) +{ + auto layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(2); + + lineEdit = new QLineEdit(this); + lineEdit->setReadOnly(true); + layout->addWidget(lineEdit); + + button = new QPushButton(QLatin1String("..."), this); +#if defined(Q_OS_MACOS) + button->setAttribute( + Qt::WA_LayoutUsesWidgetRect); // layout size from QMacStyle was not correct +#endif + layout->addWidget(button); + + connect(button, &QPushButton::clicked, this, &PropertyEditorWidget::buttonClick); + + // QAbstractItemView will call selectAll() if a QLineEdit is the focus + // proxy. Since the QLineEdit here is read-only and not meant for editing, + // do not set it as focus proxy. Otherwise, the text won't even shown for + // most stylesheets (which contain a trick to hide the content of a selected + // read-only/disabled editor widgets). + // + // setFocusProxy(lineEdit); +} + +PropertyEditorWidget::~PropertyEditorWidget() = default; + +void PropertyEditorWidget::resizeEvent(QResizeEvent* e) +{ + button->setFixedWidth(e->size().height()); + button->setFixedHeight(e->size().height()); +} + +void PropertyEditorWidget::showValue(const QVariant& d) +{ + lineEdit->setText(d.toString()); +} + +QVariant PropertyEditorWidget::value() const +{ + return variant; +} + +void PropertyEditorWidget::setValue(const QVariant& val) +{ + variant = val; + showValue(variant); + Q_EMIT valueChanged(variant); +} + +// --------------------------------------------------------------- + +VectorListWidget::VectorListWidget(int decimals, QWidget* parent) + : PropertyEditorWidget(parent) + , decimals(decimals) +{ + connect(button, &QPushButton::clicked, this, &VectorListWidget::buttonClicked); +} + +void VectorListWidget::buttonClicked() +{ + auto dlg = new VectorListEditor(decimals, this); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->setValues(value().value>()); + QPoint p(0, 0); + p = this->mapToGlobal(p); + dlg->move(p); + connect(dlg, &VectorListEditor::accepted, this, [this, dlg] { + QVariant data = QVariant::fromValue>(dlg->getValues()); + setValue(data); + }); + + dlg->exec(); +} + +void VectorListWidget::showValue(const QVariant& d) +{ + QLocale loc; + QString data; + const QList& value = d.value>(); + if (value.isEmpty()) { + data = QString::fromLatin1("[]"); + } + else { + data = QString::fromLatin1("[%1 %2 %3], ...") + .arg(loc.toString(value[0].x, 'f', lowPrec), + loc.toString(value[0].y, 'f', lowPrec), + loc.toString(value[0].z, 'f', lowPrec)); + } + lineEdit->setText(data); +} +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyVectorListItem) + +PropertyVectorListItem::PropertyVectorListItem() = default; + +QVariant PropertyVectorListItem::toString(const QVariant& prop) const +{ + QLocale loc; + QString data; + const QList& value = prop.value>(); + if (value.isEmpty()) { + data = QString::fromLatin1("[]"); + } + else { + data = QString::fromLatin1("[%1 %2 %3], ...") + .arg(loc.toString(value[0].x, 'f', lowPrec), + loc.toString(value[0].y, 'f', lowPrec), + loc.toString(value[0].z, 'f', lowPrec)); + } + + if (hasExpression()) { + data += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); + } + return {data}; +} + +QVariant PropertyVectorListItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const std::vector& value = + static_cast(prop)->getValue(); + QList list; + std::copy(value.begin(), value.end(), std::back_inserter(list)); + return QVariant::fromValue>(list); +} + +void PropertyVectorListItem::setValue(const QVariant& value) +{ + if (!value.canConvert>()) { + return; + } + const QList& val = value.value>(); + QString data; + QTextStream str(&data); + str << "["; + for (const auto& it : val) { + str << QString::fromLatin1("(%1, %2, %3), ") + .arg(it.x, 0, 'g', highPrec) + .arg(it.y, 0, 'g', highPrec) + .arg(it.z, 0, 'g', highPrec); + } + str << "]"; + setPropertyValue(data); +} + +QWidget* PropertyVectorListItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto pe = new VectorListWidget(decimals(), parent); + QObject::connect(pe, &VectorListWidget::valueChanged, method); + pe->setDisabled(isReadOnly()); + return pe; +} + +void PropertyVectorListItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto pe = qobject_cast(editor); + pe->setValue(data); +} + +QVariant PropertyVectorListItem::editorData(QWidget* editor) const +{ + auto pe = qobject_cast(editor); + return pe->value(); +} + +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyVectorDistanceItem) + +PropertyVectorDistanceItem::PropertyVectorDistanceItem() +{ + m_x = static_cast(PropertyUnitItem::create()); + m_x->setParent(this); + m_x->setPropertyName(QLatin1String("x")); + this->appendChild(m_x); + m_y = static_cast(PropertyUnitItem::create()); + m_y->setParent(this); + m_y->setPropertyName(QLatin1String("y")); + this->appendChild(m_y); + m_z = static_cast(PropertyUnitItem::create()); + m_z->setParent(this); + m_z->setPropertyName(QLatin1String("z")); + this->appendChild(m_z); +} + +QVariant PropertyVectorDistanceItem::toString(const QVariant& prop) const +{ + const Base::Vector3d& value = prop.value(); + QString data = QString::fromLatin1("[") + + Base::Quantity(value.x, Base::Unit::Length).getUserString() + QString::fromLatin1(" ") + + Base::Quantity(value.y, Base::Unit::Length).getUserString() + QString::fromLatin1(" ") + + Base::Quantity(value.z, Base::Unit::Length).getUserString() + QString::fromLatin1("]"); + if (hasExpression()) { + data += QString::fromLatin1(" ( %1 )").arg(QString::fromStdString(getExpressionString())); + } + return {data}; +} + + +QVariant PropertyVectorDistanceItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const Base::Vector3d& value = static_cast(prop)->getValue(); + return QVariant::fromValue(value); +} + +void PropertyVectorDistanceItem::setValue(const QVariant& variant) +{ + if (hasExpression() || !variant.canConvert()) { + return; + } + const Base::Vector3d& value = variant.value(); + + Base::Quantity x = Base::Quantity(value.x, Base::Unit::Length); + Base::Quantity y = Base::Quantity(value.y, Base::Unit::Length); + Base::Quantity z = Base::Quantity(value.z, Base::Unit::Length); + + Base::QuantityFormat format(Base::QuantityFormat::Default, highPrec); + QString data = QString::fromLatin1("(%1, %2, %3)") + .arg(Base::UnitsApi::toNumber(x, format), + Base::UnitsApi::toNumber(y, format), + Base::UnitsApi::toNumber(z, format)); + setPropertyValue(data); +} + +void PropertyVectorDistanceItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto le = qobject_cast(editor); + le->setProperty("coords", data); + le->setText(toString(data).toString()); +} + +QWidget* PropertyVectorDistanceItem::createEditor(QWidget* parent, + const std::function& /*method*/) const +{ + auto le = new VectorLineEdit(decimals(), parent); + le->setFrame(false); + le->setReadOnly(true); + + if (isBound()) { + le->bind(getPath()); + le->setAutoApply(autoApply()); + } + + return le; +} + +QVariant PropertyVectorDistanceItem::editorData(QWidget* editor) const +{ + auto le = qobject_cast(editor); + return {le->text()}; +} + +Base::Quantity PropertyVectorDistanceItem::x() const +{ + return Base::Quantity(data(1, Qt::EditRole).value().x, Base::Unit::Length); +} + +void PropertyVectorDistanceItem::setX(Base::Quantity x) +{ + setData(QVariant::fromValue(Base::Vector3d(x.getValue(), y().getValue(), z().getValue()))); +} + +Base::Quantity PropertyVectorDistanceItem::y() const +{ + return Base::Quantity(data(1, Qt::EditRole).value().y, Base::Unit::Length); +} + +void PropertyVectorDistanceItem::setY(Base::Quantity y) +{ + setData(QVariant::fromValue(Base::Vector3d(x().getValue(), y.getValue(), z().getValue()))); +} + +Base::Quantity PropertyVectorDistanceItem::z() const +{ + return Base::Quantity(data(1, Qt::EditRole).value().z, Base::Unit::Length); +} + +void PropertyVectorDistanceItem::setZ(Base::Quantity z) +{ + setData(QVariant::fromValue(Base::Vector3d(x().getValue(), y().getValue(), z.getValue()))); +} + +void PropertyVectorDistanceItem::propertyBound() +{ + if (isBound()) { + m_x->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("x")); + m_y->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("y")); + m_z->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("z")); + }; +} + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyPositionItem) + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyDirectionItem) + +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyMatrixItem) + +PropertyMatrixItem::PropertyMatrixItem() +{ + const int decimals = highPrec; + m_a11 = static_cast(PropertyFloatItem::create()); + m_a11->setParent(this); + m_a11->setPropertyName(QLatin1String("A11")); + m_a11->setDecimals(decimals); + this->appendChild(m_a11); + m_a12 = static_cast(PropertyFloatItem::create()); + m_a12->setParent(this); + m_a12->setPropertyName(QLatin1String("A12")); + m_a12->setDecimals(decimals); + this->appendChild(m_a12); + m_a13 = static_cast(PropertyFloatItem::create()); + m_a13->setParent(this); + m_a13->setPropertyName(QLatin1String("A13")); + m_a13->setDecimals(decimals); + this->appendChild(m_a13); + m_a14 = static_cast(PropertyFloatItem::create()); + m_a14->setParent(this); + m_a14->setPropertyName(QLatin1String("A14")); + m_a14->setDecimals(decimals); + this->appendChild(m_a14); + m_a21 = static_cast(PropertyFloatItem::create()); + m_a21->setParent(this); + m_a21->setPropertyName(QLatin1String("A21")); + m_a21->setDecimals(decimals); + this->appendChild(m_a21); + m_a22 = static_cast(PropertyFloatItem::create()); + m_a22->setParent(this); + m_a22->setPropertyName(QLatin1String("A22")); + m_a22->setDecimals(decimals); + this->appendChild(m_a22); + m_a23 = static_cast(PropertyFloatItem::create()); + m_a23->setParent(this); + m_a23->setPropertyName(QLatin1String("A23")); + m_a23->setDecimals(decimals); + this->appendChild(m_a23); + m_a24 = static_cast(PropertyFloatItem::create()); + m_a24->setParent(this); + m_a24->setPropertyName(QLatin1String("A24")); + m_a24->setDecimals(decimals); + this->appendChild(m_a24); + m_a31 = static_cast(PropertyFloatItem::create()); + m_a31->setParent(this); + m_a31->setPropertyName(QLatin1String("A31")); + m_a31->setDecimals(decimals); + this->appendChild(m_a31); + m_a32 = static_cast(PropertyFloatItem::create()); + m_a32->setParent(this); + m_a32->setPropertyName(QLatin1String("A32")); + m_a32->setDecimals(decimals); + this->appendChild(m_a32); + m_a33 = static_cast(PropertyFloatItem::create()); + m_a33->setParent(this); + m_a33->setPropertyName(QLatin1String("A33")); + m_a33->setDecimals(decimals); + this->appendChild(m_a33); + m_a34 = static_cast(PropertyFloatItem::create()); + m_a34->setParent(this); + m_a34->setPropertyName(QLatin1String("A34")); + m_a34->setDecimals(decimals); + this->appendChild(m_a34); + m_a41 = static_cast(PropertyFloatItem::create()); + m_a41->setParent(this); + m_a41->setPropertyName(QLatin1String("A41")); + m_a41->setDecimals(decimals); + this->appendChild(m_a41); + m_a42 = static_cast(PropertyFloatItem::create()); + m_a42->setParent(this); + m_a42->setPropertyName(QLatin1String("A42")); + m_a42->setDecimals(decimals); + this->appendChild(m_a42); + m_a43 = static_cast(PropertyFloatItem::create()); + m_a43->setParent(this); + m_a43->setPropertyName(QLatin1String("A43")); + m_a43->setDecimals(decimals); + this->appendChild(m_a43); + m_a44 = static_cast(PropertyFloatItem::create()); + m_a44->setParent(this); + m_a44->setPropertyName(QLatin1String("A44")); + m_a44->setDecimals(decimals); + this->appendChild(m_a44); +} + +QVariant PropertyMatrixItem::toString(const QVariant& prop) const +{ + QLocale loc; + const Base::Matrix4D& value = prop.value(); + // NOLINTBEGIN + QString text = QString::fromLatin1("[%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16]") + .arg(loc.toString(value[0][0], 'f', lowPrec), //(unsigned short usNdx) + loc.toString(value[0][1], 'f', lowPrec), + loc.toString(value[0][2], 'f', lowPrec), + loc.toString(value[0][3], 'f', lowPrec), + loc.toString(value[1][0], 'f', lowPrec), + loc.toString(value[1][1], 'f', lowPrec), + loc.toString(value[1][2], 'f', lowPrec), + loc.toString(value[1][3], 'f', lowPrec), + loc.toString(value[2][0], 'f', lowPrec)) + .arg(loc.toString(value[2][1], 'f', lowPrec), + loc.toString(value[2][2], 'f', lowPrec), + loc.toString(value[2][3], 'f', lowPrec), + loc.toString(value[3][0], 'f', lowPrec), + loc.toString(value[3][1], 'f', lowPrec), + loc.toString(value[3][2], 'f', lowPrec), + loc.toString(value[3][3], 'f', lowPrec)); + // NOLINTEND + return {text}; +} + +QVariant PropertyMatrixItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const Base::Matrix4D& value = static_cast(prop)->getValue(); + return QVariant::fromValue(value); +} + +QVariant PropertyMatrixItem::toolTip(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const Base::Matrix4D& value = static_cast(prop)->getValue(); + return {QString::fromStdString(value.analyse())}; +} + +void PropertyMatrixItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + const Base::Matrix4D& val = value.value(); + // NOLINTBEGIN + QString data = + QString::fromLatin1( + "FreeCAD.Matrix(%1, %2, %3, %4, %5, %6, %7, %8, %9, %10, %11, %12, %13, %14, %15, %16)") + .arg(val[0][0], 0, 'g', highPrec) + .arg(val[0][1], 0, 'g', highPrec) + .arg(val[0][2], 0, 'g', highPrec) + .arg(val[0][3], 0, 'g', highPrec) + .arg(val[1][0], 0, 'g', highPrec) + .arg(val[1][1], 0, 'g', highPrec) + .arg(val[1][2], 0, 'g', highPrec) + .arg(val[1][3], 0, 'g', highPrec) + .arg(val[2][0], 0, 'g', highPrec) + .arg(val[2][1], 0, 'g', highPrec) + .arg(val[2][2], 0, 'g', highPrec) + .arg(val[2][3], 0, 'g', highPrec) + .arg(val[3][0], 0, 'g', highPrec) + .arg(val[3][1], 0, 'g', highPrec) + .arg(val[3][2], 0, 'g', highPrec) + .arg(val[3][3], 0, 'g', highPrec); + // NOLINTEND + setPropertyValue(data); +} + +QWidget* PropertyMatrixItem::createEditor(QWidget* parent, + const std::function& /*method*/) const +{ + auto le = new QLineEdit(parent); + le->setFrame(false); + le->setReadOnly(true); + return le; +} + +void PropertyMatrixItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + QLocale loc; + auto le = qobject_cast(editor); + const Base::Matrix4D& value = data.value(); + // NOLINTBEGIN + QString text = QString::fromLatin1("[%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16]") + .arg(loc.toString(value[0][0], 'f', lowPrec), //(unsigned short usNdx) + loc.toString(value[0][1], 'f', lowPrec), + loc.toString(value[0][2], 'f', lowPrec), + loc.toString(value[0][3], 'f', lowPrec), + loc.toString(value[1][0], 'f', lowPrec), + loc.toString(value[1][1], 'f', lowPrec), + loc.toString(value[1][2], 'f', lowPrec), + loc.toString(value[1][3], 'f', lowPrec), + loc.toString(value[2][0], 'f', lowPrec)) + .arg(loc.toString(value[2][1], 'f', lowPrec), + loc.toString(value[2][2], 'f', lowPrec), + loc.toString(value[2][3], 'f', lowPrec), + loc.toString(value[3][0], 'f', lowPrec), + loc.toString(value[3][1], 'f', lowPrec), + loc.toString(value[3][2], 'f', lowPrec), + loc.toString(value[3][3], 'f', lowPrec)); + // NOLINTEND + le->setText(text); +} + +QVariant PropertyMatrixItem::editorData(QWidget* editor) const +{ + auto le = qobject_cast(editor); + return {le->text()}; +} + +// clang-format off +double PropertyMatrixItem::getA11() const +{ + return data(1, Qt::EditRole).value()[0][0]; +} + +void PropertyMatrixItem::setA11(double A11) +{ + setData(QVariant::fromValue(Base::Matrix4D(A11, getA12(), getA13(),getA14(), + getA21(), getA22(), getA23(), getA24(), + getA31(), getA32(), getA33(), getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA12() const +{ + return data(1, Qt::EditRole).value()[0][1]; +} + +void PropertyMatrixItem::setA12(double A12) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), A12, getA13(), getA14(), + getA21(), getA22(), getA23(), getA24(), + getA31(), getA32(), getA33(), getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA13() const +{ + return data(1, Qt::EditRole).value()[0][2]; +} + +void PropertyMatrixItem::setA13(double A13) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), A13, getA14(), + getA21(), getA22(), getA23(), getA24(), + getA31(), getA32(), getA33(), getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA14() const +{ + return data(1, Qt::EditRole).value()[0][3]; +} + +void PropertyMatrixItem::setA14(double A14) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), A14, + getA21(), getA22(), getA23(), getA24(), + getA31(), getA32(), getA33(), getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA21() const +{ + return data(1, Qt::EditRole).value()[1][0]; +} + +void PropertyMatrixItem::setA21(double A21) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + A21, getA22(), getA23(), getA24(), + getA31(), getA32(), getA33(), getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA22() const +{ + return data(1, Qt::EditRole).value()[1][1]; +} + +void PropertyMatrixItem::setA22(double A22) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), A22, getA23(), getA24(), + getA31(), getA32(), getA33(), getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA23() const +{ + return data(1, Qt::EditRole).value()[1][2]; +} + +void PropertyMatrixItem::setA23(double A23) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), getA22(), A23, getA24(), + getA31(), getA32(), getA33(), getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA24() const +{ + return data(1, Qt::EditRole).value()[1][3]; +} + +void PropertyMatrixItem::setA24(double A24) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), getA22(), getA23(), A24, + getA31(), getA32(), getA33(), getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA31() const +{ + return data(1, Qt::EditRole).value()[2][0]; +} + +void PropertyMatrixItem::setA31(double A31) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), getA22(), getA23(), getA24(), + A31, getA32(), getA33(), getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA32() const +{ + return data(1, Qt::EditRole).value()[2][1]; +} + +void PropertyMatrixItem::setA32(double A32) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), getA22(), getA23(), getA24(), + getA31(), A32, getA33(), getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA33() const +{ + return data(1, Qt::EditRole).value()[2][2]; +} + +void PropertyMatrixItem::setA33(double A33) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), getA22(), getA23(), getA24(), + getA31(), getA32(), A33, getA34(), + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA34() const +{ + return data(1, Qt::EditRole).value()[2][3]; +} + +void PropertyMatrixItem::setA34(double A34) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), getA22(), getA23(), getA24(), + getA31(), getA32(), getA33(), A34, + getA41(), getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA41() const +{ + return data(1, Qt::EditRole).value()[3][0]; +} + +void PropertyMatrixItem::setA41(double A41) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), getA22(), getA23(), getA24(), + getA31(), getA32(), getA33(), getA34(), + A41, getA42(), getA43(), getA44()))); +} + +double PropertyMatrixItem::getA42() const +{ + return data(1, Qt::EditRole).value()[3][1]; +} + +void PropertyMatrixItem::setA42(double A42) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), getA22(), getA23(), getA24(), + getA31(), getA32(), getA33(), getA34(), + getA41(), A42, getA43(), getA44()))); +} + +double PropertyMatrixItem::getA43() const +{ + return data(1, Qt::EditRole).value()[3][2]; +} + +void PropertyMatrixItem::setA43(double A43) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), getA22(), getA23(), getA24(), + getA31(), getA32(), getA33(), getA34(), + getA41(), getA42(), A43, getA44()))); +} + +double PropertyMatrixItem::getA44() const +{ + return data(1, Qt::EditRole).value()[3][3]; +} + +void PropertyMatrixItem::setA44(double A44) +{ + setData(QVariant::fromValue(Base::Matrix4D(getA11(), getA12(), getA13(), getA14(), + getA21(), getA22(), getA23(), getA24(), + getA31(), getA32(), getA33(), getA34(), + getA41(), getA42(), getA43(), A44))); +} +// clang-format on + +// --------------------------------------------------------------- + +RotationHelper::RotationHelper() + : init_axis(false) + , changed_value(false) + , rot_angle(0) + , rot_axis(0, 0, 1) +{} + +void RotationHelper::setChanged(bool value) +{ + changed_value = value; +} + +bool RotationHelper::hasChangedAndReset() +{ + if (!changed_value) { + return false; + } + + changed_value = false; + return true; +} + +bool RotationHelper::isAxisInitialized() const +{ + return init_axis; +} + +void RotationHelper::setValue(const Base::Vector3d& axis, double angle) +{ + rot_axis = axis; + rot_angle = angle; + init_axis = true; +} + +void RotationHelper::getValue(Base::Vector3d& axis, double& angle) const +{ + axis = rot_axis; + angle = rot_angle; +} + +double RotationHelper::getAngle(const Base::Rotation& val) const +{ + double angle {}; + Base::Vector3d dir; + val.getRawValue(dir, angle); + if (dir * this->rot_axis < 0.0) { + angle = -angle; + } + return angle; +} + +Base::Rotation RotationHelper::setAngle(double angle) +{ + Base::Rotation rot; + rot.setValue(this->rot_axis, Base::toRadians(angle)); + changed_value = true; + rot_angle = angle; + return rot; +} + +Base::Vector3d RotationHelper::getAxis() const +{ + // We must store the rotation axis in a member because + // if we read the value from the property we would always + // get a normalized vector which makes it quite unhandy + // to work with + return this->rot_axis; +} + +Base::Rotation RotationHelper::setAxis(const Base::Rotation& value, const Base::Vector3d& axis) +{ + this->rot_axis = axis; + Base::Rotation rot = value; + Base::Vector3d dummy; + double angle {}; + rot.getValue(dummy, angle); + if (dummy * axis < 0.0) { + angle = -angle; + } + rot.setValue(axis, angle); + changed_value = true; + return rot; +} + +void RotationHelper::assignProperty(const Base::Rotation& value, double eps) +{ + double angle {}; + Base::Vector3d dir; + value.getRawValue(dir, angle); + Base::Vector3d cross = this->rot_axis.Cross(dir); + double len2 = cross.Sqr(); + if (angle != 0) { + // vectors are not parallel + if (len2 > eps) { + this->rot_axis = dir; + } + // vectors point into opposite directions + else if (this->rot_axis.Dot(dir) < 0) { + this->rot_axis = -this->rot_axis; + } + } + this->rot_angle = Base::toDegrees(angle); +} + +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyRotationItem) + +PropertyRotationItem::PropertyRotationItem() +{ + m_a = static_cast(PropertyUnitItem::create()); + m_a->setParent(this); + m_a->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Angle"))); + this->appendChild(m_a); + m_d = static_cast(PropertyVectorItem::create()); + m_d->setParent(this); + m_d->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Axis"))); + m_d->setReadOnly(true); + this->appendChild(m_d); +} + +PropertyRotationItem::~PropertyRotationItem() = default; + +Base::Quantity PropertyRotationItem::getAngle() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return Base::Quantity(0.0); + } + + const Base::Rotation& val = value.value(); + double angle = h.getAngle(val); + return Base::Quantity(Base::toDegrees(angle), Base::Unit::Angle); +} + +void PropertyRotationItem::setAngle(Base::Quantity angle) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + Base::Rotation rot = h.setAngle(angle.getValue()); + setValue(QVariant::fromValue(rot)); +} + +Base::Vector3d PropertyRotationItem::getAxis() const +{ + return h.getAxis(); +} + +void PropertyRotationItem::setAxis(const Base::Vector3d& axis) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + auto rot = value.value(); + rot = h.setAxis(rot, axis); + setValue(QVariant::fromValue(rot)); +} + +void PropertyRotationItem::assignProperty(const App::Property* prop) +{ + // Choose an adaptive epsilon to avoid changing the axis when they are considered to + // be equal. See https://forum.freecad.org/viewtopic.php?f=10&t=24662&start=10 + double eps = std::pow(10.0, -2 * (decimals() + 1)); // NOLINT + if (prop->isDerivedFrom()) { + const Base::Rotation& value = static_cast(prop)->getValue(); + h.assignProperty(value, eps); + } +} + +QVariant PropertyRotationItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const Base::Rotation& value = static_cast(prop)->getValue(); + double angle {}; + Base::Vector3d dir; + value.getRawValue(dir, angle); + if (!h.isAxisInitialized()) { + if (m_a->hasExpression()) { + QString str = m_a->expressionAsString(); + angle = str.toDouble(); + } + else { + angle = Base::toDegrees(angle); + } + + PropertyItem* x = m_d->child(0); + PropertyItem* y = m_d->child(1); + PropertyItem* z = m_d->child(2); + if (x->hasExpression()) { + QString str = x->expressionAsString(); + dir.x = str.toDouble(); + } + if (y->hasExpression()) { + QString str = y->expressionAsString(); + dir.y = str.toDouble(); + } + if (z->hasExpression()) { + QString str = z->expressionAsString(); + dir.z = str.toDouble(); + } + h.setValue(dir, angle); + } + return QVariant::fromValue(value); +} + +QVariant PropertyRotationItem::toolTip(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const Base::Rotation& p = static_cast(prop)->getValue(); + double angle {}; + Base::Vector3d dir; + p.getRawValue(dir, angle); + angle = Base::toDegrees(angle); + + QLocale loc; + QString data = QString::fromUtf8("Axis: (%1 %2 %3)\n" + "Angle: %4") + .arg(loc.toString(dir.x, 'f', decimals()), + loc.toString(dir.y, 'f', decimals()), + loc.toString(dir.z, 'f', decimals()), + Base::Quantity(angle, Base::Unit::Angle).getUserString()); + return {data}; +} + +QVariant PropertyRotationItem::toString(const QVariant& prop) const +{ + const Base::Rotation& p = prop.value(); + double angle {}; + Base::Vector3d dir; + p.getRawValue(dir, angle); + angle = Base::toDegrees(angle); + + QLocale loc; + QString data = QString::fromUtf8("[(%1 %2 %3); %4]") + .arg(loc.toString(dir.x, 'f', lowPrec), + loc.toString(dir.y, 'f', lowPrec), + loc.toString(dir.z, 'f', lowPrec), + Base::Quantity(angle, Base::Unit::Angle).getUserString()); + return {data}; +} + +void PropertyRotationItem::setValue(const QVariant& value) +{ + if (!value.canConvert()) { + return; + } + // Accept this only if the user changed the axis, angle or position but + // not if >this< item loses focus + if (!h.hasChangedAndReset()) { + return; + } + + Base::Vector3d axis; + double angle {}; + h.getValue(axis, angle); + Base::QuantityFormat format(Base::QuantityFormat::Default, highPrec); + QString data = QString::fromLatin1("App.Rotation(App.Vector(%1,%2,%3),%4)") + .arg(Base::UnitsApi::toNumber(axis.x, format), + Base::UnitsApi::toNumber(axis.y, format), + Base::UnitsApi::toNumber(axis.z, format), + Base::UnitsApi::toNumber(angle, format)); + setPropertyValue(data); +} + +QWidget* PropertyRotationItem::createEditor(QWidget* parent, + const std::function& method) const +{ + Q_UNUSED(parent) + Q_UNUSED(method) + return nullptr; +} + +void PropertyRotationItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + Q_UNUSED(editor) + Q_UNUSED(data) +} + +QVariant PropertyRotationItem::editorData(QWidget* editor) const +{ + Q_UNUSED(editor) + return {}; +} + +void PropertyRotationItem::propertyBound() +{ + if (isBound()) { + m_a->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("Angle")); + + m_d->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("Axis")); + } +} + +// -------------------------------------------------------------------- + +PlacementEditor::PlacementEditor(QString name, QWidget* parent) + : LabelButton(parent) + , _task(nullptr) + , propertyname {std::move(name)} +{ + propertyname.replace(QLatin1String(" "), QLatin1String("")); +} + +PlacementEditor::~PlacementEditor() = default; + +void PlacementEditor::browse() +{ + Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); + Gui::Dialog::TaskPlacement* task {}; + task = qobject_cast(dlg); + if (dlg && !task) { + // there is already another task dialog which must be closed first + Gui::Control().showDialog(dlg); + return; + } + if (!task) { + task = new Gui::Dialog::TaskPlacement(); + } + if (!_task) { + _task = task; + connect(task, &TaskPlacement::placementChanged, this, &PlacementEditor::updateValue); + } + task->setPlacement(value().value()); + task->setPropertyName(propertyname); + task->setSelection(Gui::Selection().getSelectionEx()); + task->bindObject(); + Gui::Control().showDialog(task); +} + +void PlacementEditor::showValue(const QVariant& d) +{ + const Base::Placement& p = d.value(); + double angle {}; + Base::Vector3d dir; + Base::Vector3d pos; + p.getRotation().getRawValue(dir, angle); + angle = Base::toDegrees(angle); + pos = p.getPosition(); + + QLocale loc; + QString data = QString::fromUtf8("[(%1 %2 %3);%4 \xc2\xb0;(%5 %6 %7)]") + .arg(loc.toString(dir.x, 'f', lowPrec), + loc.toString(dir.y, 'f', lowPrec), + loc.toString(dir.z, 'f', lowPrec), + loc.toString(angle, 'f', lowPrec), + loc.toString(pos.x, 'f', lowPrec), + loc.toString(pos.y, 'f', lowPrec), + loc.toString(pos.z, 'f', lowPrec)); + getLabel()->setText(data); +} + +void PlacementEditor::updateValue(const QVariant& v, bool incr, bool data) +{ + if (data) { + if (incr) { + QVariant u = value(); + const Base::Placement& plm = u.value(); + const Base::Placement& rel = v.value(); + Base::Placement newp = rel * plm; + setValue(QVariant::fromValue(newp)); + } + else { + setValue(v); + } + } +} + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyPlacementItem) + +PropertyPlacementItem::PropertyPlacementItem() +{ + m_a = static_cast(PropertyUnitItem::create()); + m_a->setParent(this); + m_a->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Angle"))); + this->appendChild(m_a); + m_d = static_cast(PropertyVectorItem::create()); + m_d->setParent(this); + m_d->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Axis"))); + m_d->setReadOnly(true); + this->appendChild(m_d); + m_p = static_cast(PropertyVectorDistanceItem::create()); + m_p->setParent(this); + m_p->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Position"))); + m_p->setReadOnly(true); + this->appendChild(m_p); +} + +PropertyPlacementItem::~PropertyPlacementItem() = default; + +Base::Quantity PropertyPlacementItem::getAngle() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return Base::Quantity(0.0); + } + + const Base::Placement& val = value.value(); + double angle = h.getAngle(val.getRotation()); + return Base::Quantity(Base::toDegrees(angle), Base::Unit::Angle); +} + +void PropertyPlacementItem::setAngle(Base::Quantity angle) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + auto val = value.value(); + Base::Rotation rot = h.setAngle(angle.getValue()); + val.setRotation(rot); + setValue(QVariant::fromValue(val)); +} + +Base::Vector3d PropertyPlacementItem::getAxis() const +{ + return h.getAxis(); +} + +void PropertyPlacementItem::setAxis(const Base::Vector3d& axis) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + auto val = value.value(); + Base::Rotation rot = val.getRotation(); + rot = h.setAxis(rot, axis); + val.setRotation(rot); + setValue(QVariant::fromValue(val)); +} + +Base::Vector3d PropertyPlacementItem::getPosition() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return Base::Vector3d(0, 0, 0); + } + const Base::Placement& val = value.value(); + return val.getPosition(); +} + +void PropertyPlacementItem::setPosition(const Base::Vector3d& pos) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + auto val = value.value(); + val.setPosition(pos); + h.setChanged(true); + setValue(QVariant::fromValue(val)); +} + +void PropertyPlacementItem::assignProperty(const App::Property* prop) +{ + // Choose an adaptive epsilon to avoid changing the axis when they are considered to + // be equal. See https://forum.freecad.org/viewtopic.php?f=10&t=24662&start=10 + double eps = std::pow(10.0, -2 * (decimals() + 1)); // NOLINT + if (prop->isDerivedFrom()) { + const Base::Placement& value = static_cast(prop)->getValue(); + h.assignProperty(value.getRotation(), eps); + } +} + +QVariant PropertyPlacementItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const Base::Placement& value = static_cast(prop)->getValue(); + double angle {}; + Base::Vector3d dir; + value.getRotation().getRawValue(dir, angle); + if (!h.isAxisInitialized()) { + if (m_a->hasExpression()) { + QString str = m_a->expressionAsString(); + angle = str.toDouble(); + } + else { + angle = Base::toDegrees(angle); + } + + PropertyItem* x = m_d->child(0); + PropertyItem* y = m_d->child(1); + PropertyItem* z = m_d->child(2); + if (x->hasExpression()) { + QString str = x->expressionAsString(); + dir.x = str.toDouble(); + } + if (y->hasExpression()) { + QString str = y->expressionAsString(); + dir.y = str.toDouble(); + } + if (z->hasExpression()) { + QString str = z->expressionAsString(); + dir.z = str.toDouble(); + } + h.setValue(dir, angle); + } + return QVariant::fromValue(value); +} + +QVariant PropertyPlacementItem::toolTip(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const Base::Placement& p = static_cast(prop)->getValue(); + double angle {}; + Base::Vector3d dir; + Base::Vector3d pos; + p.getRotation().getRawValue(dir, angle); + angle = Base::toDegrees(angle); + pos = p.getPosition(); + + QLocale loc; + QString data = QString::fromUtf8("Axis: (%1 %2 %3)\n" + "Angle: %4\n" + "Position: (%5 %6 %7)") + .arg(loc.toString(dir.x, 'f', decimals()), + loc.toString(dir.y, 'f', decimals()), + loc.toString(dir.z, 'f', decimals()), + Base::Quantity(angle, Base::Unit::Angle).getUserString(), + Base::Quantity(pos.x, Base::Unit::Length).getUserString(), + Base::Quantity(pos.y, Base::Unit::Length).getUserString(), + Base::Quantity(pos.z, Base::Unit::Length).getUserString()); + return {data}; +} + +QVariant PropertyPlacementItem::toString(const QVariant& prop) const +{ + const Base::Placement& p = prop.value(); + double angle {}; + Base::Vector3d dir; + Base::Vector3d pos; + p.getRotation().getRawValue(dir, angle); + angle = Base::toDegrees(angle); + pos = p.getPosition(); + + QLocale loc; + QString data = QString::fromUtf8("[(%1 %2 %3); %4; (%5 %6 %7)]") + .arg(loc.toString(dir.x, 'f', lowPrec), + loc.toString(dir.y, 'f', lowPrec), + loc.toString(dir.z, 'f', lowPrec), + Base::Quantity(angle, Base::Unit::Angle).getUserString(), + Base::Quantity(pos.x, Base::Unit::Length).getUserString(), + Base::Quantity(pos.y, Base::Unit::Length).getUserString(), + Base::Quantity(pos.z, Base::Unit::Length).getUserString()); + return {data}; +} + +void PropertyPlacementItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + // Accept this only if the user changed the axis, angle or position but + // not if >this< item loses focus + if (!h.hasChangedAndReset()) { + return; + } + + const Base::Placement& val = value.value(); + Base::Vector3d pos = val.getPosition(); + + Base::Vector3d axis; + double angle {}; + h.getValue(axis, angle); + + Base::QuantityFormat format(Base::QuantityFormat::Default, highPrec); + QString data = QString::fromLatin1("App.Placement(" + "App.Vector(%1,%2,%3)," + "App.Rotation(App.Vector(%4,%5,%6),%7))") + .arg(Base::UnitsApi::toNumber(pos.x, format), + Base::UnitsApi::toNumber(pos.y, format), + Base::UnitsApi::toNumber(pos.z, format), + Base::UnitsApi::toNumber(axis.x, format), + Base::UnitsApi::toNumber(axis.y, format), + Base::UnitsApi::toNumber(axis.z, format), + Base::UnitsApi::toNumber(angle, format)); + setPropertyValue(data); +} + +QWidget* PropertyPlacementItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto pe = new PlacementEditor(this->propertyName(), parent); + QObject::connect(pe, &PlacementEditor::valueChanged, method); + + // The Placement dialog only works if property is part of a DocumentObject + bool readonly = isReadOnly(); + if (auto prop = getFirstProperty()) { + readonly |= (!prop->getContainer()->isDerivedFrom()); + } + pe->setDisabled(readonly); + return pe; +} + +void PropertyPlacementItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto pe = qobject_cast(editor); + pe->setValue(data); +} + +QVariant PropertyPlacementItem::editorData(QWidget* editor) const +{ + auto pe = qobject_cast(editor); + return pe->value(); +} + +void PropertyPlacementItem::propertyBound() +{ + if (isBound()) { + m_a->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("Rotation") + << App::ObjectIdentifier::String("Angle")); + + m_d->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("Rotation") + << App::ObjectIdentifier::String("Axis")); + + m_p->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("Base")); + } +} + + +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyEnumItem) + +PropertyEnumItem::PropertyEnumItem() + : m_enum(nullptr) +{ + if (PropertyView::showAll()) { + m_enum = static_cast(PropertyStringListItem::create()); + m_enum->setParent(this); + m_enum->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Enum"))); + this->appendChild(m_enum); + } +} + +void PropertyEnumItem::propertyBound() +{ + if (m_enum && isBound()) { + m_enum->bind(App::ObjectIdentifier(getPath()) << App::ObjectIdentifier::String("Enum")); + } +} + +void PropertyEnumItem::setEnum(const QStringList& values) +{ + setData(values); +} + +QStringList PropertyEnumItem::getEnum() const +{ + QStringList res; + auto prop = getFirstProperty(); + if (prop && prop->isDerivedFrom()) { + const auto prop_enum = static_cast(prop); + std::vector enums = prop_enum->getEnumVector(); + for (const auto& it : enums) { + res.push_back(QString::fromStdString(it)); + } + } + return res; +} + +QVariant PropertyEnumItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const auto prop_enum = static_cast(prop); + if (!prop_enum->isValid()) { + return {QString()}; + } + return {QString::fromUtf8(prop_enum->getValueAsString())}; +} + +void PropertyEnumItem::setValue(const QVariant& value) +{ + if (hasExpression()) { + return; + } + + QString data; + + if (value.userType() == QMetaType::QStringList) { + QStringList values = value.toStringList(); + QTextStream str(&data); + str << "["; + for (const auto& it : values) { + QString text(it); + text.replace(QString::fromUtf8("'"), QString::fromUtf8("\\'")); + + std::string pystr = Base::Tools::escapedUnicodeFromUtf8(text.toUtf8()); + pystr = Base::InterpreterSingleton::strToPython(pystr.c_str()); + str << "u\"" << pystr.c_str() << "\", "; + } + str << "]"; + setPropertyValue(data); + } + else if (value.canConvert()) { + QByteArray val = value.toString().toUtf8(); + std::string str = Base::Tools::escapedUnicodeFromUtf8(val); + data = QString::fromLatin1("u\"%1\"").arg(QString::fromStdString(str)); + setPropertyValue(data); + } +} + +namespace +{ + +class EnumItems; + +struct EnumItem +{ + QString text; + QString fullText; + std::shared_ptr children; + explicit EnumItem(QString t = QString(), QString f = QString()) + : text(std::move(t)) + , fullText(std::move(f)) + {} + void populate(QMenu* menu); +}; + +class EnumItems: public std::vector +{ +}; + +void EnumItem::populate(QMenu* menu) +{ + if (!children || children->empty()) { + auto action = menu->addAction(text); + action->setData(fullText); + return; + } + auto subMenu = menu->addMenu(text); + for (auto& item : *children) { + item.populate(subMenu); + } +} + +std::shared_ptr getEnumItems(const QStringList& commonModes) // NOLINT +{ + int index = -1; + std::shared_ptr enumItems; + for (auto& mode : commonModes) { + ++index; + auto fields = mode.split(QStringLiteral("|")); + if (!enumItems && fields.size() <= 1) { + continue; + } + if (!enumItems) { + enumItems = std::make_shared(); + for (int i = 0; i < index; ++i) { + enumItems->emplace_back(commonModes[i], mode); + } + } + auto children = enumItems; + int j = -1; + for (auto& field : fields) { + ++j; + field = field.trimmed(); + auto it = children->end(); + if (field.isEmpty()) { + if (!children->empty()) { + --it; + } + else { + continue; + } + } + else { + it = std::find_if(children->begin(), + children->end(), + [&field](const EnumItem& item) { + return item.text == field; + }); + if (it == children->end()) { + it = children->emplace(children->end(), field, mode); + } + } + if (j + 1 == (int)fields.size()) { + break; + } + if (!it->children) { + it->children = std::make_shared(); + } + children = it->children; + } + } + + return enumItems; +} + +} // anonymous namespace + +QStringList PropertyEnumItem::getCommonModes() const +{ + const std::vector& items = getPropertyData(); + + QStringList commonModes; + QStringList modes; + for (auto it = items.begin(); it != items.end(); ++it) { + if ((*it)->is()) { + auto prop = static_cast(*it); + if (!prop->hasEnums()) { + commonModes.clear(); + return {}; + } + const std::vector& value = prop->getEnumVector(); + if (it == items.begin()) { + for (const auto& jt : value) { + commonModes << QString::fromUtf8(jt.c_str()); + } + } + else { + for (const auto& jt : value) { + if (commonModes.contains(QString::fromUtf8(jt.c_str()))) { + modes << QString::fromUtf8(jt.c_str()); + } + } + + commonModes = modes; + modes.clear(); + } + } + } + + return commonModes; +} + +QWidget* PropertyEnumItem::createEditor(QWidget* parent, const std::function& method) const +{ + QStringList commonModes = getCommonModes(); + if (commonModes.isEmpty()) { + return nullptr; + } + + std::shared_ptr enumItems = getEnumItems(commonModes); + + if (!enumItems) { + auto cb = new QComboBox(parent); + cb->setFrame(false); + cb->setDisabled(isReadOnly()); + cb->addItems(commonModes); + QObject::connect(cb, qOverload(&QComboBox::activated), method); + return cb; + } + + auto button = new PropertyEnumButton(parent); + button->setDisabled(isReadOnly()); + auto menu = new QMenu(button); + for (auto& item : *enumItems) { + item.populate(menu); + } + button->setMenu(menu); + QObject::connect(menu, &QMenu::aboutToShow, this, [=]() { + menu->setMinimumWidth(button->width()); + }); + QObject::connect(menu, &QMenu::triggered, this, [=](QAction* action) { + button->setText(action->data().toString()); + Q_EMIT button->picked(); + }); + QObject::connect(button, &PropertyEnumButton::picked, method); + return button; +} + +void PropertyEnumItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + if (auto cb = qobject_cast(editor)) { + cb->setEditable(false); + cb->setCurrentIndex(cb->findText(data.toString())); + } + else if (auto btn = qobject_cast(editor)) { + btn->setText(data.toString()); + } +} + +QVariant PropertyEnumItem::editorData(QWidget* editor) const +{ + if (auto cb = qobject_cast(editor)) { + return {cb->currentText()}; + } + if (auto btn = qobject_cast(editor)) { + return btn->text(); + } + return {}; +} + +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyStringListItem) + +PropertyStringListItem::PropertyStringListItem() = default; + +QWidget* PropertyStringListItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto le = new Gui::LabelEditor(parent); + le->setAutoFillBackground(true); + le->setDisabled(isReadOnly()); + QObject::connect(le, &Gui::LabelEditor::textChanged, method); + return le; +} + +void PropertyStringListItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto le = qobject_cast(editor); + QStringList list = data.toStringList(); + le->setText(list.join(QChar::fromLatin1('\n'))); +} + +QVariant PropertyStringListItem::editorData(QWidget* editor) const +{ + auto le = qobject_cast(editor); + QString complete = le->text(); + QStringList list = complete.split(QChar::fromLatin1('\n')); + return {list}; +} + +QVariant PropertyStringListItem::toString(const QVariant& prop) const +{ + QStringList list = prop.toStringList(); + const int size = 10; + if (list.size() > size) { + list = list.mid(0, size); + list.append(QLatin1String("...")); + } + + QString text = QString::fromUtf8("[%1]").arg(list.join(QLatin1String(","))); + + return {text}; +} + +QVariant PropertyStringListItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + QStringList list; + const std::vector& value = + (static_cast(prop))->getValues(); + for (const auto& jt : value) { + list << QString::fromUtf8(jt.c_str()); + } + + return {list}; +} + +void PropertyStringListItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + QStringList values = value.toStringList(); + QString data; + QTextStream str(&data); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + str.setCodec("UTF-8"); +#endif + + str << "["; + for (const auto& it : values) { + QString text(it); + std::string pystr = Base::InterpreterSingleton::strToPython(text.toUtf8().constData()); + str << "\"" << QString::fromUtf8(pystr.c_str()) << "\", "; + } + str << "]"; + + setPropertyValue(data); +} + +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFloatListItem) + +PropertyFloatListItem::PropertyFloatListItem() = default; + +QWidget* PropertyFloatListItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto le = new Gui::LabelEditor(parent); + le->setAutoFillBackground(true); + le->setInputType(Gui::LabelEditor::Float); + le->setDisabled(isReadOnly()); + QObject::connect(le, &Gui::LabelEditor::textChanged, method); + return le; +} + +void PropertyFloatListItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto le = qobject_cast(editor); + QStringList list = data.toStringList(); + le->setText(list.join(QChar::fromLatin1('\n'))); +} + +QVariant PropertyFloatListItem::editorData(QWidget* editor) const +{ + auto le = qobject_cast(editor); + QString complete = le->text(); + QStringList list = complete.split(QChar::fromLatin1('\n')); + return {list}; +} + +QVariant PropertyFloatListItem::toString(const QVariant& prop) const +{ + QStringList list = prop.toStringList(); + const int size = 10; + if (list.size() > size) { + list = list.mid(0, size); + list.append(QLatin1String("...")); + } + QString text = QString::fromUtf8("[%1]").arg(list.join(QLatin1String(","))); + return {text}; +} + +QVariant PropertyFloatListItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + QStringList list; + const std::vector& value = + static_cast(prop)->getValues(); + for (double jt : value) { + list << QString::number(jt, 'f', decimals()); + } + + return {list}; +} + +void PropertyFloatListItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + QStringList values = value.toStringList(); + QString data; + QTextStream str(&data); + str << "["; + for (const auto& it : values) { + str << it << ","; + } + str << "]"; + if (data == QString::fromUtf8("[,]")) { + data = QString::fromUtf8("[]"); + } + setPropertyValue(data); +} + +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyIntegerListItem) + +PropertyIntegerListItem::PropertyIntegerListItem() = default; + +QWidget* PropertyIntegerListItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto le = new Gui::LabelEditor(parent); + le->setAutoFillBackground(true); + le->setInputType(Gui::LabelEditor::Integer); + le->setDisabled(isReadOnly()); + QObject::connect(le, &Gui::LabelEditor::textChanged, method); + return le; +} + +void PropertyIntegerListItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto le = qobject_cast(editor); + QStringList list = data.toStringList(); + le->setText(list.join(QChar::fromLatin1('\n'))); +} + +QVariant PropertyIntegerListItem::editorData(QWidget* editor) const +{ + auto le = qobject_cast(editor); + QString complete = le->text(); + QStringList list = complete.split(QChar::fromLatin1('\n')); + return {list}; +} + +QVariant PropertyIntegerListItem::toString(const QVariant& prop) const +{ + QStringList list = prop.toStringList(); + const int size = 10; + if (list.size() > size) { + list = list.mid(0, size); + list.append(QLatin1String("...")); + } + QString text = QString::fromUtf8("[%1]").arg(list.join(QLatin1String(","))); + + return {text}; +} + +QVariant PropertyIntegerListItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + QStringList list; + const std::vector& value = + static_cast(prop)->getValues(); + for (long jt : value) { + list << QString::number(jt); + } + + return {list}; +} + +void PropertyIntegerListItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + QStringList values = value.toStringList(); + QString data; + QTextStream str(&data); + str << "["; + for (const auto& value : values) { + str << value << ","; + } + str << "]"; + if (data == QString::fromUtf8("[,]")) { + data = QString::fromUtf8("[]"); + } + setPropertyValue(data); +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyColorItem) + +PropertyColorItem::PropertyColorItem() = default; + +QVariant PropertyColorItem::decoration(const QVariant& value) const +{ + auto color = value.value(); + + int size = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize); + QPixmap p(size, size); + p.fill(color); + + return QVariant(p); +} + +QVariant PropertyColorItem::toString(const QVariant& prop) const +{ + auto value = prop.value(); + QString color = + QString::fromLatin1("[%1, %2, %3]").arg(value.red()).arg(value.green()).arg(value.blue()); + return {color}; +} + +QVariant PropertyColorItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + App::Color value = static_cast(prop)->getValue(); + return QVariant(value.asValue()); +} + +void PropertyColorItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + auto col = value.value(); + QString data = + QString::fromLatin1("(%1,%2,%3)").arg(col.red()).arg(col.green()).arg(col.blue()); + setPropertyValue(data); +} + +QWidget* PropertyColorItem::createEditor(QWidget* parent, const std::function& method) const +{ + auto cb = new Gui::ColorButton(parent); + cb->setDisabled(isReadOnly()); + QObject::connect(cb, &Gui::ColorButton::changed, method); + return cb; +} + +void PropertyColorItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto cb = qobject_cast(editor); + auto color = data.value(); + cb->setColor(color); +} + +QVariant PropertyColorItem::editorData(QWidget* editor) const +{ + auto cb = qobject_cast(editor); + QVariant var; + var.setValue(cb->color()); + return var; +} + +// -------------------------------------------------------------------- + +namespace Gui::PropertyEditor +{ +class Material +{ +public: + QColor diffuseColor; + QColor ambientColor; + QColor specularColor; + QColor emissiveColor; + float shininess {}; + float transparency {}; +}; +} // namespace Gui::PropertyEditor + +Q_DECLARE_METATYPE(Gui::PropertyEditor::Material) // NOLINT + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyMaterialItem) + +PropertyMaterialItem::PropertyMaterialItem() +{ + const int min = 0; + const int max = 100; + const int steps = 5; + diffuse = static_cast(PropertyColorItem::create()); + diffuse->setParent(this); + diffuse->setPropertyName(QLatin1String("DiffuseColor")); + this->appendChild(diffuse); + + ambient = static_cast(PropertyColorItem::create()); + ambient->setParent(this); + ambient->setPropertyName(QLatin1String("AmbientColor")); + this->appendChild(ambient); + + specular = static_cast(PropertyColorItem::create()); + specular->setParent(this); + specular->setPropertyName(QLatin1String("SpecularColor")); + this->appendChild(specular); + + emissive = static_cast(PropertyColorItem::create()); + emissive->setParent(this); + emissive->setPropertyName(QLatin1String("EmissiveColor")); + this->appendChild(emissive); + + shininess = + static_cast(PropertyIntegerConstraintItem::create()); + shininess->setRange(min, max); + shininess->setStepSize(steps); + shininess->setParent(this); + shininess->setPropertyName(QLatin1String("Shininess")); + this->appendChild(shininess); + + transparency = + static_cast(PropertyIntegerConstraintItem::create()); + transparency->setRange(min, max); + transparency->setStepSize(steps); + transparency->setParent(this); + transparency->setPropertyName(QLatin1String("Transparency")); + this->appendChild(transparency); +} + +PropertyMaterialItem::~PropertyMaterialItem() = default; + +void PropertyMaterialItem::propertyBound() +{} + +QColor PropertyMaterialItem::getDiffuseColor() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return {}; + } + + auto val = value.value(); + return val.diffuseColor; +} + +void PropertyMaterialItem::setDiffuseColor(const QColor& color) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + auto mat = value.value(); + mat.diffuseColor = color; + setValue(QVariant::fromValue(mat)); +} + +QColor PropertyMaterialItem::getAmbientColor() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return {}; + } + + auto val = value.value(); + return val.ambientColor; +} + +void PropertyMaterialItem::setAmbientColor(const QColor& color) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + auto mat = value.value(); + mat.ambientColor = color; + setValue(QVariant::fromValue(mat)); +} + +QColor PropertyMaterialItem::getSpecularColor() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return {}; + } + + auto val = value.value(); + return val.specularColor; +} + +void PropertyMaterialItem::setSpecularColor(const QColor& color) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + auto mat = value.value(); + mat.specularColor = color; + setValue(QVariant::fromValue(mat)); +} + +QColor PropertyMaterialItem::getEmissiveColor() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return {}; + } + + auto val = value.value(); + return val.emissiveColor; +} + +void PropertyMaterialItem::setEmissiveColor(const QColor& color) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + auto mat = value.value(); + mat.emissiveColor = color; + setValue(QVariant::fromValue(mat)); +} + +int PropertyMaterialItem::getShininess() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return 0; + } + + auto val = value.value(); + return toPercent(val.shininess); +} + +void PropertyMaterialItem::setShininess(int s) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + auto mat = value.value(); + mat.shininess = fromPercent(s); + setValue(QVariant::fromValue(mat)); +} + +int PropertyMaterialItem::getTransparency() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return 0; + } + + auto val = value.value(); + return toPercent(val.transparency); +} + +void PropertyMaterialItem::setTransparency(int t) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + auto mat = value.value(); + mat.transparency = fromPercent(t); + setValue(QVariant::fromValue(mat)); +} + +QVariant PropertyMaterialItem::decoration(const QVariant& value) const +{ + // use the diffuse color + auto val = value.value(); + QColor color = val.diffuseColor; + + int size = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize); + QPixmap p(size, size); + p.fill(color); + + return QVariant(p); +} + +QVariant PropertyMaterialItem::toString(const QVariant& prop) const +{ + // use the diffuse color + auto val = prop.value(); + QColor value = val.diffuseColor; + QString color = + QString::fromLatin1("[%1, %2, %3]").arg(value.red()).arg(value.green()).arg(value.blue()); + return {color}; +} + +QVariant PropertyMaterialItem::toolTip(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const App::Material& value = static_cast(prop)->getValue(); + auto dc = value.diffuseColor.asValue(); + auto ac = value.ambientColor.asValue(); + auto sc = value.specularColor.asValue(); + auto ec = value.emissiveColor.asValue(); + + QString data = QString::fromUtf8("Diffuse color: [%1, %2, %3]\n" + "Ambient color: [%4, %5, %6]\n" + "Specular color: [%7, %8, %9]\n" + "Emissive color: [%10, %11, %12]\n" + "Shininess: %13\n" + "Transparency: %14") + .arg(dc.red()) + .arg(dc.green()) + .arg(dc.blue()) + .arg(ac.red()) + .arg(ac.green()) + .arg(ac.blue()) + .arg(sc.red()) + .arg(sc.green()) + .arg(sc.blue()) + .arg(ec.red()) + .arg(ec.green()) + .arg(ec.blue()) + .arg(toPercent(value.shininess)) + .arg(toPercent(value.transparency)); + + return {data}; +} + +QVariant PropertyMaterialItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const App::Material& value = static_cast(prop)->getValue(); + Material mat; + + mat.diffuseColor = value.diffuseColor.asValue(); + mat.ambientColor = value.ambientColor.asValue(); + mat.specularColor = value.specularColor.asValue(); + mat.emissiveColor = value.emissiveColor.asValue(); + mat.shininess = value.shininess; + mat.transparency = value.transparency; + + return QVariant::fromValue(mat); +} + +void PropertyMaterialItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + + auto mat = value.value(); + App::Color dc; + dc.setValue(mat.diffuseColor); + App::Color ac; + ac.setValue(mat.ambientColor); + App::Color sc; + sc.setValue(mat.specularColor); + App::Color ec; + ec.setValue(mat.emissiveColor); + float s = mat.shininess; + float t = mat.transparency; + + QString data = QString::fromLatin1("App.Material(" + "DiffuseColor=(%1,%2,%3)," + "AmbientColor=(%4,%5,%6)," + "SpecularColor=(%7,%8,%9)," + "EmissiveColor=(%10,%11,%12)," + "Shininess=(%13)," + "Transparency=(%14)," + ")") + .arg(dc.r, 0, 'f', decimals()) + .arg(dc.g, 0, 'f', decimals()) + .arg(dc.b, 0, 'f', decimals()) + .arg(ac.r, 0, 'f', decimals()) + .arg(ac.g, 0, 'f', decimals()) + .arg(ac.b, 0, 'f', decimals()) + .arg(sc.r, 0, 'f', decimals()) + .arg(sc.g, 0, 'f', decimals()) + .arg(sc.b, 0, 'f', decimals()) + .arg(ec.r, 0, 'f', decimals()) + .arg(ec.g, 0, 'f', decimals()) + .arg(ec.b, 0, 'f', decimals()) + .arg(s, 0, 'f', decimals()) + .arg(t, 0, 'f', decimals()); + + setPropertyValue(data); +} + +QWidget* PropertyMaterialItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto cb = new Gui::ColorButton(parent); + cb->setDisabled(isReadOnly()); + QObject::connect(cb, &Gui::ColorButton::changed, method); + return cb; +} + +void PropertyMaterialItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + if (!data.canConvert()) { + return; + } + + auto val = data.value(); + auto cb = qobject_cast(editor); + cb->setColor(val.diffuseColor); +} + +QVariant PropertyMaterialItem::editorData(QWidget* editor) const +{ + auto cb = qobject_cast(editor); + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return {}; + } + + auto val = value.value(); + val.diffuseColor = cb->color(); + return QVariant::fromValue(val); +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyMaterialListItem) + +PropertyMaterialListItem::PropertyMaterialListItem() +{ + const int min = 0; + const int max = 100; + const int steps = 5; + + // This editor gets a list of materials but it only edits the first item. + diffuse = static_cast(PropertyColorItem::create()); + diffuse->setParent(this); + diffuse->setPropertyName(QLatin1String("DiffuseColor")); + this->appendChild(diffuse); + + ambient = static_cast(PropertyColorItem::create()); + ambient->setParent(this); + ambient->setPropertyName(QLatin1String("AmbientColor")); + this->appendChild(ambient); + + specular = static_cast(PropertyColorItem::create()); + specular->setParent(this); + specular->setPropertyName(QLatin1String("SpecularColor")); + this->appendChild(specular); + + emissive = static_cast(PropertyColorItem::create()); + emissive->setParent(this); + emissive->setPropertyName(QLatin1String("EmissiveColor")); + this->appendChild(emissive); + + shininess = + static_cast(PropertyIntegerConstraintItem::create()); + shininess->setRange(min, max); + shininess->setStepSize(steps); + shininess->setParent(this); + shininess->setPropertyName(QLatin1String("Shininess")); + this->appendChild(shininess); + + transparency = + static_cast(PropertyIntegerConstraintItem::create()); + transparency->setRange(min, max); + transparency->setStepSize(steps); + transparency->setParent(this); + transparency->setPropertyName(QLatin1String("Transparency")); + this->appendChild(transparency); +} + +PropertyMaterialListItem::~PropertyMaterialListItem() = default; + +void PropertyMaterialListItem::propertyBound() +{} + +QColor PropertyMaterialListItem::getDiffuseColor() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return {}; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return {}; + } + + if (!list[0].canConvert()) { + return {}; + } + + auto mat = list[0].value(); + return mat.diffuseColor; +} + +void PropertyMaterialListItem::setDiffuseColor(const QColor& color) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return; + } + + if (!list[0].canConvert()) { + return; + } + + auto mat = list[0].value(); + mat.diffuseColor = color; + list[0] = QVariant::fromValue(mat); + setValue(list); +} + +QColor PropertyMaterialListItem::getAmbientColor() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return {}; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return {}; + } + + if (!list[0].canConvert()) { + return {}; + } + + auto mat = list[0].value(); + return mat.ambientColor; +} + +void PropertyMaterialListItem::setAmbientColor(const QColor& color) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return; + } + + if (!list[0].canConvert()) { + return; + } + + auto mat = list[0].value(); + mat.ambientColor = color; + list[0] = QVariant::fromValue(mat); + setValue(list); +} + +QColor PropertyMaterialListItem::getSpecularColor() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return {}; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return {}; + } + + if (!list[0].canConvert()) { + return {}; + } + + auto mat = list[0].value(); + return mat.specularColor; +} + +void PropertyMaterialListItem::setSpecularColor(const QColor& color) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return; + } + + if (!list[0].canConvert()) { + return; + } + + auto mat = list[0].value(); + mat.specularColor = color; + list[0] = QVariant::fromValue(mat); + setValue(list); +} + +QColor PropertyMaterialListItem::getEmissiveColor() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return {}; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return {}; + } + + if (!list[0].canConvert()) { + return {}; + } + + auto mat = list[0].value(); + return mat.emissiveColor; +} + +void PropertyMaterialListItem::setEmissiveColor(const QColor& color) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return; + } + + if (!list[0].canConvert()) { + return; + } + + auto mat = list[0].value(); + mat.emissiveColor = color; + list[0] = QVariant::fromValue(mat); + setValue(list); +} + +int PropertyMaterialListItem::getShininess() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return 0; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return 0; + } + + if (!list[0].canConvert()) { + return 0; + } + + auto mat = list[0].value(); + return toPercent(mat.shininess); +} + +void PropertyMaterialListItem::setShininess(int s) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return; + } + + if (!list[0].canConvert()) { + return; + } + + auto mat = list[0].value(); + mat.shininess = fromPercent(s); + list[0] = QVariant::fromValue(mat); + setValue(list); +} + +int PropertyMaterialListItem::getTransparency() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return 0; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return 0; + } + + if (!list[0].canConvert()) { + return 0; + } + + auto mat = list[0].value(); + return toPercent(mat.transparency); +} + +void PropertyMaterialListItem::setTransparency(int t) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return; + } + + if (!list[0].canConvert()) { + return; + } + + auto mat = list[0].value(); + mat.transparency = fromPercent(t); + list[0] = QVariant::fromValue(mat); + setValue(list); +} + +QVariant PropertyMaterialListItem::decoration(const QVariant& value) const +{ + if (!value.canConvert()) { + return {}; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return {}; + } + + if (!list[0].canConvert()) { + return {}; + } + + // use the diffuse color + auto mat = list[0].value(); + QColor color = mat.diffuseColor; + + int size = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize); + QPixmap p(size, size); + p.fill(color); + + return QVariant(p); +} + +QVariant PropertyMaterialListItem::toString(const QVariant& prop) const +{ + if (!prop.canConvert()) { + return {}; + } + + QVariantList list = prop.toList(); + if (list.isEmpty()) { + return {}; + } + + if (!list[0].canConvert()) { + return {}; + } + + // use the diffuse color + auto mat = list[0].value(); + QColor value = mat.diffuseColor; + QString color = + QString::fromLatin1("[%1, %2, %3]").arg(value.red()).arg(value.green()).arg(value.blue()); + return {color}; +} + +QVariant PropertyMaterialListItem::toolTip(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const std::vector& values = + static_cast(prop)->getValues(); + if (values.empty()) { + return {}; + } + + App::Material value = values.front(); + auto dc = value.diffuseColor.asValue(); + auto ac = value.ambientColor.asValue(); + auto sc = value.specularColor.asValue(); + auto ec = value.emissiveColor.asValue(); + + QString data = QString::fromUtf8("Diffuse color: [%1, %2, %3]\n" + "Ambient color: [%4, %5, %6]\n" + "Specular color: [%7, %8, %9]\n" + "Emissive color: [%10, %11, %12]\n" + "Shininess: %13\n" + "Transparency: %14") + .arg(dc.red()) + .arg(dc.green()) + .arg(dc.blue()) + .arg(ac.red()) + .arg(ac.green()) + .arg(ac.blue()) + .arg(sc.red()) + .arg(sc.green()) + .arg(sc.blue()) + .arg(ec.red()) + .arg(ec.green()) + .arg(ec.blue()) + .arg(toPercent(value.shininess)) + .arg(toPercent(value.transparency)); + + return {data}; +} + +QVariant PropertyMaterialListItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + const std::vector& value = + static_cast(prop)->getValues(); + QVariantList variantList; + + for (const auto& it : value) { + Material mat; + mat.diffuseColor = it.diffuseColor.asValue(); + mat.ambientColor = it.ambientColor.asValue(); + mat.specularColor = it.specularColor.asValue(); + mat.emissiveColor = it.emissiveColor.asValue(); + mat.shininess = it.shininess; + mat.transparency = it.transparency; + + variantList << QVariant::fromValue(mat); + } + + return variantList; +} + +void PropertyMaterialListItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return; + } + + // Setting an appearance using the property editor resets the + // per-face appearance + list = list.mid(0, 1); + + QString data; + QTextStream str(&data); + str << "("; + + auto mat = list[0].value(); + App::Color dc; + dc.setValue(mat.diffuseColor); + App::Color ac; + ac.setValue(mat.ambientColor); + App::Color sc; + sc.setValue(mat.specularColor); + App::Color ec; + ec.setValue(mat.emissiveColor); + float s = mat.shininess; + float t = mat.transparency; + + QString item = QString::fromLatin1("App.Material(" + "DiffuseColor=(%1,%2,%3)," + "AmbientColor=(%4,%5,%6)," + "SpecularColor=(%7,%8,%9)," + "EmissiveColor=(%10,%11,%12)," + "Shininess=(%13)," + "Transparency=(%14)," + ")") + .arg(dc.r, 0, 'f', decimals()) + .arg(dc.g, 0, 'f', decimals()) + .arg(dc.b, 0, 'f', decimals()) + .arg(ac.r, 0, 'f', decimals()) + .arg(ac.g, 0, 'f', decimals()) + .arg(ac.b, 0, 'f', decimals()) + .arg(sc.r, 0, 'f', decimals()) + .arg(sc.g, 0, 'f', decimals()) + .arg(sc.b, 0, 'f', decimals()) + .arg(ec.r, 0, 'f', decimals()) + .arg(ec.g, 0, 'f', decimals()) + .arg(ec.b, 0, 'f', decimals()) + .arg(s, 0, 'f', decimals()) + .arg(t, 0, 'f', decimals()); + str << item << ")"; + + setPropertyValue(data); +} + +QWidget* PropertyMaterialListItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto cb = new Gui::ColorButton(parent); + cb->setDisabled(isReadOnly()); + QObject::connect(cb, &Gui::ColorButton::changed, method); + return cb; +} + +void PropertyMaterialListItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + if (!data.canConvert()) { + return; + } + + QVariantList list = data.toList(); + if (list.isEmpty()) { + return; + } + + if (!list[0].canConvert()) { + return; + } + + // use the diffuse color + auto mat = list[0].value(); + QColor color = mat.diffuseColor; + + auto cb = qobject_cast(editor); + cb->setColor(color); +} + +QVariant PropertyMaterialListItem::editorData(QWidget* editor) const +{ + auto cb = qobject_cast(editor); + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) { + return {}; + } + + QVariantList list = value.toList(); + if (list.isEmpty()) { + return {}; + } + + if (!list[0].canConvert()) { + return {}; + } + + // use the diffuse color + auto mat = list[0].value(); + mat.diffuseColor = cb->color(); + list[0] = QVariant::fromValue(mat); + + return list; +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFileItem) + +PropertyFileItem::PropertyFileItem() = default; + +QVariant PropertyFileItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + std::string value = static_cast(prop)->getValue(); + return {QString::fromUtf8(value.c_str())}; +} + +void PropertyFileItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + QString val = value.toString(); + QString data = QString::fromLatin1("\"%1\"").arg(val); + setPropertyValue(data); +} + +QVariant PropertyFileItem::toolTip(const App::Property* prop) const +{ + return value(prop); +} + +QWidget* PropertyFileItem::createEditor(QWidget* parent, const std::function& method) const +{ + auto fc = new Gui::FileChooser(parent); + fc->setAutoFillBackground(true); + fc->setDisabled(isReadOnly()); + QObject::connect(fc, &Gui::FileChooser::fileNameSelected, method); + return fc; +} + +void PropertyFileItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + const App::Property* prop = getFirstProperty(); + if (const auto propFile = dynamic_cast(prop)) { + std::string filter = propFile->getFilter(); + auto fc = qobject_cast(editor); + if (!filter.empty()) { + fc->setFilter(Base::Tools::fromStdString(filter)); + } + fc->setFileName(data.toString()); + } +} + +QVariant PropertyFileItem::editorData(QWidget* editor) const +{ + auto fc = qobject_cast(editor); + return {fc->fileName()}; +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyPathItem) + +PropertyPathItem::PropertyPathItem() = default; + +QVariant PropertyPathItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + std::string value = static_cast(prop)->getValue().string(); + return {QString::fromUtf8(value.c_str())}; +} + +void PropertyPathItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + + QString val = value.toString(); + QString data = QString::fromLatin1("\"%1\"").arg(val); + setPropertyValue(data); +} + +QVariant PropertyPathItem::toolTip(const App::Property* prop) const +{ + return value(prop); +} + +QWidget* PropertyPathItem::createEditor(QWidget* parent, const std::function& method) const +{ + auto fc = new Gui::FileChooser(parent); + fc->setMode(FileChooser::Directory); + fc->setAutoFillBackground(true); + fc->setDisabled(isReadOnly()); + QObject::connect(fc, &Gui::FileChooser::fileNameSelected, method); + return fc; +} + +void PropertyPathItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto fc = qobject_cast(editor); + fc->setFileName(data.toString()); +} + +QVariant PropertyPathItem::editorData(QWidget* editor) const +{ + auto fc = qobject_cast(editor); + return {fc->fileName()}; +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyTransientFileItem) + +PropertyTransientFileItem::PropertyTransientFileItem() = default; + +QVariant PropertyTransientFileItem::value(const App::Property* prop) const +{ + assert(prop && prop->isDerivedFrom()); + + std::string value = static_cast(prop)->getValue(); + return {QString::fromUtf8(value.c_str())}; +} + +void PropertyTransientFileItem::setValue(const QVariant& value) +{ + if (hasExpression() || !value.canConvert()) { + return; + } + + QString val = value.toString(); + QString data = QString::fromLatin1("\"%1\"").arg(val); + setPropertyValue(data); +} + +QVariant PropertyTransientFileItem::toolTip(const App::Property* prop) const +{ + return value(prop); +} + +QWidget* PropertyTransientFileItem::createEditor(QWidget* parent, + const std::function& method) const +{ + auto fc = new Gui::FileChooser(parent); + fc->setAutoFillBackground(true); + fc->setDisabled(isReadOnly()); + QObject::connect(fc, &Gui::FileChooser::fileNameSelected, method); + return fc; +} + +void PropertyTransientFileItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + auto fc = qobject_cast(editor); + fc->setFileName(data.toString()); + + const auto prop = dynamic_cast(getFirstProperty()); + + if (prop) { + std::string filter = prop->getFilter(); + if (!filter.empty()) { + fc->setFilter(QString::fromStdString(filter)); + } + } +} + +QVariant PropertyTransientFileItem::editorData(QWidget* editor) const +{ + auto fc = qobject_cast(editor); + return {fc->fileName()}; +} + +// --------------------------------------------------------------- + +LinkSelection::LinkSelection(App::SubObjectT link) + : link(std::move(link)) +{} + +LinkSelection::~LinkSelection() = default; + +void LinkSelection::select() +{ + auto sobj = link.getSubObject(); + if (!sobj) { + QMessageBox::critical(getMainWindow(), tr("Error"), tr("Object not found")); + return; + } + Gui::Selection().selStackPush(); + Gui::Selection().clearSelection(); + Gui::Selection().addSelection(link.getDocumentName().c_str(), + link.getObjectName().c_str(), + link.getSubName().c_str()); + this->deleteLater(); +} + +// --------------------------------------------------------------- + +LinkLabel::LinkLabel(QWidget* parent, const App::Property* prop) + : QWidget(parent) + , objProp(prop) + , dlg(nullptr) +{ + auto layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(1); + + label = new QLabel(this); + label->setAutoFillBackground(true); + label->setTextFormat(Qt::RichText); + // Below is necessary for the hytperlink to be clickable without losing focus + label->setTextInteractionFlags(Qt::TextBrowserInteraction); + layout->addWidget(label); + + editButton = new QPushButton(QLatin1String("..."), this); +#if defined(Q_OS_MACOS) + editButton->setAttribute( + Qt::WA_LayoutUsesWidgetRect); // layout size from QMacStyle was not correct +#endif + editButton->setToolTip(tr("Change the linked object")); + layout->addWidget(editButton); + + this->setFocusPolicy(Qt::StrongFocus); + this->setFocusProxy(label); + + // setLayout(layout); + + connect(label, &QLabel::linkActivated, this, &LinkLabel::onLinkActivated); + connect(editButton, &QPushButton::clicked, this, &LinkLabel::onEditClicked); +} + +LinkLabel::~LinkLabel() = default; + +void LinkLabel::updatePropertyLink() +{ + QString text; + auto owner = objProp.getObject(); + auto prop = Base::freecad_dynamic_cast(objProp.getProperty()); + + link = QVariant(); + + if (owner && prop) { + auto links = DlgPropertyLink::getLinksFromProperty(prop); + if (links.size() == 1) { + auto& sobj = links.front(); + link = QVariant::fromValue(sobj); + QString linkcolor = QApplication::palette().color(QPalette::Link).name(); + text = QString::fromLatin1("" + "

" + "%5" + "

") + .arg(QLatin1String(sobj.getDocumentName().c_str()), + QLatin1String(sobj.getObjectName().c_str()), + QString::fromUtf8(sobj.getSubName().c_str()), + linkcolor, + DlgPropertyLink::formatObject(owner->getDocument(), + sobj.getObject(), + sobj.getSubName().c_str())); + } + else if (!links.empty()) { + text = DlgPropertyLink::formatLinks(owner->getDocument(), links); + } + } + label->setText(text); +} + +QVariant LinkLabel::propertyLink() const +{ + return link; +} + +void LinkLabel::onLinkActivated(const QString& s) +{ + Q_UNUSED(s); + auto select = new LinkSelection(qvariant_cast(link)); + QTimer::singleShot(50, select, &LinkSelection::select); // NOLINT +} + +void LinkLabel::onEditClicked() +{ + if (!dlg) { + dlg = new DlgPropertyLink(this); + dlg->init(objProp, true); + connect(dlg, &DlgPropertyLink::accepted, this, &LinkLabel::onLinkChanged); + } + else { + dlg->init(objProp, false); + } + + dlg->show(); +} + +void LinkLabel::onLinkChanged() +{ + if (dlg) { + auto links = dlg->currentLinks(); + if (links != dlg->originalLinks()) { + link = QVariant::fromValue(links); + Q_EMIT linkChanged(link); + updatePropertyLink(); + } + } +} + +void LinkLabel::resizeEvent(QResizeEvent* e) +{ + editButton->setFixedWidth(e->size().height()); +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyLinkItem) + +PropertyLinkItem::PropertyLinkItem() = default; + +QVariant PropertyLinkItem::toString(const QVariant& prop) const +{ + QString res; + if (!propertyItems.empty()) { + App::DocumentObjectT owner(propertyItems[0]); + res = DlgPropertyLink::formatLinks(owner.getDocument(), + qvariant_cast>(prop)); + } + return res; +} + +QVariant PropertyLinkItem::data(int column, int role) const +{ + if (!propertyItems.empty() && column == 1 + && (role == Qt::ForegroundRole || role == Qt::ToolTipRole)) { + if (auto propLink = dynamic_cast(propertyItems[0])) { + if (role == Qt::ForegroundRole && propLink->checkRestore() > 1) { + return QVariant::fromValue(QColor(0xff, 0, 0)); // NOLINT + } + if (role == Qt::ToolTipRole) { + if (auto xlink = dynamic_cast(propertyItems[0])) { + const char* filePath = xlink->getFilePath(); + if (filePath && filePath[0]) { + return QVariant::fromValue(QString::fromUtf8(filePath)); + } + } + } + } + } + + return PropertyItem::data(column, role); +} + +QVariant PropertyLinkItem::value(const App::Property* prop) const +{ + auto propLink = Base::freecad_dynamic_cast(prop); + if (!propLink) { + return {}; + } + + auto links = DlgPropertyLink::getLinksFromProperty(propLink); + if (links.empty()) { + return {}; + } + + return QVariant::fromValue(links); +} + +void PropertyLinkItem::setValue(const QVariant& value) +{ + auto links = qvariant_cast>(value); + setPropertyValue(DlgPropertyLink::linksToPython(links)); +} + +QWidget* PropertyLinkItem::createEditor(QWidget* parent, const std::function& method) const +{ + if (propertyItems.empty()) { + return nullptr; + } + auto ll = new LinkLabel(parent, propertyItems.front()); + ll->setAutoFillBackground(true); + ll->setDisabled(isReadOnly()); + QObject::connect(ll, &LinkLabel::linkChanged, method); + return ll; +} + +void PropertyLinkItem::setEditorData(QWidget* editor, const QVariant& data) const +{ + (void)data; + auto ll = dynamic_cast(editor); + return ll->updatePropertyLink(); +} + +QVariant PropertyLinkItem::editorData(QWidget* editor) const +{ + auto ll = dynamic_cast(editor); + return ll->propertyLink(); +} + +// -------------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyLinkListItem) + +PropertyLinkListItem::PropertyLinkListItem() = default; + +PropertyItemEditorFactory::PropertyItemEditorFactory() = default; + +PropertyItemEditorFactory::~PropertyItemEditorFactory() = default; + +QWidget* PropertyItemEditorFactory::createEditor(int /*type*/, QWidget* /*parent*/) const +{ + // do not allow to create any editor widgets because we do that in subclasses of PropertyItem + return nullptr; +} + +QByteArray PropertyItemEditorFactory::valuePropertyName(int /*type*/) const +{ + // do not allow to set properties because we do that in subclasses of PropertyItem + return ""; +} +// NOLINTEND(cppcoreguidelines-pro-*,cppcoreguidelines-prefer-member-initializer) + +#include "moc_PropertyItem.cpp" diff --git a/src/Gui/propertyeditor/PropertyItem.h b/src/Gui/propertyeditor/PropertyItem.h index 54d9ca860d..682826232b 100644 --- a/src/Gui/propertyeditor/PropertyItem.h +++ b/src/Gui/propertyeditor/PropertyItem.h @@ -1,1272 +1,1304 @@ -/*************************************************************************** - * Copyright (c) 2004 Werner Mayer * - * * - * 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 PROPERTYEDITORITEM_H -#define PROPERTYEDITORITEM_H - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#ifdef Q_MOC_RUN -Q_DECLARE_METATYPE(Base::Vector3f) -Q_DECLARE_METATYPE(Base::Vector3d) -Q_DECLARE_METATYPE(QList) -Q_DECLARE_METATYPE(Base::Matrix4D) -Q_DECLARE_METATYPE(Base::Placement) -Q_DECLARE_METATYPE(Base::Rotation) -Q_DECLARE_METATYPE(Base::Quantity) -Q_DECLARE_METATYPE(QList) -#endif - - -#define PROPERTYITEM_HEADER \ -public: \ - static void *create(void); \ - static void init(void); - -#define PROPERTYITEM_SOURCE(_class_) \ -void * _class_::create(void) { \ - return new _class_ ();\ -} \ -void _class_::init(void) { \ - (void)new Gui::PropertyEditor::PropertyItemProducer<_class_>(#_class_); \ -} - -namespace Gui { - -namespace Dialog { -class TaskPlacement; -class DlgPropertyLink; -} - -namespace PropertyEditor { - -class PropertyItem; -class PropertyModel; -class PropertyEditorWidget; - -/** - * The PropertyItemFactory provides methods for the dynamic creation of property items. - * \author Werner Mayer - */ -class GuiExport PropertyItemFactory : public Base::Factory -{ -public: - static PropertyItemFactory& instance(); - - PropertyItem* createPropertyItem (const char* sName) const; - -private: - PropertyItemFactory() = default; - ~PropertyItemFactory() override = default; -}; - -template -class PropertyItemProducer : public Base::AbstractProducer -{ -public: - explicit PropertyItemProducer(const char* className) { - PropertyItemFactory::instance().AddProducer(className, this); - } - ~PropertyItemProducer() override = default; - void* Produce () const override { - return CLASS::create(); - } -}; - -class PropertyItemAttorney { -public: - static QVariant toString(PropertyItem* item, const QVariant& v); -}; - -class GuiExport PropertyItem : public QObject, public ExpressionBinding -{ - Q_OBJECT - PROPERTYITEM_HEADER - -public: - ~PropertyItem() override; - - /** Sets the current property objects. */ - void setPropertyData( const std::vector& ); - void updateData(); - const std::vector& getPropertyData() const; - bool hasProperty(const App::Property*) const; - virtual void assignProperty(const App::Property*); - bool removeProperty(const App::Property*); - App::Property* getFirstProperty(); - const App::Property* getFirstProperty() const; - - /** Creates the appropriate editor for this item and sets the editor to the value of overrideValue(). */ - virtual QWidget* createEditor(QWidget* parent, const std::function& method) const; - virtual void setEditorData(QWidget *editor, const QVariant& data) const; - virtual QVariant editorData(QWidget *editor) const; - virtual bool isSeparator() const { return false; } - - QWidget* createExpressionEditor(QWidget* parent, const std::function& method) const; - void setExpressionEditorData(QWidget *editor, const QVariant& data) const; - QVariant expressionEditorData(QWidget *editor) const; - - PropertyEditorWidget* createPropertyEditorWidget(QWidget* parent) const; - - /**override the bind functions to ensure we issue the propertyBound() call, which is then overloaded by - childs which like to be informed of a binding*/ - void bind(const App::Property& prop) override; - void bind(const App::ObjectIdentifier& _path) override; - virtual void propertyBound() {} - QString expressionAsString() const; - - void setParent(PropertyItem* parent); - PropertyItem *parent() const; - void appendChild(PropertyItem *child); - void insertChild(int, PropertyItem *child); - void moveChild(int from, int to); - void removeChildren(int from, int to); - PropertyItem *takeChild(int); - - void setReadOnly(bool); - bool isReadOnly() const; - bool testStatus(App::Property::Status pos) const; - void setDecimals(int); - int decimals() const; - - void setLinked(bool); - bool isLinked() const; - - bool isExpanded() const; - void setExpanded(bool e); - - PropertyItem *child(int row); - int childCount() const; - int columnCount() const; - QString propertyName() const; - void setPropertyName(const QString& name, const QString& realName=QString()); - void setPropertyValue(const QString&); - virtual QVariant data(int column, int role) const; - bool setData (const QVariant& value); - Qt::ItemFlags flags(int column) const; - virtual int row() const; - void reset(); - - bool hasAnyExpression() const; - -protected: - PropertyItem(); - - virtual QVariant displayName() const; - virtual QVariant decoration(const QVariant&) const; - virtual QVariant toolTip(const App::Property*) const; - virtual QVariant toString(const QVariant&) const; - virtual QVariant value(const App::Property*) const; - virtual void setValue(const QVariant&); - virtual void initialize(); - - //gets called when the bound expression is changed - void onChange() override; - -private: - QVariant dataProperty(int role) const; - QVariant dataValue(int role) const; - QString toString(const Py::Object&) const; - QString asNone(const Py::Object&) const; - QString asString(const Py::Object&) const; - QString asSequence(const Py::Object&) const; - QString asMapping(const Py::Object&) const; - -protected: - QString propName; - QString displayText; - std::vector propertyItems; - PropertyItem *parentItem; - QList childItems; - bool readonly; - int precision; - bool linked; - bool expanded; - - friend class PropertyItemAttorney; -}; - -/** - * Change a string property. - * \author Werner Mayer - */ -class GuiExport PropertyStringItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyStringItem(); -}; - -/** - * Change a font property. - * \author Werner Mayer - */ -class GuiExport PropertyFontItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyFontItem(); -}; - -/** - * Dummy property to separate groups of properties. - * \author Werner Mayer - */ -class GuiExport PropertySeparatorItem : public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - bool isSeparator() const override { return true; } - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - - int row() const override { - return _row<0?PropertyItem::row():_row; - } - -private: - friend PropertyModel; - int _row = -1; -}; - -/** - * Change a number. - * \author Werner Mayer - */ -class GuiExport PropertyIntegerItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyIntegerItem(); -}; - -/** - * Change a number with constraints. - * \author Werner Mayer - */ -class GuiExport PropertyIntegerConstraintItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - - void setRange(int min, int max) - { - this->min = min; - this->max = max; - } - - void setStepSize(int steps) - { - this->steps = steps; - } - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyIntegerConstraintItem(); - -private: - int min = INT_MIN; - int max = INT_MAX; - int steps = 1; -}; - -/** - * Change a floating point number. - * \author Werner Mayer - */ -class GuiExport PropertyFloatItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyFloatItem(); -}; - -/** - * Change a Unit based floating point number. - * \author Juergen Riegel - */ -class GuiExport PropertyUnitItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - - PropertyUnitItem(); -}; - -/** - * Change a Unit based floating point number within constraints. - * \author Stefan Troeger - */ -class GuiExport PropertyUnitConstraintItem: public PropertyUnitItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - void setEditorData(QWidget *editor, const QVariant& data) const override; - - void setRange(double min, double max) - { - this->min = min; - this->max = max; - } - - void setStepSize(double steps) - { - this->steps = steps; - } - -protected: - PropertyUnitConstraintItem(); - -private: - double min = double(INT_MIN); - double max = double(INT_MAX); - double steps = 0.1; -}; - -/** - * Change a floating point number with constraints. - * \author Werner Mayer - */ -class GuiExport PropertyFloatConstraintItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - - void setRange(double min, double max) - { - this->min = min; - this->max = max; - } - - void setStepSize(double steps) - { - this->steps = steps; - } - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyFloatConstraintItem(); - -private: - double min = double(INT_MIN); - double max = double(INT_MAX); - double steps = 0.1; -}; - -/** - * Change a floating point number with many decimal points (hard coded as 16) - */ -class GuiExport PropertyPrecisionItem: public PropertyFloatConstraintItem -{ - Q_OBJECT - PROPERTYITEM_HEADER -protected: - PropertyPrecisionItem(); -}; - -/** - * Change a floating point number. - * \author Werner Mayer - */ -class GuiExport PropertyAngleItem : public PropertyUnitConstraintItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - -protected: - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant toString(const QVariant&) const override; - -protected: - PropertyAngleItem(); -}; - -/** - * Edit properties of boolean type. - * \author Werner Mayer - */ -class GuiExport PropertyBoolItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyBoolItem(); -}; - -/** - * Edit properties of vector type. - * \author Werner Mayer - */ -class PropertyFloatItem; -class GuiExport PropertyVectorItem: public PropertyItem -{ - Q_OBJECT - Q_PROPERTY(double x READ x WRITE setX DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double y READ y WRITE setY DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double z READ z WRITE setZ DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - - double x() const; - void setX(double x); - double y() const; - void setY(double y); - double z() const; - void setZ(double z); - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyVectorItem(); - void propertyBound() override; - -private: - PropertyFloatItem* m_x; - PropertyFloatItem* m_y; - PropertyFloatItem* m_z; -}; - -class PropertyEditorWidget : public QWidget -{ - Q_OBJECT - -public: - explicit PropertyEditorWidget (QWidget * parent = nullptr); - ~PropertyEditorWidget() override; - - QVariant value() const; - -public Q_SLOTS: - void setValue(const QVariant&); - -protected: - virtual void showValue(const QVariant& data); - void resizeEvent(QResizeEvent*) override; - -Q_SIGNALS: - void buttonClick(); - void valueChanged(const QVariant &); - -protected: - QVariant variant; - QLineEdit *lineEdit; - QPushButton *button; -}; - -class VectorListWidget : public PropertyEditorWidget -{ - Q_OBJECT - -public: - explicit VectorListWidget (int decimals, QWidget * parent = nullptr); - -protected: - void showValue(const QVariant& data) override; - -private Q_SLOTS: - void buttonClicked(); - -private: - int decimals; -}; - -/** - * Edit properties of vector list type. - * \author Werner Mayer - */ -class GuiExport PropertyVectorListItem : public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyVectorListItem(); -}; - -/** - * Edit properties of vector type which hold distances. - * \author Stefan Troeger - */ -class PropertyUnitItem; -class GuiExport PropertyVectorDistanceItem: public PropertyItem -{ - Q_OBJECT - Q_PROPERTY(Base::Quantity x READ x WRITE setX DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(Base::Quantity y READ y WRITE setY DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(Base::Quantity z READ z WRITE setZ DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - - void propertyBound() override; - - Base::Quantity x() const; - void setX(Base::Quantity x); - Base::Quantity y() const; - void setY(Base::Quantity y); - Base::Quantity z() const; - void setZ(Base::Quantity z); - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - - PropertyVectorDistanceItem(); - -private: - PropertyUnitItem* m_x; - PropertyUnitItem* m_y; - PropertyUnitItem* m_z; -}; - -class GuiExport PropertyPositionItem: public PropertyVectorDistanceItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - -}; - -class GuiExport PropertyDirectionItem: public PropertyVectorDistanceItem -{ - Q_OBJECT - PROPERTYITEM_HEADER -}; - -class GuiExport PropertyMatrixItem: public PropertyItem -{ - Q_OBJECT - Q_PROPERTY(double A11 READ getA11 WRITE setA11 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A12 READ getA12 WRITE setA12 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A13 READ getA13 WRITE setA13 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A14 READ getA14 WRITE setA14 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A21 READ getA21 WRITE setA21 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A22 READ getA22 WRITE setA22 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A23 READ getA23 WRITE setA23 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A24 READ getA24 WRITE setA24 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A31 READ getA31 WRITE setA31 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A32 READ getA32 WRITE setA32 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A33 READ getA33 WRITE setA33 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A34 READ getA34 WRITE setA34 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A41 READ getA41 WRITE setA41 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A42 READ getA42 WRITE setA42 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A43 READ getA43 WRITE setA43 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(double A44 READ getA44 WRITE setA44 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - - double getA11() const; - void setA11(double A11); - double getA12() const; - void setA12(double A12); - double getA13() const; - void setA13(double A13); - double getA14() const; - void setA14(double A14); - double getA21() const; - void setA21(double A21); - double getA22() const; - void setA22(double A22); - double getA23() const; - void setA23(double A23); - double getA24() const; - void setA24(double A24); - double getA31() const; - void setA31(double A31); - double getA32() const; - void setA32(double A32); - double getA33() const; - void setA33(double A33); - double getA34() const; - void setA34(double A34); - double getA41() const; - void setA41(double A41); - double getA42() const; - void setA42(double A42); - double getA43() const; - void setA43(double A43); - double getA44() const; - void setA44(double A44); - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyMatrixItem(); - QVariant toolTip(const App::Property*) const override; - -private: - PropertyFloatItem* m_a11; - PropertyFloatItem* m_a12; - PropertyFloatItem* m_a13; - PropertyFloatItem* m_a14; - PropertyFloatItem* m_a21; - PropertyFloatItem* m_a22; - PropertyFloatItem* m_a23; - PropertyFloatItem* m_a24; - PropertyFloatItem* m_a31; - PropertyFloatItem* m_a32; - PropertyFloatItem* m_a33; - PropertyFloatItem* m_a34; - PropertyFloatItem* m_a41; - PropertyFloatItem* m_a42; - PropertyFloatItem* m_a43; - PropertyFloatItem* m_a44; -}; - -class RotationHelper -{ -public: - RotationHelper(); - void setChanged(bool); - bool hasChangedAndReset(); - bool isAxisInitialized() const; - void setValue(const Base::Vector3d& axis, double angle); - void getValue(Base::Vector3d& axis, double& angle) const; - double getAngle(const Base::Rotation& val) const; - Base::Rotation setAngle(double); - Base::Vector3d getAxis() const; - Base::Rotation setAxis(const Base::Rotation& value, const Base::Vector3d& axis); - void assignProperty(const Base::Rotation& value, double eps); - -private: - bool init_axis; - bool changed_value; - double rot_angle; - Base::Vector3d rot_axis; -}; - -/** - * Edit properties of rotation type. - * \author Werner Mayer - */ -class GuiExport PropertyRotationItem: public PropertyItem -{ - Q_OBJECT - Q_PROPERTY(Base::Quantity Angle READ getAngle WRITE setAngle DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(Base::Vector3d Axis READ getAxis WRITE setAxis DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - - void propertyBound() override; - void assignProperty(const App::Property*) override; - - Base::Quantity getAngle() const; - void setAngle(Base::Quantity); - Base::Vector3d getAxis() const; - void setAxis(const Base::Vector3d&); - -protected: - PropertyRotationItem(); - ~PropertyRotationItem() override; - QVariant toolTip(const App::Property*) const override; - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -private: - mutable RotationHelper h; - PropertyUnitItem * m_a; - PropertyVectorItem* m_d; -}; - -class PlacementEditor : public Gui::LabelButton -{ - Q_OBJECT - -public: - explicit PlacementEditor(QString name, QWidget * parent = nullptr); - ~PlacementEditor() override; - -private Q_SLOTS: - void updateValue(const QVariant& v, bool, bool); - -private: - void browse() override; - void showValue(const QVariant& d) override; - -private: - QPointer _task; - QString propertyname; -}; - -/** - * Edit properties of placement type. - * \author Werner Mayer - */ -class GuiExport PropertyPlacementItem: public PropertyItem -{ - Q_OBJECT - Q_PROPERTY(Base::Quantity Angle READ getAngle WRITE setAngle DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(Base::Vector3d Axis READ getAxis WRITE setAxis DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(Base::Vector3d Position READ getPosition WRITE setPosition DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - - void propertyBound() override; - void assignProperty(const App::Property*) override; - - Base::Quantity getAngle() const; - void setAngle(Base::Quantity); - Base::Vector3d getAxis() const; - void setAxis(const Base::Vector3d&); - Base::Vector3d getPosition() const; - void setPosition(const Base::Vector3d&); - -protected: - PropertyPlacementItem(); - ~PropertyPlacementItem() override; - QVariant toolTip(const App::Property*) const override; - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -private: - mutable RotationHelper h; - PropertyUnitItem * m_a; - PropertyVectorItem* m_d; - PropertyVectorDistanceItem* m_p; -}; - -class PropertyStringListItem; - -/** - * Edit properties of enum type. - * \author Werner Mayer - */ -class GuiExport PropertyEnumItem: public PropertyItem -{ - Q_OBJECT - Q_PROPERTY(QStringList Enum READ getEnum WRITE setEnum DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - - QStringList getEnum() const; - void setEnum(const QStringList&); - -private: - QStringList getCommonModes() const; - -protected: - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - void propertyBound() override; - -protected: - PropertyEnumItem(); - -private: - PropertyStringListItem* m_enum; -}; - -class PropertyEnumButton : public QPushButton -{ - Q_OBJECT -public: - explicit PropertyEnumButton(QWidget *parent = nullptr) - :QPushButton(parent) - {} - -Q_SIGNALS: - void picked(); -}; - -/** - * Edit properties of string list type. - * \author Werner Mayer - */ -class GuiExport PropertyStringListItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyStringListItem(); -}; - -/** - * Edit properties of float list type. - * \author Werner Mayer - */ -class GuiExport PropertyFloatListItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyFloatListItem(); -}; - -/** - * Edit properties of float list type. - * \author Werner Mayer - */ -class GuiExport PropertyIntegerListItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyIntegerListItem(); -}; - -/** - * Change a color property. - * \author Werner Mayer - */ -class GuiExport PropertyColorItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant decoration(const QVariant&) const override; - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyColorItem(); -}; - -/** -* Change a material property. -* \author Werner Mayer -*/ -class GuiExport PropertyMaterialItem : public PropertyItem -{ - Q_OBJECT - Q_PROPERTY(QColor AmbientColor READ getAmbientColor WRITE setAmbientColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(QColor DiffuseColor READ getDiffuseColor WRITE setDiffuseColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(QColor SpecularColor READ getSpecularColor WRITE setSpecularColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(QColor EmissiveColor READ getEmissiveColor WRITE setEmissiveColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(float Shininess READ getShininess WRITE setShininess DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(float Transparency READ getTransparency WRITE setTransparency DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - - void propertyBound() override; - - QColor getAmbientColor() const; - void setAmbientColor(const QColor&); - QColor getDiffuseColor() const; - void setDiffuseColor(const QColor&); - QColor getSpecularColor() const; - void setSpecularColor(const QColor&); - QColor getEmissiveColor() const; - void setEmissiveColor(const QColor&); - int getShininess() const; - void setShininess(int); - int getTransparency() const; - void setTransparency(int); - -protected: - PropertyMaterialItem(); - ~PropertyMaterialItem() override; - - QVariant decoration(const QVariant&) const override; - QVariant toolTip(const App::Property*) const override; - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -private: - PropertyColorItem* ambient; - PropertyColorItem* diffuse; - PropertyColorItem* specular; - PropertyColorItem* emissive; - PropertyIntegerConstraintItem* shininess; - PropertyIntegerConstraintItem* transparency; -}; - -class GuiExport PropertyMaterialListItem : public PropertyItem -{ - Q_OBJECT - Q_PROPERTY(QColor AmbientColor READ getAmbientColor WRITE setAmbientColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(QColor DiffuseColor READ getDiffuseColor WRITE setDiffuseColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(QColor SpecularColor READ getSpecularColor WRITE setSpecularColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(QColor EmissiveColor READ getEmissiveColor WRITE setEmissiveColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(float Shininess READ getShininess WRITE setShininess DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - Q_PROPERTY(float Transparency READ getTransparency WRITE setTransparency DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - - void propertyBound() override; - - QColor getAmbientColor() const; - void setAmbientColor(const QColor&); - QColor getDiffuseColor() const; - void setDiffuseColor(const QColor&); - QColor getSpecularColor() const; - void setSpecularColor(const QColor&); - QColor getEmissiveColor() const; - void setEmissiveColor(const QColor&); - int getShininess() const; - void setShininess(int); - int getTransparency() const; - void setTransparency(int); - -protected: - PropertyMaterialListItem(); - ~PropertyMaterialListItem() override; - - QVariant decoration(const QVariant&) const override; - QVariant toolTip(const App::Property*) const override; - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -private: - PropertyColorItem* ambient; - PropertyColorItem* diffuse; - PropertyColorItem* specular; - PropertyColorItem* emissive; - PropertyIntegerConstraintItem* shininess; - PropertyIntegerConstraintItem* transparency; -}; - -/** - * Change a file. - * \author Werner Mayer - */ -class GuiExport PropertyFileItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyFileItem(); - QVariant toolTip(const App::Property*) const override; -}; - -/** - * Change a path. - * \author Werner Mayer - */ -class GuiExport PropertyPathItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyPathItem(); - QVariant toolTip(const App::Property*) const override; -}; - -/** - * Show path of included file. - * \author Werner Mayer - */ -class GuiExport PropertyTransientFileItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - -protected: - PropertyTransientFileItem(); - QVariant toolTip(const App::Property*) const override; -}; - -class LinkSelection : public QObject -{ - Q_OBJECT - -public: - explicit LinkSelection(App::SubObjectT); - ~LinkSelection() override; - -public Q_SLOTS: - void select(); - -private: - App::SubObjectT link; -}; - - -class LinkLabel : public QWidget -{ - Q_OBJECT - -public: - LinkLabel (QWidget * parent, const App::Property *prop); - ~LinkLabel() override; - void updatePropertyLink(); - QVariant propertyLink() const; - -protected: - void resizeEvent(QResizeEvent*) override; - -protected Q_SLOTS: - void onLinkActivated(const QString&); - void onEditClicked(); - void onLinkChanged(); - -Q_SIGNALS: - void linkChanged(const QVariant&); - -private: - QLabel* label; - QPushButton* editButton; - QVariant link; - App::DocumentObjectT objProp; - - Gui::Dialog::DlgPropertyLink* dlg; -}; - -/** - * Edit properties of link type. - * \author Werner Mayer - */ -class GuiExport PropertyLinkItem: public PropertyItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - - QWidget* createEditor(QWidget* parent, const std::function& method) const override; - void setEditorData(QWidget *editor, const QVariant& data) const override; - QVariant editorData(QWidget *editor) const override; - -protected: - QVariant toString(const QVariant&) const override; - QVariant value(const App::Property*) const override; - void setValue(const QVariant&) override; - QVariant data(int column, int role) const override; - -protected: - PropertyLinkItem(); -}; - -/** - * Edit properties of link list type. - * \author Werner Mayer - */ -class GuiExport PropertyLinkListItem: public PropertyLinkItem -{ - Q_OBJECT - PROPERTYITEM_HEADER - -protected: - PropertyLinkListItem(); -}; - -class PropertyItemEditorFactory : public QItemEditorFactory -{ -public: - PropertyItemEditorFactory(); - ~PropertyItemEditorFactory() override; - - QWidget *createEditor(int userType, QWidget *parent) const override; - QByteArray valuePropertyName(int userType) const override; -}; - -} // namespace PropertyEditor -} // namespace Gui - -#endif // PROPERTYEDITORITEM_H +/*************************************************************************** + * Copyright (c) 2004 Werner Mayer * + * * + * 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 PROPERTYEDITORITEM_H +#define PROPERTYEDITORITEM_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef Q_MOC_RUN +Q_DECLARE_METATYPE(Base::Vector3f) +Q_DECLARE_METATYPE(Base::Vector3d) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(Base::Matrix4D) +Q_DECLARE_METATYPE(Base::Placement) +Q_DECLARE_METATYPE(Base::Rotation) +Q_DECLARE_METATYPE(Base::Quantity) +Q_DECLARE_METATYPE(QList) +#endif + + +#define PROPERTYITEM_HEADER \ +public: \ + static void* create(void); \ + static void init(void); + +#define PROPERTYITEM_SOURCE(_class_) \ + void* _class_::create(void) \ + { \ + return new _class_(); \ + } \ + void _class_::init(void) \ + { \ + (void)new Gui::PropertyEditor::PropertyItemProducer<_class_>(#_class_); \ + } + +namespace Gui +{ + +namespace Dialog +{ +class TaskPlacement; +class DlgPropertyLink; +} // namespace Dialog + +namespace PropertyEditor +{ + +class PropertyItem; +class PropertyModel; +class PropertyEditorWidget; + +/** + * The PropertyItemFactory provides methods for the dynamic creation of property items. + * \author Werner Mayer + */ +class GuiExport PropertyItemFactory: public Base::Factory +{ +public: + static PropertyItemFactory& instance(); + + PropertyItem* createPropertyItem(const char* sName) const; + +private: + PropertyItemFactory() = default; + ~PropertyItemFactory() override = default; +}; + +template +class PropertyItemProducer: public Base::AbstractProducer +{ +public: + explicit PropertyItemProducer(const char* className) + { + PropertyItemFactory::instance().AddProducer(className, this); + } + ~PropertyItemProducer() override = default; + void* Produce() const override + { + return CLASS::create(); + } +}; + +class PropertyItemAttorney +{ +public: + static QVariant toString(PropertyItem* item, const QVariant& v); +}; + +class GuiExport PropertyItem: public QObject, public ExpressionBinding +{ + Q_OBJECT + PROPERTYITEM_HEADER + +public: + ~PropertyItem() override; + + /** Sets the current property objects. */ + void setPropertyData(const std::vector&); + void updateData(); + const std::vector& getPropertyData() const; + bool hasProperty(const App::Property*) const; + virtual void assignProperty(const App::Property*); + bool removeProperty(const App::Property*); + App::Property* getFirstProperty(); + const App::Property* getFirstProperty() const; + + /** Creates the appropriate editor for this item and sets the editor to the value of + * overrideValue(). */ + virtual QWidget* createEditor(QWidget* parent, const std::function& method) const; + virtual void setEditorData(QWidget* editor, const QVariant& data) const; + virtual QVariant editorData(QWidget* editor) const; + virtual bool isSeparator() const + { + return false; + } + + QWidget* createExpressionEditor(QWidget* parent, const std::function& method) const; + void setExpressionEditorData(QWidget* editor, const QVariant& data) const; + QVariant expressionEditorData(QWidget* editor) const; + + PropertyEditorWidget* createPropertyEditorWidget(QWidget* parent) const; + + /**override the bind functions to ensure we issue the propertyBound() call, which is then + overloaded by childs which like to be informed of a binding*/ + void bind(const App::Property& prop) override; + void bind(const App::ObjectIdentifier& _path) override; + virtual void propertyBound() + {} + QString expressionAsString() const; + + void setParent(PropertyItem* parent); + PropertyItem* parent() const; + void appendChild(PropertyItem* child); + void insertChild(int, PropertyItem* child); + void moveChild(int from, int to); + void removeChildren(int from, int to); + PropertyItem* takeChild(int); + + void setReadOnly(bool); + bool isReadOnly() const; + bool testStatus(App::Property::Status pos) const; + void setDecimals(int); + int decimals() const; + + void setLinked(bool); + bool isLinked() const; + + bool isExpanded() const; + void setExpanded(bool e); + + PropertyItem* child(int row); + int childCount() const; + int columnCount() const; + QString propertyName() const; + void setPropertyName(const QString& name, const QString& realName = QString()); + void setPropertyValue(const QString&); + virtual QVariant data(int column, int role) const; + bool setData(const QVariant& value); + Qt::ItemFlags flags(int column) const; + virtual int row() const; + void reset(); + + bool hasAnyExpression() const; + +protected: + PropertyItem(); + + virtual QVariant displayName() const; + virtual QVariant decoration(const QVariant&) const; + virtual QVariant toolTip(const App::Property*) const; + virtual QVariant toString(const QVariant&) const; + virtual QVariant value(const App::Property*) const; + virtual void setValue(const QVariant&); + virtual void initialize(); + + // gets called when the bound expression is changed + void onChange() override; + +private: + QVariant dataProperty(int role) const; + QVariant dataValue(int role) const; + QString toString(const Py::Object&) const; + QString asNone(const Py::Object&) const; + QString asString(const Py::Object&) const; + QString asSequence(const Py::Object&) const; + QString asMapping(const Py::Object&) const; + +protected: + QString propName; + QString displayText; + std::vector propertyItems; + PropertyItem* parentItem; + QList childItems; + bool readonly; + int precision; + bool linked; + bool expanded; + + friend class PropertyItemAttorney; +}; + +/** + * Change a string property. + * \author Werner Mayer + */ +class GuiExport PropertyStringItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyStringItem(); +}; + +/** + * Change a font property. + * \author Werner Mayer + */ +class GuiExport PropertyFontItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyFontItem(); +}; + +/** + * Dummy property to separate groups of properties. + * \author Werner Mayer + */ +class GuiExport PropertySeparatorItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + bool isSeparator() const override + { + return true; + } + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + + int row() const override + { + return _row < 0 ? PropertyItem::row() : _row; + } + +private: + friend PropertyModel; + int _row = -1; +}; + +/** + * Change a number. + * \author Werner Mayer + */ +class GuiExport PropertyIntegerItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyIntegerItem(); +}; + +/** + * Change a number with constraints. + * \author Werner Mayer + */ +class GuiExport PropertyIntegerConstraintItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + + void setRange(int min, int max) + { + this->min = min; + this->max = max; + } + + void setStepSize(int steps) + { + this->steps = steps; + } + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyIntegerConstraintItem(); + +private: + int min = INT_MIN; + int max = INT_MAX; + int steps = 1; +}; + +/** + * Change a floating point number. + * \author Werner Mayer + */ +class GuiExport PropertyFloatItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyFloatItem(); +}; + +/** + * Change a Unit based floating point number. + * \author Juergen Riegel + */ +class GuiExport PropertyUnitItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + + PropertyUnitItem(); +}; + +/** + * Change a Unit based floating point number within constraints. + * \author Stefan Troeger + */ +class GuiExport PropertyUnitConstraintItem: public PropertyUnitItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + void setEditorData(QWidget* editor, const QVariant& data) const override; + + void setRange(double min, double max) + { + this->min = min; + this->max = max; + } + + void setStepSize(double steps) + { + this->steps = steps; + } + +protected: + PropertyUnitConstraintItem(); + +private: + double min = double(INT_MIN); + double max = double(INT_MAX); + double steps = 0.1; +}; + +/** + * Change a floating point number with constraints. + * \author Werner Mayer + */ +class GuiExport PropertyFloatConstraintItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + + void setRange(double min, double max) + { + this->min = min; + this->max = max; + } + + void setStepSize(double steps) + { + this->steps = steps; + } + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyFloatConstraintItem(); + +private: + double min = double(INT_MIN); + double max = double(INT_MAX); + double steps = 0.1; +}; + +/** + * Change a floating point number with many decimal points (hard coded as 16) + */ +class GuiExport PropertyPrecisionItem: public PropertyFloatConstraintItem +{ + Q_OBJECT + PROPERTYITEM_HEADER +protected: + PropertyPrecisionItem(); +}; + +/** + * Change a floating point number. + * \author Werner Mayer + */ +class GuiExport PropertyAngleItem: public PropertyUnitConstraintItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + +protected: + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant toString(const QVariant&) const override; + +protected: + PropertyAngleItem(); +}; + +/** + * Edit properties of boolean type. + * \author Werner Mayer + */ +class GuiExport PropertyBoolItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyBoolItem(); +}; + +/** + * Edit properties of vector type. + * \author Werner Mayer + */ +class PropertyFloatItem; +class GuiExport PropertyVectorItem: public PropertyItem +{ + // clang-format off + Q_OBJECT + Q_PROPERTY(double x READ x WRITE setX DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double y READ y WRITE setY DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double z READ z WRITE setZ DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + PROPERTYITEM_HEADER + // clang-format on + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + + double x() const; + void setX(double x); + double y() const; + void setY(double y); + double z() const; + void setZ(double z); + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyVectorItem(); + void propertyBound() override; + +private: + PropertyFloatItem* m_x; + PropertyFloatItem* m_y; + PropertyFloatItem* m_z; +}; + +class PropertyEditorWidget: public QWidget +{ + Q_OBJECT + +public: + explicit PropertyEditorWidget(QWidget* parent = nullptr); + ~PropertyEditorWidget() override; + + QVariant value() const; + +public Q_SLOTS: + void setValue(const QVariant&); + +protected: + virtual void showValue(const QVariant& data); + void resizeEvent(QResizeEvent*) override; + +Q_SIGNALS: + void buttonClick(); + void valueChanged(const QVariant&); + +protected: + QVariant variant; + QLineEdit* lineEdit; + QPushButton* button; +}; + +class VectorListWidget: public PropertyEditorWidget +{ + Q_OBJECT + +public: + explicit VectorListWidget(int decimals, QWidget* parent = nullptr); + +protected: + void showValue(const QVariant& data) override; + +private Q_SLOTS: + void buttonClicked(); + +private: + int decimals; +}; + +/** + * Edit properties of vector list type. + * \author Werner Mayer + */ +class GuiExport PropertyVectorListItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyVectorListItem(); +}; + +/** + * Edit properties of vector type which hold distances. + * \author Stefan Troeger + */ +class PropertyUnitItem; +class GuiExport PropertyVectorDistanceItem: public PropertyItem +{ + // clang-format off + Q_OBJECT + Q_PROPERTY(Base::Quantity x READ x WRITE setX DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(Base::Quantity y READ y WRITE setY DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(Base::Quantity z READ z WRITE setZ DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + PROPERTYITEM_HEADER + // clang-format on + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + + void propertyBound() override; + + Base::Quantity x() const; + void setX(Base::Quantity x); + Base::Quantity y() const; + void setY(Base::Quantity y); + Base::Quantity z() const; + void setZ(Base::Quantity z); + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + + PropertyVectorDistanceItem(); + +private: + PropertyUnitItem* m_x; + PropertyUnitItem* m_y; + PropertyUnitItem* m_z; +}; + +class GuiExport PropertyPositionItem: public PropertyVectorDistanceItem +{ + Q_OBJECT + PROPERTYITEM_HEADER +}; + +class GuiExport PropertyDirectionItem: public PropertyVectorDistanceItem +{ + Q_OBJECT + PROPERTYITEM_HEADER +}; + +class GuiExport PropertyMatrixItem: public PropertyItem +{ + // clang-format off + Q_OBJECT + Q_PROPERTY(double A11 READ getA11 WRITE setA11 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A12 READ getA12 WRITE setA12 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A13 READ getA13 WRITE setA13 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A14 READ getA14 WRITE setA14 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A21 READ getA21 WRITE setA21 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A22 READ getA22 WRITE setA22 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A23 READ getA23 WRITE setA23 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A24 READ getA24 WRITE setA24 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A31 READ getA31 WRITE setA31 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A32 READ getA32 WRITE setA32 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A33 READ getA33 WRITE setA33 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A34 READ getA34 WRITE setA34 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A41 READ getA41 WRITE setA41 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A42 READ getA42 WRITE setA42 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A43 READ getA43 WRITE setA43 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(double A44 READ getA44 WRITE setA44 DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + PROPERTYITEM_HEADER + // clang-format on + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + + double getA11() const; + void setA11(double A11); + double getA12() const; + void setA12(double A12); + double getA13() const; + void setA13(double A13); + double getA14() const; + void setA14(double A14); + double getA21() const; + void setA21(double A21); + double getA22() const; + void setA22(double A22); + double getA23() const; + void setA23(double A23); + double getA24() const; + void setA24(double A24); + double getA31() const; + void setA31(double A31); + double getA32() const; + void setA32(double A32); + double getA33() const; + void setA33(double A33); + double getA34() const; + void setA34(double A34); + double getA41() const; + void setA41(double A41); + double getA42() const; + void setA42(double A42); + double getA43() const; + void setA43(double A43); + double getA44() const; + void setA44(double A44); + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyMatrixItem(); + QVariant toolTip(const App::Property*) const override; + +private: + PropertyFloatItem* m_a11; + PropertyFloatItem* m_a12; + PropertyFloatItem* m_a13; + PropertyFloatItem* m_a14; + PropertyFloatItem* m_a21; + PropertyFloatItem* m_a22; + PropertyFloatItem* m_a23; + PropertyFloatItem* m_a24; + PropertyFloatItem* m_a31; + PropertyFloatItem* m_a32; + PropertyFloatItem* m_a33; + PropertyFloatItem* m_a34; + PropertyFloatItem* m_a41; + PropertyFloatItem* m_a42; + PropertyFloatItem* m_a43; + PropertyFloatItem* m_a44; +}; + +class RotationHelper +{ +public: + RotationHelper(); + void setChanged(bool); + bool hasChangedAndReset(); + bool isAxisInitialized() const; + void setValue(const Base::Vector3d& axis, double angle); + void getValue(Base::Vector3d& axis, double& angle) const; + double getAngle(const Base::Rotation& val) const; + Base::Rotation setAngle(double); + Base::Vector3d getAxis() const; + Base::Rotation setAxis(const Base::Rotation& value, const Base::Vector3d& axis); + void assignProperty(const Base::Rotation& value, double eps); + +private: + bool init_axis; + bool changed_value; + double rot_angle; + Base::Vector3d rot_axis; +}; + +/** + * Edit properties of rotation type. + * \author Werner Mayer + */ +class GuiExport PropertyRotationItem: public PropertyItem +{ + // clang-format off + Q_OBJECT + Q_PROPERTY(Base::Quantity Angle READ getAngle WRITE setAngle DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(Base::Vector3d Axis READ getAxis WRITE setAxis DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + PROPERTYITEM_HEADER + // clang-format on + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + + void propertyBound() override; + void assignProperty(const App::Property*) override; + + Base::Quantity getAngle() const; + void setAngle(Base::Quantity); + Base::Vector3d getAxis() const; + void setAxis(const Base::Vector3d&); + +protected: + PropertyRotationItem(); + ~PropertyRotationItem() override; + QVariant toolTip(const App::Property*) const override; + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +private: + mutable RotationHelper h; + PropertyUnitItem* m_a; + PropertyVectorItem* m_d; +}; + +class PlacementEditor: public Gui::LabelButton +{ + Q_OBJECT + +public: + explicit PlacementEditor(QString name, QWidget* parent = nullptr); + ~PlacementEditor() override; + +private Q_SLOTS: + void updateValue(const QVariant& v, bool, bool); + +private: + void browse() override; + void showValue(const QVariant& d) override; + +private: + QPointer _task; + QString propertyname; +}; + +/** + * Edit properties of placement type. + * \author Werner Mayer + */ +class GuiExport PropertyPlacementItem: public PropertyItem +{ + // clang-format off + Q_OBJECT + Q_PROPERTY(Base::Quantity Angle READ getAngle WRITE setAngle DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(Base::Vector3d Axis READ getAxis WRITE setAxis DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(Base::Vector3d Position READ getPosition WRITE setPosition DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + PROPERTYITEM_HEADER + // clang-format on + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + + void propertyBound() override; + void assignProperty(const App::Property*) override; + + Base::Quantity getAngle() const; + void setAngle(Base::Quantity); + Base::Vector3d getAxis() const; + void setAxis(const Base::Vector3d&); + Base::Vector3d getPosition() const; + void setPosition(const Base::Vector3d&); + +protected: + PropertyPlacementItem(); + ~PropertyPlacementItem() override; + QVariant toolTip(const App::Property*) const override; + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +private: + mutable RotationHelper h; + PropertyUnitItem* m_a; + PropertyVectorItem* m_d; + PropertyVectorDistanceItem* m_p; +}; + +class PropertyStringListItem; + +/** + * Edit properties of enum type. + * \author Werner Mayer + */ +class GuiExport PropertyEnumItem: public PropertyItem +{ + // clang-format off + Q_OBJECT + Q_PROPERTY(QStringList Enum READ getEnum WRITE setEnum DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + PROPERTYITEM_HEADER + // clang-format on + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + + QStringList getEnum() const; + void setEnum(const QStringList&); + +private: + QStringList getCommonModes() const; + +protected: + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + void propertyBound() override; + +protected: + PropertyEnumItem(); + +private: + PropertyStringListItem* m_enum; +}; + +class PropertyEnumButton: public QPushButton +{ + Q_OBJECT +public: + explicit PropertyEnumButton(QWidget* parent = nullptr) + : QPushButton(parent) + {} + +Q_SIGNALS: + void picked(); +}; + +/** + * Edit properties of string list type. + * \author Werner Mayer + */ +class GuiExport PropertyStringListItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyStringListItem(); +}; + +/** + * Edit properties of float list type. + * \author Werner Mayer + */ +class GuiExport PropertyFloatListItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyFloatListItem(); +}; + +/** + * Edit properties of float list type. + * \author Werner Mayer + */ +class GuiExport PropertyIntegerListItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyIntegerListItem(); +}; + +/** + * Change a color property. + * \author Werner Mayer + */ +class GuiExport PropertyColorItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant decoration(const QVariant&) const override; + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyColorItem(); +}; + +/** + * Change a material property. + * \author Werner Mayer + */ +class GuiExport PropertyMaterialItem: public PropertyItem +{ + // clang-format off + Q_OBJECT + Q_PROPERTY(QColor AmbientColor READ getAmbientColor WRITE setAmbientColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(QColor DiffuseColor READ getDiffuseColor WRITE setDiffuseColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(QColor SpecularColor READ getSpecularColor WRITE setSpecularColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(QColor EmissiveColor READ getEmissiveColor WRITE setEmissiveColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(float Shininess READ getShininess WRITE setShininess DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(float Transparency READ getTransparency WRITE setTransparency DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + PROPERTYITEM_HEADER + // clang-format on + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + + void propertyBound() override; + + QColor getAmbientColor() const; + void setAmbientColor(const QColor&); + QColor getDiffuseColor() const; + void setDiffuseColor(const QColor&); + QColor getSpecularColor() const; + void setSpecularColor(const QColor&); + QColor getEmissiveColor() const; + void setEmissiveColor(const QColor&); + int getShininess() const; + void setShininess(int); + int getTransparency() const; + void setTransparency(int); + +protected: + PropertyMaterialItem(); + ~PropertyMaterialItem() override; + + QVariant decoration(const QVariant&) const override; + QVariant toolTip(const App::Property*) const override; + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +private: + PropertyColorItem* ambient; + PropertyColorItem* diffuse; + PropertyColorItem* specular; + PropertyColorItem* emissive; + PropertyIntegerConstraintItem* shininess; + PropertyIntegerConstraintItem* transparency; +}; + +class GuiExport PropertyMaterialListItem: public PropertyItem +{ + // clang-format off + Q_OBJECT + Q_PROPERTY(QColor AmbientColor READ getAmbientColor WRITE setAmbientColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(QColor DiffuseColor READ getDiffuseColor WRITE setDiffuseColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(QColor SpecularColor READ getSpecularColor WRITE setSpecularColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(QColor EmissiveColor READ getEmissiveColor WRITE setEmissiveColor DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(float Shininess READ getShininess WRITE setShininess DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(float Transparency READ getTransparency WRITE setTransparency DESIGNABLE true USER true) // clazy:exclude=qproperty-without-notify + PROPERTYITEM_HEADER + // clang-format on + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + + void propertyBound() override; + + QColor getAmbientColor() const; + void setAmbientColor(const QColor&); + QColor getDiffuseColor() const; + void setDiffuseColor(const QColor&); + QColor getSpecularColor() const; + void setSpecularColor(const QColor&); + QColor getEmissiveColor() const; + void setEmissiveColor(const QColor&); + int getShininess() const; + void setShininess(int); + int getTransparency() const; + void setTransparency(int); + +protected: + PropertyMaterialListItem(); + ~PropertyMaterialListItem() override; + + QVariant decoration(const QVariant&) const override; + QVariant toolTip(const App::Property*) const override; + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +private: + PropertyColorItem* ambient; + PropertyColorItem* diffuse; + PropertyColorItem* specular; + PropertyColorItem* emissive; + PropertyIntegerConstraintItem* shininess; + PropertyIntegerConstraintItem* transparency; +}; + +/** + * Change a file. + * \author Werner Mayer + */ +class GuiExport PropertyFileItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyFileItem(); + QVariant toolTip(const App::Property*) const override; +}; + +/** + * Change a path. + * \author Werner Mayer + */ +class GuiExport PropertyPathItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyPathItem(); + QVariant toolTip(const App::Property*) const override; +}; + +/** + * Show path of included file. + * \author Werner Mayer + */ +class GuiExport PropertyTransientFileItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + +protected: + PropertyTransientFileItem(); + QVariant toolTip(const App::Property*) const override; +}; + +class LinkSelection: public QObject +{ + Q_OBJECT + +public: + explicit LinkSelection(App::SubObjectT); + ~LinkSelection() override; + +public Q_SLOTS: + void select(); + +private: + App::SubObjectT link; +}; + + +class LinkLabel: public QWidget +{ + Q_OBJECT + +public: + LinkLabel(QWidget* parent, const App::Property* prop); + ~LinkLabel() override; + void updatePropertyLink(); + QVariant propertyLink() const; + +protected: + void resizeEvent(QResizeEvent*) override; + +protected Q_SLOTS: + void onLinkActivated(const QString&); + void onEditClicked(); + void onLinkChanged(); + +Q_SIGNALS: + void linkChanged(const QVariant&); + +private: + QLabel* label; + QPushButton* editButton; + QVariant link; + App::DocumentObjectT objProp; + + Gui::Dialog::DlgPropertyLink* dlg; +}; + +/** + * Edit properties of link type. + * \author Werner Mayer + */ +class GuiExport PropertyLinkItem: public PropertyItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + + QWidget* createEditor(QWidget* parent, const std::function& method) const override; + void setEditorData(QWidget* editor, const QVariant& data) const override; + QVariant editorData(QWidget* editor) const override; + +protected: + QVariant toString(const QVariant&) const override; + QVariant value(const App::Property*) const override; + void setValue(const QVariant&) override; + QVariant data(int column, int role) const override; + +protected: + PropertyLinkItem(); +}; + +/** + * Edit properties of link list type. + * \author Werner Mayer + */ +class GuiExport PropertyLinkListItem: public PropertyLinkItem +{ + Q_OBJECT + PROPERTYITEM_HEADER + +protected: + PropertyLinkListItem(); +}; + +class PropertyItemEditorFactory: public QItemEditorFactory +{ +public: + PropertyItemEditorFactory(); + ~PropertyItemEditorFactory() override; + + QWidget* createEditor(int userType, QWidget* parent) const override; + QByteArray valuePropertyName(int userType) const override; +}; + +} // namespace PropertyEditor +} // namespace Gui + +#endif // PROPERTYEDITORITEM_H diff --git a/src/Gui/propertyeditor/PropertyModel.cpp b/src/Gui/propertyeditor/PropertyModel.cpp index 1b9e356c5e..d20c8e0dfa 100644 --- a/src/Gui/propertyeditor/PropertyModel.cpp +++ b/src/Gui/propertyeditor/PropertyModel.cpp @@ -1,559 +1,600 @@ -/*************************************************************************** - * Copyright (c) 2004 Werner Mayer * - * * - * 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 "PropertyItem.h" -#include "PropertyModel.h" -#include "PropertyView.h" - - -using namespace Gui; -using namespace Gui::PropertyEditor; - - -/* TRANSLATOR Gui::PropertyEditor::PropertyModel */ - -PropertyModel::PropertyModel(QObject* parent) - : QAbstractItemModel(parent) -{ - rootItem = static_cast(PropertyItem::create()); -} - -PropertyModel::~PropertyModel() -{ - delete rootItem; -} - -QModelIndex PropertyModel::buddy ( const QModelIndex & index ) const -{ - if (index.column() == 1) - return index; - return index.sibling(index.row(), 1); -} - -int PropertyModel::columnCount ( const QModelIndex & parent ) const -{ - // , hence always 2 - if (parent.isValid()) - return static_cast(parent.internalPointer())->columnCount(); - else - return rootItem->columnCount(); -} - -QVariant PropertyModel::data ( const QModelIndex & index, int role ) const -{ - if (!index.isValid()) - return {}; - - auto item = static_cast(index.internalPointer()); - return item->data(index.column(), role); -} - -bool PropertyModel::setData(const QModelIndex& index, const QVariant & value, int role) -{ - if (!index.isValid()) - return false; - - // we check whether the data has really changed, otherwise we ignore it - if (role == Qt::EditRole) { - auto item = static_cast(index.internalPointer()); - QVariant data = item->data(index.column(), role); - if (data.userType() == QMetaType::Double && value.userType() == QMetaType::Double) { - // since we store some properties as floats we get some round-off - // errors here. Thus, we use an epsilon here. - // NOTE: Since 0.14 PropertyFloat uses double precision, so this is maybe unnecessary now? - double d = data.toDouble(); - double v = value.toDouble(); - if (fabs(d-v) > DBL_EPSILON) - return item->setData(value); - } - // Special case handling for quantities - else if (data.canConvert() && value.canConvert()) { - const Base::Quantity& val1 = data.value(); - const Base::Quantity& val2 = value.value(); - if (!(val1 == val2)) - return item->setData(value); - } - else if (data != value) - return item->setData(value); - } - - return true; -} - -Qt::ItemFlags PropertyModel::flags(const QModelIndex &index) const -{ - auto item = static_cast(index.internalPointer()); - return item->flags(index.column()); -} - -QModelIndex PropertyModel::index ( int row, int column, const QModelIndex & parent ) const -{ - PropertyItem *parentItem; - - if (!parent.isValid()) - parentItem = rootItem; - else - parentItem = static_cast(parent.internalPointer()); - - PropertyItem *childItem = parentItem->child(row); - if (childItem) - return createIndex(row, column, childItem); - else - return {}; -} - -QModelIndex PropertyModel::parent ( const QModelIndex & index ) const -{ - if (!index.isValid()) - return {}; - - auto childItem = static_cast(index.internalPointer()); - PropertyItem *parentItem = childItem->parent(); - - if (parentItem == rootItem) - return {}; - - return createIndex(parentItem->row(), 0, parentItem); -} - -int PropertyModel::rowCount ( const QModelIndex & parent ) const -{ - PropertyItem *parentItem; - - if (!parent.isValid()) - parentItem = rootItem; - else - parentItem = static_cast(parent.internalPointer()); - - return parentItem->childCount(); -} - -QVariant PropertyModel::headerData (int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal) { - if (role != Qt::DisplayRole) - return {}; - if (section == 0) - return tr("Property"); - if (section == 1) - return tr("Value"); - } - - return {}; -} - -bool PropertyModel::setHeaderData (int, Qt::Orientation, const QVariant &, int) -{ - return false; -} - -QStringList PropertyModel::propertyPathFromIndex(const QModelIndex& index) const -{ - QStringList path; - if (index.isValid()) { - auto item = static_cast(index.internalPointer()); - while (item && item != this->rootItem) { - path.push_front(item->propertyName()); - item = item->parent(); - } - } - - return path; -} - -QModelIndex PropertyModel::propertyIndexFromPath(const QStringList& path) const -{ - if (path.size() < 2) - return {}; - - auto it = groupItems.find(path.front()); - if (it == groupItems.end()) - return {}; - - PropertyItem *item = it->second.groupItem; - QModelIndex index = this->index(item->row(), 0, QModelIndex()); - - for (int j=1; jchildCount(); ichild(i); - if (child->propertyName() == path[j]) { - index = this->index(i, 1, index); - item = child; - found = true; - break; - } - } - if (!found) - return j==1?QModelIndex():index; - } - return index; -} - -static void setPropertyItemName(PropertyItem *item, const char *propName, QString groupName) { - QString name = QString::fromLatin1(propName); - QString realName = name; - if(name.size()>groupName.size()+1 - && name.startsWith(groupName + QLatin1Char('_'))) - name = name.right(name.size()-groupName.size()-1); - - item->setPropertyName(name, realName); -} - -static PropertyItem *createPropertyItem(App::Property *prop) -{ - const char *editor = prop->getEditorName(); - if (!editor || !editor[0]) { - if (PropertyView::showAll()) - editor = "Gui::PropertyEditor::PropertyItem"; - else - return nullptr; - } - auto item = static_cast( - PropertyItemFactory::instance().createPropertyItem(editor)); - if (!item) { - qWarning("No property item for type %s found\n", editor); - } - return item; -} - -PropertyModel::GroupInfo &PropertyModel::getGroupInfo(App::Property *prop) -{ - const char* group = prop->getGroup(); - bool isEmpty = (!group || group[0] == '\0'); - QString groupName = QString::fromLatin1( - isEmpty ? QT_TRANSLATE_NOOP("App::Property", "Base") : group); - - auto res = groupItems.insert(std::make_pair(groupName, GroupInfo())); - if (res.second) { - auto &groupInfo = res.first->second; - groupInfo.groupItem = static_cast(PropertySeparatorItem::create()); - groupInfo.groupItem->setReadOnly(true); - groupInfo.groupItem->setExpanded(true); - groupInfo.groupItem->setParent(rootItem); - groupInfo.groupItem->setPropertyName(groupName); - - auto it = res.first; - int row = 0; - if (it != groupItems.begin()) { - --it; - row = it->second.groupItem->_row + 1; - ++it; - } - groupInfo.groupItem->_row = row; - beginInsertRows(QModelIndex(), row, row); - rootItem->insertChild(row, groupInfo.groupItem); - // update row index for all group items behind - for (++it; it!=groupItems.end(); ++it) - ++it->second.groupItem->_row; - endInsertRows(); - } - - return res.first->second; -} - -void PropertyModel::buildUp(const PropertyModel::PropertyList& props) -{ - // If props empty, then simply reset all property items, but keep the group - // items. - if (props.empty()) { - resetGroups(); - return; - } - - // First step, init group info - initGroups(); - - // Second step, either find existing items or create new items for the given - // properties. There is no signaling of model change at this stage. The - // change information is kept pending in GroupInfo::children - findOrCreateChildren(props); - - // Third step, signal item insertion and movement. - insertOrMoveChildren(); - - - // Final step, signal item removal. This is separated from the above because - // of the possibility of moving items between groups. - removeChildren(); -} - -void PropertyModel::resetGroups() -{ - beginResetModel(); - for(auto &v : groupItems) { - auto &groupInfo = v.second; - groupInfo.groupItem->reset(); - groupInfo.children.clear(); - } - itemMap.clear(); - endResetModel(); -} - -void PropertyModel::initGroups() -{ - for (auto &v : groupItems) { - auto &groupInfo = v.second; - groupInfo.children.clear(); - } -} - -void PropertyModel::findOrCreateChildren(const PropertyModel::PropertyList& props) -{ - for (const auto & jt : props) { - App::Property* prop = jt.second.front(); - - PropertyItem *item = nullptr; - for (auto prop : jt.second) { - auto it = itemMap.find(prop); - if (it == itemMap.end() || !it->second) { - continue; - } - item = it->second; - break; - } - - if (!item) { - item = createPropertyItem(prop); - if (!item) { - continue; - } - } - - GroupInfo &groupInfo = getGroupInfo(prop); - groupInfo.children.push_back(item); - - item->setLinked(boost::ends_with(jt.first,"*")); - setPropertyItemName(item, prop->getName(), groupInfo.groupItem->propertyName()); - - if (jt.second != item->getPropertyData()) { - for (auto prop : item->getPropertyData()) { - itemMap.erase(prop); - } - for (auto prop : jt.second) { - itemMap[prop] = item; - } - // TODO: is it necessary to make sure the item has no pending commit? - item->setPropertyData(jt.second); - } - else { - item->updateData(); - } - } -} - -void PropertyModel::insertOrMoveChildren() -{ - for (auto &v : groupItems) { - auto &groupInfo = v.second; - int beginChange = -1; - int endChange = 0; - int beginInsert = -1; - int endInsert = 0; - int row = -1; - QModelIndex midx = this->index(groupInfo.groupItem->_row, 0, QModelIndex()); - - auto flushInserts = [&]() { - if (beginInsert < 0) - return; - beginInsertRows(midx, beginInsert, endInsert); - for (int i=beginInsert; i<=endInsert; ++i) - groupInfo.groupItem->insertChild(i, groupInfo.children[i]); - endInsertRows(); - beginInsert = -1; - }; - - auto flushChanges = [&]() { - if (beginChange < 0) - return; - (void)endChange; - // There is no need to signal dataChange(), because PropertyEditor - // will call PropertyModel::updateProperty() on any property - // changes. - // - // dataChanged(this->index(beginChange,0,midx), this->index(endChange,1,midx)); - beginChange = -1; - }; - - for (auto item : groupInfo.children) { - ++row; - if (!item->parent()) { - flushChanges(); - item->setParent(groupInfo.groupItem); - if (beginInsert < 0) { - beginInsert = row; - } - endInsert = row; - } - else { - flushInserts(); - int oldRow = item->row(); - // Dynamic property can rename group, so must check - auto groupItem = item->parent(); - assert(groupItem); - if (oldRow == row && groupItem == groupInfo.groupItem) { - if (beginChange < 0) - beginChange = row; - endChange = row; - } - else { - flushChanges(); - beginMoveRows(createIndex(groupItem->row(), 0, groupItem), - oldRow, oldRow, midx, row); - if (groupItem == groupInfo.groupItem) { - groupInfo.groupItem->moveChild(oldRow, row); - } - else { - groupItem->takeChild(oldRow); - item->setParent(groupInfo.groupItem); - groupInfo.groupItem->insertChild(row, item); - } - endMoveRows(); - } - } - } - - flushChanges(); - flushInserts(); - } -} - -void PropertyModel::removeChildren() -{ - for (auto &v : groupItems) { - auto &groupInfo = v.second; - int first, last; - getRange(groupInfo, first, last); - if (last > first) { - QModelIndex midx = this->index(groupInfo.groupItem->_row, 0, QModelIndex()); - // This can trigger a recursive call of PropertyView::onTimer() - beginRemoveRows(midx, first, last - 1); - groupInfo.groupItem->removeChildren(first, last - 1); - endRemoveRows(); - } - else { - assert(last == first); - } - } -} - -void PropertyModel::getRange(const PropertyModel::GroupInfo& groupInfo, int& first, int& last) const -{ - first = static_cast(groupInfo.children.size()); - last = groupInfo.groupItem->childCount(); -} - -void PropertyModel::updateProperty(const App::Property& prop) -{ - auto it = itemMap.find(const_cast(&prop)); - if (it == itemMap.end() || !it->second || !it->second->parent()) - return; - - int column = 1; - PropertyItem *item = it->second; - item->updateData(); - QModelIndex parent = this->index(item->parent()->row(), 0, QModelIndex()); - item->assignProperty(&prop); - QModelIndex data = this->index(item->row(), column, parent); - Q_EMIT dataChanged(data, data); - updateChildren(item, column, data); -} - -void PropertyModel::appendProperty(const App::Property& _prop) -{ - auto prop = const_cast(&_prop); - if (!prop->getName()) - return; - auto it = itemMap.find(prop); - if (it == itemMap.end() || !it->second) - return; - - PropertyItem *item = createPropertyItem(prop); - GroupInfo &groupInfo = getGroupInfo(prop); - - int row = 0; - for (int c=groupInfo.groupItem->childCount(); rowchild(row); - App::Property *firstProp = item->getFirstProperty(); - if (firstProp && firstProp->testStatus(App::Property::PropDynamic) - && item->propertyName() >= child->propertyName()) - break; - } - - QModelIndex midx = this->index(groupInfo.groupItem->_row,0,QModelIndex()); - beginInsertRows(midx, row, row); - groupInfo.groupItem->insertChild(row, item); - setPropertyItemName(item, prop->getName(), groupInfo.groupItem->propertyName()); - item->setPropertyData({prop}); - endInsertRows(); -} - -void PropertyModel::removeProperty(const App::Property& _prop) -{ - auto prop = const_cast(&_prop); - auto it = itemMap.find(prop); - if (it == itemMap.end() || !it->second) - return; - - PropertyItem *item = it->second; - if (item->removeProperty(prop)) { - PropertyItem *parent = item->parent(); - int row = item->row(); - beginRemoveRows(this->index(parent->row(), 0, QModelIndex()), row, row); - parent->removeChildren(row,row); - endRemoveRows(); - } -} - -void PropertyModel::updateChildren(PropertyItem* item, int column, const QModelIndex& parent) -{ - int numChild = item->childCount(); - if (numChild > 0) { - QModelIndex topLeft = this->index(0, column, parent); - QModelIndex bottomRight = this->index(numChild, column, parent); - Q_EMIT dataChanged(topLeft, bottomRight); - } -} - -bool PropertyModel::removeRows(int row, int count, const QModelIndex& parent) -{ - PropertyItem* item; - if (!parent.isValid()) - item = rootItem; - else - item = static_cast(parent.internalPointer()); - - int start = row; - int end = row+count-1; - beginRemoveRows(parent, start, end); - item->removeChildren(start, end); - endRemoveRows(); - return true; -} - -#include "moc_PropertyModel.cpp" +/*************************************************************************** + * Copyright (c) 2004 Werner Mayer * + * * + * 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 "PropertyItem.h" +#include "PropertyModel.h" +#include "PropertyView.h" + + +using namespace Gui; +using namespace Gui::PropertyEditor; + + +/* TRANSLATOR Gui::PropertyEditor::PropertyModel */ + +PropertyModel::PropertyModel(QObject* parent) + : QAbstractItemModel(parent) +{ + rootItem = static_cast(PropertyItem::create()); +} + +PropertyModel::~PropertyModel() +{ + delete rootItem; +} + +QModelIndex PropertyModel::buddy(const QModelIndex& index) const +{ + if (index.column() == 1) { + return index; + } + return index.sibling(index.row(), 1); +} + +int PropertyModel::columnCount(const QModelIndex& parent) const +{ + // , hence always 2 + if (parent.isValid()) { + return static_cast(parent.internalPointer())->columnCount(); + } + else { + return rootItem->columnCount(); + } +} + +QVariant PropertyModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) { + return {}; + } + + auto item = static_cast(index.internalPointer()); + return item->data(index.column(), role); +} + +bool PropertyModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid()) { + return false; + } + + // we check whether the data has really changed, otherwise we ignore it + if (role == Qt::EditRole) { + auto item = static_cast(index.internalPointer()); + QVariant data = item->data(index.column(), role); + if (data.userType() == QMetaType::Double && value.userType() == QMetaType::Double) { + // since we store some properties as floats we get some round-off + // errors here. Thus, we use an epsilon here. + // NOTE: Since 0.14 PropertyFloat uses double precision, so this is maybe unnecessary + // now? + double d = data.toDouble(); + double v = value.toDouble(); + if (fabs(d - v) > DBL_EPSILON) { + return item->setData(value); + } + } + // Special case handling for quantities + else if (data.canConvert() && value.canConvert()) { + const Base::Quantity& val1 = data.value(); + const Base::Quantity& val2 = value.value(); + if (!(val1 == val2)) { + return item->setData(value); + } + } + else if (data != value) { + return item->setData(value); + } + } + + return true; +} + +Qt::ItemFlags PropertyModel::flags(const QModelIndex& index) const +{ + auto item = static_cast(index.internalPointer()); + return item->flags(index.column()); +} + +QModelIndex PropertyModel::index(int row, int column, const QModelIndex& parent) const +{ + PropertyItem* parentItem; + + if (!parent.isValid()) { + parentItem = rootItem; + } + else { + parentItem = static_cast(parent.internalPointer()); + } + + PropertyItem* childItem = parentItem->child(row); + if (childItem) { + return createIndex(row, column, childItem); + } + else { + return {}; + } +} + +QModelIndex PropertyModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) { + return {}; + } + + auto childItem = static_cast(index.internalPointer()); + PropertyItem* parentItem = childItem->parent(); + + if (parentItem == rootItem) { + return {}; + } + + return createIndex(parentItem->row(), 0, parentItem); +} + +int PropertyModel::rowCount(const QModelIndex& parent) const +{ + PropertyItem* parentItem; + + if (!parent.isValid()) { + parentItem = rootItem; + } + else { + parentItem = static_cast(parent.internalPointer()); + } + + return parentItem->childCount(); +} + +QVariant PropertyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) { + if (role != Qt::DisplayRole) { + return {}; + } + if (section == 0) { + return tr("Property"); + } + if (section == 1) { + return tr("Value"); + } + } + + return {}; +} + +bool PropertyModel::setHeaderData(int, Qt::Orientation, const QVariant&, int) +{ + return false; +} + +QStringList PropertyModel::propertyPathFromIndex(const QModelIndex& index) const +{ + QStringList path; + if (index.isValid()) { + auto item = static_cast(index.internalPointer()); + while (item && item != this->rootItem) { + path.push_front(item->propertyName()); + item = item->parent(); + } + } + + return path; +} + +QModelIndex PropertyModel::propertyIndexFromPath(const QStringList& path) const +{ + if (path.size() < 2) { + return {}; + } + + auto it = groupItems.find(path.front()); + if (it == groupItems.end()) { + return {}; + } + + PropertyItem* item = it->second.groupItem; + QModelIndex index = this->index(item->row(), 0, QModelIndex()); + + for (int j = 1; j < path.size(); ++j) { + bool found = false; + for (int i = 0, c = item->childCount(); i < c; ++i) { + auto child = item->child(i); + if (child->propertyName() == path[j]) { + index = this->index(i, 1, index); + item = child; + found = true; + break; + } + } + if (!found) { + return j == 1 ? QModelIndex() : index; + } + } + return index; +} + +static void setPropertyItemName(PropertyItem* item, const char* propName, QString groupName) +{ + QString name = QString::fromLatin1(propName); + QString realName = name; + if (name.size() > groupName.size() + 1 && name.startsWith(groupName + QLatin1Char('_'))) { + name = name.right(name.size() - groupName.size() - 1); + } + + item->setPropertyName(name, realName); +} + +static PropertyItem* createPropertyItem(App::Property* prop) +{ + const char* editor = prop->getEditorName(); + if (!editor || !editor[0]) { + if (PropertyView::showAll()) { + editor = "Gui::PropertyEditor::PropertyItem"; + } + else { + return nullptr; + } + } + auto item = + static_cast(PropertyItemFactory::instance().createPropertyItem(editor)); + if (!item) { + qWarning("No property item for type %s found\n", editor); + } + return item; +} + +PropertyModel::GroupInfo& PropertyModel::getGroupInfo(App::Property* prop) +{ + const char* group = prop->getGroup(); + bool isEmpty = (!group || group[0] == '\0'); + QString groupName = + QString::fromLatin1(isEmpty ? QT_TRANSLATE_NOOP("App::Property", "Base") : group); + + auto res = groupItems.insert(std::make_pair(groupName, GroupInfo())); + if (res.second) { + auto& groupInfo = res.first->second; + groupInfo.groupItem = static_cast(PropertySeparatorItem::create()); + groupInfo.groupItem->setReadOnly(true); + groupInfo.groupItem->setExpanded(true); + groupInfo.groupItem->setParent(rootItem); + groupInfo.groupItem->setPropertyName(groupName); + + auto it = res.first; + int row = 0; + if (it != groupItems.begin()) { + --it; + row = it->second.groupItem->_row + 1; + ++it; + } + groupInfo.groupItem->_row = row; + beginInsertRows(QModelIndex(), row, row); + rootItem->insertChild(row, groupInfo.groupItem); + // update row index for all group items behind + for (++it; it != groupItems.end(); ++it) { + ++it->second.groupItem->_row; + } + endInsertRows(); + } + + return res.first->second; +} + +void PropertyModel::buildUp(const PropertyModel::PropertyList& props) +{ + // If props empty, then simply reset all property items, but keep the group + // items. + if (props.empty()) { + resetGroups(); + return; + } + + // First step, init group info + initGroups(); + + // Second step, either find existing items or create new items for the given + // properties. There is no signaling of model change at this stage. The + // change information is kept pending in GroupInfo::children + findOrCreateChildren(props); + + // Third step, signal item insertion and movement. + insertOrMoveChildren(); + + + // Final step, signal item removal. This is separated from the above because + // of the possibility of moving items between groups. + removeChildren(); +} + +void PropertyModel::resetGroups() +{ + beginResetModel(); + for (auto& v : groupItems) { + auto& groupInfo = v.second; + groupInfo.groupItem->reset(); + groupInfo.children.clear(); + } + itemMap.clear(); + endResetModel(); +} + +void PropertyModel::initGroups() +{ + for (auto& v : groupItems) { + auto& groupInfo = v.second; + groupInfo.children.clear(); + } +} + +void PropertyModel::findOrCreateChildren(const PropertyModel::PropertyList& props) +{ + for (const auto& jt : props) { + App::Property* prop = jt.second.front(); + + PropertyItem* item = nullptr; + for (auto prop : jt.second) { + auto it = itemMap.find(prop); + if (it == itemMap.end() || !it->second) { + continue; + } + item = it->second; + break; + } + + if (!item) { + item = createPropertyItem(prop); + if (!item) { + continue; + } + } + + GroupInfo& groupInfo = getGroupInfo(prop); + groupInfo.children.push_back(item); + + item->setLinked(boost::ends_with(jt.first, "*")); + setPropertyItemName(item, prop->getName(), groupInfo.groupItem->propertyName()); + + if (jt.second != item->getPropertyData()) { + for (auto prop : item->getPropertyData()) { + itemMap.erase(prop); + } + for (auto prop : jt.second) { + itemMap[prop] = item; + } + // TODO: is it necessary to make sure the item has no pending commit? + item->setPropertyData(jt.second); + } + else { + item->updateData(); + } + } +} + +void PropertyModel::insertOrMoveChildren() +{ + for (auto& v : groupItems) { + auto& groupInfo = v.second; + int beginChange = -1; + int endChange = 0; + int beginInsert = -1; + int endInsert = 0; + int row = -1; + QModelIndex midx = this->index(groupInfo.groupItem->_row, 0, QModelIndex()); + + auto flushInserts = [&]() { + if (beginInsert < 0) { + return; + } + beginInsertRows(midx, beginInsert, endInsert); + for (int i = beginInsert; i <= endInsert; ++i) { + groupInfo.groupItem->insertChild(i, groupInfo.children[i]); + } + endInsertRows(); + beginInsert = -1; + }; + + auto flushChanges = [&]() { + if (beginChange < 0) { + return; + } + (void)endChange; + // There is no need to signal dataChange(), because PropertyEditor + // will call PropertyModel::updateProperty() on any property + // changes. + // + // dataChanged(this->index(beginChange,0,midx), this->index(endChange,1,midx)); + beginChange = -1; + }; + + for (auto item : groupInfo.children) { + ++row; + if (!item->parent()) { + flushChanges(); + item->setParent(groupInfo.groupItem); + if (beginInsert < 0) { + beginInsert = row; + } + endInsert = row; + } + else { + flushInserts(); + int oldRow = item->row(); + // Dynamic property can rename group, so must check + auto groupItem = item->parent(); + assert(groupItem); + if (oldRow == row && groupItem == groupInfo.groupItem) { + if (beginChange < 0) { + beginChange = row; + } + endChange = row; + } + else { + flushChanges(); + beginMoveRows(createIndex(groupItem->row(), 0, groupItem), + oldRow, + oldRow, + midx, + row); + if (groupItem == groupInfo.groupItem) { + groupInfo.groupItem->moveChild(oldRow, row); + } + else { + groupItem->takeChild(oldRow); + item->setParent(groupInfo.groupItem); + groupInfo.groupItem->insertChild(row, item); + } + endMoveRows(); + } + } + } + + flushChanges(); + flushInserts(); + } +} + +void PropertyModel::removeChildren() +{ + for (auto& v : groupItems) { + auto& groupInfo = v.second; + int first, last; + getRange(groupInfo, first, last); + if (last > first) { + QModelIndex midx = this->index(groupInfo.groupItem->_row, 0, QModelIndex()); + // This can trigger a recursive call of PropertyView::onTimer() + beginRemoveRows(midx, first, last - 1); + groupInfo.groupItem->removeChildren(first, last - 1); + endRemoveRows(); + } + else { + assert(last == first); + } + } +} + +void PropertyModel::getRange(const PropertyModel::GroupInfo& groupInfo, int& first, int& last) const +{ + first = static_cast(groupInfo.children.size()); + last = groupInfo.groupItem->childCount(); +} + +void PropertyModel::updateProperty(const App::Property& prop) +{ + auto it = itemMap.find(const_cast(&prop)); + if (it == itemMap.end() || !it->second || !it->second->parent()) { + return; + } + + int column = 1; + PropertyItem* item = it->second; + item->updateData(); + QModelIndex parent = this->index(item->parent()->row(), 0, QModelIndex()); + item->assignProperty(&prop); + QModelIndex data = this->index(item->row(), column, parent); + Q_EMIT dataChanged(data, data); + updateChildren(item, column, data); +} + +void PropertyModel::appendProperty(const App::Property& _prop) +{ + auto prop = const_cast(&_prop); + if (!prop->getName()) { + return; + } + auto it = itemMap.find(prop); + if (it == itemMap.end() || !it->second) { + return; + } + + PropertyItem* item = createPropertyItem(prop); + GroupInfo& groupInfo = getGroupInfo(prop); + + int row = 0; + for (int c = groupInfo.groupItem->childCount(); row < c; ++row) { + PropertyItem* child = groupInfo.groupItem->child(row); + App::Property* firstProp = item->getFirstProperty(); + if (firstProp && firstProp->testStatus(App::Property::PropDynamic) + && item->propertyName() >= child->propertyName()) { + break; + } + } + + QModelIndex midx = this->index(groupInfo.groupItem->_row, 0, QModelIndex()); + beginInsertRows(midx, row, row); + groupInfo.groupItem->insertChild(row, item); + setPropertyItemName(item, prop->getName(), groupInfo.groupItem->propertyName()); + item->setPropertyData({prop}); + endInsertRows(); +} + +void PropertyModel::removeProperty(const App::Property& _prop) +{ + auto prop = const_cast(&_prop); + auto it = itemMap.find(prop); + if (it == itemMap.end() || !it->second) { + return; + } + + PropertyItem* item = it->second; + if (item->removeProperty(prop)) { + PropertyItem* parent = item->parent(); + int row = item->row(); + beginRemoveRows(this->index(parent->row(), 0, QModelIndex()), row, row); + parent->removeChildren(row, row); + endRemoveRows(); + } +} + +void PropertyModel::updateChildren(PropertyItem* item, int column, const QModelIndex& parent) +{ + int numChild = item->childCount(); + if (numChild > 0) { + QModelIndex topLeft = this->index(0, column, parent); + QModelIndex bottomRight = this->index(numChild, column, parent); + Q_EMIT dataChanged(topLeft, bottomRight); + } +} + +bool PropertyModel::removeRows(int row, int count, const QModelIndex& parent) +{ + PropertyItem* item; + if (!parent.isValid()) { + item = rootItem; + } + else { + item = static_cast(parent.internalPointer()); + } + + int start = row; + int end = row + count - 1; + beginRemoveRows(parent, start, end); + item->removeChildren(start, end); + endRemoveRows(); + return true; +} + +#include "moc_PropertyModel.cpp"