| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | """ |
| | 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 |
| |
|