generated from dopt-python/py311
basic recursive build functionality
This commit is contained in:
@@ -6,6 +6,8 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
from pprint import pprint
|
||||||
|
from typing import TypeAlias, TypedDict
|
||||||
|
|
||||||
from PySide6.QtCore import QDate, QModelIndex, QStringListModel, Qt, QTimer, Signal
|
from PySide6.QtCore import QDate, QModelIndex, QStringListModel, Qt, QTimer, Signal
|
||||||
from PySide6.QtGui import QAction, QStandardItem, QStandardItemModel
|
from PySide6.QtGui import QAction, QStandardItem, QStandardItemModel
|
||||||
@@ -22,6 +24,7 @@ from PySide6.QtWidgets import (
|
|||||||
QGroupBox,
|
QGroupBox,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
|
QLayout,
|
||||||
QLineEdit,
|
QLineEdit,
|
||||||
QListWidget,
|
QListWidget,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
@@ -395,18 +398,22 @@ class ContactPersonForm_Search(QWidget):
|
|||||||
|
|
||||||
|
|
||||||
class FormFieldType(enum.StrEnum):
|
class FormFieldType(enum.StrEnum):
|
||||||
|
GROUP = enum.auto()
|
||||||
TEXT = enum.auto()
|
TEXT = enum.auto()
|
||||||
LONGTEXT = enum.auto()
|
LONGTEXT = enum.auto()
|
||||||
DATE = enum.auto()
|
DATE = enum.auto()
|
||||||
DATETIME = enum.auto()
|
DATETIME = enum.auto()
|
||||||
DROPDOWN = enum.auto()
|
DROPDOWN = enum.auto()
|
||||||
|
DYNAMIC_LIST = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
@dc.dataclass(slots=True)
|
@dc.dataclass(slots=True)
|
||||||
class FormField:
|
class FormField:
|
||||||
label: str
|
label: str
|
||||||
type: FormFieldType
|
type: FormFieldType
|
||||||
required: bool
|
children: Sequence[FormField] = dc.field(default_factory=list)
|
||||||
|
parent: FormField | None = None
|
||||||
|
required: bool = False
|
||||||
placeholder: str | None = None
|
placeholder: str | None = None
|
||||||
fill_value: str | None = None
|
fill_value: str | None = None
|
||||||
readonly: bool = False
|
readonly: bool = False
|
||||||
@@ -419,7 +426,7 @@ class FormField:
|
|||||||
self.key = str(uuid.uuid4())
|
self.key = str(uuid.uuid4())
|
||||||
|
|
||||||
self.label = self.label.strip()
|
self.label = self.label.strip()
|
||||||
if not self.label.endswith(":"):
|
if not self.label.endswith(":") and self.type is not FormFieldType.GROUP:
|
||||||
self.label += ":"
|
self.label += ":"
|
||||||
if self.required:
|
if self.required:
|
||||||
self.label += "*"
|
self.label += "*"
|
||||||
@@ -427,6 +434,9 @@ class FormField:
|
|||||||
if self.type is FormFieldType.DROPDOWN and self.options is None:
|
if self.type is FormFieldType.DROPDOWN and self.options is None:
|
||||||
raise ValueError("Invalid field definition: Dropdown requires options")
|
raise ValueError("Invalid field definition: Dropdown requires options")
|
||||||
|
|
||||||
|
for child in self.children:
|
||||||
|
child.parent = self
|
||||||
|
|
||||||
|
|
||||||
@dc.dataclass(slots=True)
|
@dc.dataclass(slots=True)
|
||||||
class FormFieldDynList:
|
class FormFieldDynList:
|
||||||
@@ -440,105 +450,119 @@ class FormFieldGroup:
|
|||||||
fields: Sequence[FormField] | FormFieldDynList
|
fields: Sequence[FormField] | FormFieldDynList
|
||||||
|
|
||||||
|
|
||||||
FORM_FIELD_DEF = [
|
# @dc.dataclass(slots=True)
|
||||||
FormField(
|
# class FormFieldRegistry:
|
||||||
"test",
|
# widget: QWidget
|
||||||
FormFieldType.DROPDOWN,
|
# form_field: FormField
|
||||||
required=False,
|
|
||||||
options=["test1", "test2"],
|
|
||||||
),
|
|
||||||
FormField(
|
|
||||||
"test",
|
|
||||||
FormFieldType.DROPDOWN,
|
|
||||||
required=False,
|
|
||||||
options=["test1", "test2"],
|
|
||||||
fill_value="test1",
|
|
||||||
),
|
|
||||||
FormField(
|
|
||||||
"test",
|
|
||||||
FormFieldType.DROPDOWN,
|
|
||||||
required=False,
|
|
||||||
options=["test1", "test2"],
|
|
||||||
fill_value="test2",
|
|
||||||
readonly=True,
|
|
||||||
),
|
|
||||||
FormField(
|
|
||||||
"test",
|
|
||||||
FormFieldType.DROPDOWN,
|
|
||||||
required=False,
|
|
||||||
options=["test1", "test2", "test3"],
|
|
||||||
fill_value="test3",
|
|
||||||
),
|
|
||||||
FormField("Projektname", FormFieldType.TEXT, True, "Bitte füllen..."),
|
|
||||||
FormField("Beschreibung", FormFieldType.LONGTEXT, False),
|
|
||||||
FormField(
|
|
||||||
"Externe Daten",
|
|
||||||
FormFieldType.TEXT,
|
|
||||||
True,
|
|
||||||
fill_value="Lorem ipsum und so weiter...",
|
|
||||||
readonly=True,
|
|
||||||
),
|
|
||||||
FormField("Auftragsdatum", FormFieldType.DATE, True),
|
|
||||||
FormField("Startdatum", FormFieldType.DATE, True),
|
|
||||||
FormField(
|
|
||||||
"MS Datum 1",
|
|
||||||
FormFieldType.DATE,
|
|
||||||
False,
|
|
||||||
"",
|
|
||||||
fill_value="26.07.2026",
|
|
||||||
readonly=True,
|
|
||||||
),
|
|
||||||
FormField(
|
|
||||||
"MS Datum 2",
|
|
||||||
FormFieldType.DATE,
|
|
||||||
False,
|
|
||||||
"",
|
|
||||||
fill_value="30.08.2026",
|
|
||||||
readonly=False,
|
|
||||||
),
|
|
||||||
FormField(
|
|
||||||
"Wichtige Notizen",
|
|
||||||
FormFieldType.LONGTEXT,
|
|
||||||
True,
|
|
||||||
"Text eingeben...",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
FORM_FIELD_DEF2 = [
|
|
||||||
FormField("Projektname", FormFieldType.TEXT, True, "Bitte füllen..."),
|
class WidgetRegistryEntry(TypedDict):
|
||||||
FormField("Beschreibung", FormFieldType.LONGTEXT, False),
|
widget: QWidget
|
||||||
FormField(
|
form_field: FormField
|
||||||
"Externe Daten",
|
|
||||||
FormFieldType.TEXT,
|
|
||||||
True,
|
WidgetRegistry: TypeAlias = dict[str, WidgetRegistryEntry]
|
||||||
fill_value="Lorem ipsum und so weiter...",
|
|
||||||
readonly=True,
|
|
||||||
),
|
# FORM_FIELD_DEF = [
|
||||||
FormField("Auftragsdatum", FormFieldType.DATE, True),
|
# FormField(
|
||||||
FormField("Startdatum", FormFieldType.DATE, True),
|
# "test",
|
||||||
FormField(
|
# FormFieldType.DROPDOWN,
|
||||||
"MS Datum 1",
|
# required=False,
|
||||||
FormFieldType.DATE,
|
# options=["test1", "test2"],
|
||||||
False,
|
# ),
|
||||||
"",
|
# FormField(
|
||||||
fill_value="26.07.2026",
|
# "test",
|
||||||
readonly=True,
|
# FormFieldType.DROPDOWN,
|
||||||
),
|
# required=False,
|
||||||
FormField(
|
# options=["test1", "test2"],
|
||||||
"MS Datum 2",
|
# fill_value="test1",
|
||||||
FormFieldType.DATE,
|
# ),
|
||||||
False,
|
# FormField(
|
||||||
"",
|
# "test",
|
||||||
fill_value="30.08.2026",
|
# FormFieldType.DROPDOWN,
|
||||||
readonly=False,
|
# required=False,
|
||||||
),
|
# options=["test1", "test2"],
|
||||||
FormField(
|
# fill_value="test2",
|
||||||
"Wichtige Notizen",
|
# readonly=True,
|
||||||
FormFieldType.LONGTEXT,
|
# ),
|
||||||
True,
|
# FormField(
|
||||||
"Text eingeben...",
|
# "test",
|
||||||
),
|
# FormFieldType.DROPDOWN,
|
||||||
]
|
# required=False,
|
||||||
|
# options=["test1", "test2", "test3"],
|
||||||
|
# fill_value="test3",
|
||||||
|
# ),
|
||||||
|
# FormField("Projektname", FormFieldType.TEXT, True, "Bitte füllen..."),
|
||||||
|
# FormField("Beschreibung", FormFieldType.LONGTEXT, False),
|
||||||
|
# FormField(
|
||||||
|
# "Externe Daten",
|
||||||
|
# FormFieldType.TEXT,
|
||||||
|
# True,
|
||||||
|
# fill_value="Lorem ipsum und so weiter...",
|
||||||
|
# readonly=True,
|
||||||
|
# ),
|
||||||
|
# FormField("Auftragsdatum", FormFieldType.DATE, True),
|
||||||
|
# FormField("Startdatum", FormFieldType.DATE, True),
|
||||||
|
# FormField(
|
||||||
|
# "MS Datum 1",
|
||||||
|
# FormFieldType.DATE,
|
||||||
|
# False,
|
||||||
|
# "",
|
||||||
|
# fill_value="26.07.2026",
|
||||||
|
# readonly=True,
|
||||||
|
# ),
|
||||||
|
# FormField(
|
||||||
|
# "MS Datum 2",
|
||||||
|
# FormFieldType.DATE,
|
||||||
|
# False,
|
||||||
|
# "",
|
||||||
|
# fill_value="30.08.2026",
|
||||||
|
# readonly=False,
|
||||||
|
# ),
|
||||||
|
# FormField(
|
||||||
|
# "Wichtige Notizen",
|
||||||
|
# FormFieldType.LONGTEXT,
|
||||||
|
# True,
|
||||||
|
# "Text eingeben...",
|
||||||
|
# ),
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# FORM_FIELD_DEF2 = [
|
||||||
|
# FormField("Projektname", FormFieldType.TEXT, True, "Bitte füllen..."),
|
||||||
|
# FormField("Beschreibung", FormFieldType.LONGTEXT, False),
|
||||||
|
# FormField(
|
||||||
|
# "Externe Daten",
|
||||||
|
# FormFieldType.TEXT,
|
||||||
|
# True,
|
||||||
|
# fill_value="Lorem ipsum und so weiter...",
|
||||||
|
# readonly=True,
|
||||||
|
# ),
|
||||||
|
# FormField("Auftragsdatum", FormFieldType.DATE, True),
|
||||||
|
# FormField("Startdatum", FormFieldType.DATE, True),
|
||||||
|
# FormField(
|
||||||
|
# "MS Datum 1",
|
||||||
|
# FormFieldType.DATE,
|
||||||
|
# False,
|
||||||
|
# "",
|
||||||
|
# fill_value="26.07.2026",
|
||||||
|
# readonly=True,
|
||||||
|
# ),
|
||||||
|
# FormField(
|
||||||
|
# "MS Datum 2",
|
||||||
|
# FormFieldType.DATE,
|
||||||
|
# False,
|
||||||
|
# "",
|
||||||
|
# fill_value="30.08.2026",
|
||||||
|
# readonly=False,
|
||||||
|
# ),
|
||||||
|
# FormField(
|
||||||
|
# "Wichtige Notizen",
|
||||||
|
# FormFieldType.LONGTEXT,
|
||||||
|
# True,
|
||||||
|
# "Text eingeben...",
|
||||||
|
# ),
|
||||||
|
# ]
|
||||||
|
|
||||||
|
|
||||||
FORM_FIELDS_CONTACT_PERSON = [
|
FORM_FIELDS_CONTACT_PERSON = [
|
||||||
@@ -991,13 +1015,27 @@ FORM_FIELDS_LANGUAGES = [
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
FORM_DYN_LIST = FormFieldDynList("Dynamische Liste", FORM_FIELDS_SCHOOL)
|
# FORM_DYN_LIST = FormFieldDynList("Dynamische Liste", FORM_FIELDS_SCHOOL)
|
||||||
|
|
||||||
|
|
||||||
FORM_FIELD_GROUPS = [
|
FORM_FIELDS = [
|
||||||
FormFieldGroup(
|
# FormFieldGroup(
|
||||||
|
# "Status && Projektrelevanz",
|
||||||
|
# [
|
||||||
|
# FormField(
|
||||||
|
# "Projektrelevanz",
|
||||||
|
# FormFieldType.DROPDOWN,
|
||||||
|
# required=True,
|
||||||
|
# options=["ja", "nein"],
|
||||||
|
# fill_value="nein",
|
||||||
|
# ),
|
||||||
|
# ],
|
||||||
|
# ),
|
||||||
|
FormField(
|
||||||
"Status && Projektrelevanz",
|
"Status && Projektrelevanz",
|
||||||
[
|
FormFieldType.GROUP,
|
||||||
|
key="state_relevance",
|
||||||
|
children=[
|
||||||
FormField(
|
FormField(
|
||||||
"Projektrelevanz",
|
"Projektrelevanz",
|
||||||
FormFieldType.DROPDOWN,
|
FormFieldType.DROPDOWN,
|
||||||
@@ -1007,14 +1045,20 @@ FORM_FIELD_GROUPS = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
FormFieldGroup("Daten Kontaktperson", FORM_FIELDS_CONTACT_PERSON),
|
FormField(
|
||||||
FormFieldGroup("Stammdaten (ERGÄNZUNG KINDERALTER DYNAMISCH)", FORM_FIELDS_MASTER_DATA),
|
"Daten Kontaktperson",
|
||||||
FormFieldGroup("weitere Informationen", FORM_FIELDS_ADDITIONAL_DATA),
|
FormFieldType.GROUP,
|
||||||
FormFieldGroup("Schule (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_SCHOOL),
|
key="data_contact_person",
|
||||||
FormFieldGroup("Studium/Ausbildung (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_HIGHER_EDUCATION),
|
children=FORM_FIELDS_CONTACT_PERSON,
|
||||||
FormFieldGroup("Arbeitserfahrung (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_WORK_EXPERIENCE),
|
),
|
||||||
FormFieldGroup("Sprachkenntnisse (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_LANGUAGES),
|
# FormFieldGroup("Daten Kontaktperson", FORM_FIELDS_CONTACT_PERSON),
|
||||||
FormFieldGroup("Dynamische Liste", FORM_DYN_LIST),
|
# FormFieldGroup("Stammdaten (ERGÄNZUNG KINDERALTER DYNAMISCH)", FORM_FIELDS_MASTER_DATA),
|
||||||
|
# FormFieldGroup("weitere Informationen", FORM_FIELDS_ADDITIONAL_DATA),
|
||||||
|
# FormFieldGroup("Schule (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_SCHOOL),
|
||||||
|
# FormFieldGroup("Studium/Ausbildung (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_HIGHER_EDUCATION),
|
||||||
|
# FormFieldGroup("Arbeitserfahrung (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_WORK_EXPERIENCE),
|
||||||
|
# FormFieldGroup("Sprachkenntnisse (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_LANGUAGES),
|
||||||
|
# FormFieldGroup("Dynamische Liste", FORM_DYN_LIST),
|
||||||
# FormFieldGroup("Test-2", FORM_FIELD_DEF2),
|
# FormFieldGroup("Test-2", FORM_FIELD_DEF2),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1022,7 +1066,7 @@ FORM_FIELD_GROUPS = [
|
|||||||
class AutoForm(QWidget):
|
class AutoForm(QWidget):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
form_field_groups: Sequence[FormFieldGroup],
|
form_fields: Sequence[FormField],
|
||||||
add_buttons: bool = True,
|
add_buttons: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -1042,25 +1086,52 @@ class AutoForm(QWidget):
|
|||||||
padding: 0 5px; /* Etwas Luft links und rechts vom Text */
|
padding: 0 5px; /* Etwas Luft links und rechts vom Text */
|
||||||
color: #334155; /* Dunkelgraue Schrift */
|
color: #334155; /* Dunkelgraue Schrift */
|
||||||
}
|
}
|
||||||
|
QComboBox {
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
QComboBox:disabled {
|
||||||
|
background-color: #f1f5f9; /* Helles System-Grau */
|
||||||
|
color: #333D4B; /* Gut lesbare, aber gedeckte Schrift */
|
||||||
|
border: 1px dashed #cbd5e1; /* Gestrichelter Rand wie beim Datum */
|
||||||
|
}
|
||||||
|
QComboBox::drop-down:disabled {
|
||||||
|
border: none;
|
||||||
|
image: none;
|
||||||
|
}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# --- LAYOUT ---
|
# --- LAYOUT ---
|
||||||
self.main_layout = QVBoxLayout(self)
|
self.main_layout = QVBoxLayout(self)
|
||||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.form_field_groups = form_field_groups
|
self.top_level_form_layout = QFormLayout()
|
||||||
|
self.main_layout.addLayout(self.top_level_form_layout)
|
||||||
|
self.top_level_form_layout.setSpacing(10)
|
||||||
|
|
||||||
self.widgets: dict[str, QWidget] = {}
|
self.form_fields = form_fields
|
||||||
|
|
||||||
for fg in form_field_groups:
|
self.widget_registry: WidgetRegistry = {}
|
||||||
_sub = fg.fields
|
|
||||||
if isinstance(_sub, FormFieldDynList):
|
self._build_ui_recursively(
|
||||||
widget = DynamicListWidget(_sub.fields, _sub.label)
|
self.form_fields,
|
||||||
else:
|
self.top_level_form_layout,
|
||||||
widget = FormGroupWidget(_sub, fg.label)
|
self.widget_registry,
|
||||||
self.main_layout.addWidget(widget)
|
)
|
||||||
# TODO widget list is not updated with dynamic lists
|
|
||||||
# self._add_widgets(widget.widgets)
|
# TODO: REMOVE
|
||||||
# self.widgets.update(form_group.widgets)
|
# for fg in form_field_groups:
|
||||||
|
# _sub = fg.fields
|
||||||
|
# if isinstance(_sub, FormFieldDynList):
|
||||||
|
# widget = DynamicListWidget(_sub.fields, _sub.label)
|
||||||
|
# else:
|
||||||
|
# widget = FormGroupWidget(_sub, fg.label)
|
||||||
|
# self.main_layout.addWidget(widget)
|
||||||
|
# TODO widget list is not updated with dynamic lists
|
||||||
|
# self._add_widgets(widget.widgets)
|
||||||
|
# self.widgets.update(form_group.widgets)
|
||||||
|
# print("------------->>> Widget Registry")
|
||||||
|
# pprint(self.widget_registry)
|
||||||
|
|
||||||
# buttons
|
# buttons
|
||||||
self.add_buttons = add_buttons
|
self.add_buttons = add_buttons
|
||||||
@@ -1086,18 +1157,174 @@ class AutoForm(QWidget):
|
|||||||
self.reset_btn.clicked.connect(self.reset_form)
|
self.reset_btn.clicked.connect(self.reset_form)
|
||||||
self.layout_btn.addWidget(self.reset_btn)
|
self.layout_btn.addWidget(self.reset_btn)
|
||||||
|
|
||||||
def _add_widgets(
|
# def _add_widgets(
|
||||||
|
# self,
|
||||||
|
# widgets: dict[str, QWidget],
|
||||||
|
# ) -> None:
|
||||||
|
# current_keys = set(self.widget_registry.keys())
|
||||||
|
# new_keys = set(widgets.keys())
|
||||||
|
# shared_keys = current_keys.intersection(new_keys)
|
||||||
|
|
||||||
|
# if shared_keys:
|
||||||
|
# raise ValueError(f"Tried to add fields with already assigned keys: {shared_keys}")
|
||||||
|
|
||||||
|
# self.widget_registry.update(widgets)
|
||||||
|
|
||||||
|
def _build_ui_recursively(
|
||||||
self,
|
self,
|
||||||
widgets: dict[str, QWidget],
|
schema: Sequence[FormField],
|
||||||
|
parent_layout: QFormLayout,
|
||||||
|
widget_registry: WidgetRegistry,
|
||||||
|
prefix: str = "",
|
||||||
) -> None:
|
) -> None:
|
||||||
current_keys = set(self.widgets.keys())
|
for field in schema:
|
||||||
new_keys = set(widgets.keys())
|
full_key = f"{prefix}.{field.key}" if prefix else field.key
|
||||||
shared_keys = current_keys.intersection(new_keys)
|
|
||||||
|
|
||||||
if shared_keys:
|
widget: QWidget | None = None
|
||||||
raise ValueError(f"Tried to add fields with already assigned keys: {shared_keys}")
|
|
||||||
|
|
||||||
self.widgets.update(widgets)
|
match field.type:
|
||||||
|
case FormFieldType.GROUP:
|
||||||
|
group_box = QGroupBox(field.label)
|
||||||
|
group_layout = QFormLayout(group_box)
|
||||||
|
self._build_ui_recursively(
|
||||||
|
schema=field.children,
|
||||||
|
parent_layout=group_layout,
|
||||||
|
widget_registry=widget_registry,
|
||||||
|
prefix=f"{full_key}",
|
||||||
|
)
|
||||||
|
parent_layout.addRow(group_box)
|
||||||
|
|
||||||
|
case FormFieldType.TEXT:
|
||||||
|
widget = QLineEdit()
|
||||||
|
if field.placeholder:
|
||||||
|
widget.setPlaceholderText(field.placeholder)
|
||||||
|
if field.fill_value:
|
||||||
|
widget.setText(field.fill_value)
|
||||||
|
if field.readonly:
|
||||||
|
widget.setReadOnly(True) # Falls es ein Fixwert ist
|
||||||
|
widget.setProperty("styleClass", "stempel")
|
||||||
|
|
||||||
|
widget_registry[full_key] = {
|
||||||
|
"widget": widget,
|
||||||
|
"form_field": field,
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.tooltip:
|
||||||
|
tooltip_layout = self._add_tooltip(widget, field.tooltip)
|
||||||
|
parent_layout.addRow(field.label, tooltip_layout)
|
||||||
|
else:
|
||||||
|
parent_layout.addRow(field.label, widget)
|
||||||
|
|
||||||
|
case FormFieldType.LONGTEXT:
|
||||||
|
widget = QPlainTextEdit()
|
||||||
|
widget.setMaximumHeight(80) # Kompakte Höhe für Formulare
|
||||||
|
if field.placeholder:
|
||||||
|
widget.setPlaceholderText(field.placeholder)
|
||||||
|
if field.readonly:
|
||||||
|
widget.setReadOnly(True) # Falls es ein Fixwert ist
|
||||||
|
widget.setProperty("styleClass", "stempel")
|
||||||
|
|
||||||
|
widget_registry[full_key] = {
|
||||||
|
"widget": widget,
|
||||||
|
"form_field": field,
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.tooltip:
|
||||||
|
tooltip_layout = self._add_tooltip(widget, field.tooltip)
|
||||||
|
parent_layout.addRow(field.label, tooltip_layout)
|
||||||
|
else:
|
||||||
|
parent_layout.addRow(field.label, widget)
|
||||||
|
|
||||||
|
case FormFieldType.DATE:
|
||||||
|
widget = QDateEdit() # Oder QDateEdit
|
||||||
|
widget.setCalendarPopup(True)
|
||||||
|
cal = widget.calendarWidget()
|
||||||
|
if cal:
|
||||||
|
cal.setFirstDayOfWeek(Qt.DayOfWeek.Monday)
|
||||||
|
cal.setGridVisible(True)
|
||||||
|
cal.setStyleSheet("""
|
||||||
|
QCalendarWidget QAbstractItemView {
|
||||||
|
selection-background-color: #2980b9;
|
||||||
|
selection-color: white;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
if field.fill_value:
|
||||||
|
set_date = QDate.fromString(field.fill_value, "dd.MM.yyyy")
|
||||||
|
if not set_date.isValid():
|
||||||
|
raise ValueError(
|
||||||
|
f"Could not parse date field value >{field.fill_value}<"
|
||||||
|
)
|
||||||
|
widget.setDate(set_date)
|
||||||
|
else:
|
||||||
|
widget.setDate(QDate.currentDate())
|
||||||
|
|
||||||
|
if field.readonly:
|
||||||
|
widget = QLineEdit()
|
||||||
|
widget.setReadOnly(True) # Falls es ein Fixwert ist
|
||||||
|
widget.setProperty("styleClass", "stempel")
|
||||||
|
if field.fill_value:
|
||||||
|
widget.setText(field.fill_value)
|
||||||
|
|
||||||
|
widget_registry[full_key] = {
|
||||||
|
"widget": widget,
|
||||||
|
"form_field": field,
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.tooltip:
|
||||||
|
tooltip_layout = self._add_tooltip(widget, field.tooltip)
|
||||||
|
parent_layout.addRow(field.label, tooltip_layout)
|
||||||
|
else:
|
||||||
|
parent_layout.addRow(field.label, widget)
|
||||||
|
|
||||||
|
case FormFieldType.DROPDOWN:
|
||||||
|
widget = QComboBox()
|
||||||
|
assert field.options
|
||||||
|
widget.addItem("--- Bitte wählen ---")
|
||||||
|
widget.addItems(field.options)
|
||||||
|
if field.placeholder:
|
||||||
|
widget.setPlaceholderText(field.placeholder)
|
||||||
|
if field.fill_value:
|
||||||
|
widget.setCurrentText(field.fill_value)
|
||||||
|
else:
|
||||||
|
widget.setCurrentIndex(0)
|
||||||
|
if field.readonly:
|
||||||
|
widget.setEnabled(False)
|
||||||
|
widget.setProperty("styleClass", "stempel")
|
||||||
|
|
||||||
|
widget_registry[full_key] = {
|
||||||
|
"widget": widget,
|
||||||
|
"form_field": field,
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.tooltip:
|
||||||
|
tooltip_layout = self._add_tooltip(widget, field.tooltip)
|
||||||
|
parent_layout.addRow(field.label, tooltip_layout)
|
||||||
|
else:
|
||||||
|
parent_layout.addRow(field.label, widget)
|
||||||
|
|
||||||
|
# case FormFieldType.DYNAMIC_LIST:
|
||||||
|
# widget = DynamicListWidget(field.label)
|
||||||
|
|
||||||
|
case _:
|
||||||
|
raise NotImplementedError(f"Not supported field type: {field.type.value}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_tooltip(
|
||||||
|
widget: QWidget,
|
||||||
|
tooltip: str,
|
||||||
|
) -> QHBoxLayout:
|
||||||
|
field_layout = QHBoxLayout()
|
||||||
|
field_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
field_layout.setSpacing(5)
|
||||||
|
info_btn = QPushButton("ℹ️")
|
||||||
|
info_btn.setFixedSize(28, 27)
|
||||||
|
info_btn.setFlat(True)
|
||||||
|
info_btn.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
|
info_btn.setToolTip(tooltip)
|
||||||
|
field_layout.addWidget(widget)
|
||||||
|
field_layout.addWidget(info_btn)
|
||||||
|
|
||||||
|
return field_layout
|
||||||
|
|
||||||
def _disable_save(self) -> None:
|
def _disable_save(self) -> None:
|
||||||
self.save_btn.setEnabled(False)
|
self.save_btn.setEnabled(False)
|
||||||
@@ -1118,46 +1345,48 @@ class AutoForm(QWidget):
|
|||||||
# time.sleep(0.5)
|
# time.sleep(0.5)
|
||||||
|
|
||||||
# Wir gehen unsere Feld-Definitionen durch
|
# Wir gehen unsere Feld-Definitionen durch
|
||||||
for fg in self.form_field_groups:
|
# for fg in self.form_fields:
|
||||||
if isinstance(fg.fields, DynamicListWidget):
|
# if isinstance(fg.fields, DynamicListWidget):
|
||||||
|
# continue
|
||||||
|
|
||||||
|
for key, registry_entry in self.widget_registry.items(): # type: ignore
|
||||||
|
# widget = self.widget_registry[field.key]
|
||||||
|
widget = registry_entry["widget"]
|
||||||
|
form_field = registry_entry["form_field"]
|
||||||
|
|
||||||
|
# 1. Zuerst setzen wir das Design des Feldes wieder auf "Normal" zurück.
|
||||||
|
# Falls der Nutzer den Fehler vorher schon korrigiert hat, muss der rote Rand weg!
|
||||||
|
if not form_field.readonly:
|
||||||
|
widget.setStyleSheet("")
|
||||||
|
|
||||||
|
# 2. Ist es überhaupt ein Pflichtfeld?
|
||||||
|
if not form_field.required:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for field in fg.fields: # type: ignore
|
is_empty = False
|
||||||
widget = self.widgets[field.key]
|
if isinstance(widget, (QLineEdit, QDateEdit)):
|
||||||
|
if not widget.text().strip():
|
||||||
|
is_empty = True
|
||||||
|
|
||||||
# 1. Zuerst setzen wir das Design des Feldes wieder auf "Normal" zurück.
|
elif isinstance(widget, QPlainTextEdit):
|
||||||
# Falls der Nutzer den Fehler vorher schon korrigiert hat, muss der rote Rand weg!
|
if not widget.toPlainText().strip():
|
||||||
if not field.readonly:
|
is_empty = True
|
||||||
widget.setStyleSheet("")
|
|
||||||
|
|
||||||
# 2. Ist es überhaupt ein Pflichtfeld?
|
if not is_empty:
|
||||||
if not field.required:
|
continue
|
||||||
continue
|
|
||||||
|
|
||||||
is_empty = False
|
error = form_field.label.replace("*", "").replace(":", "")
|
||||||
if isinstance(widget, (QLineEdit, QDateEdit)):
|
if form_field.parent is not None:
|
||||||
if not widget.text().strip():
|
error = f"{form_field.parent.label}: {error}"
|
||||||
is_empty = True
|
errors.append(error)
|
||||||
|
|
||||||
elif isinstance(widget, QPlainTextEdit):
|
# Optisches Feedback: Heller roter Hintergrund und roter Rand
|
||||||
if not widget.toPlainText().strip():
|
widget.setStyleSheet("""
|
||||||
is_empty = True
|
border: 1px solid #ef4444;
|
||||||
|
background-color: #ffe9e9;
|
||||||
if not is_empty:
|
padding: 4px;
|
||||||
continue
|
border-radius: 4px;
|
||||||
|
""")
|
||||||
error = field.label.replace("*", "").replace(":", "")
|
|
||||||
if fg.label:
|
|
||||||
error = f"{fg.label}: {error}"
|
|
||||||
errors.append(error)
|
|
||||||
|
|
||||||
# Optisches Feedback: Heller roter Hintergrund und roter Rand
|
|
||||||
widget.setStyleSheet("""
|
|
||||||
border: 1px solid #ef4444;
|
|
||||||
background-color: #ffe9e9;
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
""")
|
|
||||||
|
|
||||||
# --- ERGEBNIS AUSWERTEN ---
|
# --- ERGEBNIS AUSWERTEN ---
|
||||||
if errors:
|
if errors:
|
||||||
@@ -1177,43 +1406,50 @@ class AutoForm(QWidget):
|
|||||||
self._enable_save()
|
self._enable_save()
|
||||||
|
|
||||||
def reset_form(self) -> None:
|
def reset_form(self) -> None:
|
||||||
for fg in self.form_field_groups:
|
for key, registry_entry in self.widget_registry.items(): # type: ignore
|
||||||
if isinstance(fg.fields, DynamicListWidget):
|
# widget = self.widget_registry[field.key]
|
||||||
|
widget = registry_entry["widget"]
|
||||||
|
form_field = registry_entry["form_field"]
|
||||||
|
|
||||||
|
# for fg in self.form_fields:
|
||||||
|
# if isinstance(fg.fields, DynamicListWidget):
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# for field in fg.fields: # type: ignore
|
||||||
|
# widget = self.widget_registry[field.key]
|
||||||
|
|
||||||
|
if form_field.readonly:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for field in fg.fields: # type: ignore
|
if isinstance(widget, QLineEdit):
|
||||||
widget = self.widgets[field.key]
|
widget.clear()
|
||||||
|
if form_field.fill_value:
|
||||||
|
widget.setText(form_field.fill_value)
|
||||||
|
elif isinstance(widget, QPlainTextEdit):
|
||||||
|
widget.clear()
|
||||||
|
elif isinstance(widget, QDateEdit):
|
||||||
|
if form_field.fill_value:
|
||||||
|
set_date = QDate.fromString(form_field.fill_value, "dd.MM.yyyy")
|
||||||
|
if not set_date.isValid():
|
||||||
|
raise ValueError(
|
||||||
|
f"Could not parse date field value >{form_field.fill_value}<"
|
||||||
|
)
|
||||||
|
widget.setDate(set_date)
|
||||||
|
else:
|
||||||
|
widget.setDate(QDate.currentDate())
|
||||||
|
elif isinstance(widget, QComboBox):
|
||||||
|
if form_field.fill_value:
|
||||||
|
widget.setCurrentText(form_field.fill_value)
|
||||||
|
else:
|
||||||
|
widget.setCurrentIndex(0)
|
||||||
|
|
||||||
if field.readonly:
|
widget.setStyleSheet("")
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(widget, QLineEdit):
|
|
||||||
widget.clear()
|
|
||||||
if field.fill_value:
|
|
||||||
widget.setText(field.fill_value)
|
|
||||||
elif isinstance(widget, QPlainTextEdit):
|
|
||||||
widget.clear()
|
|
||||||
elif isinstance(widget, QDateEdit):
|
|
||||||
if field.fill_value:
|
|
||||||
set_date = QDate.fromString(field.fill_value, "dd.MM.yyyy")
|
|
||||||
if not set_date.isValid():
|
|
||||||
raise ValueError(
|
|
||||||
f"Could not parse date field value >{field.fill_value}<"
|
|
||||||
)
|
|
||||||
widget.setDate(set_date)
|
|
||||||
else:
|
|
||||||
widget.setDate(QDate.currentDate())
|
|
||||||
elif isinstance(widget, QComboBox):
|
|
||||||
if field.fill_value:
|
|
||||||
widget.setCurrentText(field.fill_value)
|
|
||||||
|
|
||||||
widget.setStyleSheet("")
|
|
||||||
|
|
||||||
def get_form_data(self) -> ...:
|
def get_form_data(self) -> ...:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
for key, widget in self.widgets.items():
|
for key, widget in self.widget_registry.items():
|
||||||
if isinstance(widget, (QLineEdit, QDateEdit)):
|
if isinstance(widget, (QLineEdit, QDateEdit)):
|
||||||
data[key] = widget.text()
|
data[key] = widget.text()
|
||||||
elif isinstance(widget, QPlainTextEdit):
|
elif isinstance(widget, QPlainTextEdit):
|
||||||
@@ -1839,7 +2075,7 @@ class SearchFormPage(QWidget):
|
|||||||
title.setStyleSheet("font-size: 14px; font-style: italic;") # font-weight: bold;
|
title.setStyleSheet("font-size: 14px; font-style: italic;") # font-weight: bold;
|
||||||
container_layout.addWidget(title)
|
container_layout.addWidget(title)
|
||||||
# container_layout.addWidget(MyFormPart(FORM_FIELD_DEF, "Test-Gruppe"))
|
# container_layout.addWidget(MyFormPart(FORM_FIELD_DEF, "Test-Gruppe"))
|
||||||
container_layout.addWidget(AutoForm(FORM_FIELD_GROUPS))
|
container_layout.addWidget(AutoForm(FORM_FIELDS))
|
||||||
|
|
||||||
container_layout.addSpacing(30)
|
container_layout.addSpacing(30)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user