From 0545df705214349e6f61706fe4076ed95cc0e521 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 7 Dec 2020 21:00:43 -0800 Subject: [PATCH] Basic property container with editor, no adding of properties yet --- src/Mod/Path/CMakeLists.txt | 4 + src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Gui/Resources/panels/PropertyContainer.ui | 57 +++++ src/Mod/Path/PathScripts/PathGuiInit.py | 1 + src/Mod/Path/PathScripts/PathProperty.py | 201 +++++++++++++++ .../Path/PathScripts/PathPropertyContainer.py | 89 +++++++ .../PathScripts/PathPropertyContainerGui.py | 235 ++++++++++++++++++ .../Path/PathScripts/PathPropertyEditor.py | 219 ++++++++++++++++ 8 files changed, 807 insertions(+) create mode 100644 src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui create mode 100644 src/Mod/Path/PathScripts/PathProperty.py create mode 100644 src/Mod/Path/PathScripts/PathPropertyContainer.py create mode 100644 src/Mod/Path/PathScripts/PathPropertyContainerGui.py create mode 100644 src/Mod/Path/PathScripts/PathPropertyEditor.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 444be1ecd4..28bc67fa96 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -96,6 +96,10 @@ SET(PathScripts_SRCS PathScripts/PathProfileFaces.py PathScripts/PathProfileFacesGui.py PathScripts/PathProfileGui.py + PathScripts/PathProperty.py + PathScripts/PathPropertyContainer.py + PathScripts/PathPropertyContainerGui.py + PathScripts/PathPropertyEditor.py PathScripts/PathSanity.py PathScripts/PathSelection.py PathScripts/PathSetupSheet.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index c99246c667..a8d0ab5d21 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -121,6 +121,7 @@ panels/PageOpVcarveEdit.ui panels/PathEdit.ui panels/PointEdit.ui + panels/PropertyContainer.ui panels/SetupGlobal.ui panels/SetupOp.ui panels/ToolBitEditor.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui b/src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui new file mode 100644 index 0000000000..21df9f9cb5 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PropertyContainer.ui @@ -0,0 +1,57 @@ + + + Form + + + + 0 + 0 + 552 + 651 + + + + Form + + + + + + QAbstractItemView::AllEditTriggers + + + true + + + true + + + false + + + + + + + + + + Remove + + + + + + + Add + + + + + + + + + + + diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index cd48d41309..29f285d59a 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -66,6 +66,7 @@ def Startup(): # from PathScripts import PathProfileEdgesGui # from PathScripts import PathProfileFacesGui from PathScripts import PathProfileGui + from PathScripts import PathPropertyContainerGui from PathScripts import PathSanity from PathScripts import PathSetupSheetGui from PathScripts import PathSimpleCopy diff --git a/src/Mod/Path/PathScripts/PathProperty.py b/src/Mod/Path/PathScripts/PathProperty.py new file mode 100644 index 0000000000..7e3eeb1e6f --- /dev/null +++ b/src/Mod/Path/PathScripts/PathProperty.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import PathScripts.PathLog as PathLog + +__title__ = "Property type abstraction for editing purposes" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Prototype objects to allow extraction of setup sheet values and editing." + + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +#PathLog.trackModule(PathLog.thisModule()) + +class Property(object): + '''Base class for all prototype properties''' + def __init__(self, name, propType, category, info): + self.name = name + self.propType = propType + self.category = category + self.info = info + self.editorMode = 0 + self.value = None + + def setValue(self, value): + self.value = value + def getValue(self): + return self.value + + def setEditorMode(self, mode): + self.editorMode = mode + + def displayString(self): + if self.value is None: + t = self.typeString() + p = 'an' if t[0] in ['A', 'E', 'I', 'O', 'U'] else 'a' + return "%s %s" % (p, t) + return self.value + + def typeString(self): + return "Property" + + def setupProperty(self, obj, name, category, value): + created = False + if not hasattr(obj, name): + obj.addProperty(self.propType, name, category, self.info) + self.initProperty(obj, name) + created = True + setattr(obj, name, value) + return created + + def initProperty(self, obj, name): + pass + + def setValueFromString(self, string): + self.setValue(self.valueFromString(string)) + + def valueFromString(self, string): + return string + +class PropertyEnumeration(Property): + def typeString(self): + return "Enumeration" + + def setValue(self, value): + if list == type(value): + self.enums = value # pylint: disable=attribute-defined-outside-init + else: + super(PropertyEnumeration, self).setValue(value) + + def getEnumValues(self): + return self.enums + + def initProperty(self, obj, name): + setattr(obj, name, self.enums) + +class PropertyQuantity(Property): + def displayString(self): + if self.value is None: + return Property.displayString(self) + return self.value.getUserPreferred()[0] + +class PropertyAngle(PropertyQuantity): + def typeString(self): + return "Angle" + +class PropertyDistance(PropertyQuantity): + def typeString(self): + return "Distance" + +class PropertyLength(PropertyQuantity): + def typeString(self): + return "Length" + +class PropertyPercent(Property): + def typeString(self): + return "Percent" + +class PropertyFloat(Property): + def typeString(self): + return "Float" + + def valueFromString(self, string): + return float(string) + +class PropertyInteger(Property): + def typeString(self): + return "Integer" + + def valueFromString(self, string): + return int(string) + +class PropertyBool(Property): + def typeString(self): + return "Bool" + + def valueFromString(self, string): + return bool(string) + +class PropertyString(Property): + def typeString(self): + return "String" + +class PropertyMap(Property): + def typeString(self): + return "Map" + + def displayString(self, value): + return str(value) + +class OpPrototype(object): + + PropertyType = { + 'App::PropertyAngle': PropertyAngle, + 'App::PropertyBool': PropertyBool, + 'App::PropertyDistance': PropertyDistance, + 'App::PropertyEnumeration': PropertyEnumeration, + 'App::PropertyFile': PropertyString, + 'App::PropertyFloat': PropertyFloat, + 'App::PropertyFloatConstraint': Property, + 'App::PropertyFloatList': Property, + 'App::PropertyInteger': PropertyInteger, + 'App::PropertyIntegerList': PropertyInteger, + 'App::PropertyLength': PropertyLength, + 'App::PropertyLink': Property, + 'App::PropertyLinkList': Property, + 'App::PropertyLinkSubListGlobal': Property, + 'App::PropertyMap': PropertyMap, + 'App::PropertyPercent': PropertyPercent, + 'App::PropertyString': PropertyString, + 'App::PropertyStringList': Property, + 'App::PropertyVectorDistance': Property, + 'App::PropertyVectorList': Property, + 'Part::PropertyPartShape': Property, + } + + def __init__(self, name): + self.Label = name + self.properties = {} + self.DoNotSetDefaultValues = True + self.Proxy = None + + def __setattr__(self, name, val): + if name in ['Label', 'DoNotSetDefaultValues', 'properties', 'Proxy']: + if name == 'Proxy': + val = None # make sure the proxy is never set + return super(OpPrototype, self).__setattr__(name, val) + self.properties[name].setValue(val) + + def addProperty(self, typeString, name, category, info = None): + prop = self.PropertyType[typeString](name, typeString, category, info) + self.properties[name] = prop + return self + + def setEditorMode(self, name, mode): + self.properties[name].setEditorMode(mode) + + def getProperty(self, name): + return self.properties[name] + + def setupProperties(self, setup): + return [p for p in self.properties if p.name in setup] diff --git a/src/Mod/Path/PathScripts/PathPropertyContainer.py b/src/Mod/Path/PathScripts/PathPropertyContainer.py new file mode 100644 index 0000000000..a1b52b8e08 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyContainer.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import PySide + +__title__ = 'Generic property container to store some values.' +__author__ = 'sliptonic (Brad Collette)' +__url__ = 'https://www.freecadweb.org' +__doc__ = 'A generic container for typed properties in arbitrary categories.' + +def translate(context, text, disambig=None): + return PySide.QtCore.QCoreApplication.translate(context, text, disambig) + + +class PropertyContainer(object): + '''Property container object.''' + + CustomPropertyGroups = 'CustomPropertyGroups' + CustomPropertyGroupDefault = 'User' + + def __init__(self, obj): + self.obj = obj + obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyContainer', 'List of custom property groups')) + obj.setEditorMode(self.CustomPropertyGroups, 2) # hide + + def __getstate__(self): + return None + + def __setstate__(self, state): + for obj in FreeCAD.ActiveDocument.Objects: + if hasattr(obj, 'Proxy') and obj.Proxy == self: + self.obj = obj + obj.setEditorMode(self.CustomPropertyGroups, 2) # hide + break + return None + + def onDocumentRestored(self, obj): + obj.setEditorMode(self.CustomPropertyGroups, 2) # hide + + def getCustomProperties(self): + '''Return a list of all custom properties created in this container.''' + return [p for p in self.obj.PropertiesList if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups] + + def addCustomProperty(self, propertyType, name, group=None, desc=None): + '''addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group.''' + if desc is None: + desc = '' + if group is None: + group = self.CustomPropertyGroupDefault + groups = self.obj.CustomPropertyGroups + if not group in groups: + groups.append(group) + self.obj.CustomPropertyGroups = groups + self.obj.addProperty(propertyType, name, group, desc) + +def Create(name = 'PropertyContainer'): + obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name) + obj.Proxy = PropertyContainer(obj) + return obj + +def IsPropertyContainer(obj): + '''Returns True if the supplied object is a property container (or its Proxy).''' + + if type(obj) == PropertyContainer: + return True + if hasattr(obj, 'Proxy'): + return IsPropertyContainer(obj.Proxy) + return False + diff --git a/src/Mod/Path/PathScripts/PathPropertyContainerGui.py b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py new file mode 100644 index 0000000000..53b79fa517 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyContainerGui.py @@ -0,0 +1,235 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +import PathScripts.PathGui as PathGui +import PathScripts.PathIconViewProvider as PathIconViewProvider +import PathScripts.PathLog as PathLog +import PathScripts.PathPropertyContainer as PathPropertyContainer +import PathScripts.PathPropertyEditor as PathPropertyEditor +import PathScripts.PathUtil as PathUtil + +from PySide import QtCore, QtGui + +__title__ = "Property Container Editor" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Task panel editor for a PropertyContainer" + +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.trackModule(PathLog.thisModule()) + +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +class ViewProvider: + '''ViewProvider for a PropertyContainer. + It's sole job is to provide an icon and invoke the TaskPanel on edit.''' + + def __init__(self, vobj, name): + PathLog.track(name) + vobj.Proxy = self + self.icon = name + # mode = 2 + self.obj = None + self.vobj = None + + def attach(self, vobj): + PathLog.track() + self.vobj = vobj + self.obj = vobj.Object + + def getIcon(self): + return ":/icons/Path-SetupSheet.svg" + + def __getstate__(self): + return None + + def __setstate__(self, state): + # pylint: disable=unused-argument + return None + + def getDisplayMode(self, mode): + # pylint: disable=unused-argument + return 'Default' + + def setEdit(self, vobj, mode=0): + # pylint: disable=unused-argument + PathLog.track() + taskPanel = TaskPanel(vobj) + FreeCADGui.Control.closeDialog() + FreeCADGui.Control.showDialog(taskPanel) + taskPanel.setupUi() + return True + + def unsetEdit(self, vobj, mode): + # pylint: disable=unused-argument + FreeCADGui.Control.closeDialog() + return + + def claimChildren(self): + return [] + + def doubleClicked(self, vobj): + self.setEdit(vobj) + +class Delegate(QtGui.QStyledItemDelegate): + RoleObject = QtCore.Qt.UserRole + 1 + RoleProperty = QtCore.Qt.UserRole + 2 + RoleEditor = QtCore.Qt.UserRole + 3 + + + #def paint(self, painter, option, index): + # #PathLog.track(index.column(), type(option)) + + def createEditor(self, parent, option, index): + # pylint: disable=unused-argument + editor = PathPropertyEditor.Editor(index.data(self.RoleObject), index.data(self.RoleProperty)) + index.model().setData(index, editor, self.RoleEditor) + return editor.widget(parent) + + def setEditorData(self, widget, index): + PathLog.track(index.row(), index.column()) + index.data(self.RoleEditor).setEditorData(widget) + + def setModelData(self, widget, model, index): + # pylint: disable=unused-argument + PathLog.track(index.row(), index.column()) + editor = index.data(self.RoleEditor) + editor.setModelData(widget) + index.model().setData(index, editor.displayString(), QtCore.Qt.DisplayRole) + + def updateEditorGeometry(self, widget, option, index): + # pylint: disable=unused-argument + widget.setGeometry(option.rect) + +class TaskPanel: + ColumnName = 0 + ColumnVal = 1 + ColumnDesc = 2 + + def __init__(self, vobj): + self.obj = vobj.Object + self.props = sorted(self.obj.Proxy.getCustomProperties()) + self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyContainer.ui") + + # initialized later + self.delegate = None + self.model = None + FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyContainer", "Edit PropertyContainer")) + + def updateData(self, topLeft, bottomRight): + # pylint: disable=unused-argument + #if 0 == topLeft.column(): + # isset = self.model.item(topLeft.row(), 0).checkState() == QtCore.Qt.Checked + # self.model.item(topLeft.row(), 1).setEnabled(isset) + # self.model.item(topLeft.row(), 2).setEnabled(isset) + print("index = ({}, {}) - ({}, {})".format(topLeft.row(), topLeft.column(), bottomRight.row(), bottomRight.column())) + if topLeft.column() == self.ColumnDesc: + obj = topLeft.data(Delegate.RoleObject) + prop = topLeft.data(Delegate.RoleProperty) + + def setupUi(self): + PathLog.track() + + self.delegate = Delegate(self.form) + self.model = QtGui.QStandardItemModel(len(self.props), 3, self.form) + self.model.setHorizontalHeaderLabels(['Property', 'Value', 'Description']) + + for i,name in enumerate(self.props): + info = self.obj.getDocumentationOfProperty(name) + value = self.obj.getPropertyByName(name) + self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole) + self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject) + self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty) + self.model.setData(self.model.index(i, self.ColumnVal), str(value), QtCore.Qt.DisplayRole) + self.model.setData(self.model.index(i, self.ColumnDesc), info, QtCore.Qt.EditRole) + + self.model.item(i, self.ColumnName).setEditable(False) + + self.form.table.setModel(self.model) + self.form.table.setItemDelegateForColumn(self.ColumnVal, self.delegate) + self.form.table.resizeColumnsToContents() + + self.model.dataChanged.connect(self.updateData) + self.form.table.selectionModel().selectionChanged.connect(self.propertySelected) + self.form.add.clicked.connect(self.propertyAdd) + self.form.remove.clicked.connect(self.propertyRemove) + self.propertySelected([]) + + def accept(self): + #propertiesCreatedRemoved = False + #for i,name in enumerate(self.props): + # prop = self.prototype.getProperty(name) + # propName = self.propertyName(name) + # enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked + # if enabled and not prop.getValue() is None: + # if prop.setupProperty(self.obj, propName, self.propertyGroup(), prop.getValue()): + # propertiesCreatedRemoved = True + # else: + # if hasattr(self.obj, propName): + # self.obj.removeProperty(propName) + # propertiesCreatedRemoved = True + + FreeCAD.ActiveDocument.commitTransaction() + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + + def reject(self): + FreeCAD.ActiveDocument.abortTransaction() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + + def propertySelected(self, selection): + PathLog.track() + if selection: + self.form.remove.setEnabled(True) + else: + self.form.remove.setEnabled(False) + + def propertyAdd(self): + PathLog.track() + + def propertyRemove(self): + PathLog.track() + rows = [] + for index in self.form.table.selectionModel().selectedIndexes(): + if not index.row() in rows: + rows.append(index.row()) + for row in reversed(sorted(rows)): + self.obj.removeProperty(self.model.item(row).data(QtCore.Qt.EditRole)) + self.model.removeRow(row) + + +def Create(name = 'PropertyContainer'): + '''Create(name = 'PropertyContainer') ... creates a new setup sheet''' + FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyContainer", "Create PropertyContainer")) + pcont = PathPropertyContainer.Create(name) + PathIconViewProvider.Attach(pcont.ViewObject, name) + return pcont + +PathIconViewProvider.RegisterViewProvider('PropertyContainer', ViewProvider) + diff --git a/src/Mod/Path/PathScripts/PathPropertyEditor.py b/src/Mod/Path/PathScripts/PathPropertyEditor.py new file mode 100644 index 0000000000..9b0cd81afd --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyEditor.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import PathScripts.PathLog as PathLog +import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype + +from PySide import QtCore, QtGui + +__title__ = "Path Property Editor" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Task panel editor for Properties" + +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + +LOGLEVEL = False + +if LOGLEVEL: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +class _PropertyEditor(object): + '''Base class of all property editors - just outlines the TableView delegate interface.''' + def __init__(self, obj, prop): + self.obj = obj + self.prop = prop + + def widget(self, parent): + '''widget(parent) ... called by the delegate to get a new editor widget. + Must be implemented by subclasses and return the widget.''' + pass # pylint: disable=unnecessary-pass + + def setEditorData(self, widget): + '''setEditorData(widget) ... called by the delegate to initialize the editor. + The widget is the object returned by widget(). + Must be implemented by subclasses.''' + pass # pylint: disable=unnecessary-pass + + def setModelData(self, widget): + '''setModelData(widget) ... called by the delegate to store new values. + Must be implemented by subclasses.''' + pass # pylint: disable=unnecessary-pass + + def propertyValue(self): + return self.obj.getPropertyByName(self.prop) + + def setProperty(self, value): + setattr(self.obj, self.prop, value) + + def displayString(self): + return self.propertyValue() + +class _PropertyEditorBool(_PropertyEditor): + '''Editor for boolean values - uses a combo box.''' + + def widget(self, parent): + return QtGui.QComboBox(parent) + + def setEditorData(self, widget): + widget.clear() + widget.addItems(['false', 'true']) + index = 1 if self.propertyValue() else 0 + widget.setCurrentIndex(index) + + def setModelData(self, widget): + self.setProperty(widget.currentText() == 'true') + +class _PropertyEditorString(_PropertyEditor): + '''Editor for string values - uses a line edit.''' + + def widget(self, parent): + return QtGui.QLineEdit(parent) + + def setEditorData(self, widget): + text = '' if self.propertyValue() is None else self.propertyValue() + widget.setText(text) + + def setModelData(self, widget): + self.setProperty(widget.text()) + +class _PropertyEditorQuantity(_PropertyEditor): + + def widget(self, parent): + return QtGui.QLineEdit(parent) + + def setEditorData(self, widget): + quantity = self.propertyValue() + if quantity is None: + quantity = self.defaultQuantity() + widget.setText(quantity.getUserPreferred()[0]) + + def defaultQuantity(self): + pass + + def setModelData(self, widget): + self.setProperty(FreeCAD.Units.Quantity(widget.text())) + + def displayString(self): + if self.propertyValue() is None: + return '' + return self.propertyValue().getUserPreferred()[0] + +class _PropertyEditorAngle(_PropertyEditorQuantity): + '''Editor for angle values - uses a line edit''' + + def defaultQuantity(self): + return FreeCAD.Units.Quantity(0, FreeCAD.Units.Angle) + +class _PropertyEditorLength(_PropertyEditorQuantity): + '''Editor for length values - uses a line edit.''' + + def defaultQuantity(self): + return FreeCAD.Units.Quantity(0, FreeCAD.Units.Length) + +class _PropertyEditorPercent(_PropertyEditor): + '''Editor for percent values - uses a spin box.''' + + def widget(self, parent): + return QtGui.QSpinBox(parent) + + def setEditorData(self, widget): + widget.setRange(0, 100) + value = self.propertyValue() + if value is None: + value = 0 + widget.setValue(value) + + def setModelData(self, widget): + self.setProperty(widget.value()) + +class _PropertyEditorInteger(_PropertyEditor): + '''Editor for integer values - uses a spin box.''' + + def widget(self, parent): + return QtGui.QSpinBox(parent) + + def setEditorData(self, widget): + value = self.propertyValue() + if value is None: + value = 0 + widget.setValue(value) + + def setModelData(self, widget): + self.setProperty(widget.value()) + +class _PropertyEditorFloat(_PropertyEditor): + '''Editor for float values - uses a double spin box.''' + + def widget(self, parent): + return QtGui.QDoubleSpinBox(parent) + + def setEditorData(self, widget): + value = self.propertyValue() + if value is None: + value = 0.0 + widget.setValue(value) + + def setModelData(self, widget): + self.setProperty(widget.value()) + +class _PropertyEditorFile(_PropertyEditor): + + def widget(self, parent): + return QtGui.QLineEdit(parent) + + def setEditorData(self, widget): + text = '' if self.propertyValue() is None else self.propertyValue() + widget.setText(text) + + def setModelData(self, widget): + self.setProperty(widget.text()) + +_EditorFactory = { + 'App::PropertyAngle' : _PropertyEditorAngle, + 'App::PropertyBool' : _PropertyEditorBool, + 'App::PropertyDistance' : _PropertyEditorLength, + #'App::PropertyEnumeration' : _PropertyEditorEnum, + #'App::PropertyFile' : _PropertyEditorFile, + 'App::PropertyFloat' : _PropertyEditorFloat, + 'App::PropertyInteger' : _PropertyEditorInteger, + 'App::PropertyLength' : _PropertyEditorLength, + 'App::PropertyPercent' : _PropertyEditorPercent, + 'App::PropertyString' : _PropertyEditorString, + } + +def Types(): + '''Return the types of properties supported.''' + return [t for t in _EditorFactory] + +def Editor(obj, prop): + '''Returns an editor class to be used for the given property.''' + factory = _EditorFactory[obj.getTypeIdOfProperty(prop)] + if factory: + return factory(obj, prop) + return None