+ добавлено подсвечивание предупреждений и ошибок в таблице выбранных переменных
260 lines
11 KiB
Python
260 lines
11 KiB
Python
from PySide2.QtWidgets import (
|
||
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
|
||
QAbstractItemView, QHeaderView, QLabel
|
||
)
|
||
from PySide2.QtGui import QColor, QBrush, QPalette
|
||
from PySide2.QtCore import Qt
|
||
from enum import IntEnum
|
||
from generateVars import type_map
|
||
|
||
class rows(IntEnum):
|
||
No = 0
|
||
include = 1
|
||
name = 2
|
||
type = 3
|
||
pt_type = 4
|
||
iq_type = 5
|
||
ret_type = 6
|
||
short_name = 7
|
||
|
||
|
||
class VariableTableWidget(QTableWidget):
|
||
def __init__(self, parent=None):
|
||
super().__init__(0, 8, parent)
|
||
# Таблица переменных
|
||
self.setHorizontalHeaderLabels([
|
||
'№', # новый столбец
|
||
'En',
|
||
'Name',
|
||
'Origin Type',
|
||
'Pointer Type',
|
||
'IQ Type',
|
||
'Return Type',
|
||
'Short Name'
|
||
])
|
||
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
||
self.var_list = []
|
||
|
||
self.type_options = list(dict.fromkeys(type_map.values()))
|
||
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
|
||
self.iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
|
||
|
||
header = self.horizontalHeader()
|
||
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
|
||
|
||
for col in range(self.columnCount()):
|
||
if col == self.columnCount() - 1:
|
||
header.setSectionResizeMode(col, QHeaderView.Stretch)
|
||
else:
|
||
header.setSectionResizeMode(col, QHeaderView.Interactive)
|
||
|
||
parent_widget = self.parentWidget()
|
||
# Сделаем колонки с номерами фиксированной ширины
|
||
self.setColumnWidth(rows.No, 30)
|
||
self.setColumnWidth(rows.include, 30)
|
||
self.setColumnWidth(rows.pt_type, 85)
|
||
self.setColumnWidth(rows.iq_type, 85)
|
||
self.setColumnWidth(rows.ret_type, 85)
|
||
|
||
self.setColumnWidth(rows.name, 300)
|
||
self.setColumnWidth(rows.type, 100)
|
||
self._resizing = False
|
||
self.horizontalHeader().sectionResized.connect(self.on_section_resized)
|
||
|
||
|
||
def populate(self, vars_list, structs, on_change_callback):
|
||
self.var_list = vars_list
|
||
self.type_options = list(dict.fromkeys(type_map.values()))
|
||
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
|
||
iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
|
||
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
|
||
self.setRowCount(len(filtered_vars))
|
||
self.verticalHeader().setVisible(False)
|
||
style_with_padding = "padding-left: 5px; padding-right: 5px; font-size: 14pt;" # регулируй отступы по горизонтали
|
||
|
||
for row, var in enumerate(filtered_vars):
|
||
# №
|
||
no_item = QTableWidgetItem(str(row))
|
||
no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||
self.setItem(row, rows.No, no_item)
|
||
|
||
# Enable
|
||
cb = QCheckBox()
|
||
cb.setChecked(var.get('enable', 'false') == 'true')
|
||
cb.stateChanged.connect(on_change_callback)
|
||
cb.setStyleSheet(style_with_padding)
|
||
self.setCellWidget(row, rows.include, cb)
|
||
|
||
# Name
|
||
name_edit = QLineEdit(var['name'])
|
||
if var['type'] in structs:
|
||
completer = QCompleter(structs[var['type']].keys())
|
||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||
name_edit.setCompleter(completer)
|
||
name_edit.textChanged.connect(on_change_callback)
|
||
name_edit.setStyleSheet(style_with_padding)
|
||
self.setCellWidget(row, rows.name, name_edit)
|
||
|
||
# Origin Type (readonly)
|
||
origin_edit = QLineEdit(var.get('type', ''))
|
||
origin_edit.setReadOnly(True) # делает поле не редактируемым
|
||
origin_edit.setEnabled(True) # включен, чтобы можно было копировать и получать текст
|
||
origin_edit.setFocusPolicy(Qt.NoFocus) # не фокусируется при клике
|
||
origin_edit.setStyleSheet(style_with_padding)
|
||
self.setCellWidget(row, rows.type, origin_edit)
|
||
|
||
# pt_type
|
||
pt_combo = QComboBox()
|
||
pt_combo.addItems(self.display_type_options)
|
||
value = var['pt_type'].replace('pt_', '')
|
||
if value not in self.display_type_options:
|
||
pt_combo.addItem(value)
|
||
pt_combo.setCurrentText(value)
|
||
pt_combo.currentTextChanged.connect(on_change_callback)
|
||
pt_combo.setStyleSheet(style_with_padding)
|
||
self.setCellWidget(row, rows.pt_type, pt_combo)
|
||
|
||
# iq_type
|
||
iq_combo = QComboBox()
|
||
iq_combo.addItems(self.iq_types)
|
||
value = var['iq_type'].replace('t_', '')
|
||
if value not in self.iq_types:
|
||
iq_combo.addItem(value)
|
||
iq_combo.setCurrentText(value)
|
||
iq_combo.currentTextChanged.connect(on_change_callback)
|
||
iq_combo.setStyleSheet(style_with_padding)
|
||
self.setCellWidget(row, rows.iq_type, iq_combo)
|
||
|
||
# return_type
|
||
ret_combo = QComboBox()
|
||
ret_combo.addItems(self.iq_types)
|
||
ret_combo.setCurrentText(var.get('return_type', ''))
|
||
ret_combo.currentTextChanged.connect(on_change_callback)
|
||
ret_combo.setStyleSheet(style_with_padding)
|
||
self.setCellWidget(row, rows.ret_type, ret_combo)
|
||
|
||
# short_name
|
||
short_name_val = var.get('shortname', var['name'])
|
||
short_name_edit = QLineEdit(short_name_val)
|
||
short_name_edit.textChanged.connect(on_change_callback)
|
||
short_name_edit.setStyleSheet(style_with_padding)
|
||
self.setCellWidget(row, rows.short_name, short_name_edit)
|
||
|
||
self.check()
|
||
|
||
def check(self):
|
||
warning_color = (QColor("#FFFACD")) # Жёлтый для длинных shortname
|
||
error_color = (QColor("#FFB6C1")) # Светло-красный для отсутствующих переменных
|
||
tooltip_shortname = "Short Name длиннее 10 символов — будет обрезано при генерации"
|
||
tooltip_missing = f'Имя переменной не найдено среди переменных. Добавьте её через кнопку "Добавить переменные"'
|
||
|
||
for row in range(self.rowCount()):
|
||
# Получаем имя переменной (столбец `name`)
|
||
name_widget = self.cellWidget(row, rows.name)
|
||
name = name_widget.text() if name_widget else ""
|
||
|
||
# Получаем shortname
|
||
short_name_edit = self.cellWidget(row, rows.short_name)
|
||
shortname = short_name_edit.text() if short_name_edit else ""
|
||
|
||
# Флаги ошибок
|
||
long_shortname = len(shortname) > 10
|
||
found = any(v.get('name') == name for v in self.var_list)
|
||
|
||
# Выбираем цвет и подсказку
|
||
color = None
|
||
tooltip = ""
|
||
if not found:
|
||
color = error_color
|
||
tooltip = tooltip_missing
|
||
elif long_shortname:
|
||
color = warning_color
|
||
tooltip = tooltip_shortname
|
||
|
||
self.highlight_row(row, color, tooltip)
|
||
|
||
|
||
def read_data(self):
|
||
result = []
|
||
for row in range(self.rowCount()):
|
||
cb = self.cellWidget(row, rows.include)
|
||
name = self.cellWidget(row, rows.name).text()
|
||
pt = self.cellWidget(row, rows.pt_type).currentText()
|
||
iq = self.cellWidget(row, rows.iq_type).currentText()
|
||
ret = self.cellWidget(row, rows.ret_type).currentText()
|
||
shortname = self.cellWidget(row, rows.short_name).text()
|
||
origin_type = self.cellWidget(row, rows.type).text()
|
||
|
||
result.append({
|
||
'show_var': True,
|
||
'enable': cb.isChecked(),
|
||
'name': name,
|
||
'pt_type': f'pt_{pt}',
|
||
'iq_type': iq,
|
||
'return_type': ret,
|
||
'shortname': shortname,
|
||
'type': origin_type,
|
||
})
|
||
return result
|
||
|
||
def on_section_resized(self, logicalIndex, oldSize, newSize):
|
||
if self._resizing:
|
||
return # предотвращаем рекурсию
|
||
|
||
min_width = 50
|
||
delta = newSize - oldSize
|
||
right_index = logicalIndex + 1
|
||
|
||
if right_index >= self.columnCount():
|
||
# Если правая колока - нет соседа, ограничиваем минимальную ширину
|
||
if newSize < min_width:
|
||
self._resizing = True
|
||
self.setColumnWidth(logicalIndex, min_width)
|
||
self._resizing = False
|
||
return
|
||
|
||
self._resizing = True
|
||
try:
|
||
right_width = self.columnWidth(right_index)
|
||
new_right_width = right_width - delta
|
||
|
||
# Если соседняя колонка станет уже минимальной - подкорректируем левую
|
||
if new_right_width < min_width:
|
||
new_right_width = min_width
|
||
newSize = oldSize + (right_width - min_width)
|
||
self.setColumnWidth(logicalIndex, newSize)
|
||
|
||
self.setColumnWidth(right_index, new_right_width)
|
||
finally:
|
||
self._resizing = False
|
||
|
||
|
||
|
||
|
||
def highlight_row(self, row: int, color: QColor = None, tooltip: str = ""):
|
||
"""
|
||
Подсвечивает строку таблицы цветом `color`, не меняя шрифт.
|
||
Работает с QLineEdit, QComboBox, QCheckBox (включая обёртки).
|
||
Если `color=None`, сбрасывает подсветку.
|
||
"""
|
||
css_reset = "background-color: none; font: inherit;"
|
||
css_color = f"background-color: {color.name()};" if color else css_reset
|
||
|
||
for col in range(self.columnCount()):
|
||
item = self.item(row, col)
|
||
widget = self.cellWidget(row, col)
|
||
|
||
# Подсветка обычной item-ячейки (например, тип переменной)
|
||
if item is not None:
|
||
if color:
|
||
item.setBackground(QBrush(color))
|
||
item.setToolTip(tooltip)
|
||
else:
|
||
item.setBackground(QBrush(Qt.NoBrush))
|
||
item.setToolTip("")
|
||
|
||
# Подсветка виджетов — здесь главная доработка
|
||
elif widget is not None:
|
||
# Надёжная подсветка: через styleSheet
|
||
widget.setStyleSheet(css_color)
|
||
widget.setToolTip(tooltip if color else "") |