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()