314 lines
12 KiB
Python
314 lines
12 KiB
Python
from PySide6.QtWidgets import (
|
||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||
QLineEdit
|
||
)
|
||
from PySide6.QtCore import Signal, Qt
|
||
from PySide6.QtGui import QRegularExpressionValidator
|
||
from PySide6.QtCore import QRegularExpression
|
||
|
||
|
||
class NumberLineEdit(QLineEdit):
|
||
"""Специализированное поле для ввода только чисел"""
|
||
|
||
def __init__(self, max_length: int, parent=None):
|
||
super().__init__(parent)
|
||
self.setMaxLength(max_length)
|
||
|
||
# Создаем валидатор для цифр
|
||
regex = QRegularExpression("[0-9]*")
|
||
validator = QRegularExpressionValidator(regex)
|
||
self.setValidator(validator)
|
||
|
||
|
||
class SerialNumberWidget(QWidget):
|
||
"""Виджет для ввода серийного номера (только цифры)"""
|
||
|
||
valueChanged = Signal()
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.init_ui()
|
||
|
||
def init_ui(self):
|
||
layout = QVBoxLayout()
|
||
layout.setSpacing(5)
|
||
|
||
# Описание формата
|
||
desc_label = QLabel("Формат: Axxx/Dxxxxxxxx/Vxxx/Nxxx (только цифры)")
|
||
layout.addWidget(desc_label)
|
||
|
||
# Контейнер для полей ввода
|
||
fields_container = QWidget()
|
||
fields_layout = QVBoxLayout(fields_container)
|
||
fields_layout.setSpacing(2)
|
||
fields_layout.setContentsMargins(0, 0, 0, 0)
|
||
|
||
# Верхний ряд - метки полей
|
||
labels_layout = QHBoxLayout()
|
||
labels_layout.setSpacing(2)
|
||
|
||
labels_layout.addSpacing(10)
|
||
|
||
label_a = QLabel("A")
|
||
label_a.setFixedWidth(40)
|
||
label_a.setAlignment(Qt.AlignCenter)
|
||
labels_layout.addWidget(label_a)
|
||
|
||
labels_layout.addSpacing(30)
|
||
|
||
label_d = QLabel("D")
|
||
label_d.setFixedWidth(70)
|
||
label_d.setAlignment(Qt.AlignCenter)
|
||
labels_layout.addWidget(label_d)
|
||
|
||
labels_layout.addSpacing(30)
|
||
|
||
label_v = QLabel("V")
|
||
label_v.setFixedWidth(40)
|
||
label_v.setAlignment(Qt.AlignCenter)
|
||
labels_layout.addWidget(label_v)
|
||
|
||
labels_layout.addSpacing(30)
|
||
|
||
label_n = QLabel("N")
|
||
label_n.setFixedWidth(40)
|
||
label_n.setAlignment(Qt.AlignCenter)
|
||
labels_layout.addWidget(label_n)
|
||
|
||
labels_layout.addStretch()
|
||
fields_layout.addLayout(labels_layout)
|
||
|
||
# Ряд с полями ввода
|
||
input_layout = QHBoxLayout()
|
||
input_layout.setSpacing(2)
|
||
|
||
input_layout.addSpacing(10)
|
||
|
||
# Поле A
|
||
self.part_a = NumberLineEdit(3)
|
||
self.part_a.setFixedWidth(40)
|
||
self.part_a.setPlaceholderText("xxx")
|
||
self.part_a.setAlignment(Qt.AlignCenter)
|
||
self.part_a.textChanged.connect(self.validate_and_update)
|
||
self.part_a.returnPressed.connect(lambda: self.focus_next_field(self.part_a, self.part_d))
|
||
input_layout.addWidget(self.part_a)
|
||
|
||
input_layout.addSpacing(30)
|
||
|
||
# Поле D
|
||
self.part_d = NumberLineEdit(8)
|
||
self.part_d.setFixedWidth(70)
|
||
self.part_d.setPlaceholderText("xxxxxxxx")
|
||
self.part_d.setAlignment(Qt.AlignCenter)
|
||
self.part_d.textChanged.connect(self.validate_and_update)
|
||
self.part_d.returnPressed.connect(lambda: self.focus_next_field(self.part_d, self.part_v))
|
||
input_layout.addWidget(self.part_d)
|
||
|
||
input_layout.addSpacing(30)
|
||
|
||
# Поле V
|
||
self.part_v = NumberLineEdit(3)
|
||
self.part_v.setFixedWidth(40)
|
||
self.part_v.setPlaceholderText("xxx")
|
||
self.part_v.setAlignment(Qt.AlignCenter)
|
||
self.part_v.textChanged.connect(self.validate_and_update)
|
||
self.part_v.returnPressed.connect(lambda: self.focus_next_field(self.part_v, self.part_n))
|
||
input_layout.addWidget(self.part_v)
|
||
|
||
input_layout.addSpacing(30)
|
||
|
||
# Поле N
|
||
self.part_n = NumberLineEdit(3)
|
||
self.part_n.setFixedWidth(40)
|
||
self.part_n.setPlaceholderText("xxx")
|
||
self.part_n.setAlignment(Qt.AlignCenter)
|
||
self.part_n.textChanged.connect(self.validate_and_update)
|
||
self.part_n.returnPressed.connect(lambda: self.focus_next_field(self.part_n, None))
|
||
input_layout.addWidget(self.part_n)
|
||
|
||
input_layout.addStretch()
|
||
fields_layout.addLayout(input_layout)
|
||
|
||
# Нижний ряд - индикаторы заполнения
|
||
indicators_layout = QHBoxLayout()
|
||
indicators_layout.setSpacing(2)
|
||
|
||
indicators_layout.addSpacing(10)
|
||
|
||
self.indicator_a = QLabel("0/3")
|
||
self.indicator_a.setFixedWidth(40)
|
||
self.indicator_a.setAlignment(Qt.AlignCenter)
|
||
indicators_layout.addWidget(self.indicator_a)
|
||
|
||
indicators_layout.addSpacing(30)
|
||
|
||
self.indicator_d = QLabel("0/8")
|
||
self.indicator_d.setFixedWidth(70)
|
||
self.indicator_d.setAlignment(Qt.AlignCenter)
|
||
indicators_layout.addWidget(self.indicator_d)
|
||
|
||
indicators_layout.addSpacing(30)
|
||
|
||
self.indicator_v = QLabel("0/3")
|
||
self.indicator_v.setFixedWidth(40)
|
||
self.indicator_v.setAlignment(Qt.AlignCenter)
|
||
indicators_layout.addWidget(self.indicator_v)
|
||
|
||
indicators_layout.addSpacing(30)
|
||
|
||
self.indicator_n = QLabel("0/3")
|
||
self.indicator_n.setFixedWidth(40)
|
||
self.indicator_n.setAlignment(Qt.AlignCenter)
|
||
indicators_layout.addWidget(self.indicator_n)
|
||
|
||
indicators_layout.addStretch()
|
||
fields_layout.addLayout(indicators_layout)
|
||
|
||
layout.addWidget(fields_container)
|
||
|
||
# Предпросмотр с темным стилем
|
||
preview_label = QLabel("Результирующий серийный номер:")
|
||
preview_label.setStyleSheet("font-weight: bold; margin-top: 15px;")
|
||
layout.addWidget(preview_label)
|
||
|
||
self.preview = QLabel()
|
||
self.preview.setStyleSheet("""
|
||
QLabel {
|
||
font-family: monospace;
|
||
font-size: 16px;
|
||
padding: 12px;
|
||
background-color: #2b2b2b;
|
||
color: #00ff00;
|
||
border: 2px solid #555;
|
||
border-radius: 5px;
|
||
font-weight: bold;
|
||
}
|
||
""")
|
||
self.preview.setAlignment(Qt.AlignCenter)
|
||
layout.addWidget(self.preview)
|
||
|
||
# Статус заполнения (без стилей)
|
||
self.status_label = QLabel()
|
||
self.status_label.setAlignment(Qt.AlignCenter)
|
||
layout.addWidget(self.status_label)
|
||
|
||
layout.addStretch()
|
||
self.setLayout(layout)
|
||
|
||
# Первоначальное обновление
|
||
self.update_preview()
|
||
|
||
def focus_next_field(self, current_field, next_field):
|
||
"""Перемещает фокус на следующее поле при нажатии Enter"""
|
||
if next_field:
|
||
next_field.setFocus()
|
||
|
||
def validate_and_update(self):
|
||
"""Проверяет ввод и обновляет предпросмотр"""
|
||
# Автоматический переход при полном заполнении поля
|
||
sender = self.sender()
|
||
if sender:
|
||
text = sender.text()
|
||
max_length = sender.maxLength()
|
||
|
||
if len(text) == max_length:
|
||
if sender == self.part_a:
|
||
self.part_d.setFocus()
|
||
elif sender == self.part_d:
|
||
self.part_v.setFocus()
|
||
elif sender == self.part_v:
|
||
self.part_n.setFocus()
|
||
elif sender == self.part_n:
|
||
self.part_n.clearFocus()
|
||
|
||
self.update_preview()
|
||
self.valueChanged.emit()
|
||
|
||
def update_preview(self):
|
||
"""Обновляет предпросмотр и индикаторы"""
|
||
a_text = self.part_a.text()
|
||
d_text = self.part_d.text()
|
||
v_text = self.part_v.text()
|
||
n_text = self.part_n.text()
|
||
|
||
# Обновляем индикаторы
|
||
self.indicator_a.setText(f"{len(a_text)}/3")
|
||
self.indicator_d.setText(f"{len(d_text)}/8")
|
||
self.indicator_v.setText(f"{len(v_text)}/3")
|
||
self.indicator_n.setText(f"{len(n_text)}/3")
|
||
|
||
# Формируем серийный номер
|
||
a = a_text if len(a_text) == 3 else a_text.ljust(3, 'x')
|
||
d = d_text if len(d_text) == 8 else d_text.ljust(8, 'x')
|
||
v = v_text if len(v_text) == 3 else v_text.ljust(3, 'x')
|
||
n = n_text if len(n_text) == 3 else n_text.ljust(3, 'x')
|
||
|
||
serial = f"A{a}/D{d}/V{v}/N{n}"
|
||
self.preview.setText(serial)
|
||
|
||
# Проверяем полноту заполнения
|
||
all_filled = len(a_text) == 3 and len(d_text) == 8 and len(v_text) == 3 and len(n_text) == 3
|
||
|
||
if all_filled:
|
||
self.status_label.setText("✓ Все поля заполнены корректно")
|
||
else:
|
||
missing = []
|
||
if len(a_text) < 3: missing.append("A")
|
||
if len(d_text) < 8: missing.append("D")
|
||
if len(v_text) < 3: missing.append("V")
|
||
if len(n_text) < 3: missing.append("N")
|
||
|
||
if missing:
|
||
self.status_label.setText(
|
||
f"Не заполнены поля: {', '.join(missing)} (недостающие символы будут заменены на 'x')"
|
||
)
|
||
else:
|
||
self.status_label.setText("")
|
||
|
||
def get_serial(self) -> str:
|
||
"""Возвращает серийный номер с заполнением недостающих символов 'x'"""
|
||
a = self.part_a.text().ljust(3, 'x')[:3]
|
||
d = self.part_d.text().ljust(8, 'x')[:8]
|
||
v = self.part_v.text().ljust(3, 'x')[:3]
|
||
n = self.part_n.text().ljust(3, 'x')[:3]
|
||
|
||
return f"A{a}/D{d}/V{v}/N{n}"
|
||
|
||
def set_serial(self, serial: str):
|
||
"""Устанавливает серийный номер из строки"""
|
||
try:
|
||
import re
|
||
pattern = r'A([^/]+)/D([^/]+)/V([^/]+)/N(.+)'
|
||
match = re.match(pattern, serial)
|
||
|
||
if match:
|
||
a, d, v, n = match.groups()
|
||
|
||
a = a.replace('A', '') if a.startswith('A') else a
|
||
d = d.replace('D', '') if d.startswith('D') else d
|
||
v = v.replace('V', '') if v.startswith('V') else v
|
||
n = n.replace('N', '') if n.startswith('N') else n
|
||
|
||
a = a.rstrip('x')
|
||
d = d.rstrip('x')
|
||
v = v.rstrip('x')
|
||
n = n.rstrip('x')
|
||
|
||
a = ''.join(c for c in a if c.isdigit())
|
||
d = ''.join(c for c in d if c.isdigit())
|
||
v = ''.join(c for c in v if c.isdigit())
|
||
n = ''.join(c for c in n if c.isdigit())
|
||
|
||
self.part_a.setText(a)
|
||
self.part_d.setText(d)
|
||
self.part_v.setText(v)
|
||
self.part_n.setText(n)
|
||
except Exception as e:
|
||
print(f"Ошибка парсинга серийного номера: {e}")
|
||
|
||
def clear(self):
|
||
"""Очищает все поля"""
|
||
self.part_a.clear()
|
||
self.part_d.clear()
|
||
self.part_v.clear()
|
||
self.part_n.clear() |