| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| """ |
| Widgets for editing specific types of DocumentObject properties. |
| Includes a factory method to create the appropriate widget based on type. |
| """ |
|
|
| import FreeCAD |
| import FreeCADGui |
| from PySide import QtGui, QtCore |
| from typing import Optional |
|
|
|
|
| class BasePropertyEditorWidget(QtGui.QWidget): |
| """ |
| Base class for property editor widgets. Includes a factory method |
| to create specific subclasses based on property type. |
| """ |
|
|
| |
| propertyChanged = QtCore.Signal() |
|
|
| def __init__(self, obj: FreeCAD.DocumentObject, prop_name: str, parent: QtGui.QWidget = None): |
| super().__init__(parent) |
| self._obj = obj |
| self._prop_name = prop_name |
| self._layout = QtGui.QHBoxLayout(self) |
| self._layout.setContentsMargins(0, 0, 0, 0) |
| self._editor_widget: QtGui.QWidget = None |
| self._editor_mode: int = 0 |
| self._is_read_only: bool = False |
| self._update_editor_mode() |
| self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) |
|
|
| def attachTo(self, obj: FreeCAD.DocumentObject, prop_name: Optional[str] = None): |
| """Attach the editor to a (potentially different) object/property.""" |
| self._obj = obj |
| self._prop_name = prop_name if prop_name else self._prop_name |
| self._update_editor_mode() |
| self.updateWidget() |
|
|
| def _update_editor_mode(self): |
| """Fetch and store the current editor mode for the property.""" |
| if self._obj and self._prop_name: |
| self._editor_mode = self._obj.getEditorMode(self._prop_name) |
| self._is_read_only = self._editor_mode == 2 |
| return |
| self._editor_mode = 0 |
| self._is_read_only = False |
|
|
| def updateWidget(self): |
| """Update the editor widget's display from the object property.""" |
| |
| raise NotImplementedError |
|
|
| def updateProperty(self): |
| """Update the object property from the editor widget's value.""" |
| |
| |
| raise NotImplementedError |
|
|
| @classmethod |
| def for_property( |
| cls, obj: FreeCAD.DocumentObject, prop_name: str, parent: QtGui.QWidget = None |
| ) -> "BasePropertyEditorWidget": |
| """ |
| Factory method to create the appropriate editor widget subclass. |
| """ |
| if not obj or not hasattr(obj, "getPropertyByName"): |
| return LabelPropertyEditorWidget(obj, prop_name, parent) |
|
|
| prop_value = obj.getPropertyByName(prop_name) |
| prop_type = obj.getTypeIdOfProperty(prop_name) |
|
|
| if isinstance(prop_value, FreeCAD.Units.Quantity): |
| return QuantityPropertyEditorWidget(obj, prop_name, parent) |
| elif isinstance(prop_value, bool): |
| return BoolPropertyEditorWidget(obj, prop_name, parent) |
| elif isinstance(prop_value, int): |
| return IntPropertyEditorWidget(obj, prop_name, parent) |
| elif prop_type == "App::PropertyEnumeration": |
| return EnumPropertyEditorWidget(obj, prop_name, parent) |
| else: |
| |
| return LabelPropertyEditorWidget(obj, prop_name, parent) |
|
|
|
|
| class QuantityPropertyEditorWidget(BasePropertyEditorWidget): |
| """Editor widget for Quantity properties.""" |
|
|
| def __init__(self, obj: FreeCAD.DocumentObject, prop_name: str, parent: QtGui.QWidget = None): |
| super().__init__(obj, prop_name, parent) |
| ui = FreeCADGui.UiLoader() |
| self._editor_widget: FreeCADGui.QuantitySpinBox = ui.createWidget("Gui::QuantitySpinBox") |
| self._layout.addWidget(self._editor_widget) |
| self.updateWidget() |
| |
| self._editor_widget.editingFinished.connect(self.updateProperty) |
|
|
| def updateWidget(self): |
| value: FreeCAD.Units.Quantity = self._obj.getPropertyByName(self._prop_name) |
| |
| self._editor_widget.blockSignals(True) |
| self._editor_widget.setProperty("value", value) |
| self._editor_widget.blockSignals(False) |
| self._editor_widget.setEnabled(not self._is_read_only) |
|
|
| def updateProperty(self): |
| current_value = self._obj.getPropertyByName(self._prop_name) |
| new_value_str: str = self._editor_widget.property("value").UserString |
| new_value = FreeCAD.Units.Quantity(new_value_str) |
| if new_value_str != current_value: |
| self._obj.setPropertyByName(self._prop_name, new_value) |
| self.propertyChanged.emit() |
|
|
|
|
| class BoolPropertyEditorWidget(BasePropertyEditorWidget): |
| """Editor widget for Boolean properties.""" |
|
|
| def __init__(self, obj: FreeCAD.DocumentObject, prop_name: str, parent: QtGui.QWidget = None): |
| super().__init__(obj, prop_name, parent) |
| self._editor_widget: QtGui.QComboBox = QtGui.QComboBox() |
| self._editor_widget.addItems(["False", "True"]) |
| self._layout.addWidget(self._editor_widget) |
| self.updateWidget() |
| self._editor_widget.currentIndexChanged.connect(self._on_index_changed) |
|
|
| def updateWidget(self): |
| value: bool = self._obj.getPropertyByName(self._prop_name) |
| self._editor_widget.blockSignals(True) |
| self._editor_widget.setCurrentIndex(1 if value else 0) |
| self._editor_widget.blockSignals(False) |
| self._editor_widget.setEnabled(not self._is_read_only) |
|
|
| def _on_index_changed(self, index: int): |
| """Slot connected to currentIndexChanged signal.""" |
| if self._is_read_only: |
| return |
| current_value: bool = self._obj.getPropertyByName(self._prop_name) |
| new_value: bool = bool(index) |
| if new_value != current_value: |
| self._obj.setPropertyByName(self._prop_name, new_value) |
| self.propertyChanged.emit() |
|
|
| def updateProperty(self): |
| """Update property based on current widget state (for consistency).""" |
| self._on_index_changed(self._editor_widget.currentIndex()) |
|
|
|
|
| class IntPropertyEditorWidget(BasePropertyEditorWidget): |
| """Editor widget for Integer properties.""" |
|
|
| def __init__(self, obj: FreeCAD.DocumentObject, prop_name: str, parent: QtGui.QWidget = None): |
| super().__init__(obj, prop_name, parent) |
| self._editor_widget: QtGui.QSpinBox = QtGui.QSpinBox() |
| self._editor_widget.setMinimum(-2147483648) |
| self._editor_widget.setMaximum(2147483647) |
| self._layout.addWidget(self._editor_widget) |
| self.updateWidget() |
| self._editor_widget.editingFinished.connect(self.updateProperty) |
|
|
| def updateWidget(self): |
| value = self._obj.getPropertyByName(self._prop_name) |
| self._editor_widget.blockSignals(True) |
| self._editor_widget.setValue(value or 0) |
| self._editor_widget.blockSignals(False) |
| self._editor_widget.setEnabled(not self._is_read_only) |
|
|
| def updateProperty(self): |
| current_value: int = self._obj.getPropertyByName(self._prop_name) |
| new_value: int = self._editor_widget.value() |
| if new_value != current_value: |
| self._obj.setPropertyByName(self._prop_name, new_value) |
| self.propertyChanged.emit() |
|
|
|
|
| class EnumPropertyEditorWidget(BasePropertyEditorWidget): |
| """Editor widget for Enumeration properties.""" |
|
|
| def __init__(self, obj: FreeCAD.DocumentObject, prop_name: str, parent: QtGui.QWidget = None): |
| super().__init__(obj, prop_name, parent) |
| self._editor_widget: QtGui.QComboBox = QtGui.QComboBox() |
| self._layout.addWidget(self._editor_widget) |
| self._populate_enum() |
| self.updateWidget() |
| self._editor_widget.currentIndexChanged.connect(self._on_index_changed) |
|
|
| def _populate_enum(self): |
| self._editor_widget.clear() |
| enums: list[str] = self._obj.getEnumerationsOfProperty(self._prop_name) |
| self._editor_widget.addItems(enums) |
|
|
| def attachTo(self, obj: FreeCAD.DocumentObject, prop_name: Optional[str] = None): |
| """Override attachTo to repopulate enums if object changes.""" |
| super().attachTo(obj, prop_name) |
| self._populate_enum() |
|
|
| def updateWidget(self): |
| value: str = self._obj.getPropertyByName(self._prop_name) |
| self._editor_widget.blockSignals(True) |
| index: int = self._editor_widget.findText(value) |
| self._editor_widget.setCurrentIndex(index if index >= 0 else 0) |
| self._editor_widget.blockSignals(False) |
| self._editor_widget.setEnabled(not self._is_read_only) |
|
|
| def _on_index_changed(self, index: int): |
| """Slot connected to currentIndexChanged signal.""" |
| if self._is_read_only: |
| return |
| current_value: str = self._obj.getPropertyByName(self._prop_name) |
| new_value: str = self._editor_widget.itemText(index) |
| if new_value != current_value: |
| self._obj.setPropertyByName(self._prop_name, new_value) |
| self.propertyChanged.emit() |
|
|
| def updateProperty(self): |
| """Update property based on current widget state (for consistency).""" |
| self._on_index_changed(self._editor_widget.currentIndex()) |
|
|
|
|
| class LabelPropertyEditorWidget(BasePropertyEditorWidget): |
| """Read-only label for unsupported or invalid property types.""" |
|
|
| def __init__(self, obj: FreeCAD.DocumentObject, prop_name: str, parent: QtGui.QWidget = None): |
| super().__init__(obj, prop_name, parent) |
| self._editor_widget: QtGui.QLabel = QtGui.QLabel("N/A") |
| self._editor_widget.setTextInteractionFlags( |
| QtGui.Qt.TextSelectableByMouse | QtGui.Qt.TextSelectableByKeyboard |
| ) |
| self._layout.addWidget(self._editor_widget) |
| self.updateWidget() |
|
|
| def updateWidget(self): |
| text = "N/A" |
| text: str = str(self._obj.getPropertyByName(self._prop_name)) |
| self._editor_widget.setText(text) |
|
|
| def updateProperty(self): |
| |
| pass |
|
|