def __init__(self, a_settings: Settings, a_measure_id: int, a_db_connection: Connection, a_parent=None): super().__init__(a_parent) self.ui = CreateProtocolForm() self.ui.setupUi(self) self.ui.default_button.setHidden(True) assert a_measure_id != 0, "Measure id must not be zero!" self.settings = a_settings self.restoreGeometry(self.settings.get_last_geometry(self.__class__.__name__)) self.measure_db = MeasuresDB(a_db_connection) self.measure_config = self.measure_db.get(a_measure_id) self.marks_widget = MarksWidget(self.__class__.__name__, self.settings, a_db_connection, a_measure_id=self.measure_config.id) self.ui.marks_widget_layout.addWidget(self.marks_widget) self.default_marks_widgets = self.get_default_marks_widgets() self.set_up_params_to_ui() self.ui.points_table.horizontalHeader().restoreState(self.settings.get_last_header_state( self.__class__.__name__)) self.ui.points_table.horizontalHeader().setSectionsMovable(True) self.measure_manager = MeasureCases(self.ui.points_table, self.measure_config.cases, a_allow_editing=False) self.ui.cases_bar_layout.addWidget(self.measure_manager.cases_bar) self.connect_signals() self.ui.marks_and_points_tabwidget.setCurrentIndex(0)
def __init__(self, a_control_db_connection: Connection, a_db_name: str, a_settings: Settings, a_parent=None): """ Для отображения таблицы измерений используется QSqlRelationalTableModel (это сильно упрощает жизнь) При этом для остальных операций (добавление, удаление) используется другое соединение sqlite3.Connection Соединения могут быть подключены к БД параллельно :param a_control_db_connection: Соединения для управления БД :param a_db_name: Имя файла БД :param a_settings: Настройки в ini :param a_parent: Widget parent """ super().__init__(a_parent) self.ui = StartForm() self.ui.setupUi(self) self.parent = a_parent self.settings = a_settings self.setWindowTitle("Калибратор N4-25") self.ui.source_mode_button.clicked.connect(self.source_mode_chosen) self.ui.no_template_mode_button.clicked.connect( self.no_template_mode_chosen) self.ui.template_mode_button.clicked.connect(self.template_mode_chosen) self.ui.create_protocol_button.clicked.connect(self.create_protocol) self.ui.measures_table.doubleClicked.connect(self.create_protocol) self.parent.show() geometry = self.settings.get_last_geometry(self.__class__.__name__) if not geometry.isEmpty(): self.parent.restoreGeometry(geometry) else: self.parent.resize(self.size()) self.control_db_connection = a_control_db_connection self.measure_db = MeasuresDB(a_control_db_connection) self.display_db_connection = QtSql.QSqlDatabase.addDatabase("QSQLITE") self.display_db_model = QtSql.QSqlRelationalTableModel(self) self.sort_proxy_model = CustomSortingModel(self) self.header_context = self.config_measure_table(a_db_name)
def __init__(self): super().__init__() self.ui = MainForm() self.ui.setupUi(self) self.active_window = None try: self.settings = Settings(self) ini_ok = True except BadIniException: ini_ok = False QtWidgets.QMessageBox.critical( self, "Ошибка", 'Файл конфигурации поврежден. Пожалуйста, ' 'удалите файл "settings.ini" и запустите программу заново') if ini_ok: self.db_name = "measures.db" self.db_connection = MeasuresDB.create_db(self.db_name) self.show_start_window() self.show() self.clb_signal_off_timer = QtCore.QTimer() # noinspection PyTypeChecker self.clb_signal_off_timer.timeout.connect(self.close) self.SIGNAL_OFF_TIME_MS = 200 self.clb_driver = clb_dll.set_up_driver(clb_dll.debug_dll_path) self.usb_driver = clb_dll.UsbDrv(self.clb_driver) self.usb_state = clb_dll.UsbDrv.UsbState.DISABLED self.calibrator = clb_dll.ClbDrv(self.clb_driver) self.clb_state = clb.State.DISCONNECTED self.usb_check_timer = QtCore.QTimer(self) self.usb_check_timer.timeout.connect(self.usb_tick) self.usb_check_timer.start(10) self.fast_config = None self.ui.enter_settings_action.triggered.connect(self.open_settings) else: self.close()
class CreateProtocolDialog(QtWidgets.QDialog): GET_MARK_RE = re_compile(r"%.*__") def __init__(self, a_settings: Settings, a_measure_id: int, a_db_connection: Connection, a_parent=None): super().__init__(a_parent) self.ui = CreateProtocolForm() self.ui.setupUi(self) self.ui.default_button.setHidden(True) assert a_measure_id != 0, "Measure id must not be zero!" self.settings = a_settings self.restoreGeometry(self.settings.get_last_geometry(self.__class__.__name__)) self.measure_db = MeasuresDB(a_db_connection) self.measure_config = self.measure_db.get(a_measure_id) self.marks_widget = MarksWidget(self.__class__.__name__, self.settings, a_db_connection, a_measure_id=self.measure_config.id) self.ui.marks_widget_layout.addWidget(self.marks_widget) self.default_marks_widgets = self.get_default_marks_widgets() self.set_up_params_to_ui() self.ui.points_table.horizontalHeader().restoreState(self.settings.get_last_header_state( self.__class__.__name__)) self.ui.points_table.horizontalHeader().setSectionsMovable(True) self.measure_manager = MeasureCases(self.ui.points_table, self.measure_config.cases, a_allow_editing=False) self.ui.cases_bar_layout.addWidget(self.measure_manager.cases_bar) self.connect_signals() self.ui.marks_and_points_tabwidget.setCurrentIndex(0) def connect_signals(self): for widgets in self.default_marks_widgets: widgets[0].customContextMenuRequested.connect(self.show_label_custom_menu) self.ui.choose_protocol_template_button.clicked.connect(self.choose_template_pattern_file) self.ui.choose_save_folder_button.clicked.connect(self.choose_save_protocol_folder) self.ui.to_excel_button.clicked.connect(self.copy_to_excel) self.ui.accept_button.clicked.connect(self.save_pressed) self.ui.reject_button.clicked.connect(self.reject) def get_default_marks_widgets(self): default_marks_widgets = [ (self.ui.name_label, self.ui.device_name_edit), (self.ui.device_creator_label, self.ui.device_creator_edit), (self.ui.system_label, self.ui.system_combobox), (self.ui.user_label, self.ui.user_name_edit), (self.ui.serial_number_label, self.ui.serial_number_edit), (self.ui.owner_label, self.ui.owner_edit), (self.ui.date_label, self.ui.date_edit), (self.ui.comment_label, self.ui.comment_edit) ] return default_marks_widgets def show_label_custom_menu(self, a_position: QtCore.QPoint): label = self.sender() assert isinstance(label, QtWidgets.QLabel), "show_label_custom_menu must be connected to QLabel!" menu = QtWidgets.QMenu(label) copy_mark_act = menu.addAction("Копировать метку") copy_mark_act.triggered.connect(self.copy_label_mark) menu.popup(label.mapToGlobal(a_position)) # noinspection DuplicatedCode def set_up_params_to_ui(self): self.ui.date_edit.setDate(QtCore.QDate.fromString(self.measure_config.date, "dd.MM.yyyy")) self.ui.device_name_edit.setText(self.measure_config.device_name) self.ui.device_creator_edit.setText(self.measure_config.device_creator) self.ui.owner_edit.setText(self.measure_config.owner) self.ui.user_name_edit.setText(self.measure_config.user) self.ui.serial_number_edit.setText(self.measure_config.serial_num) self.ui.system_combobox.setCurrentIndex(self.measure_config.device_system) self.ui.comment_edit.setText(self.measure_config.comment) self.ui.template_protocol_edit.setText(self.settings.template_filepath) self.ui.save_folder_edit.setText(self.settings.save_folder) def copy_label_mark(self): # noinspection PyTypeChecker label = self.sender().parent().parent() assert isinstance(label, QtWidgets.QLabel), "This slot must be called by Qmenu of QLabel!!" mark_text = self.extract_mark_from_label(label) QtWidgets.QApplication.clipboard().setText(mark_text) def extract_mark_from_label(self, label): mark_match = self.GET_MARK_RE.search(label.text()) assert mark_match is not None, "Label must contain mark in format %*__ !!" mark_text = mark_match.group(0) return mark_text @staticmethod def extract_name_from_label(label: QtWidgets.QLabel) -> str: return label.text()[label.text().find("<p>") + 3 : label.text().find(" (")] # noinspection PyUnresolvedReferences def extract_value_from_widget(self, a_widget: QtWidgets.QWidget): if isinstance(a_widget, QtWidgets.QLineEdit): return a_widget.text() elif a_widget == self.ui.date_edit: return a_widget.text() elif a_widget == self.ui.system_combobox: return cfg.enum_to_device_system[a_widget.currentIndex()] else: assert True, "Unexpected widget" def choose_template_pattern_file(self): file = QtWidgets.QFileDialog.getOpenFileName(self, "Выберите файл, содержащий шаблон протокола", self.ui.template_protocol_edit.text(), "Текстовый документ ODT (*.odt)") filepath = file[0] if filepath: self.ui.template_protocol_edit.setText(filepath) def choose_save_protocol_folder(self): folder = QtWidgets.QFileDialog.getExistingDirectory(self, "Выберите каталог", self.ui.save_folder_edit.text(), QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontResolveSymlinks) if folder: self.ui.save_folder_edit.setText(folder) def save_pressed(self): try: self.save() if self.marks_widget.save(): if self.generate_protocol(): self.close() else: self.ui.marks_and_points_tabwidget.setCurrentIndex(0) except Exception as err: utils.exception_handler(err) # noinspection DuplicatedCode def save(self): self.measure_config.date = self.ui.date_edit.text() self.measure_config.device_name = self.ui.device_name_edit.text() self.measure_config.device_creator = self.ui.device_creator_edit.text() self.measure_config.device_system = self.ui.system_combobox.currentIndex() self.measure_config.owner = self.ui.owner_edit.text() self.measure_config.user = self.ui.user_name_edit.text() self.measure_config.serial_num = self.ui.serial_number_edit.text() self.measure_config.comment = self.ui.comment_edit.text() self.measure_db.update_measure(self.measure_config) self.settings.template_filepath = self.ui.template_protocol_edit.text() self.settings.save_folder = self.ui.save_folder_edit.text() def get_src_dst_path(self) -> Union[Tuple[str, str], None]: src_file = self.ui.template_protocol_edit.text() dst_folder = self.ui.save_folder_edit.text() if os.path.exists(src_file) and os.path.isfile(src_file): if os.path.exists(dst_folder) and os.path.isdir(dst_folder): dst_file = self.generate_filename(dst_folder) if os.path.exists(dst_file): res = QtWidgets.QMessageBox.question(self, "Создание протокола", "Файл для текущего измерения уже существует.\n" "Хотите заменить его?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if res == QtWidgets.QMessageBox.Yes: return src_file, dst_file else: return None else: return src_file, dst_file else: QtWidgets.QMessageBox.critical(self, "Ошибка", "Путь к каталогу сохранения указан неверно", QtWidgets.QMessageBox.Ok) return None else: QtWidgets.QMessageBox.critical(self, "Ошибка", "Путь к шаблону протокола указан неверно", QtWidgets.QMessageBox.Ok) return None def generate_protocol(self): paths = self.get_src_dst_path() if paths is not None: src_file, dst_file = paths marks_map = self.marks_widget.get_marks_map() for widgets in self.default_marks_widgets: marks_map.append((self.extract_mark_from_label(widgets[0]), self.extract_value_from_widget(widgets[1]))) if odf_output.replace_text_in_odt(src_file, dst_file, marks_map, self.create_tables_to_export()): QtWidgets.QMessageBox.information(self, "Успех", "Протокол успешно сгенерирован") return True else: QtWidgets.QMessageBox.critical(self, "Ошибка", "При создании протокола произошла ошибка") return False else: return False def generate_filename(self, a_dst_folder: str) -> str: dst_folder = a_dst_folder.rstrip("\\").rstrip("/") + os.path.sep dst_file = ' '.join([self.measure_config.date, self.measure_config.time.replace(':', '.') + '.', self.measure_config.device_name + ".odt"]) return os.path.sep.join([os.path.dirname(dst_folder), dst_file]) def create_tables_to_export(self) -> list: exported_tables = [] for case, table in self.measure_manager.export_tables(): table_to_draw = odf_output.TableToDraw(case) for row in table: # Последняя колонка - всегда частота table_to_draw.add_point(row[-1], row[:-1]) exported_tables.append(table_to_draw) return exported_tables def copy_to_excel(self): parameters = "" for widgets in self.default_marks_widgets: parameters += "{0}\t{1}\n".format(self.extract_name_from_label(widgets[0]), self.extract_value_from_widget(widgets[1])) for _map in self.marks_widget.get_names_map(): parameters += "{0}\t{1}\n".format(_map[0], _map[1]) parameters += "\nРезультаты измерений:\n\n" for measure in self.create_tables_to_export(): parameters += "Тип сигнала: {0}\nПредел измерения: {1}\nДопустимая погрешность: {2}\n".format( measure.signal_type, measure.limit, measure.error_limit) for frequency in measure.points.keys(): if int(frequency) != 0: parameters += ' '.join(["Частота:", str(frequency), "Гц\n"]) for points in measure.points[frequency]: for point in points: parameters += str(point) + "\t" parameters += "\n" parameters += "\n" QtWidgets.QApplication.clipboard().setText(parameters) def closeEvent(self, a_event: QtGui.QCloseEvent) -> None: self.settings.save_geometry(self.__class__.__name__, self.saveGeometry()) self.settings.save_header_state(self.__class__.__name__, self.ui.points_table.horizontalHeader().saveState()) self.measure_manager.close() # Вызывается вручную, чтобы marks_widget сохранил состояние своего хэдера self.marks_widget.close() a_event.accept() def __del__(self): print("create protocol deleted")
def __init__(self, a_calibrator: clb_dll.ClbDrv, a_measure_config: Measure, a_db_connection: Connection, a_settings: Settings, a_parent=None): super().__init__(a_parent) self.ui = MeasureForm() self.ui.setupUi(self) self.setWindowTitle("Калибратор N4-25. Измерение") self.parent = a_parent self.warning_animation = None self.pause_icon = QtGui.QIcon(QtGui.QPixmap(":/icons/icons/pause.png")) self.play_icon = QtGui.QIcon(QtGui.QPixmap(":/icons/icons/play.png")) self.ui.pause_button.setIconSize(QtCore.QSize(21, 21)) self.set_up_icons() self.settings = a_settings self.parent.show() geometry = self.settings.get_last_geometry(self.__class__.__name__) if not geometry.isEmpty(): self.parent.restoreGeometry(geometry) else: self.parent.resize(self.size()) # Вызывать после self.parent.show() !!! Иначе состояние столбцов не восстановится self.ui.measure_table.horizontalHeader().restoreState( self.settings.get_last_header_state(self.__class__.__name__)) self.db_connection = a_db_connection self.calibrator = a_calibrator self.calibrator.signal_enable = False self.clb_state = clb.State.DISCONNECTED self.measure_config = a_measure_config self.measures_db = MeasuresDB(self.db_connection) # Нужно создать заранее, чтобы было id для сохранения меток self.measure_config.id = self.measures_db.new_measure( self.measure_config) self.measure_manager = MeasureCases(self.ui.measure_table, self.measure_config.cases, a_allow_editing=True) self.ui.cases_bar_layout.addWidget(self.measure_manager.cases_bar) # --------------------Создение переменных self.started = False self.soft_approach_points = [] self.soft_approach_timer = QTimer(self) soft_approach_time_ms = 4000 # Минимум стабильной передачи - 200 мс self.NEXT_SOFT_POINT_TIME_MS = 200 self.SOFT_APPROACH_POINTS_COUNT = int(soft_approach_time_ms // self.NEXT_SOFT_POINT_TIME_MS) # Нужен, чтобы убедиться, что фиксированный диапазон выставлен, после чего включить сигнал self.start_measure_timer = QTimer(self) # Нужен, чтобы убедиться, что сигнал выключен, после чего менять параметры сигнала self.stop_measure_timer = QTimer(self) self.wait_dialog = None self.units_text = "В" self.value_to_user = utils.value_to_user_with_units(self.units_text) self.current_point = PointData() self.highest_amplitude = 0 self.lowest_amplitude = 0 self.fixed_step = 0 self.fixed_step_list = self.settings.fixed_step_list # --------------------Создение переменных # Вызывать после создания self.measure_manager self.connect_signals() self.current_case = self.measure_manager.current_case() self.current_case_changed() self.clb_check_timer = QTimer(self) self.clb_check_timer.timeout.connect(self.sync_clb_parameters) self.clb_check_timer.start(10)
class MeasureWindow(QtWidgets.QWidget): remove_points = pyqtSignal(list) close_confirmed = pyqtSignal() def __init__(self, a_calibrator: clb_dll.ClbDrv, a_measure_config: Measure, a_db_connection: Connection, a_settings: Settings, a_parent=None): super().__init__(a_parent) self.ui = MeasureForm() self.ui.setupUi(self) self.setWindowTitle("Калибратор N4-25. Измерение") self.parent = a_parent self.warning_animation = None self.pause_icon = QtGui.QIcon(QtGui.QPixmap(":/icons/icons/pause.png")) self.play_icon = QtGui.QIcon(QtGui.QPixmap(":/icons/icons/play.png")) self.ui.pause_button.setIconSize(QtCore.QSize(21, 21)) self.set_up_icons() self.settings = a_settings self.parent.show() geometry = self.settings.get_last_geometry(self.__class__.__name__) if not geometry.isEmpty(): self.parent.restoreGeometry(geometry) else: self.parent.resize(self.size()) # Вызывать после self.parent.show() !!! Иначе состояние столбцов не восстановится self.ui.measure_table.horizontalHeader().restoreState( self.settings.get_last_header_state(self.__class__.__name__)) self.db_connection = a_db_connection self.calibrator = a_calibrator self.calibrator.signal_enable = False self.clb_state = clb.State.DISCONNECTED self.measure_config = a_measure_config self.measures_db = MeasuresDB(self.db_connection) # Нужно создать заранее, чтобы было id для сохранения меток self.measure_config.id = self.measures_db.new_measure( self.measure_config) self.measure_manager = MeasureCases(self.ui.measure_table, self.measure_config.cases, a_allow_editing=True) self.ui.cases_bar_layout.addWidget(self.measure_manager.cases_bar) # --------------------Создение переменных self.started = False self.soft_approach_points = [] self.soft_approach_timer = QTimer(self) soft_approach_time_ms = 4000 # Минимум стабильной передачи - 200 мс self.NEXT_SOFT_POINT_TIME_MS = 200 self.SOFT_APPROACH_POINTS_COUNT = int(soft_approach_time_ms // self.NEXT_SOFT_POINT_TIME_MS) # Нужен, чтобы убедиться, что фиксированный диапазон выставлен, после чего включить сигнал self.start_measure_timer = QTimer(self) # Нужен, чтобы убедиться, что сигнал выключен, после чего менять параметры сигнала self.stop_measure_timer = QTimer(self) self.wait_dialog = None self.units_text = "В" self.value_to_user = utils.value_to_user_with_units(self.units_text) self.current_point = PointData() self.highest_amplitude = 0 self.lowest_amplitude = 0 self.fixed_step = 0 self.fixed_step_list = self.settings.fixed_step_list # --------------------Создение переменных # Вызывать после создания self.measure_manager self.connect_signals() self.current_case = self.measure_manager.current_case() self.current_case_changed() self.clb_check_timer = QTimer(self) self.clb_check_timer.timeout.connect(self.sync_clb_parameters) self.clb_check_timer.start(10) def set_up_icons(self): self.ui.status_warning_label.hide() self.warning_animation = QtGui.QMovie(":/icons/gif/warning.gif") self.ui.status_warning_label.setMovie(self.warning_animation) self.warning_animation.setScaledSize(QtCore.QSize(28, 28)) self.warning_animation.setSpeed(500) self.warning_animation.finished.connect( self.ui.status_warning_label.hide) def set_window_elements(self): self.ui.apply_frequency_button.setDisabled( clb.is_dc_signal[self.current_case.signal_type]) self.ui.frequency_edit.setDisabled( clb.is_dc_signal[self.current_case.signal_type]) def update_case_params(self): self.units_text = clb.signal_type_to_units[ self.current_case.signal_type] self.value_to_user = utils.value_to_user_with_units(self.units_text) self.current_point = PointData() self.highest_amplitude = clb.bound_amplitude( utils.increase_by_percent(self.current_case.limit, cfg.FIRST_POINT_START_DEVIATION_PERCENT), self.current_case.signal_type) self.lowest_amplitude = -self.highest_amplitude if clb.is_dc_signal[ self.current_case.signal_type] else 0 def fill_fixed_step_combobox(self): values = self.settings.fixed_step_list self.ui.fixed_step_combobox.clear() for val in values: try: value_str = self.value_to_user(val) self.ui.fixed_step_combobox.addItem(value_str) except ValueError: pass self.ui.fixed_step_combobox.setCurrentIndex( self.settings.fixed_step_idx) # noinspection DuplicatedCode def connect_signals(self): self.settings.fixed_step_changed.connect(self.fill_fixed_step_combobox) self.ui.clb_list_combobox.currentTextChanged.connect( self.connect_to_clb) self.ui.start_stop_button.clicked.connect(self.start_stop_measure) self.ui.save_point_button.clicked.connect(self.save_point) self.ui.go_to_point_button.clicked.connect(self.go_to_point) self.ui.delete_point_button.clicked.connect(self.delete_point) self.remove_points.connect(self.measure_manager.view().remove_selected) self.ui.rough_plus_button.clicked.connect( self.rough_plus_button_clicked) self.ui.rough_minus_button.clicked.connect( self.rough_minus_button_clicked) self.ui.common_plus_button.clicked.connect( self.common_plus_button_clicked) self.ui.common_minus_button.clicked.connect( self.common_minus_button_clicked) self.ui.exact_plus_button.clicked.connect( self.exact_plus_button_clicked) self.ui.exact_minus_button.clicked.connect( self.exact_minus_button_clicked) self.ui.fixed_plus_button.clicked.connect( self.fixed_plus_button_clicked) self.ui.fixed_minus_button.clicked.connect( self.fixed_minus_button_clicked) self.ui.amplitude_edit.textEdited.connect( self.amplitude_edit_text_changed) self.ui.apply_amplitude_button.clicked.connect( self.apply_amplitude_button_clicked) self.ui.amplitude_edit.returnPressed.connect( self.apply_amplitude_button_clicked) self.ui.frequency_edit.textEdited.connect( self.frequency_edit_text_changed) self.ui.apply_frequency_button.clicked.connect( self.apply_frequency_button_clicked) self.ui.frequency_edit.returnPressed.connect( self.apply_frequency_button_clicked) self.ui.fixed_step_combobox.currentTextChanged.connect( self.set_fixed_step) self.ui.pause_button.clicked.connect(self.pause_or_resume_measure) self.start_measure_timer.timeout.connect(self.check_fixed_range) self.soft_approach_timer.timeout.connect(self.set_amplitude_soft) self.ui.edit_parameters_button.clicked.connect(self.update_config) self.measure_manager.current_case_changed.connect( self.current_case_changed) self.stop_measure_timer.timeout.connect(self.current_case_changed) @pyqtSlot(list) def update_clb_list(self, a_clb_list: list): self.ui.clb_list_combobox.clear() for clb_name in a_clb_list: self.ui.clb_list_combobox.addItem(clb_name) @pyqtSlot(clb.State) def update_clb_status(self, a_status: clb.State): self.clb_state = a_status self.ui.clb_state_label.setText(clb.enum_to_state[a_status]) def connect_to_clb(self, a_clb_name): self.calibrator.connect(a_clb_name) def sync_clb_parameters(self): if self.calibrator.amplitude_changed(): self.set_amplitude(self.calibrator.amplitude) if self.calibrator.frequency_changed(): self.set_frequency(self.calibrator.frequency) if self.calibrator.signal_type_changed(): if self.calibrator.signal_type != self.current_case.signal_type: self.calibrator.signal_type = self.current_case.signal_type def signal_enable_changed(self, a_enable): if not self.started and self.calibrator.signal_enable: # Пока измерение не начато, запрещаем включать сигнал self.enable_signal(False) self.update_pause_button_state(False) else: if a_enable: self.update_pause_button_state(True) else: self.update_pause_button_state(False) def start_stop_measure(self): if not self.started: if self.ask_for_start_measure(): self.started = True self.ui.start_stop_button.setText("Закончить\nповерку") self.ui.pause_button.setEnabled(True) self.start_measure() else: self.ask_for_close() def ask_for_start_measure(self): message = "Начать поверку?\n\n" \ "На калибраторе будет включен сигнал и установлены следующие параметры:\n\n" \ "Режим измерения: Фиксированный диапазон\n" \ "Тип сигнала: {0}\n" \ "Амплитуда: {1}".format( clb.enum_to_signal_type[self.current_case.signal_type], self.value_to_user(self.highest_amplitude)) if clb.is_ac_signal[self.current_case.signal_type]: message += "\nЧастота: {0} Гц".format( utils.float_to_string(self.calibrator.frequency)) reply = QMessageBox.question(self, "Подтвердите действие", message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: return True else: return False def start_measure(self): self.set_amplitude(self.highest_amplitude) self.calibrator.mode = clb.Mode.FIXED_RANGE self.calibrator.signal_type = self.current_case.signal_type self.start_measure_timer.start(1100) def update_pause_button_state(self, a_signal_enabled: bool): self.soft_approach_points.clear() if a_signal_enabled: self.ui.pause_button.setChecked(a_signal_enabled) self.ui.pause_button.setText("Пауза") self.ui.pause_button.setIcon(self.pause_icon) else: self.ui.pause_button.setChecked(a_signal_enabled) self.ui.pause_button.setText("Возобновить") self.ui.pause_button.setIcon(self.play_icon) def check_fixed_range(self): if self.calibrator.mode == clb.Mode.FIXED_RANGE and self.calibrator.amplitude == self.highest_amplitude: self.enable_signal(True) self.start_measure_timer.stop() # Вызывается по таймауту stop_measure_timer и по изменению параметров сигнала в self.measure_manager def current_case_changed(self): """ Вызывается каждый раз, когда меняются параметры сигнала """ try: if not self.calibrator.signal_enable: self.current_case = self.measure_manager.current_case() self.set_window_elements() self.update_case_params() # Чтобы обновились единицы измерения self.set_amplitude(self.calibrator.amplitude) self.set_frequency(self.calibrator.frequency) self.calibrator.signal_type = self.current_case.signal_type self.fill_fixed_step_combobox() self.stop_measure_timer.stop() self.close_wait_dialog() else: self.enable_signal(False) self.stop_measure_timer.start(1100) self.show_wait_dialog() except AssertionError as err: utils.exception_handler(err) def show_wait_dialog(self): if self.wait_dialog is None: self.wait_dialog = QtWidgets.QDialog(self) self.wait_dialog.setWindowTitle("Подождите") layout = QtWidgets.QVBoxLayout(self.wait_dialog) layout.addWidget( QtWidgets.QLabel("Выключается сигнал...", self.wait_dialog)) self.wait_dialog.setFont(self.font()) self.wait_dialog.setFixedSize(250, 50) self.wait_dialog.setLayout(layout) self.wait_dialog.adjustSize() self.wait_dialog.exec() def close_wait_dialog(self): if self.wait_dialog is not None: self.wait_dialog.close() self.wait_dialog = None def pause_or_resume_measure(self): if self.calibrator.signal_enable: self.enable_signal(False) else: # Иконка и состояние чекбокса меняется до утвердительного ответа, принудительно меняем обратно self.update_pause_button_state(False) if self.ask_for_start_measure(): self.start_measure() # def keyPressEvent(self, event: QtGui.QKeyEvent): # if self.ui.measure_table.hasFocus(): # key = event.key() # if key == Qt.Key_Return or key == Qt.Key_Enter: # rows: List[QModelIndex] = self.get_selected_rows() # if rows: # self.ui.measure_table.edit(rows[0]) # else: # event.accept() def wheelEvent(self, event: QWheelEvent): if not (self.ui.measure_table.underMouse() and self.settings.disable_scroll_on_table): steps = qt_utils.get_wheel_steps(event) steps = -steps if self.settings.mouse_inversion else steps keys = event.modifiers() if (keys & Qt.ControlModifier) and (keys & Qt.ShiftModifier): self.set_amplitude(self.calibrator.amplitude + (self.fixed_step * steps)) elif keys & Qt.ShiftModifier: self.tune_amplitude(self.settings.exact_step * steps) elif keys & Qt.ControlModifier: self.tune_amplitude(self.settings.rough_step * steps) else: self.tune_amplitude(self.settings.common_step * steps) event.accept() def set_amplitude(self, a_amplitude: float): self.calibrator.amplitude = utils.bound(a_amplitude, self.lowest_amplitude, self.highest_amplitude) self.ui.amplitude_edit.setText( self.value_to_user(self.calibrator.amplitude)) self.amplitude_edit_text_changed() self.update_current_point(self.calibrator.amplitude) def set_frequency(self, a_frequency): self.calibrator.frequency = a_frequency current_frequency = 0 if clb.is_dc_signal[ self.current_case.signal_type] else self.calibrator.frequency self.ui.frequency_edit.setText( utils.float_to_string(current_frequency)) self.update_current_frequency(current_frequency) def tune_amplitude(self, a_step): self.set_amplitude( utils.relative_step_change( self.calibrator.amplitude, a_step, clb.signal_type_to_min_step[self.current_case.signal_type], a_normalize_value=self.current_case.limit)) def enable_signal(self, a_signal_enable): self.calibrator.signal_enable = a_signal_enable self.update_pause_button_state(a_signal_enable) def update_current_point(self, a_current_value: float): """ Обновляет данные, которые будут записаны в таблицу по кнопке "Сохранить точку" :param a_current_value: Новое значение амплитуды """ self.current_point.amplitude = self.guess_point(a_current_value) self.current_point.approach_side = PointData.ApproachSide.UP \ if a_current_value < self.current_point.value else PointData.ApproachSide.DOWN self.current_point.value = a_current_value def guess_point(self, a_point_value: float): if self.current_case.minimal_discrete == 0: return a_point_value else: return round(a_point_value / self.current_case.minimal_discrete ) * self.current_case.minimal_discrete def update_current_frequency(self, a_current_frequency): self.current_point.frequency = a_current_frequency def save_point(self): if self.clb_state != clb.State.WAITING_SIGNAL: try: if self.measure_manager.view().is_point_measured( self.current_point.amplitude, self.current_point.frequency, self.current_point.approach_side): side_text = "СНИЗУ" if self.current_point.approach_side == PointData.ApproachSide.DOWN \ else "СВЕРХУ" point_text = "{0}".format( self.value_to_user(self.current_point.amplitude)) if clb.is_ac_signal[self.current_case.signal_type]: point_text += " : {0} Гц".format( utils.float_to_string( self.current_point.frequency)) ask_dlg = QMessageBox(self) ask_dlg.setWindowTitle("Выберите действие") ask_dlg.setText( "Значение {0} уже измерено для точки {1}.\n" "Выберите действие для точки {3}({2})".format( side_text, point_text, side_text, point_text)) average_btn = ask_dlg.addButton("Усреднить", QMessageBox.YesRole) overwrite_btn = ask_dlg.addButton("Перезаписать", QMessageBox.YesRole) ask_dlg.addButton("Отменить", QMessageBox.NoRole) ask_dlg.exec() if ask_dlg.clickedButton() == overwrite_btn: self.measure_manager.view().append(self.current_point) elif ask_dlg.clickedButton() == average_btn: self.measure_manager.view().append(self.current_point, a_average=True) else: if self.clb_state == clb.State.READY: self.measure_manager.view().append(self.current_point) else: self.measure_manager.view().append( PointData( a_point=self.current_point.amplitude, a_frequency=self.current_point.frequency)) except AssertionError as err: utils.exception_handler(err) else: self.clb_not_ready_warning() def clb_not_ready_warning(self): QtWidgets.QApplication.beep() self.ui.status_warning_label.show() self.warning_animation.start() def go_to_point(self): rows = self.measure_manager.view().get_selected_rows() if rows: row_idx = rows[0].row() target_amplitude = utils.parse_input( self.measure_manager.view().get_point_by_row(row_idx)) target_frequency = float( self.measure_manager.view().get_frequency_by_row( row_idx).replace(',', '.')) if target_amplitude != self.calibrator.amplitude: measured_up = self.measure_manager.view( ).is_point_measured_by_row(row_idx, PointData.ApproachSide.UP) measured_down = self.measure_manager.view( ).is_point_measured_by_row(row_idx, PointData.ApproachSide.DOWN) if measured_down == measured_up: # Точка измерена полностью либо совсем не измерена, подходим с ближайшей стороны if self.calibrator.amplitude > target_amplitude: change_value_foo = utils.increase_by_percent else: change_value_foo = utils.decrease_by_percent else: if measured_up: change_value_foo = utils.decrease_by_percent else: change_value_foo = utils.increase_by_percent target_amplitude = change_value_foo( target_amplitude, self.settings.start_deviation, a_normalize_value=self.current_case.limit) target_amplitude = utils.bound(target_amplitude, self.lowest_amplitude, self.highest_amplitude) target_frequency = clb.bound_frequency( target_frequency, self.current_case.signal_type) self.start_approach_to_point(target_amplitude, target_frequency) def start_approach_to_point(self, a_amplitude, a_frequency): if self.calibrator.signal_enable and \ (self.calibrator.frequency == a_frequency or clb.is_dc_signal[self.current_case.signal_type]): self.soft_approach_points = utils.calc_smooth_approach( a_from=self.calibrator.amplitude, a_to=a_amplitude, a_count=self.SOFT_APPROACH_POINTS_COUNT, sigma=0.001, a_dt=self.NEXT_SOFT_POINT_TIME_MS) self.soft_approach_timer.start(self.NEXT_SOFT_POINT_TIME_MS) else: self.set_amplitude(a_amplitude) self.set_frequency(a_frequency) def set_amplitude_soft(self): try: if self.soft_approach_points: self.set_amplitude(self.soft_approach_points.pop(0)) else: self.soft_approach_timer.stop() except AssertionError as err: print(err) def delete_point(self): rows = self.measure_manager.view().get_selected_rows() if rows: row_indexes = [] deleted_points = "" for index_model in rows: point_str = self.measure_manager.view().get_point_by_row( index_model.row()) deleted_points += "\n{0}".format(point_str) if clb.is_ac_signal[self.current_case.signal_type]: freq = self.measure_manager.view().get_frequency_by_row( index_model.row()) deleted_points += " : {0} Гц".format( utils.float_to_string(float(freq))) row_indexes.append(index_model.row()) reply = QMessageBox.question( self, "Подтвердите действие", "Удалить следующие точки?\n" + deleted_points, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.remove_points.emit(row_indexes) def amplitude_edit_text_changed(self): try: parsed = utils.parse_input(self.ui.amplitude_edit.text()) except ValueError: parsed = "" qt_utils.update_edit_color(self.calibrator.amplitude, parsed, self.ui.amplitude_edit) def apply_amplitude_button_clicked(self): try: new_amplitude = utils.parse_input(self.ui.amplitude_edit.text()) self.set_amplitude(new_amplitude) except ValueError: # Отлавливает некорректный ввод pass def frequency_edit_text_changed(self): qt_utils.update_edit_color( self.calibrator.frequency, self.ui.frequency_edit.text().replace(",", "."), self.ui.frequency_edit) def apply_frequency_button_clicked(self): try: new_frequency = utils.parse_input(self.ui.frequency_edit.text()) self.set_frequency(new_frequency) self.frequency_edit_text_changed() except ValueError: # Отлавливает некорректный ввод pass def rough_plus_button_clicked(self): self.tune_amplitude(self.settings.rough_step) def rough_minus_button_clicked(self): self.tune_amplitude(-self.settings.rough_step) def common_plus_button_clicked(self): self.tune_amplitude(self.settings.common_step) def common_minus_button_clicked(self): self.tune_amplitude(-self.settings.common_step) def exact_plus_button_clicked(self): self.tune_amplitude(self.settings.exact_step) def exact_minus_button_clicked(self): self.tune_amplitude(-self.settings.exact_step) def fixed_plus_button_clicked(self): self.set_amplitude(self.calibrator.amplitude + self.fixed_step) def fixed_minus_button_clicked(self): self.set_amplitude(self.calibrator.amplitude - self.fixed_step) def set_fixed_step(self, a_new_step: str): try: self.fixed_step = utils.parse_input(a_new_step) except ValueError: self.fixed_step = 0 def update_config(self): try: edit_template_params_dialog = EditMeasureParamsDialog( self.settings, self.measure_config, self.db_connection, self) edit_template_params_dialog.exec() except AssertionError as err: utils.exception_handler(err) def ask_for_close(self): try: reply = QMessageBox.question(self, "Подтвердите действие", "Завершить поверку?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.enable_signal(False) # После закрытия measure_manager все кейсы синхронизированы с данными в таблицах self.measure_manager.close() if self.started: self.measures_db.save_measure(self.measure_config) else: self.measures_db.delete(self.measure_config.id) self.save_settings() self.close_confirmed.emit() except AssertionError as err: utils.exception_handler(err) def save_settings(self): self.settings.fixed_step_idx = self.ui.fixed_step_combobox.currentIndex( ) self.settings.save_geometry(self.__class__.__name__, self.parent.saveGeometry()) self.settings.save_header_state( self.__class__.__name__, self.ui.measure_table.horizontalHeader().saveState()) def __del__(self): print(self.__class__.__name__, "deleted")
class StartWindow(QtWidgets.QWidget): source_mode_chosen = pyqtSignal() no_template_mode_chosen = pyqtSignal() template_mode_chosen = pyqtSignal() def __init__(self, a_control_db_connection: Connection, a_db_name: str, a_settings: Settings, a_parent=None): """ Для отображения таблицы измерений используется QSqlRelationalTableModel (это сильно упрощает жизнь) При этом для остальных операций (добавление, удаление) используется другое соединение sqlite3.Connection Соединения могут быть подключены к БД параллельно :param a_control_db_connection: Соединения для управления БД :param a_db_name: Имя файла БД :param a_settings: Настройки в ini :param a_parent: Widget parent """ super().__init__(a_parent) self.ui = StartForm() self.ui.setupUi(self) self.parent = a_parent self.settings = a_settings self.setWindowTitle("Калибратор N4-25") self.ui.source_mode_button.clicked.connect(self.source_mode_chosen) self.ui.no_template_mode_button.clicked.connect( self.no_template_mode_chosen) self.ui.template_mode_button.clicked.connect(self.template_mode_chosen) self.ui.create_protocol_button.clicked.connect(self.create_protocol) self.ui.measures_table.doubleClicked.connect(self.create_protocol) self.parent.show() geometry = self.settings.get_last_geometry(self.__class__.__name__) if not geometry.isEmpty(): self.parent.restoreGeometry(geometry) else: self.parent.resize(self.size()) self.control_db_connection = a_control_db_connection self.measure_db = MeasuresDB(a_control_db_connection) self.display_db_connection = QtSql.QSqlDatabase.addDatabase("QSQLITE") self.display_db_model = QtSql.QSqlRelationalTableModel(self) self.sort_proxy_model = CustomSortingModel(self) self.header_context = self.config_measure_table(a_db_name) def config_measure_table(self, a_db_name: str): self.display_db_connection.setDatabaseName(a_db_name) res = self.display_db_connection.open() assert res, "Can't open database {0}!".format(a_db_name) self.display_db_model.setTable("measures") self.display_db_model.setRelation( MeasureColumn.DEVICE_SYSTEM, QtSql.QSqlRelation("system", "id", "name")) for column in range(self.display_db_model.columnCount()): self.display_db_model.setHeaderData(column, QtCore.Qt.Horizontal, MEASURE_COLUMN_TO_NAME[column]) self.sort_proxy_model.setSourceModel(self.display_db_model) self.ui.measures_table.setModel(self.sort_proxy_model) # Чтобы был приятный цвет выделения self.ui.measures_table.setItemDelegate( NonOverlappingDoubleClick(self.ui.measures_table)) self.ui.measures_table.selectionModel().currentChanged.connect( self.current_selection_changed) self.ui.measures_table.selectionModel().modelChanged.connect( self.current_selection_changed) self.ui.measures_table.selectionModel().selectionChanged.connect( self.current_selection_changed) self.ui.measures_table.horizontalHeader().restoreState( self.settings.get_last_header_state(self.__class__.__name__)) self.ui.measures_table.setColumnHidden(MeasureColumn.ID, True) header_context = qt_utils.TableHeaderContextMenu( self, self.ui.measures_table, True) self.ui.measures_table.horizontalHeader().setSectionsMovable(True) self.ui.measures_table.customContextMenuRequested.connect( self.show_table_custom_menu) self.update_table() return header_context def current_selection_changed(self): measure_id = self.get_selected_id() if measure_id is None: self.ui.create_protocol_button.setEnabled(False) else: self.ui.create_protocol_button.setEnabled(True) def update_table(self): self.display_db_model.select() self.current_selection_changed() def create_protocol(self): try: measure_id = self.get_selected_id() assert measure_id is not None, "measure id must not be None!" create_protocol_dialog = CreateProtocolDialog( self.settings, measure_id, self.control_db_connection, self) create_protocol_dialog.exec() self.update_table() except Exception as err: utils.exception_handler(err) def show_table_custom_menu(self, a_position: QtCore.QPoint): menu = QtWidgets.QMenu(self) delete_measure_act = menu.addAction("Удалить измерение") delete_measure_act.triggered.connect(self.delete_measure) menu.popup(self.ui.measures_table.viewport().mapToGlobal(a_position)) def delete_measure(self): measure_id = self.get_selected_id() if measure_id is not None: reply = QtWidgets.QMessageBox.question( self, "Подтвердите действие", "Вы действительно хотите удалить " "выбранное измерение?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: self.measure_db.delete(measure_id) self.update_table() def get_selected_id(self): selected = self.ui.measures_table.selectionModel().selectedRows() if selected: selected_row = selected[0].row() measure_id = self.sort_proxy_model.index(selected_row, MeasureColumn.ID).data() return measure_id else: return None def closeEvent(self, a_event: QtGui.QCloseEvent) -> None: self.settings.save_geometry(self.__class__.__name__, self.parent.saveGeometry()) self.settings.save_header_state( self.__class__.__name__, self.ui.measures_table.horizontalHeader().saveState()) self.display_db_connection.close() self.header_context.delete_connections() a_event.accept()