# -*- 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.PathPropertyBag as PathPropertyBag import PathScripts.PathPropertyEditor as PathPropertyEditor import PathScripts.PathUtil as PathUtil from PySide import QtCore, QtGui __title__ = "Property Bag Editor" __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Task panel editor for a PropertyBag" PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) #PathLog.trackModule(PathLog.thisModule()) # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) class ViewProvider(object): '''ViewProvider for a PropertyBag. 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 PropertyCreate(object): def __init__(self, obj, grp, typ, another): self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyCreate.ui") for g in sorted(obj.CustomPropertyGroups): self.form.propertyGroup.addItem(g) if grp: self.form.propertyGroup.setCurrentText(grp) for t in sorted(PathPropertyBag.SupportedPropertyType): self.form.propertyType.addItem(t) if PathPropertyBag.SupportedPropertyType[t] == typ: typ = t if typ: self.form.propertyType.setCurrentText(typ) else: self.form.propertyType.setCurrentText('String') self.form.createAnother.setChecked(another) self.form.propertyGroup.currentTextChanged.connect(self.updateUI) self.form.propertyGroup.currentIndexChanged.connect(self.updateUI) self.form.propertyName.textChanged.connect(self.updateUI) self.form.propertyType.currentIndexChanged.connect(self.updateUI) self.form.enumValues.textChanged.connect(self.updateUI) def updateUI(self): typeSet = True if self.propertyIsEnumeration(): self.form.enumLabel.setEnabled(True) self.form.enumValues.setEnabled(True) typeSet = self.form.enumValues.text().strip() != '' else: self.form.enumLabel.setEnabled(False) self.form.enumValues.setEnabled(False) if self.form.enumValues.text().strip(): self.form.enumValues.setText('') ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok) if typeSet and self.propertyName() and self.propertyGroup(): ok.setEnabled(True) else: ok.setEnabled(False) def propertyName(self): return self.form.propertyName.text().strip() def propertyGroup(self): return self.form.propertyGroup.currentText().strip() def propertyType(self): return PathPropertyBag.SupportedPropertyType[self.form.propertyType.currentText()].strip() def propertyInfo(self): return self.form.propertyInfo.toPlainText().strip() def createAnother(self): return self.form.createAnother.isChecked() def propertyEnumerations(self): return [s.strip() for s in self.form.enumValues.text().strip().split(',')] def propertyIsEnumeration(self): return self.propertyType() == 'App::PropertyEnumeration' def exec_(self, name): if name: # property exists - this is an edit operation self.form.propertyName.setText(name) if self.propertyIsEnumeration(): self.form.enumValues.setText(','.join(self.obj.getEnumerationsOfProperty(name))) self.form.propertyInfo.setText(self.obj.getDocumentationOfProperty(name)) self.form.propertyName.setEnabled(False) self.form.propertyType.setEnabled(False) self.form.createAnother.setEnabled(False) else: self.form.propertyName.setText('') self.form.propertyInfo.setText('') self.form.enumValues.setText('') #self.form.propertyName.setFocus() self.updateUI() return self.form.exec_() Panel = [] class TaskPanel(object): ColumnName = 0 #ColumnType = 1 ColumnVal = 1 #TableHeaders = ['Property', 'Type', 'Value'] TableHeaders = ['Property', 'Value'] def __init__(self, vobj): self.obj = vobj.Object self.props = sorted(self.obj.Proxy.getCustomProperties()) self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui") # initialized later self.model = None self.delegate = None FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Edit PropertyBag")) Panel.append(self) def updateData(self, topLeft, bottomRight): pass def _setupProperty(self, i, name): typ = PathPropertyBag.getPropertyTypeName(self.obj.getTypeIdOfProperty(name)) val = PathUtil.getPropertyValueString(self.obj, name) info = self.obj.getDocumentationOfProperty(name) self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole) #self.model.setData(self.model.index(i, self.ColumnType), typ, 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), val, QtCore.Qt.DisplayRole) self.model.setData(self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole) #self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole) self.model.setData(self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole) self.model.item(i, self.ColumnName).setEditable(False) #self.model.item(i, self.ColumnType).setEditable(False) def setupUi(self): PathLog.track() self.delegate = Delegate(self.form) self.model = QtGui.QStandardItemModel(len(self.props), len(self.TableHeaders), self.form) self.model.setHorizontalHeaderLabels(self.TableHeaders) for i,name in enumerate(self.props): self._setupProperty(i, name) 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.form.modify.clicked.connect(self.propertyModify) self.propertySelected([]) def accept(self): 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.modify.setEnabled(True) self.form.remove.setEnabled(True) else: self.form.modify.setEnabled(False) self.form.remove.setEnabled(False) def addCustomProperty(self, obj, dialog): name = dialog.propertyName() typ = dialog.propertyType() grp = dialog.propertyGroup() info = dialog.propertyInfo() self.obj.Proxy.addCustomProperty(typ, name, grp, info) if dialog.propertyIsEnumeration(): setattr(self.obj, name, dialog.propertyEnumerations()) return (name, info) def propertyAdd(self): PathLog.track() more = False grp = None typ = None while True: dialog = PropertyCreate(self.obj, grp, typ, more) if dialog.exec_(None): # if we block signals the view doesn't get updated, surprise, surprise #self.model.blockSignals(True) name, info = self.addCustomProperty(self.obj, dialog) index = 0 for i in range(self.model.rowCount()): index = i if self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) > dialog.propertyName(): break self.model.insertRows(index, 1) self._setupProperty(index, name) self.form.table.selectionModel().setCurrentIndex(self.model.index(index, 0), QtCore.QItemSelectionModel.Rows) #self.model.blockSignals(False) more = dialog.createAnother() else: more = False if not more: break def propertyModify(self): PathLog.track() rows = [] for index in self.form.table.selectionModel().selectedIndexes(): row = index.row() if row in rows: continue rows.append(row) obj = self.model.item(row, self.ColumnVal).data(Delegate.RoleObject) nam = self.model.item(row, self.ColumnVal).data(Delegate.RoleProperty) grp = obj.getGroupOfProperty(nam) typ = obj.getTypeIdOfProperty(nam) dialog = PropertyCreate(self.obj, grp, typ, False) if dialog.exec_(nam): val = getattr(obj, nam) obj.removeProperty(nam) name, info = self.addCustomProperty(self.obj, dialog) try: setattr(obj, nam, val) except: # this can happen if the old enumeration value doesn't exist anymore pass newVal = PathUtil.getPropertyValueString(obj, nam) self.model.setData(self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole) #self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole) self.model.setData(self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole) def propertyRemove(self): PathLog.track() # first find all rows which need to be removed rows = [] for index in self.form.table.selectionModel().selectedIndexes(): if not index.row() in rows: rows.append(index.row()) # then remove them in reverse order so the indexes of the remaining rows # to delete are still valid for row in reversed(sorted(rows)): self.obj.removeProperty(self.model.item(row).data(QtCore.Qt.EditRole)) self.model.removeRow(row) def Create(name = 'PropertyBag'): '''Create(name = 'PropertyBag') ... creates a new setup sheet''' FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Create PropertyBag")) pcont = PathPropertyBag.Create(name) PathIconViewProvider.Attach(pcont.ViewObject, name) return pcont PathIconViewProvider.RegisterViewProvider('PropertyBag', ViewProvider) class PropertyBagCreateCommand(object): '''Command to create a property container object''' def __init__(self): pass def GetResources(self): return {'MenuText': translate('PathPropertyBag', 'Property Bag'), 'ToolTip': translate('PathPropertyBag', 'Creates an object which can be used to store reference properties.')} def IsActive(self): return not FreeCAD.ActiveDocument is None def Activated(self): sel = FreeCADGui.Selection.getSelectionEx() obj = Create() body = None if sel: if 'PartDesign::Body' == sel[0].Object.TypeId: body = sel[0].Object elif hasattr(sel[0].Object, 'getParentGeoFeatureGroup'): body = sel[0].Object.getParentGeoFeatureGroup() if body: obj.Label = 'Attributes' group = body.Group group.append(obj) body.Group = group if FreeCAD.GuiUp: FreeCADGui.addCommand('Path_PropertyBag', PropertyBagCreateCommand()) FreeCAD.Console.PrintLog("Loading PathPropertyBagGui ... done\n")