423 lines
16 KiB
Python
423 lines
16 KiB
Python
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())
|