debugVarTool/Src/tms_debugvar_lowlevel.py

740 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
LowLevelSelectorWidget (PySide2)
--------------------------------
Виджет для:
* Выбора XML файла с описанием переменных (как в примере пользователя)
* Парсинга всех <variable> и их вложенных <member>
* Построения плоского списка путей (имя/подпуть) с расчётом абсолютного адреса (base_address + offset)
* Определения структур с полями даты (year, month, day, hour, minute)
* Выбора переменной и (опционально) переменной даты / ручного ввода даты
* Выбора типов: ptr_type (pt_*), iq_type, return_type
* Форматирования адреса в виде 0x000000 (6 HEX)
* Генерации словаря/кадра для последующей LowLevel-команды (не отправляет сам)
Интеграция:
* Подключите сигнал variablePrepared(dict) к функции, формирующей и отправляющей пакет.
* Содержимое dict:
{
'address': int,
'address_hex': str, # '0x....'
'ptr_type': int, # значение enum pt_*
'iq_type': int,
'return_type': int,
'datetime': {
'year': int,
'month': int,
'day': int,
'hour': int,
'minute': int,
},
'path': str, # полный путь переменной
'type_string': str, # строка типа из XML
}
Зависимости: только PySide2 и стандартная библиотека.
"""
from __future__ import annotations
import sys
import xml.etree.ElementTree as ET
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple
from PySide2 import QtCore, QtGui, QtWidgets
from path_hints import PathHints
# ------------------------------------------------------------ Enumerations --
# Сопоставление строк из XML типу ptr_type (адаптируйте под реальный проект)
PTR_TYPE_MAP = {
'int8': 'pt_int8', 'signed char': 'pt_int8', 'char': 'pt_int8',
'int16': 'pt_int16', 'short': 'pt_int16', 'int': 'pt_int16',
'int32': 'pt_int32', 'long': 'pt_int32',
'int64': 'pt_int64', 'long long': 'pt_int64',
'uint8': 'pt_uint8', 'unsigned char': 'pt_uint8',
'uint16': 'pt_uint16', 'unsigned short': 'pt_uint16', 'unsigned int': 'pt_uint16',
'uint32': 'pt_uint32', 'unsigned long': 'pt_uint32',
'uint64': 'pt_uint64', 'unsigned long long': 'pt_uint64',
'float': 'pt_float', 'floatf': 'pt_float',
'struct': 'pt_struct', 'union': 'pt_union',
}
PT_ENUM_ORDER = [
'pt_unknown','pt_int8','pt_int16','pt_int32','pt_int64',
'pt_uint8','pt_uint16','pt_uint32','pt_uint64','pt_float',
'pt_struct','pt_union'
]
IQ_ENUM_ORDER = [
't_iq_none','t_iq','t_iq1','t_iq2','t_iq3','t_iq4','t_iq5','t_iq6',
't_iq7','t_iq8','t_iq9','t_iq10','t_iq11','t_iq12','t_iq13','t_iq14',
't_iq15','t_iq16','t_iq17','t_iq18','t_iq19','t_iq20','t_iq21','t_iq22',
't_iq23','t_iq24','t_iq25','t_iq26','t_iq27','t_iq28','t_iq29','t_iq30'
]
# Для примера: маппинг имени enum -> числовое значение (индекс по порядку)
PT_ENUM_VALUE = {name: idx for idx, name in enumerate(PT_ENUM_ORDER)}
IQ_ENUM_VALUE = {name: idx for idx, name in enumerate(IQ_ENUM_ORDER)}
# -------------------------------------------------------------- Data types --
DATE_FIELD_SET = {'year','month','day','hour','minute'}
@dataclass
class MemberNode:
name: str
offset: int = 0
type_str: str = ''
size: Optional[int] = None
children: List['MemberNode'] = field(default_factory=list)
# --- новые, но необязательные (совместимость) ---
kind: Optional[str] = None # 'array', 'union', ...
count: Optional[int] = None # size1 (число элементов в массиве)
def is_date_struct(self) -> bool:
if not self.children:
return False
child_names = {c.name for c in self.children}
return DATE_FIELD_SET.issubset(child_names)
@dataclass
class VariableNode:
name: str
address: int
type_str: str
size: Optional[int]
members: List[MemberNode] = field(default_factory=list)
# --- новые, но необязательные ---
kind: Optional[str] = None # 'array'
count: Optional[int] = None # size1
def base_address_hex(self) -> str:
return f"0x{self.address:06X}"
# --------------------------- XML Parser ----------------------------
class VariablesXML:
"""
Читает твой XML и выдаёт плоский список путей:
- Массивы -> name[i], многоуровневые -> name[i][j]
- Указатель на структуру -> дети через '->'
- Обычная структура -> дети через '.'
"""
# предположительные размеры примитивов (под STM/MCU: int=2)
_PRIM_SIZE = {
'char':1, 'signed char':1, 'unsigned char':1, 'uint8_t':1, 'int8_t':1,
'short':2, 'short int':2, 'signed short':2, 'unsigned short':2,
'uint16_t':2, 'int16_t':2,
'int':2, 'signed int':2, 'unsigned int':2,
'long':4, 'unsigned long':4, 'int32_t':4, 'uint32_t':4,
'float':4,
'long long':8, 'unsigned long long':8, 'int64_t':8, 'uint64_t':8, 'double':8,
}
def __init__(self, path: str):
self.path = path
self.timestamp: str = ''
self.variables: List[VariableNode] = []
self._parse()
# ------------------ low helpers ------------------
@staticmethod
def _parse_int_guess(txt: Optional[str]) -> Optional[int]:
if not txt:
return None
txt = txt.strip()
if txt.startswith(('0x','0X')):
return int(txt, 16)
# если в строке есть буквы A-F → возможно hex
if any(c in 'abcdefABCDEF' for c in txt):
try:
return int(txt, 16)
except ValueError:
pass
try:
return int(txt, 10)
except ValueError:
return None
@staticmethod
def _is_pointer_to_struct(t: str) -> bool:
if not t:
return False
low = t.replace('\t',' ').replace('\n',' ')
return 'struct ' in low and '*' in low
@staticmethod
def _is_struct_or_union(t: str) -> bool:
if not t:
return False
low = t.strip()
return low.startswith('struct ') or low.startswith('union ')
@staticmethod
def _strip_array_suffix(t: str) -> str:
return t[:-2].strip() if t.endswith('[]') else t
def _guess_primitive_size(self, type_str: str) -> Optional[int]:
if not type_str:
return None
base = type_str
for tok in ('volatile','const'):
base = base.replace(tok, '')
base = base.replace('*',' ')
base = base.replace('[',' ').replace(']',' ')
base = ' '.join(base.split()).strip()
return self._PRIM_SIZE.get(base)
# ------------------ XML read ------------------
def _parse(self):
tree = ET.parse(self.path)
root = tree.getroot()
ts = root.find('timestamp')
self.timestamp = ts.text.strip() if ts is not None and ts.text else ''
def parse_member(elem) -> MemberNode:
name = elem.get('name','')
offset = int(elem.get('offset','0'),16) if elem.get('offset') else 0
t = elem.get('type','') or ''
size_attr = elem.get('size')
size = int(size_attr,16) if size_attr else None
kind = elem.get('kind')
size1_attr = elem.get('size1')
count = None
if size1_attr:
count = self._parse_int_guess(size1_attr)
node = MemberNode(name=name, offset=offset, type_str=t, size=size,
kind=kind, count=count)
for ch in elem.findall('member'):
node.children.append(parse_member(ch))
return node
for var in root.findall('variable'):
addr = int(var.get('address','0'),16)
name = var.get('name','')
t = var.get('type','') or ''
size_attr = var.get('size')
size = int(size_attr,16) if size_attr else None
kind = var.get('kind')
size1_attr = var.get('size1')
count = None
if size1_attr:
count = self._parse_int_guess(size1_attr)
members = [parse_member(m) for m in var.findall('member')]
self.variables.append(
VariableNode(name=name, address=addr, type_str=t, size=size,
members=members, kind=kind, count=count)
)
# ------------------ flatten (expanded) ------------------
def flattened(self,
max_array_elems: Optional[int] = None
) -> List[Tuple[str,int,str]]:
"""
Вернёт [(path, addr, type_str), ...].
max_array_elems: ограничить разворачивание больших массивов (None = все).
"""
out: List[Tuple[str,int,str]] = []
def add(path: str, addr: int, t: str):
out.append((path, addr, t))
def compute_stride(size_bytes: Optional[int],
count: Optional[int],
base_type: Optional[str],
node_children: Optional[List[MemberNode]]) -> int:
# 1) пробуем size/count
if size_bytes and count and count > 0:
stride = size_bytes // count
if stride * count != size_bytes:
# округлённо вверх
stride = (size_bytes + count - 1) // count
if stride <= 0:
stride = 1
return stride
# 2) размер примитива по типу
if base_type:
gs = self._guess_primitive_size(base_type)
if gs:
return gs
# 3) попытка по детям (структура)
if node_children:
min_off = min(ch.offset for ch in node_children)
max_end = min_off
for ch in node_children:
sz = ch.size
if not sz:
sz = self._guess_primitive_size(ch.type_str) or 1
end = ch.offset + sz
if end > max_end:
max_end = end
stride = max_end - min_off
if stride > 0:
return stride
return 1
def expand_members(prefix_name: str,
base_addr: int,
members: List[MemberNode],
parent_is_ptr_struct: bool):
"""
Разворачиваем список members относительно базового адреса.
parent_is_ptr_struct: если True, то соединение '->' иначе '.'
"""
join = '->' if parent_is_ptr_struct else '.'
for m in members:
path_m = f"{prefix_name}{join}{m.name}" if prefix_name else m.name
addr_m = base_addr + m.offset
add(path_m, addr_m, m.type_str)
# массив?
if (m.kind == 'array') or m.type_str.endswith('[]'):
count = m.count
if count is None:
count = 0 # неизвестно → не разворачиваем
if count <= 0:
continue
base_t = self._strip_array_suffix(m.type_str)
stride = compute_stride(m.size, count, base_t, m.children if m.children else None)
limit = count if max_array_elems is None else min(count, max_array_elems)
for i in range(limit):
path_i = f"{path_m}[{i}]"
addr_i = addr_m + i*stride
add(path_i, addr_i, base_t)
# элемент массива: если структура / union → раскроем поля
if m.children and self._is_struct_or_union(base_t):
expand_members(path_i, addr_i, m.children, parent_is_ptr_struct=False)
# элемент массива: если указатель на структуру
elif self._is_pointer_to_struct(base_t):
# у таких обычно нет children в XML, но если есть — используем
expand_members(path_i, addr_i, m.children, parent_is_ptr_struct=True)
continue
# не массив
if m.children:
is_ptr_struct = self._is_pointer_to_struct(m.type_str)
expand_members(path_m, addr_m, m.children, parent_is_ptr_struct=is_ptr_struct)
# --- top-level ---
for v in self.variables:
add(v.name, v.address, v.type_str)
# top-level массив?
if (v.kind == 'array') or v.type_str.endswith('[]'):
count = v.count
if count is None:
count = 0
if count > 0:
base_t = self._strip_array_suffix(v.type_str)
stride = compute_stride(v.size, count, base_t, v.members if v.members else None)
limit = count if max_array_elems is None else min(count, max_array_elems)
for i in range(limit):
p = f"{v.name}[{i}]"
a = v.address + i*stride
add(p, a, base_t)
# массив структур?
if v.members and self._is_struct_or_union(base_t):
expand_members(p, a, v.members, parent_is_ptr_struct=False)
# массив указателей на структуры?
elif self._is_pointer_to_struct(base_t):
expand_members(p, a, v.members, parent_is_ptr_struct=True)
continue # к след. переменной
# top-level не массив
if v.members:
is_ptr_struct = self._is_pointer_to_struct(v.type_str)
expand_members(v.name, v.address, v.members, parent_is_ptr_struct=is_ptr_struct)
return out
# -------------------- date candidates (как было) --------------------
def date_struct_candidates(self) -> List[Tuple[str,int]]:
cands = []
for v in self.variables:
# верхний уровень (если есть все поля даты)
direct_names = {mm.name for mm in v.members}
if DATE_FIELD_SET.issubset(direct_names):
cands.append((v.name, v.address))
# проверка членов первого уровня
for m in v.members:
if m.is_date_struct():
cands.append((f"{v.name}.{m.name}", v.address + m.offset))
return cands
# ------------------------------------------- Address / validation helpers --
HEX_ADDR_MASK = QtCore.QRegExp(r"0x[0-9A-Fa-f]{0,6}")
class HexAddrValidator(QtGui.QRegExpValidator):
def __init__(self, parent=None):
super().__init__(HEX_ADDR_MASK, parent)
@staticmethod
def normalize(text: str) -> str:
if not text:
return '0x000000'
try:
val = int(text,16)
except ValueError:
return '0x000000'
return f"0x{val & 0xFFFFFF:06X}"
# --------------------------------------------------------- Main Widget ----
class LowLevelSelectorWidget(QtWidgets.QWidget):
variablePrepared = QtCore.Signal(dict)
xmlLoaded = QtCore.Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('LowLevel Variable Selector')
self._xml: Optional[VariablesXML] = None
self._paths = []
self._path_info = {}
self._addr_index = {}
self._hints = PathHints()
self._build_ui()
self._connect()
def _build_ui(self):
lay = QtWidgets.QVBoxLayout(self)
# --- File chooser ---
file_box = QtWidgets.QHBoxLayout()
self.btn_load = QtWidgets.QPushButton('Load XML...')
self.lbl_file = QtWidgets.QLabel('<no file>')
self.lbl_file.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
file_box.addWidget(self.btn_load)
file_box.addWidget(self.lbl_file, 1)
lay.addLayout(file_box)
self.lbl_timestamp = QtWidgets.QLabel('Timestamp: -')
lay.addWidget(self.lbl_timestamp)
form = QtWidgets.QFormLayout()
# --- Search field for variable ---
self.edit_var_search = QtWidgets.QLineEdit()
self.edit_var_search.setPlaceholderText("Введите имя/путь или адрес 0x......")
form.addRow('Variable:', self.edit_var_search)
# Popup list
self._popup = QtWidgets.QListView()
self._popup.setWindowFlags(QtCore.Qt.ToolTip)
self._popup.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self._popup.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self._popup.clicked.connect(self._on_popup_clicked)
self._model_all = QtGui.QStandardItemModel(self)
self._model_filtered = QtGui.QStandardItemModel(self)
# Address
self.edit_address = QtWidgets.QLineEdit('0x000000')
self.edit_address.setValidator(HexAddrValidator(self))
self.edit_address.setMaximumWidth(120)
form.addRow('Address:', self.edit_address)
# Manual date spins
dt_row = QtWidgets.QHBoxLayout()
self.spin_year = QtWidgets.QSpinBox(); self.spin_year.setRange(2000, 2100); self.spin_year.setValue(2025)
self.spin_month = QtWidgets.QSpinBox(); self.spin_month.setRange(1,12)
self.spin_day = QtWidgets.QSpinBox(); self.spin_day.setRange(1,31)
self.spin_hour = QtWidgets.QSpinBox(); self.spin_hour.setRange(0,23)
self.spin_minute = QtWidgets.QSpinBox(); self.spin_minute.setRange(0,59)
for w,label in [(self.spin_year,'Y'),(self.spin_month,'M'),(self.spin_day,'D'),(self.spin_hour,'h'),(self.spin_minute,'m')]:
box = QtWidgets.QVBoxLayout()
box.addWidget(QtWidgets.QLabel(label, alignment=QtCore.Qt.AlignHCenter))
box.addWidget(w)
dt_row.addLayout(box)
form.addRow('Manual Date:', dt_row)
# Types
self.cmb_ptr_type = QtWidgets.QComboBox(); self.cmb_ptr_type.addItems(PT_ENUM_ORDER)
self.cmb_iq_type = QtWidgets.QComboBox(); self.cmb_iq_type.addItems(IQ_ENUM_ORDER)
self.cmb_return_type = QtWidgets.QComboBox(); self.cmb_return_type.addItems(IQ_ENUM_ORDER)
form.addRow('ptr_type:', self.cmb_ptr_type)
form.addRow('iq_type:', self.cmb_iq_type)
form.addRow('return_type:', self.cmb_return_type)
lay.addLayout(form)
self.btn_prepare = QtWidgets.QPushButton('Prepare Variable Dict')
lay.addWidget(self.btn_prepare)
lay.addStretch(1)
self.txt_info = QtWidgets.QPlainTextEdit()
self.txt_info.setReadOnly(True)
self.txt_info.setMaximumHeight(140)
lay.addWidget(QtWidgets.QLabel('Info:'))
lay.addWidget(self.txt_info)
# Event filter for keyboard on search field
self.edit_var_search.installEventFilter(self)
def _connect(self):
self.btn_load.clicked.connect(self._on_load_xml)
self.edit_address.editingFinished.connect(self._normalize_address)
self.btn_prepare.clicked.connect(self._emit_variable)
self.edit_var_search.textEdited.connect(self._on_var_search_edited)
self.edit_var_search.returnPressed.connect(self._activate_current_popup_selection)
# ---------------- XML Load ----------------
def _on_load_xml(self):
path, _ = QtWidgets.QFileDialog.getOpenFileName(
self, 'Select variables XML', '', 'XML Files (*.xml);;All Files (*)')
if not path:
return
try:
self._xml = VariablesXML(path)
except Exception as e:
QtWidgets.QMessageBox.critical(self, 'Parse error', f'Ошибка парсинга:\n{e}')
return
self.lbl_file.setText(path)
self.lbl_timestamp.setText(f'Timestamp: {self._xml.timestamp or "-"}')
self._populate_variables()
self._apply_timestamp_to_date()
self.xmlLoaded.emit(path)
self._log(f'Loaded {path}, variables={len(self._xml.variables)}')
def _apply_timestamp_to_date(self):
if not self._xml.timestamp:
return
import datetime
try:
# Пример: "Sat Jul 19 15:27:59 2025"
dt = datetime.datetime.strptime(self._xml.timestamp, "%a %b %d %H:%M:%S %Y")
self.spin_year.setValue(dt.year)
self.spin_month.setValue(dt.month)
self.spin_day.setValue(dt.day)
self.spin_hour.setValue(dt.hour)
self.spin_minute.setValue(dt.minute)
except Exception as e:
print(f"Ошибка разбора timestamp '{self._xml.timestamp}': {e}")
def _populate_variables(self):
if not self._xml:
return
flat = self._xml.flattened()
# flat: [(path, addr, type_str), ...]
self._paths = []
self._path_info = {}
self._addr_index = {}
self._model_all.clear() # держим «сырой» полный список (можно не показывать)
self._model_filtered.clear() # текущие подсказки
# индексирование
for path, addr, t in flat:
self._paths.append(path)
self._path_info[path] = (addr, t)
if addr in self._addr_index:
self._addr_index[addr] = None
else:
self._addr_index[addr] = path
# наполняем «all» модель (необязательная, но пусть остаётся — не используем напрямую)
it = QtGui.QStandardItem(f"{path} [{addr:06X}]")
it.setData(path, QtCore.Qt.UserRole+1)
it.setData(addr, QtCore.Qt.UserRole+2)
it.setData(t, QtCore.Qt.UserRole+3)
self._model_all.appendRow(it)
# построить подсказки
self._hints.set_paths([(p, self._path_info[p][1]) for p in self._paths])
# начальное состояние попапа (пустой ввод → top-level)
self._update_popup_model(self._hints.suggest(''))
self._log(f"Variables loaded: {len(flat)}")
# --------------- Search mechanics ---------------
def _update_popup_model(self, paths: List[str]):
"""Обновляет модель попапа списком путей (full paths)."""
self._model_filtered.clear()
limit = 400
added = 0
for p in paths:
info = self._path_info.get(p)
if not info:
continue
addr, t = info
it = QtGui.QStandardItem(f"{p} [{addr:06X}]")
it.setData(p, QtCore.Qt.UserRole+1)
it.setData(addr, QtCore.Qt.UserRole+2)
it.setData(t, QtCore.Qt.UserRole+3)
self._model_filtered.appendRow(it)
added += 1
if added >= limit:
break
if added >= limit:
extra = QtGui.QStandardItem("... (more results truncated)")
extra.setEnabled(False)
self._model_filtered.appendRow(extra)
def _show_popup(self):
if self._model_filtered.rowCount() == 0:
self._popup.hide()
return
self._popup.setModel(self._model_filtered)
self._popup.setMinimumWidth(self.edit_var_search.width())
pos = self.edit_var_search.mapToGlobal(QtCore.QPoint(0, self.edit_var_search.height()))
self._popup.move(pos)
self._popup.show()
self._popup.raise_()
self._popup.setFocus()
self._popup.setCurrentIndex(self._model_filtered.index(0,0))
def _hide_popup(self):
self._popup.hide()
def _on_var_search_edited(self, text: str):
t = text.strip()
# адрес?
if t.startswith("0x") and len(t) >= 3:
try:
addr = int(t, 16)
path = self._addr_index.get(addr)
if path:
self._set_current_variable(path, from_address=True)
self._hide_popup()
return
except ValueError:
pass
# подсказки по имени
suggestions = self._hints.suggest(t)
self._update_popup_model(suggestions)
self._show_popup()
def _on_popup_clicked(self, idx: QtCore.QModelIndex):
if not idx.isValid():
return
path = idx.data(QtCore.Qt.UserRole+1)
if path:
self._set_current_variable(path)
self._hide_popup()
def _activate_current_popup_selection(self):
if self._popup.isVisible():
idx = self._popup.currentIndex()
if idx.isValid():
self._on_popup_clicked(idx)
return
# Попытка прямого совпадения
path = self.edit_var_search.text().strip()
if path in self._path_info:
self._set_current_variable(path)
def eventFilter(self, obj, ev):
if obj is self.edit_var_search and ev.type() == QtCore.QEvent.KeyPress:
if ev.key() in (QtCore.Qt.Key_Down, QtCore.Qt.Key_Up):
if not self._popup.isVisible():
self._show_popup()
else:
step = 1 if ev.key()==QtCore.Qt.Key_Down else -1
cur = self._popup.currentIndex()
row = cur.row() + step
if row < 0: row = 0
if row >= self._model_filtered.rowCount():
row = self._model_filtered.rowCount()-1
self._popup.setCurrentIndex(self._model_filtered.index(row,0))
return True
elif ev.key() == QtCore.Qt.Key_Escape:
self._hide_popup()
return True
return super().eventFilter(obj, ev)
def _set_current_variable(self, path: str, from_address=False):
if path not in self._path_info:
return
addr, type_str = self._path_info[path]
self.edit_var_search.setText(path)
self.edit_address.setText(f"0x{addr:06X}")
ptr_enum_name = self._map_type_to_ptr_enum(type_str)
self._select_combo_text(self.cmb_ptr_type, ptr_enum_name)
source = "ADDR" if from_address else "SEARCH"
self._log(f"[{source}] Selected {path} @0x{addr:06X} type={type_str} -> ptr={ptr_enum_name}")
# --------------- Date struct / address / helpers ---------------
def _normalize_address(self):
self.edit_address.setText(HexAddrValidator.normalize(self.edit_address.text()))
def _map_type_to_ptr_enum(self, type_str: str) -> str:
if not type_str:
return 'pt_unknown'
low = type_str.lower()
token = low.replace('*',' ').replace('[',' ').split()[0]
return PTR_TYPE_MAP.get(token, 'pt_unknown')
def _select_combo_text(self, combo: QtWidgets.QComboBox, text: str):
ix = combo.findText(text)
if ix >= 0:
combo.setCurrentIndex(ix)
def _collect_datetime(self) -> Dict[str,int]:
return {
'year': self.spin_year.value(),
'month': self.spin_month.value(),
'day': self.spin_day.value(),
'hour': self.spin_hour.value(),
'minute': self.spin_minute.value(),
}
def _emit_variable(self):
if not self._path_info:
QtWidgets.QMessageBox.warning(self, 'No XML', 'Сначала загрузите XML файл.')
return
path = self.edit_var_search.text().strip()
if path not in self._path_info:
QtWidgets.QMessageBox.warning(self, 'Variable', 'Переменная не выбрана / не найдена.')
return
addr, type_str = self._path_info[path]
ptr_type_name = self.cmb_ptr_type.currentText()
iq_type_name = self.cmb_iq_type.currentText()
ret_type_name = self.cmb_return_type.currentText()
out = {
'address': addr,
'address_hex': f"0x{addr:06X}",
'ptr_type': PT_ENUM_VALUE.get(ptr_type_name, 0),
'iq_type': IQ_ENUM_VALUE.get(iq_type_name, 0),
'return_type': IQ_ENUM_VALUE.get(ret_type_name, 0),
'datetime': self._collect_datetime(),
'path': path,
'type_string': type_str,
'ptr_type_name': ptr_type_name,
'iq_type_name': iq_type_name,
'return_type_name': ret_type_name,
}
self._log(f"Prepared variable: {out}")
self.variablePrepared.emit(out)
def _log(self, msg: str):
self.txt_info.appendPlainText(msg)
# ----------------------------------------------------------- Demo window --
class _DemoWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('LowLevel Selector Demo')
self.selector = LowLevelSelectorWidget(self)
self.setCentralWidget(self.selector)
self.selector.variablePrepared.connect(self.on_var)
def on_var(self, data: dict):
print('Variable prepared ->', data)
def closeEvent(self, ev):
self.setCentralWidget(None)
super().closeEvent(ev)
# ----------------------------------------------------------------- main ---
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = _DemoWindow()
w.resize(640, 520)
w.show()
sys.exit(app.exec_())