File size: 6,412 Bytes
985c397 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | # SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * Copyright (c) 2025 Samuel Abels <knipknap@gmail.com> *
# * *
# * 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 *
# * *
# ***************************************************************************
"""Widget for editing a list of properties of a DocumentObject."""
import re
from PySide import QtGui, QtCore
from .property import BasePropertyEditorWidget
def _get_label_text(prop_name):
"""Generate a human-readable label from a property name."""
# Add space before capital letters (CamelCase splitting)
s1 = re.sub(r"([A-Z][a-z]+)", r" \1", prop_name)
# Add space before sequences of capitals (e.g., ID) followed by lowercase
s2 = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1 \2", s1)
# Add space before sequences of capitals followed by end of string
s3 = re.sub(r"([A-Z]+)$", r" \1", s2)
# Remove leading/trailing spaces and capitalize
return s3.strip().capitalize()
class DocumentObjectEditorWidget(QtGui.QWidget):
"""
A widget that displays a user friendly form for editing properties of a
FreeCAD DocumentObject.
"""
# Signal emitted when any underlying property value might have changed
propertyChanged = QtCore.Signal()
def __init__(self, obj=None, properties_to_show=None, property_suffixes=None, parent=None):
"""
Initialize the editor widget.
Args:
obj (App.DocumentObject, optional): The object to edit. Defaults to None.
properties_to_show (list[str], optional): List of property names to display.
Defaults to None (shows nothing).
property_suffixes (dict[str, str], optional): Dictionary mapping property names
to suffixes for their labels.
Defaults to None.
parent (QWidget, optional): The parent widget. Defaults to None.
"""
super().__init__(parent)
self._obj = obj
self._properties_to_show = properties_to_show if properties_to_show else []
self._property_suffixes = property_suffixes if property_suffixes else {}
self._property_editors = {} # Store {prop_name: editor_widget}
self._layout = QtGui.QFormLayout(self)
self._layout.setContentsMargins(0, 0, 0, 0)
self._layout.setFieldGrowthPolicy(QtGui.QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
self._populate_form()
def _clear_form(self):
"""Remove all rows from the form layout."""
while self._layout.rowCount() > 0:
self._layout.removeRow(0)
self._property_editors.clear()
def _populate_form(self):
"""Create and add property editors to the form."""
self._clear_form()
if not self._obj:
return
for prop_name in self._properties_to_show:
# Only create an editor if the property exists on the object
if not hasattr(self._obj, prop_name):
continue
editor_widget = BasePropertyEditorWidget.for_property(self._obj, prop_name, self)
label_text = _get_label_text(prop_name)
suffix = self._property_suffixes.get(prop_name)
if suffix:
label_text = f"{label_text} ({suffix}):"
else:
label_text = f"{label_text}:"
label = QtGui.QLabel(label_text)
self._layout.addRow(label, editor_widget)
self._property_editors[prop_name] = editor_widget
# Connect the editor's signal to our own signal
editor_widget.propertyChanged.connect(self.propertyChanged)
def setObject(self, obj):
"""Set or change the DocumentObject being edited."""
if obj != self._obj:
self._obj = obj
# Re-populate might be too slow if only object changes,
# better to just re-attach existing editors.
# self._populate_form()
for prop_name, editor in self._property_editors.items():
editor.attachTo(self._obj, prop_name)
def setPropertiesToShow(self, properties_to_show, property_suffixes=None):
"""Set or change the list of properties to display."""
self._properties_to_show = properties_to_show if properties_to_show else []
self._property_suffixes = property_suffixes if property_suffixes else {}
self._populate_form() # Rebuild the form completely
def updateUI(self):
"""Update all child editor widgets from the object's properties."""
for editor in self._property_editors.values():
editor.updateWidget()
def updateObject(self):
"""Update the object's properties from all child editor widgets."""
# This might not be strictly necessary if signals are connected,
# but can be useful for explicit save actions.
for editor in self._property_editors.values():
editor.updateProperty()
|