From f4d3dc50241b1db7cfbd68f9de6ac8cbf1449c7b Mon Sep 17 00:00:00 2001 From: JoschD <26184899+JoschD@users.noreply.github.com> Date: Fri, 17 Nov 2023 17:14:08 +0100 Subject: [PATCH] choices --- .../segment_by_segment/measurement_model.py | 6 ++-- .../segment_by_segment/measurement_view.py | 12 +++++-- omc3_gui/utils/dataclass_ui.py | 31 +++++++++++++++++-- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/omc3_gui/segment_by_segment/measurement_model.py b/omc3_gui/segment_by_segment/measurement_model.py index 96ec8f2..4a8291d 100644 --- a/omc3_gui/segment_by_segment/measurement_model.py +++ b/omc3_gui/segment_by_segment/measurement_model.py @@ -25,9 +25,9 @@ class OpticsMeasurement: model_dir: Path = metafield("Model", "Path to the model folder", default=None) accel: str = metafield("Accelerator", "Name of the accelerator", default=None) output_dir: Path = metafield("Output", "Path to the sbs-output folder", default=None) - year: str = metafield("Year", "Year of the measurement (model)", default=None) - ring: int = metafield("Ring", "Ring of the accelerator", default=None) - beam: int = metafield("Beam", "Beam of the accelerator", default=None) + year: str = metafield("Year", "Year of the measurement (model)", default=None, choices=LHC_MODEL_YEARS) + ring: int = metafield("Ring", "Ring of the accelerator", default=None, choices=(1, 2, 3, 4)) + beam: int = metafield("Beam", "Beam of the accelerator", default=None, choices=(1, 2)) elements: Dict[str, Segment] = None # List of elements segments: Dict[str, Segment] = None # List of segments diff --git a/omc3_gui/segment_by_segment/measurement_view.py b/omc3_gui/segment_by_segment/measurement_view.py index 5d8a8be..73cf1c3 100644 --- a/omc3_gui/segment_by_segment/measurement_view.py +++ b/omc3_gui/segment_by_segment/measurement_view.py @@ -31,8 +31,8 @@ def __init__(self, parent=None, optics_measurement: OpticsMeasurement = None): self._update_ui() def _set_size(self, width: int = -1, height: int = -1): - # Set position to the center of the parent - parent = self.parent() + # Set position to the center of the parent (does not work in WSL for me, jdilly 2023) + # parent = self.parent() # if parent is not None: # parent_geo = parent.geometry() # parent_pos = parent.mapToGlobal(parent.pos()) # multiscreen support @@ -61,7 +61,7 @@ def _build_gui(self): self._dataclass_ui = DataClassUI.build_dataclass_ui( field_def=[ FieldUIDef(name="measurement_dir", editable=False), - *(FieldUIDef(name) for name in ("output_dir", "accel", "beam", "year", "ring")) + *(FieldUIDef(name) for name in ("model_dir", "output_dir", "accel", "beam", "year", "ring")) ], dclass=OpticsMeasurement, ) @@ -79,6 +79,12 @@ def _update_ui(self): self._dataclass_ui.reset_labels() def accept(self): + try: + self._dataclass_ui.check_choices(only_modified=True) + except ValueError as e: + QtWidgets.QMessageBox.critical(self, "Error", str(e)) + return + self._dataclass_ui.update_model() super().accept() diff --git a/omc3_gui/utils/dataclass_ui.py b/omc3_gui/utils/dataclass_ui.py index 61df357..26fce5d 100644 --- a/omc3_gui/utils/dataclass_ui.py +++ b/omc3_gui/utils/dataclass_ui.py @@ -1,9 +1,16 @@ +""" +DataClass UI +------------ + +This module allows to generate a simple UI's for dataclasses, +which allows to edit the values of a dataclass. +""" from functools import partial import inspect import re from dataclasses import MISSING, Field, dataclass, field, fields from pathlib import Path -from typing import Callable, Dict, Optional, Sequence, Tuple, Union +from typing import Callable, Dict, List, Optional, Sequence, Tuple, Union from omc3_gui.utils import file_dialogs from qtpy import QtWidgets @@ -151,6 +158,25 @@ def update_model(self): for name in self.fields.keys(): self.update_model_from_widget(name) + def check_choices(self, only_modified: bool = False): + """ Checks all edit-widgets for valid choices. """ + fields_choices = [] + for name in self.fields.keys(): + field = self.model.__dataclass_fields__[name] + field_ui = self.fields[field.name] + if only_modified and not field_ui.modified: + continue + + choices = field.metadata.get("choices") + if choices is not None: + value = self.fields[field.name].get_value() + if value not in choices: + fields_choices.append(f"{field_ui.label.text()}: {value} not in {choices}") + + if fields_choices: + full_str = "\n".join(fields_choices) + raise ValueError(f'The following fields contain wrong values:\n{full_str}') + @classmethod def build_dataclass_ui(cls, field_def: Sequence[Union[FieldUIDef, str]], dclass: Union[type, object] = None) -> 'DataClassUI': @@ -243,10 +269,11 @@ def build_dataclass_ui(cls, # Type-to-Widget Helpers ---------------------------------------------------------------- class QFullIntSpinBox(QtWidgets.QSpinBox): + """ Like a QSpinBox, but overwriting default range(0,100) with maximum integer range. """ def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None: super().__init__(parent) - self.setRange(-2**31, 2**31 - 1) + self.setRange(-2**31, 2**31 - 1) # range of signed 32-bit integers TYPE_TO_WIDGET_MAP = {