# -*- 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 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 import re 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") obj.Proxy.refreshCustomPropertyGroups() 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.propertyEnum.textChanged.connect(self.updateUI) def updateUI(self): typeSet = True if self.propertyIsEnumeration(): self.form.labelEnum.setEnabled(True) self.form.propertyEnum.setEnabled(True) typeSet = self.form.propertyEnum.text().strip() != '' else: self.form.labelEnum.setEnabled(False) self.form.propertyEnum.setEnabled(False) if self.form.propertyEnum.text().strip(): self.form.propertyEnum.setText('') ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok) if not re.match("^[A-Za-z0-9_]*$", self.form.propertyName.text()): typeSet = False if typeSet 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.propertyEnum.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.propertyEnum.setText(','.join(self.obj.getEnumerationsOfProperty(name))) self.form.propertyInfo.setText(self.obj.getDocumentationOfProperty(name)) self.form.labelName.setEnabled(False) self.form.propertyName.setEnabled(False) self.form.labelType.setEnabled(False) self.form.propertyType.setEnabled(False) self.form.createAnother.setEnabled(False) else: self.form.propertyName.setText('') self.form.propertyInfo.setText('') self.form.propertyEnum.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.form.table.doubleClicked.connect(self.propertyModifyIndex) 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() propname = self.obj.Proxy.addCustomProperty(typ, name, grp, info) if dialog.propertyIsEnumeration(): setattr(self.obj, name, dialog.propertyEnumerations()) return (propname, 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 propertyModifyIndex(self, index): PathLog.track(index.row(), index.column()) row = index.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 Exception: # 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 propertyModify(self): PathLog.track() rows = [] for index in self.form.table.selectionModel().selectedIndexes(): row = index.row() if row in rows: continue rows.append(row) self.propertyModifyIndex(index) 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")