import logging from typing import List, Tuple, Dict from uuid import uuid1 from PyQt6.QtCore import Qt from PyQt6.QtGui import QDoubleValidator from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton, QHBoxLayout, QWidget, QCheckBox, QButtonGroup, \ QRadioButton, QSplitter, QLineEdit, QGroupBox from detektor_region import DetektorRegion, DetektorRegionState from detektor_data import DetektorContainer from widgets import RoundedColorRectangleWidget class ChannelCalibrationDialog(QDialog): _only_region = None _radio_map: Dict[uuid1, QRadioButton] = {} def __init__(self, region: DetektorRegion): super().__init__() self.region = region self.setWindowTitle("Kalibrace kanálu") self.setModal(True) # Set the dialog as modal (blocks main window) self.resize(300, 150) # Main layout main_layout = QVBoxLayout() self.setLayout(main_layout) # Splitter for two columns splitter = QSplitter() splitter.setOrientation(Qt.Orientation.Horizontal) # Horizontal splitter for left/right columns # Left GroupBox (Channels) left_group = QGroupBox("Vyberte kanál") left_layout = QVBoxLayout() left_group.setLayout(left_layout) # Right GroupBox (Settings) right_group = QGroupBox("Nastavení kanálu") right_layout = QVBoxLayout() right_layout.setAlignment(Qt.AlignmentFlag.AlignTop) right_group.setLayout(right_layout) splitter.addWidget(left_group) splitter.addWidget(right_group) splitter.setStretchFactor(0, 1) # 50% splitter.setStretchFactor(1, 1) # 50% main_layout.addWidget(splitter) # Create a button group to ensure only one channel is active at a time self.radio_group = QButtonGroup() for ch in DetektorContainer().get().channels: channel_layout = QHBoxLayout() channel_layout.setContentsMargins(0, 0, 0, 0) channel_widget = QWidget() channel_widget.setLayout(channel_layout) left_layout.addWidget(channel_widget) channel_radio = QRadioButton(f"{ch.name}") self._radio_map[ch.id] = channel_radio self.radio_group.addButton(channel_radio) # Add to button group channel_layout.addWidget(channel_radio) channel_layout.addWidget(RoundedColorRectangleWidget(ch.color)) from PyQt6.QtCore import QLocale float_validator = CustomDoubleValidator() float_validator.setLocale(QLocale(QLocale.Language.C)) # Přičíst input add_layout = QVBoxLayout() add_label = QLabel("Přičíst hodnotu ke kanálu") self.add_input = QLineEdit() self.add_input.setValidator(float_validator) self.add_input.setText('0') add_layout.addWidget(add_label) add_layout.addWidget(self.add_input) # Vynásobit input multiply_layout = QVBoxLayout() multiply_label = QLabel("Vynásobit data kanálu hodnotou") self.multiply_input = QLineEdit() self.multiply_input.setValidator(float_validator) self.multiply_input.setText('1') multiply_layout.addWidget(multiply_label) multiply_layout.addWidget(self.multiply_input) only_region_checkbox = QCheckBox(f"Kalibrovat pouze výběr") # initial state is taken from region state self._only_region = (self.region.state != DetektorRegionState.UNSET) only_region_checkbox.setEnabled(self._only_region) only_region_checkbox.setChecked(self._only_region) # on click, intert the property state only_region_checkbox.stateChanged.connect(self.toggle_only_region) # Add inputs to the right group box right_layout.addLayout(add_layout) right_layout.addLayout(multiply_layout) right_layout.addWidget(only_region_checkbox) # Buttons layout (aligned at the bottom, next to each other) button_layout = QHBoxLayout() calibrate_button = QPushButton("Kalibrovat") calibrate_button.clicked.connect(self.accept) # Close the dialog when the button is clicked button_layout.addWidget(calibrate_button) cancel_button = QPushButton("Zrušit") cancel_button.clicked.connect(self.close) # Close the dialog when the button is clicked button_layout.addWidget(cancel_button) # Add buttons to the main layout below both columns main_layout.addLayout(button_layout) # Show the dialog self.exec() def toggle_only_region(self): self._only_region = not self._only_region def accept(self): for channel_id, radio in self._radio_map.items(): if radio.isChecked(): DetektorContainer().duplicate() has_channel = False for channel_id, radio in self._radio_map.items(): if radio.isChecked(): # get the current channel object of the current (duplicated dataset) channel = DetektorContainer().get().get_channel_by_uuid(channel_id) try: # validate inputs offset = float(self.add_input.text().replace(',', '.')) multiple = float(self.multiply_input.text().replace(',', '.')) except ValueError: WrongDataDialog() return logging.debug(f'Calibrating channel {channel.name} +{offset} x{multiple}') if self._only_region: start, end = self.region.get_safe_region() logging.debug(f'Calibrating only region {start} - {end}') channel.calibrate_data( offset=offset, multiple=multiple, start=start, end=end ) self.region.unset() else: channel.calibrate_data( offset=offset, multiple=multiple ) has_channel = True break # if we got here, no channel has been selected if not has_channel: # TODO: revert the duplication, since nothing has changed MissingChannelDialog() else: super().accept() class MissingChannelDialog(QDialog): def __init__(self): super().__init__() self.setWindowTitle("Vyberte kanál") self.setModal(True) # Set the dialog as modal (blocks main window) self.resize(400, 75) # Main layout main_layout = QVBoxLayout() self.setLayout(main_layout) label = QLabel("Vyberte kanál, na kterém chcete provést kalibraci") label.setAlignment(Qt.AlignmentFlag.AlignCenter) main_layout.addWidget(label) cancel_button = QPushButton("OK") cancel_button.clicked.connect(self.close) main_layout.addWidget(cancel_button) # Show the dialog self.exec() class WrongDataDialog(QDialog): def __init__(self): super().__init__() self.setWindowTitle("Špatná data") self.setModal(True) # Set the dialog as modal (blocks main window) self.resize(400, 75) # Main layout main_layout = QVBoxLayout() self.setLayout(main_layout) label = QLabel("Zadaná data nejsou platná čísla.") label.setAlignment(Qt.AlignmentFlag.AlignCenter) main_layout.addWidget(label) cancel_button = QPushButton("OK") cancel_button.clicked.connect(self.close) main_layout.addWidget(cancel_button) # Show the dialog self.exec() class CustomDoubleValidator(QDoubleValidator): def validate(self, input_str, pos): # Accept both comma and dot as decimal separator for validation if ',' in input_str: # Allow comma as intermediate state so user can type it test_str = input_str.replace(',', '.') state, _, _ = super().validate(test_str, pos) if state == QDoubleValidator.State.Acceptable: return (QDoubleValidator.State.Intermediate, input_str, pos) return (QDoubleValidator.State.Intermediate, input_str, pos) return super().validate(input_str, pos) def fixup(self, input_str): # Actually replace comma with dot in the QLineEdit return input_str.replace(',', '.')