AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
# 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 *
# * *
# ***************************************************************************
"""
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.
"""
# Signal emitted when the underlying property value might have changed
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 # The actual input widget (SpinBox, ComboBox)
self._editor_mode: int = 0 # Default to editable
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() # Ensure widget reflects new state
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."""
# Implementation specific to subclasses
raise NotImplementedError
def updateProperty(self):
"""Update the object property from the editor widget's value."""
# Implementation specific to subclasses
# Should emit propertyChanged signal if value actually changes
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:
# Default to a read-only label for other types
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() # Set initial value
# Connect signal after setting initial value to avoid premature update
self._editor_widget.editingFinished.connect(self.updateProperty)
def updateWidget(self):
value: FreeCAD.Units.Quantity = self._obj.getPropertyByName(self._prop_name)
# Block signals temporarily to prevent feedback loops
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() # Repopulate in case enums are different
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):
# Read-only, no action needed
pass