Esempio n. 1
0
    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)
Esempio n. 2
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)
Esempio n. 3
0
    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()
Esempio n. 4
0
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")
Esempio n. 5
0
    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)
Esempio n. 6
0
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")
Esempio n. 7
0
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()