Dev #7

Merged
foefl merged 74 commits from dev into main 2026-07-31 14:19:01 +00:00
Showing only changes of commit deeb038956 - Show all commits
+244 -278
View File
@@ -35,6 +35,7 @@ from PySide6.QtGui import QAction, QFont
from PySide6.QtWidgets import (
QAbstractItemView,
QApplication,
QBoxLayout,
QButtonGroup,
QCalendarWidget,
QComboBox,
@@ -272,19 +273,244 @@ class CustomWidget(QWidget):
def validate_form_data(self) -> list[str]: ...
def _add_widget_to_layout(
form_field: FormField,
widget: QWidget,
target_layout: QLayout,
widget_only: bool = False,
) -> None:
if isinstance(target_layout, QFormLayout):
if widget_only:
target_layout.addRow(widget)
elif form_field.tooltip:
tooltip_layout = _add_tooltip(widget, form_field.tooltip)
target_layout.addRow(form_field.label, tooltip_layout)
else:
target_layout.addRow(form_field.label, widget)
elif isinstance(target_layout, QBoxLayout):
if widget_only:
target_layout.addWidget(widget)
elif form_field.tooltip:
tooltip_layout = _add_tooltip(widget, form_field.tooltip)
target_layout.addLayout(tooltip_layout)
else:
target_layout.addWidget(widget)
def build_and_add_widget(
form_field: FormField,
parent_layout: QLayout,
prefix: str = "",
) -> tuple[str, QWidget]:
no_scroll_filter = NoScrollFilter(parent_layout)
if prefix and form_field.key:
full_key = f"{prefix}{COLUMN_SEP}{form_field.key}" if prefix else form_field.key
elif prefix and not form_field.key:
full_key = f"{prefix}"
else:
full_key = form_field.key
widget: QWidget
layout_widget_only: bool = False
install_no_scroll_filter: bool = True
match form_field.type:
case (
FormFieldType.DYNAMIC_LIST
| FormFieldType.DYNAMIC_DROPDOWN_NUMERIC
| FormFieldType.DYNAMIC_DROPDOWN_OPTION
| FormFieldType.CUSTOM
):
layout_widget_only = True
install_no_scroll_filter = False
match form_field.type:
case FormFieldType.GROUP:
raise TypeError("FormField Groups are not supported for direct building")
case FormFieldType.TEXT | FormFieldType.TEXT_DATE | FormFieldType.TEXT_DATETIME:
widget = QLineEdit()
if form_field.placeholder:
widget.setPlaceholderText(form_field.placeholder)
if form_field.fill_value:
widget.setText(form_field.fill_value)
if form_field.readonly:
widget.setReadOnly(True)
widget.setProperty("styleClass", "stempel")
case FormFieldType.TEXT_SEARCH:
widget = QLineEdit()
if form_field.placeholder:
widget.setPlaceholderText(form_field.placeholder)
else:
widget.setPlaceholderText("Tippen zum Suchen...")
search_completer = QCompleter()
search_completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
search_completer.setFilterMode(Qt.MatchFlag.MatchContains)
widget.setCompleter(search_completer)
case FormFieldType.LONGTEXT:
widget = QPlainTextEdit()
widget.setMaximumHeight(80)
if form_field.placeholder:
widget.setPlaceholderText(form_field.placeholder)
if form_field.readonly:
widget.setReadOnly(True)
widget.setProperty("styleClass", "stempel")
case FormFieldType.DATE:
widget = 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 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())
if form_field.readonly:
widget = QLineEdit()
widget.setReadOnly(True)
widget.setProperty("styleClass", "stempel")
if form_field.fill_value:
widget.setText(form_field.fill_value)
case FormFieldType.DATE_FLEXIBLE:
widget = FlexibleDateInput()
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.set_date(set_date)
if form_field.readonly:
widget.setReadOnly(True)
case FormFieldType.DATETIME_FLEXIBLE:
widget = FlexibleDateTimeInput()
if form_field.fill_value:
set_datetime = QDateTime.fromString(form_field.fill_value, "dd.MM.yyyy hh:mm")
if not set_datetime.isValid():
raise ValueError(
f"Could not parse date field value >{form_field.fill_value}<"
)
widget.set_datetime(set_datetime)
if form_field.readonly:
widget.setReadOnly(True)
case FormFieldType.DROPDOWN:
widget = QComboBox()
widget.addItem(DROPDOWN_DEFAULT, None)
for option in form_field.dropdown_options:
widget.addItem(option.label, option.data)
if form_field.placeholder:
widget.setPlaceholderText(form_field.placeholder)
if form_field.fill_value:
widget.setCurrentText(form_field.fill_value)
else:
widget.setCurrentIndex(0)
if form_field.readonly:
widget.setEnabled(False)
widget.setProperty("styleClass", "stempel")
case FormFieldType.EXTENDED_DROPDOWN:
widget = QComboBox()
widget.setEditable(True)
widget.setInsertPolicy(QComboBox.InsertPolicy.NoInsert)
completer = widget.completer()
assert completer
completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
completer.setFilterMode(Qt.MatchFlag.MatchContains)
completer.setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
widget.addItem(DROPDOWN_DEFAULT, None)
for option in form_field.dropdown_options:
widget.addItem(option.label, option.data)
if form_field.placeholder:
line_edit = widget.lineEdit()
assert line_edit
line_edit.setPlaceholderText(form_field.placeholder)
if form_field.fill_value:
widget.setCurrentText(form_field.fill_value)
else:
widget.setCurrentIndex(-1)
if form_field.readonly:
widget.setEnabled(False)
widget.setProperty("styleClass", "stempel")
case FormFieldType.DYNAMIC_LIST:
widget = DynamicListWidget(
form_field.children,
form_field.label,
prefix=f"{full_key}",
)
case FormFieldType.DYNAMIC_DROPDOWN_NUMERIC:
widget = DynamicDropdownWidgetNumeric(
form_field.children,
form_field.label,
prefix=f"{full_key}",
)
case FormFieldType.DYNAMIC_DROPDOWN_OPTION:
widget = DynamicDropdownWidgetOption(
form_field.children,
form_field.trigger_value,
form_field.label,
prefix=f"{full_key}",
)
case FormFieldType.CUSTOM:
widget_class = CUSTOM_WIDGETS[form_field.custom_widget]
widget = widget_class(
form_fields=form_field.children,
label=form_field.label,
prefix=f"{full_key}",
)
case _:
raise NotImplementedError(f"Not supported field type: {form_field.type.value}")
if install_no_scroll_filter:
widget.installEventFilter(no_scroll_filter)
_add_widget_to_layout(form_field, widget, parent_layout, widget_only=layout_widget_only)
return full_key, widget
def _build_ui_recursively(
schema: Sequence[FormField],
parent_layout: QFormLayout,
widget_registry: WidgetRegistry,
prefix: str = "",
) -> list[str]:
no_scroll_filter = NoScrollFilter(parent_layout)
keys: list[str] = []
for field in schema:
if prefix and field.key:
full_key = f"{prefix}{COLUMN_SEP}{field.key}" if prefix else field.key
elif prefix and not field.key:
full_key = f"{prefix}"
full_key = prefix
else:
full_key = field.key
@@ -302,283 +528,13 @@ def _build_ui_recursively(
)
parent_layout.addRow(group_box)
case FormFieldType.TEXT | FormFieldType.TEXT_DATE | FormFieldType.TEXT_DATETIME:
widget = QLineEdit()
if field.placeholder:
widget.setPlaceholderText(field.placeholder)
if field.fill_value:
widget.setText(field.fill_value)
if field.readonly:
widget.setReadOnly(True)
widget.setProperty("styleClass", "stempel")
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
widget.installEventFilter(no_scroll_filter)
if field.tooltip:
tooltip_layout = _add_tooltip(widget, field.tooltip)
parent_layout.addRow(field.label, tooltip_layout)
else:
parent_layout.addRow(field.label, widget)
case FormFieldType.TEXT_SEARCH:
widget = QLineEdit()
if field.placeholder:
widget.setPlaceholderText(field.placeholder)
else:
widget.setPlaceholderText("Tippen zum Suchen...")
search_completer = QCompleter()
search_completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
search_completer.setFilterMode(Qt.MatchFlag.MatchContains)
widget.setCompleter(search_completer)
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
widget.installEventFilter(no_scroll_filter)
if field.tooltip:
tooltip_layout = _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)
if field.placeholder:
widget.setPlaceholderText(field.placeholder)
if field.readonly:
widget.setReadOnly(True)
widget.setProperty("styleClass", "stempel")
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
widget.installEventFilter(no_scroll_filter)
if field.tooltip:
tooltip_layout = _add_tooltip(widget, field.tooltip)
parent_layout.addRow(field.label, tooltip_layout)
else:
parent_layout.addRow(field.label, widget)
case FormFieldType.DATE:
widget = 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)
widget.setProperty("styleClass", "stempel")
if field.fill_value:
widget.setText(field.fill_value)
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
widget.installEventFilter(no_scroll_filter)
if field.tooltip:
tooltip_layout = _add_tooltip(widget, field.tooltip)
parent_layout.addRow(field.label, tooltip_layout)
else:
parent_layout.addRow(field.label, widget)
case FormFieldType.DATE_FLEXIBLE:
widget = FlexibleDateInput()
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.set_date(set_date)
if field.readonly:
widget.setReadOnly(True)
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
if field.tooltip:
tooltip_layout = _add_tooltip(widget, field.tooltip)
parent_layout.addRow(field.label, tooltip_layout)
else:
parent_layout.addRow(field.label, widget)
case FormFieldType.DATETIME_FLEXIBLE:
widget = FlexibleDateTimeInput()
if field.fill_value:
set_datetime = QDateTime.fromString(field.fill_value, "dd.MM.yyyy hh:mm")
if not set_datetime.isValid():
raise ValueError(
f"Could not parse date field value >{field.fill_value}<"
)
widget.set_datetime(set_datetime)
if field.readonly:
widget.setReadOnly(True)
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
if field.tooltip:
tooltip_layout = _add_tooltip(widget, field.tooltip)
parent_layout.addRow(field.label, tooltip_layout)
else:
parent_layout.addRow(field.label, widget)
case FormFieldType.DROPDOWN:
widget = QComboBox()
widget.addItem(DROPDOWN_DEFAULT, None)
for option in field.dropdown_options:
widget.addItem(option.label, option.data)
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,
}
widget.installEventFilter(no_scroll_filter)
if field.tooltip:
tooltip_layout = _add_tooltip(widget, field.tooltip)
parent_layout.addRow(field.label, tooltip_layout)
else:
parent_layout.addRow(field.label, widget)
case FormFieldType.EXTENDED_DROPDOWN:
widget = QComboBox()
widget.setEditable(True)
widget.setInsertPolicy(QComboBox.InsertPolicy.NoInsert)
completer = widget.completer()
assert completer
completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
completer.setFilterMode(Qt.MatchFlag.MatchContains)
completer.setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
widget.addItem(DROPDOWN_DEFAULT, None)
for option in field.dropdown_options:
widget.addItem(option.label, option.data)
if field.placeholder:
line_edit = widget.lineEdit()
assert line_edit
line_edit.setPlaceholderText(field.placeholder)
if field.fill_value:
widget.setCurrentText(field.fill_value)
else:
widget.setCurrentIndex(-1)
if field.readonly:
widget.setEnabled(False)
widget.setProperty("styleClass", "stempel")
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
widget.installEventFilter(no_scroll_filter)
if field.tooltip:
tooltip_layout = _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.children,
field.label,
prefix=f"{full_key}",
)
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
parent_layout.addRow(widget)
case FormFieldType.DYNAMIC_DROPDOWN_NUMERIC:
widget = DynamicDropdownWidgetNumeric(
field.children,
field.label,
prefix=f"{full_key}",
)
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
parent_layout.addRow(widget)
case FormFieldType.DYNAMIC_DROPDOWN_OPTION:
widget = DynamicDropdownWidgetOption(
field.children,
field.trigger_value,
field.label,
prefix=f"{full_key}",
)
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
parent_layout.addRow(widget)
case FormFieldType.CUSTOM:
widget_class = CUSTOM_WIDGETS[field.custom_widget]
widget = widget_class(
form_fields=field.children,
label=field.label,
prefix=f"{full_key}",
)
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
parent_layout.addRow(widget)
case _:
raise NotImplementedError(f"Not supported field type: {field.type.value}")
keys.append(full_key)
full_key, widget = build_and_add_widget(field, parent_layout, prefix=prefix)
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
keys.append(full_key)
return keys
@@ -1589,6 +1545,11 @@ class FlexibleDateInput(QWidget):
self.button = QPushButton("📅")
self.button.setFixedWidth(30)
self.line_edit.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
)
self.button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding)
layout.addWidget(self.line_edit)
layout.addWidget(self.button)
@@ -1748,6 +1709,11 @@ class FlexibleDateTimeInput(QWidget):
self.button = QPushButton("📅")
self.button.setFixedWidth(30)
self.line_edit.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
)
self.button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding)
layout.addWidget(self.line_edit)
layout.addWidget(self.button)