from PyQt5.QtWidgets import (QMainWindow, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QLabel, QDoubleSpinBox, QComboBox, QLineEdit, QWidget, QMessageBox, QGroupBox, QFrame, QHeaderView) from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont, QColor class MediumCalculatorWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Калькулятор питательных сред - Цифровой помощник биохимика") self.setGeometry(200, 100, 1200, 700) self.setStyleSheet(""" QMainWindow { background-color: #f5f5f5; } QGroupBox { font-weight: bold; border: 2px solid #ccc; border-radius: 5px; margin-top: 10px; padding-top: 10px; background-color: white; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; color: #1565C0; } QTableWidget { gridline-color: #ddd; background-color: white; alternate-background-color: #f9f9f9; } QHeaderView::section { background-color: #1565C0; color: white; padding: 8px; border: none; font-weight: bold; } QPushButton { background-color: #2196F3; color: white; border: none; padding: 8px 15px; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #1976D2; } QPushButton:pressed { background-color: #0D47A1; } QPushButton#danger { background-color: #f44336; } QPushButton#danger:hover { background-color: #da190b; } QPushButton#success { background-color: #4CAF50; } QPushButton#success:hover { background-color: #45a049; } QDoubleSpinBox, QLineEdit { padding: 4px; border: 1px solid #ccc; border-radius: 3px; background-color: white; color: black; font-size: 12px; min-height: 20px; } QComboBox { border: 1px solid #ccc; border-radius: 3px; background-color: white; color: black; font-size: 12px; min-height: 20px; padding: 2px; } QComboBox::drop-down { border: none; width: 20px; } QComboBox QAbstractItemView { background-color: white; color: black; selection-background-color: #2196F3; selection-color: white; } QLabel { color: black; font-size: 12px; } QLabel#title { font-size: 18px; font-weight: bold; color: #0D47A1; } QLabel#info { color: #1565C0; font-size: 11px; } """) self._init_ui() def _init_ui(self): central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) layout.setSpacing(15) layout.setContentsMargins(20, 20, 20, 20) title_label = QLabel("Калькулятор питательных сред") title_label.setObjectName("title") title_label.setAlignment(Qt.AlignCenter) title_font = QFont() title_font.setPointSize(18) title_font.setBold(True) title_label.setFont(title_font) layout.addWidget(title_label) params_group = QGroupBox("Параметры среды") params_layout = QHBoxLayout() params_layout.setSpacing(20) amount_layout = QHBoxLayout() amount_layout.addWidget(QLabel("Общее количество:")) self.amount_input = QDoubleSpinBox() self.amount_input.setRange(0.001, 1000000.0) self.amount_input.setValue(1000.0) self.amount_input.setMinimumWidth(150) amount_layout.addWidget(self.amount_input) self.amount_unit_combo = QComboBox() self.amount_unit_combo.addItems(["нл", "мкл", "мл", "л"]) self.amount_unit_combo.setCurrentText("мл") self.amount_unit_combo.setMinimumWidth(80) amount_layout.addWidget(self.amount_unit_combo) params_layout.addLayout(amount_layout) solvent_layout = QHBoxLayout() solvent_layout.addWidget(QLabel("Растворитель:")) self.solvent_input = QLineEdit("Вода") self.solvent_input.setMinimumWidth(150) solvent_layout.addWidget(self.solvent_input) params_layout.addLayout(solvent_layout) params_layout.addStretch() params_group.setLayout(params_layout) layout.addWidget(params_group) table_group = QGroupBox("Состав среды") table_layout = QVBoxLayout() self.table = QTableWidget() self.table.setColumnCount(6) self.table.setHorizontalHeaderLabels(["Название", "%", "Единица", "Коэфф.", "Разбавление (x)", "Количество"]) self.table.setAlternatingRowColors(True) self.table.setSelectionBehavior(QTableWidget.SelectRows) self.table.setEditTriggers(QTableWidget.DoubleClicked | QTableWidget.EditKeyPressed) self.table.verticalHeader().setVisible(False) self.table.setColumnWidth(0, 180) self.table.setColumnWidth(1, 70) self.table.setColumnWidth(2, 90) self.table.setColumnWidth(3, 70) self.table.setColumnWidth(4, 100) self.table.setColumnWidth(5, 120) table_layout.addWidget(self.table) table_group.setLayout(table_layout) layout.addWidget(table_group) btn_group = QGroupBox("Управление") btn_layout = QHBoxLayout() btn_layout.setSpacing(10) self.add_row_btn = QPushButton("Добавить реагент") self.add_row_btn.setMinimumWidth(150) btn_layout.addWidget(self.add_row_btn) self.remove_row_btn = QPushButton("Удалить реагент") self.remove_row_btn.setObjectName("danger") self.remove_row_btn.setMinimumWidth(150) btn_layout.addWidget(self.remove_row_btn) btn_layout.addStretch() self.calculate_btn = QPushButton("Рассчитать") self.calculate_btn.setObjectName("success") self.calculate_btn.setMinimumWidth(150) btn_layout.addWidget(self.calculate_btn) self.save_btn = QPushButton("Сохранить") self.save_btn.setMinimumWidth(150) btn_layout.addWidget(self.save_btn) self.load_btn = QPushButton("Загрузить") self.load_btn.setMinimumWidth(150) btn_layout.addWidget(self.load_btn) btn_group.setLayout(btn_layout) layout.addWidget(btn_group) info_frame = QFrame() info_frame.setFrameShape(QFrame.StyledPanel) info_frame.setStyleSheet("background-color: #e3f2fd; border-radius: 5px;") info_layout = QHBoxLayout(info_frame) info_label = QLabel("Подсказка: Реагенты в массовых единицах (г, мг и т.д.) не учитываются при расчёте объёма растворителя") info_label.setObjectName("info") info_layout.addWidget(info_label) info_layout.addStretch() layout.addWidget(info_frame) self.add_initial_rows() def add_initial_rows(self): self.add_solvent_row() self.add_new_row() def add_solvent_row(self): row_count = self.table.rowCount() self.table.insertRow(row_count) self.table.setRowHeight(row_count, 30) solvent_name = self.solvent_input.text() solvent_item = QTableWidgetItem(solvent_name) solvent_item.setFlags(solvent_item.flags() & ~Qt.ItemIsEditable) solvent_item.setBackground(QColor(230, 230, 230)) solvent_item.setForeground(QColor(0, 0, 0)) font = QFont() font.setBold(True) solvent_item.setFont(font) self.table.setItem(row_count, 0, solvent_item) percent_item = QTableWidgetItem("") percent_item.setFlags(percent_item.flags() & ~Qt.ItemIsEditable) percent_item.setBackground(QColor(230, 230, 230)) percent_item.setForeground(QColor(0, 0, 0)) self.table.setItem(row_count, 1, percent_item) unit_item = QTableWidgetItem(self.amount_unit_combo.currentText()) unit_item.setFlags(unit_item.flags() & ~Qt.ItemIsEditable) unit_item.setBackground(QColor(230, 230, 230)) unit_item.setForeground(QColor(0, 0, 0)) self.table.setItem(row_count, 2, unit_item) coeff_item = QTableWidgetItem("-") coeff_item.setFlags(coeff_item.flags() & ~Qt.ItemIsEditable) coeff_item.setBackground(QColor(230, 230, 230)) coeff_item.setForeground(QColor(0, 0, 0)) self.table.setItem(row_count, 3, coeff_item) dilution_item = QTableWidgetItem("-") dilution_item.setFlags(dilution_item.flags() & ~Qt.ItemIsEditable) dilution_item.setBackground(QColor(230, 230, 230)) dilution_item.setForeground(QColor(0, 0, 0)) self.table.setItem(row_count, 4, dilution_item) result_item = QTableWidgetItem("") result_item.setFlags(result_item.flags() & ~Qt.ItemIsEditable) result_item.setBackground(QColor(240, 240, 240)) result_item.setForeground(QColor(0, 0, 0)) self.table.setItem(row_count, 5, result_item) def update_solvent_name(self): solvent_name = self.solvent_input.text() name_item = self.table.item(0, 0) if name_item: name_item.setText(solvent_name) def format_number(self, value): if value == int(value): return str(int(value)) else: formatted = f"{value:.6f}".rstrip('0').rstrip('.') if '.' in formatted and len(formatted.split('.')[1]) > 4: formatted = f"{value:.4f}".rstrip('0').rstrip('.') return formatted def add_new_row(self): row_count = self.table.rowCount() self.table.insertRow(row_count) self.table.setRowHeight(row_count, 30) name_item = QTableWidgetItem(f"Реагент_{row_count}") name_item.setForeground(QColor(0, 0, 0)) self.table.setItem(row_count, 0, name_item) percent_item = QTableWidgetItem("0") percent_item.setForeground(QColor(0, 0, 0)) self.table.setItem(row_count, 1, percent_item) unit_combo = QComboBox() unit_combo.addItems(["нг", "мкг", "мг", "г", "кг", "нл", "мкл", "мл", "л"]) unit_combo.setCurrentText("мг") self.table.setCellWidget(row_count, 2, unit_combo) coeff_item = QTableWidgetItem("1") coeff_item.setForeground(QColor(0, 0, 0)) self.table.setItem(row_count, 3, coeff_item) dilution_item = QTableWidgetItem("1") dilution_item.setForeground(QColor(0, 0, 0)) dilution_item.setFlags(dilution_item.flags() | Qt.ItemIsEditable) self.table.setItem(row_count, 4, dilution_item) result_item = QTableWidgetItem("") result_item.setFlags(result_item.flags() & ~Qt.ItemIsEditable) result_item.setBackground(QColor(250, 250, 250)) result_item.setForeground(QColor(0, 0, 0)) self.table.setItem(row_count, 5, result_item) def remove_selected_row(self): selected_rows = set() for item in self.table.selectedItems(): selected_rows.add(item.row()) for row in sorted(selected_rows, reverse=True): if row > 0: self.table.removeRow(row) def get_table_data(self) -> list: data = [] for row in range(1, self.table.rowCount()): row_data = [] name_item = self.table.item(row, 0) row_data.append(name_item.text() if name_item else "") percent_item = self.table.item(row, 1) row_data.append(percent_item.text() if percent_item else "0") unit_widget = self.table.cellWidget(row, 2) if unit_widget and isinstance(unit_widget, QComboBox): row_data.append(unit_widget.currentText()) else: row_data.append("мг") coeff_item = self.table.item(row, 3) row_data.append(coeff_item.text() if coeff_item else "1") dilution_item = self.table.item(row, 4) if dilution_item: try: dilution_factor = float(dilution_item.text()) except ValueError: dilution_factor = 1.0 row_data.append(dilution_factor) else: row_data.append(1.0) data.append(row_data) return data def update_solvent_percent(self, solvent_percent: float): percent_item = self.table.item(0, 1) if percent_item: percent_item.setText(self.format_number(solvent_percent)) def show_error(self, message: str): QMessageBox.critical(self, "Ошибка", message) def update_results(self, results: list): for row, amount in enumerate(results, start=1): if row < self.table.rowCount(): formatted_amount = self.format_number(amount) result_item = QTableWidgetItem(formatted_amount) result_item.setFlags(result_item.flags() & ~Qt.ItemIsEditable) result_item.setBackground(QColor(220, 255, 220)) result_item.setForeground(QColor(0, 0, 0)) font = QFont() font.setBold(True) result_item.setFont(font) self.table.setItem(row, 5, result_item) def update_solvent_result(self, solvent_amount: float, unit: str): formatted_amount = self.format_number(solvent_amount) result_item = self.table.item(0, 5) if result_item: result_item.setText(formatted_amount) result_item.setBackground(QColor(220, 255, 220)) result_item.setForeground(QColor(0, 0, 0)) unit_item = self.table.item(0, 2) if unit_item: unit_item.setText(unit) def update_display(self, solvent: str, total_amount: float, amount_unit: str): self.solvent_input.setText(solvent) self.update_solvent_name() self.amount_input.setValue(total_amount) self.amount_unit_combo.setCurrentText(amount_unit) def clear_results(self): for row in range(self.table.rowCount()): result_item = self.table.item(row, 5) if result_item: result_item.setText("") if row == 0: result_item.setBackground(QColor(230, 230, 230)) else: result_item.setBackground(QColor(250, 250, 250)) percent_item = self.table.item(0, 1) if percent_item: percent_item.setText("") unit_item = self.table.item(0, 2) if unit_item: unit_item.setText(self.amount_unit_combo.currentText())