# SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * Copyright (c) 2025 Samuel Abels * # * * # * 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()